liquid_feedback_core

view lf_update_issue_order.c @ 402:29835e2525bd

Improved comments/debug output in lf_update_issue_order.c
author jbe
date Sat Oct 12 18:58:51 2013 +0200 (2013-10-12)
parents ee922d286992
children 236cabef04d2
line source
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <libpq-fe.h>
5 #include <search.h>
7 static int logging = 0;
9 static char *escapeLiteral(PGconn *conn, const char *str, size_t len) {
10 // provides compatibility for PostgreSQL versions prior 9.0
11 // in future: return PQescapeLiteral(conn, str, len);
12 char *res;
13 size_t res_len;
14 res = malloc(2*len+3);
15 if (!res) return NULL;
16 res[0] = '\'';
17 res_len = PQescapeStringConn(conn, res+1, str, len, NULL);
18 res[res_len+1] = '\'';
19 res[res_len+2] = 0;
20 return res;
21 }
23 static void freemem(void *ptr) {
24 // to be used for "escapeLiteral" function
25 // provides compatibility for PostgreSQL versions prior 9.0
26 // in future: PQfreemem(ptr);
27 free(ptr);
28 }
30 // column numbers when querying "issue_supporter_in_admission_state" view in function main():
31 #define COL_MEMBER_ID 0
32 #define COL_WEIGHT 1
33 #define COL_ISSUE_ID 2
35 // data structure for a candidate (in this case a suggestion) to the proportional runoff system:
36 struct candidate {
37 char *key; // identifier of the candidate, which is the "suggestion_id" string
38 double score_per_step; // added score per step
39 double score; // current score of candidate; a score of 1.0 is needed to survive a round
40 int seat; // equals 0 for unseated candidates, or contains rank number
41 };
43 // compare two integers stored as strings (invocation like strcmp):
44 static int compare_id(char *id1, char *id2) {
45 int ldiff;
46 ldiff = strlen(id1) - strlen(id2);
47 if (ldiff) return ldiff;
48 else return strcmp(id1, id2);
49 }
51 // compare two candidates by their key (invocation like strcmp):
52 static int compare_candidate(struct candidate *c1, struct candidate *c2) {
53 return compare_id(c1->key, c2->key);
54 }
56 // candidates are stored as global variables due to the constrained twalk() interface:
57 static int candidate_count;
58 static struct candidate *candidates;
60 // function to be passed to twalk() to store candidates ordered in candidates[] array:
61 static void register_candidate(char **candidate_key, VISIT visit, int level) {
62 if (visit == postorder || visit == leaf) {
63 struct candidate *candidate;
64 candidate = candidates + (candidate_count++);
65 candidate->key = *candidate_key;
66 candidate->seat = 0;
67 if (logging) printf("Candidate #%i is suggestion #%s.\n", candidate_count, candidate->key);
68 }
69 }
71 // performs a binary search in candidates[] array to lookup a candidate by its key (which is the suggestion_id):
72 static struct candidate *candidate_by_key(char *candidate_key) {
73 struct candidate *candidate;
74 struct candidate compare;
75 compare.key = candidate_key;
76 candidate = bsearch(&compare, candidates, candidate_count, sizeof(struct candidate), (void *)compare_candidate);
77 if (!candidate) {
78 fprintf(stderr, "Candidate not found (should not happen).\n");
79 abort();
80 }
81 return candidate;
82 }
84 // ballot of the proportional runoff system, containing only one preference section:
85 struct ballot {
86 int weight; // if weight is greater than 1, then the ballot is counted multiple times
87 int count; // number of candidates
88 struct candidate **candidates; // all candidates equally preferred
89 };
91 // open issue to be assigned an "order_in_open_states":
92 struct open_issue {
93 char *issue_id;
94 int minimum_position;
95 int position;
96 };
98 // determine candidate, which is assigned the next seat (starting with the worst rank):
99 static struct candidate *loser(int round_number, struct ballot *ballots, int ballot_count) {
100 int i, j; // index variables for loops
101 int remaining; // remaining candidates to be seated
102 // reset scores of all candidates:
103 for (i=0; i<candidate_count; i++) {
104 candidates[i].score = 0.0;
105 }
106 // calculate remaining candidates to be seated:
107 remaining = candidate_count - round_number;
108 // repeat following loop, as long as there is more than one remaining candidate:
109 while (remaining > 1) {
110 if (logging) printf("There are %i remaining candidates.\n", remaining);
111 double scale; // factor to be later multiplied with score_per_step:
112 // reset score_per_step for all candidates:
113 for (i=0; i<candidate_count; i++) {
114 candidates[i].score_per_step = 0.0;
115 }
116 // calculate score_per_step for all candidates:
117 for (i=0; i<ballot_count; i++) {
118 int matches = 0;
119 for (j=0; j<ballots[i].count; j++) {
120 struct candidate *candidate;
121 candidate = ballots[i].candidates[j];
122 if (candidate->score < 1.0 && !candidate->seat) matches++;
123 }
124 if (matches) {
125 double score_inc;
126 score_inc = (double)ballots[i].weight / (double)matches;
127 for (j=0; j<ballots[i].count; j++) {
128 struct candidate *candidate;
129 candidate = ballots[i].candidates[j];
130 if (candidate->score < 1.0 && !candidate->seat) {
131 candidate->score_per_step += score_inc;
132 }
133 }
134 }
135 }
136 // calculate scale factor:
137 scale = (double)0.0; // 0.0 is used to indicate that there is no value yet
138 for (i=0; i<candidate_count; i++) {
139 double max_scale;
140 if (candidates[i].score_per_step > 0.0) {
141 max_scale = (1.0-candidates[i].score) / candidates[i].score_per_step;
142 if (scale == 0.0 || max_scale <= scale) {
143 scale = max_scale;
144 }
145 }
146 }
147 // add scale*score_per_step to each candidates score:
148 for (i=0; i<candidate_count; i++) {
149 int log_candidate = 0;
150 if (logging && candidates[i].score < 1.0 && !candidates[i].seat) log_candidate = 1;
151 if (log_candidate) printf("Score for suggestion #%s = %.4f+%.4f*%.4f", candidates[i].key, candidates[i].score, scale, candidates[i].score_per_step);
152 if (candidates[i].score_per_step > 0.0) {
153 double max_scale;
154 max_scale = (1.0-candidates[i].score) / candidates[i].score_per_step;
155 if (max_scale == scale) {
156 // score of 1.0 should be reached, so we set score directly to avoid floating point errors:
157 candidates[i].score = 1.0;
158 remaining--;
159 } else {
160 candidates[i].score += scale * candidates[i].score_per_step;
161 if (candidates[i].score >= 1.0) remaining--;
162 }
163 }
164 if (log_candidate) {
165 if (candidates[i].score >= 1.0) printf("=1\n");
166 else printf("=%.4f\n", candidates[i].score);
167 }
168 // when there is only one candidate remaining, then break inner (and thus outer) loop:
169 if (remaining <= 1) {
170 break;
171 }
172 }
173 }
174 // return remaining candidate:
175 for (i=0; i<candidate_count; i++) {
176 if (candidates[i].score < 1.0 && !candidates[i].seat) return candidates+i;
177 }
178 // if there is no remaining candidate, then something went wrong:
179 fprintf(stderr, "No remaining candidate (should not happen).");
180 abort();
181 }
183 // calculate "order_in_open_states":
184 static void calculate_order_in_open_states(struct open_issue *open_issues, int open_issue_count) {
185 int i, j, fallback_j = 0, fallback_minimum_position;
186 for (i=1; i<=open_issue_count; i++) {
187 fallback_minimum_position = -1;
188 for (j=0; j<open_issue_count; j++) {
189 if (open_issues[j].position) continue;
190 if (open_issues[j].minimum_position <= i) break;
191 if (
192 fallback_minimum_position < 0 ||
193 open_issues[j].minimum_position < fallback_minimum_position
194 ) {
195 fallback_j = j;
196 fallback_minimum_position = open_issues[j].minimum_position;
197 }
198 }
199 if (j==open_issue_count) j = fallback_j;
200 open_issues[j].position = i;
201 }
202 }
204 // write results to database (calls calculate_open_issue_order):
205 static int write_ranks(PGconn *db, char *escaped_area_id) {
206 PGresult *res;
207 char *cmd;
208 int i;
209 if (asprintf(&cmd, "BEGIN; DELETE FROM \"issue_order\" USING \"issue\" WHERE \"issue_order\".\"id\" = \"issue\".\"id\" AND \"issue\".\"area_id\" = %s", escaped_area_id) < 0) {
210 fprintf(stderr, "Could not prepare query string in memory.\n");
211 abort();
212 }
213 res = PQexec(db, cmd);
214 free(cmd);
215 if (!res) {
216 fprintf(stderr, "Error in pqlib while sending SQL command to initiate issue order update.\n");
217 return 1;
218 } else if (
219 PQresultStatus(res) != PGRES_COMMAND_OK &&
220 PQresultStatus(res) != PGRES_TUPLES_OK
221 ) {
222 fprintf(stderr, "Error while executing SQL command to initiate issue order update:\n%s", PQresultErrorMessage(res));
223 PQclear(res);
224 return 1;
225 } else {
226 PQclear(res);
227 }
228 for (i=0; i<candidate_count; i++) {
229 char *escaped_issue_id;
230 escaped_issue_id = escapeLiteral(db, candidates[i].key, strlen(candidates[i].key));
231 if (!escaped_issue_id) {
232 fprintf(stderr, "Could not escape literal in memory.\n");
233 abort();
234 }
235 if (asprintf(&cmd, "INSERT INTO \"issue_order\" (\"id\", \"order_in_admission_state\") VALUES (%s, %i)", escaped_issue_id, candidates[i].seat) < 0) {
236 fprintf(stderr, "Could not prepare query string in memory.\n");
237 abort();
238 }
239 freemem(escaped_issue_id);
240 res = PQexec(db, cmd);
241 free(cmd);
242 if (!res) {
243 fprintf(stderr, "Error in pqlib while sending SQL command to insert issue order.\n");
244 } else if (
245 PQresultStatus(res) != PGRES_COMMAND_OK &&
246 PQresultStatus(res) != PGRES_TUPLES_OK
247 ) {
248 fprintf(stderr, "Error while executing SQL command to insert issue order:\n%s", PQresultErrorMessage(res));
249 PQclear(res);
250 } else {
251 PQclear(res);
252 continue;
253 }
254 res = PQexec(db, "ROLLBACK");
255 if (res) PQclear(res);
256 return 1;
257 }
258 if (asprintf(&cmd, "SELECT \"issue_id\", \"minimum_position\" FROM \"open_issues_ordered_with_minimum_position\" WHERE \"area_id\" = %s", escaped_area_id) < 0) {
259 fprintf(stderr, "Could not prepare query string in memory.\n");
260 abort();
261 }
262 res = PQexec(db, cmd);
263 free(cmd);
264 if (!res) {
265 fprintf(stderr, "Error in pqlib while sending SQL command selecting ordered issues with minimum position.\n");
266 res = PQexec(db, "ROLLBACK");
267 if (res) PQclear(res);
268 return 1;
269 } else if (PQresultStatus(res) != PGRES_TUPLES_OK) {
270 fprintf(stderr, "Error while executing SQL command selecting ordered issues with minimum position:\n%s", PQresultErrorMessage(res));
271 PQclear(res);
272 res = PQexec(db, "ROLLBACK");
273 if (res) PQclear(res);
274 return 1;
275 } else if (PQnfields(res) < 2) {
276 fprintf(stderr, "Too few columns returned by SQL command selecting ordered issues with minimum position.\n");
277 PQclear(res);
278 res = PQexec(db, "ROLLBACK");
279 if (res) PQclear(res);
280 return 1;
281 } else {
282 int open_issue_count;
283 struct open_issue *open_issues;
284 open_issue_count = PQntuples(res);
285 open_issues = calloc(open_issue_count, sizeof(struct open_issue));
286 for (i=0; i<open_issue_count; i++) {
287 open_issues[i].issue_id = PQgetvalue(res, i, 0);
288 if (PQgetisnull(res, i, 1)) {
289 open_issues[i].minimum_position = 0;
290 } else {
291 open_issues[i].minimum_position = (int)strtol(PQgetvalue(res, i, 1), (char **)NULL, 10);
292 if (open_issues[i].minimum_position < 1) {
293 fprintf(stderr, "Unexpected minimum position value.\n");
294 PQclear(res);
295 free(open_issues);
296 res = PQexec(db, "ROLLBACK");
297 if (res) PQclear(res);
298 return 1;
299 }
300 }
301 }
302 PQclear(res);
303 calculate_order_in_open_states(open_issues, open_issue_count);
304 for (i=0; i<open_issue_count; i++) {
305 char *escaped_issue_id;
306 escaped_issue_id = escapeLiteral(db, open_issues[i].issue_id, strlen(open_issues[i].issue_id));
307 if (!escaped_issue_id) {
308 fprintf(stderr, "Could not escape literal in memory.\n");
309 abort();
310 }
311 if (asprintf(&cmd, "UPDATE \"issue_order\" SET \"order_in_open_states\" = %i WHERE \"id\" = %s)", open_issues[i].position, escaped_issue_id) < 0) {
312 fprintf(stderr, "Could not prepare query string in memory.\n");
313 abort();
314 }
315 freemem(escaped_issue_id);
316 res = PQexec(db, cmd);
317 free(cmd);
318 if (!res) {
319 fprintf(stderr, "Error in pqlib while sending SQL command to update issue order.\n");
320 } else if (
321 PQresultStatus(res) != PGRES_COMMAND_OK &&
322 PQresultStatus(res) != PGRES_TUPLES_OK
323 ) {
324 fprintf(stderr, "Error while executing SQL command to update issue order:\n%s", PQresultErrorMessage(res));
325 PQclear(res);
326 } else {
327 PQclear(res);
328 continue;
329 }
330 free(open_issues);
331 res = PQexec(db, "ROLLBACK");
332 if (res) PQclear(res);
333 return 1;
334 }
335 free(open_issues);
336 }
337 res = PQexec(db, "COMMIT");
338 if (!res) {
339 fprintf(stderr, "Error in pqlib while sending SQL command to commit transaction.\n");
340 return 1;
341 } else if (
342 PQresultStatus(res) != PGRES_COMMAND_OK &&
343 PQresultStatus(res) != PGRES_TUPLES_OK
344 ) {
345 fprintf(stderr, "Error while executing SQL command to commit transaction:\n%s", PQresultErrorMessage(res));
346 PQclear(res);
347 return 1;
348 } else {
349 PQclear(res);
350 }
351 return 0;
352 }
354 // calculate ordering of issues in admission state for an area and call write_ranks() to write it to database:
355 static int process_area(PGconn *db, PGresult *res, char *escaped_area_id) {
356 int err; // variable to store an error condition (0 = success)
357 int ballot_count = 1; // number of ballots, must be initiatized to 1, due to loop below
358 struct ballot *ballots; // data structure containing the ballots
359 int i; // index variable for loops
360 // create candidates[] and ballots[] arrays:
361 {
362 void *candidate_tree = NULL; // temporary structure to create a sorted unique list of all candidate keys
363 int tuple_count; // number of tuples returned from the database
364 char *old_member_id = NULL; // old member_id to be able to detect a new ballot in loops
365 struct ballot *ballot; // pointer to current ballot
366 int candidates_in_ballot = 0; // number of candidates in ballot
367 // reset candidate count:
368 candidate_count = 0;
369 // determine number of tuples:
370 tuple_count = PQntuples(res);
371 // trivial case, when there are no tuples:
372 if (!tuple_count) {
373 // write results to database:
374 if (logging) printf("No supporters for any issue. Writing ranks to database.\n");
375 err = write_ranks(db, escaped_area_id);
376 if (logging) printf("Done.\n");
377 return 0;
378 }
379 // calculate ballot_count and generate set of candidate keys (suggestion_id is used as key):
380 for (i=0; i<tuple_count; i++) {
381 char *member_id, *issue_id;
382 member_id = PQgetvalue(res, i, COL_MEMBER_ID);
383 issue_id = PQgetvalue(res, i, COL_ISSUE_ID);
384 if (!candidate_tree || !tfind(issue_id, &candidate_tree, (void *)compare_id)) {
385 candidate_count++;
386 if (!tsearch(issue_id, &candidate_tree, (void *)compare_id)) {
387 fprintf(stderr, "Insufficient memory while inserting into candidate tree.\n");
388 abort();
389 }
390 }
391 if (old_member_id && strcmp(old_member_id, member_id)) ballot_count++;
392 old_member_id = member_id;
393 }
394 // allocate memory for candidates[] array:
395 candidates = malloc(candidate_count * sizeof(struct candidate));
396 if (!candidates) {
397 fprintf(stderr, "Insufficient memory while creating candidate list.\n");
398 abort();
399 }
400 // transform tree of candidate keys into sorted array:
401 candidate_count = 0; // needed by register_candidate()
402 twalk(candidate_tree, (void *)register_candidate);
403 // free memory of tree structure (tdestroy() is not available on all platforms):
404 while (candidate_tree) tdelete(*(void **)candidate_tree, &candidate_tree, (void *)compare_id);
405 // allocate memory for ballots[] array:
406 ballots = calloc(ballot_count, sizeof(struct ballot));
407 if (!ballots) {
408 fprintf(stderr, "Insufficient memory while creating ballot list.\n");
409 abort();
410 }
411 // set ballot weights, determine ballot section sizes, and verify preference values:
412 ballot = ballots;
413 old_member_id = NULL;
414 for (i=0; i<tuple_count; i++) {
415 char *member_id;
416 int weight;
417 member_id = PQgetvalue(res, i, COL_MEMBER_ID);
418 weight = (int)strtol(PQgetvalue(res, i, COL_WEIGHT), (char **)NULL, 10);
419 if (weight <= 0) {
420 fprintf(stderr, "Unexpected weight value.\n");
421 free(ballots);
422 free(candidates);
423 return 1;
424 }
425 if (old_member_id && strcmp(old_member_id, member_id)) ballot++;
426 ballot->weight = weight;
427 ballot->count++;
428 old_member_id = member_id;
429 }
430 // allocate memory for ballot sections:
431 for (i=0; i<ballot_count; i++) {
432 if (ballots[i].count) {
433 ballots[i].candidates = malloc(ballots[i].count * sizeof(struct candidate *));
434 if (!ballots[i].candidates) {
435 fprintf(stderr, "Insufficient memory while creating ballot section.\n");
436 abort();
437 }
438 }
439 }
440 // fill ballot sections with candidate references:
441 old_member_id = NULL;
442 ballot = ballots;
443 for (i=0; i<tuple_count; i++) {
444 char *member_id, *issue_id;
445 member_id = PQgetvalue(res, i, COL_MEMBER_ID);
446 issue_id = PQgetvalue(res, i, COL_ISSUE_ID);
447 if (old_member_id && strcmp(old_member_id, member_id)) {
448 ballot++;
449 candidates_in_ballot = 0;
450 }
451 ballot->candidates[candidates_in_ballot++] = candidate_by_key(issue_id);
452 old_member_id = member_id;
453 }
454 // print ballots, if logging is enabled:
455 if (logging) {
456 for (i=0; i<ballot_count; i++) {
457 int j;
458 printf("Ballot #%i: ", i+1);
459 for (j=0; j<ballots[i].count; j++) {
460 if (!j) printf("issues ");
461 else printf(", ");
462 printf("#%s", ballots[i].candidates[j]->key);
463 }
464 // if (!j) printf("empty"); // should not happen
465 printf(".\n");
466 }
467 }
468 }
470 // calculate ranks based on constructed data structures:
471 for (i=0; i<candidate_count; i++) {
472 struct candidate *candidate = loser(i, ballots, ballot_count);
473 candidate->seat = candidate_count - i;
474 if (logging) printf("Assigning rank #%i to issue #%s.\n", candidate_count-i, candidate->key);
475 }
477 // free ballots[] array:
478 for (i=0; i<ballot_count; i++) {
479 // if (ballots[i].count) { // count should not be zero
480 free(ballots[i].candidates);
481 // }
482 }
483 free(ballots);
485 // write results to database:
486 if (logging) printf("Writing ranks to database.\n");
487 err = write_ranks(db, escaped_area_id);
488 if (logging) printf("Done.\n");
490 // free candidates[] array:
491 free(candidates);
493 // return error code of write_ranks() call
494 return err;
495 }
497 int main(int argc, char **argv) {
499 // variable declarations:
500 int err = 0;
501 int i, count;
502 char *conninfo;
503 PGconn *db;
504 PGresult *res;
506 // parse command line:
507 if (argc == 0) return 1;
508 if (argc == 1 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
509 FILE *out;
510 out = argc == 1 ? stderr : stdout;
511 fprintf(out, "\n");
512 fprintf(out, "Usage: %s [-v|--verbose] <conninfo>\n", argv[0]);
513 fprintf(out, "\n");
514 fprintf(out, "<conninfo> is specified by PostgreSQL's libpq,\n");
515 fprintf(out, "see http://www.postgresql.org/docs/9.1/static/libpq-connect.html\n");
516 fprintf(out, "\n");
517 fprintf(out, "Example: %s dbname=liquid_feedback\n", argv[0]);
518 fprintf(out, "\n");
519 return argc == 1 ? 1 : 0;
520 }
521 {
522 size_t len = 0;
523 int argb = 1;
524 if (
525 argc >= 2 &&
526 (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--verbose"))
527 ) {
528 argb = 2;
529 logging = 1;
530 }
531 for (i=argb; i<argc; i++) len += strlen(argv[i]) + 1;
532 conninfo = malloc(len * sizeof(char));
533 if (!conninfo) {
534 fprintf(stderr, "Error: Could not allocate memory for conninfo string.\n");
535 abort();
536 }
537 conninfo[0] = 0;
538 for (i=argb; i<argc; i++) {
539 if (i>argb) strcat(conninfo, " ");
540 strcat(conninfo, argv[i]);
541 }
542 }
544 // connect to database:
545 db = PQconnectdb(conninfo);
546 if (!db) {
547 fprintf(stderr, "Error: Could not create database handle.\n");
548 return 1;
549 }
550 if (PQstatus(db) != CONNECTION_OK) {
551 fprintf(stderr, "Could not open connection:\n%s", PQerrorMessage(db));
552 return 1;
553 }
555 // go through areas:
556 res = PQexec(db, "SELECT \"id\" FROM \"area\"");
557 if (!res) {
558 fprintf(stderr, "Error in pqlib while sending SQL command selecting areas to process.\n");
559 err = 1;
560 } else if (PQresultStatus(res) != PGRES_TUPLES_OK) {
561 fprintf(stderr, "Error while executing SQL command selecting areas to process:\n%s", PQresultErrorMessage(res));
562 err = 1;
563 PQclear(res);
564 } else if (PQnfields(res) < 1) {
565 fprintf(stderr, "Too few columns returned by SQL command selecting areas to process.\n");
566 err = 1;
567 PQclear(res);
568 } else {
569 count = PQntuples(res);
570 if (logging) printf("Number of areas to process: %i\n", count);
571 for (i=0; i<count; i++) {
572 char *area_id, *escaped_area_id;
573 char *cmd;
574 PGresult *res2;
575 area_id = PQgetvalue(res, i, 0);
576 if (logging) printf("Processing area #%s:\n", area_id);
577 escaped_area_id = escapeLiteral(db, area_id, strlen(area_id));
578 if (!escaped_area_id) {
579 fprintf(stderr, "Could not escape literal in memory.\n");
580 abort();
581 }
582 if (asprintf(&cmd, "SELECT \"member_id\", \"weight\", \"issue_id\" FROM \"issue_supporter_in_admission_state\" WHERE \"area_id\" = %s ORDER BY \"member_id\"", escaped_area_id) < 0) {
583 fprintf(stderr, "Could not prepare query string in memory.\n");
584 abort();
585 }
586 res2 = PQexec(db, cmd);
587 free(cmd);
588 if (!res2) {
589 fprintf(stderr, "Error in pqlib while sending SQL command selecting issue supporter in admission state.\n");
590 err = 1;
591 } else if (PQresultStatus(res2) != PGRES_TUPLES_OK) {
592 fprintf(stderr, "Error while executing SQL command selecting issue supporter in admission state:\n%s", PQresultErrorMessage(res));
593 err = 1;
594 PQclear(res2);
595 } else if (PQnfields(res2) < 3) {
596 fprintf(stderr, "Too few columns returned by SQL command selecting issue supporter in admission state.\n");
597 err = 1;
598 PQclear(res2);
599 } else {
600 if (process_area(db, res2, escaped_area_id)) err = 1;
601 PQclear(res2);
602 }
603 freemem(escaped_area_id);
604 }
605 PQclear(res);
606 }
608 // clean-up entries of deleted issues
609 res = PQexec(db, "DELETE FROM \"issue_order\" USING \"issue_order\" AS \"issue_order2\" NATURAL LEFT JOIN \"issue\" WHERE \"issue_order\".\"id\" = \"issue_order2\".\"id\" AND \"issue\".\"id\" ISNULL");
610 if (!res) {
611 fprintf(stderr, "Error in pqlib while sending SQL command deleting ordering data of deleted issues.\n");
612 err = 1;
613 } else if (
614 PQresultStatus(res) != PGRES_COMMAND_OK &&
615 PQresultStatus(res) != PGRES_TUPLES_OK
616 ) {
617 fprintf(stderr, "Error while executing SQL command deleting ordering data of deleted issues:\n%s", PQresultErrorMessage(res));
618 err = 1;
619 PQclear(res);
620 } else {
621 if (logging) printf("Cleaned up ordering data of %s deleted issues.\n", PQcmdTuples(res));
622 PQclear(res);
623 }
625 // cleanup and exit:
626 PQfinish(db);
627 if (!err) {
628 if (logging) printf("Successfully terminated.\n");
629 } else {
630 fprintf(stderr, "Exiting with error code %i.\n", err);
631 }
632 return err;
634 }

Impressum / About Us