liquid_feedback_core
view lf_update_issue_order.c @ 491:aa94c7dbb20f
New function "get_initiatives_for_notification"
| author | jbe | 
|---|---|
| date | Sun Apr 03 19:42:09 2016 +0200 (2016-04-03) | 
| parents | d301dc24b25c | 
| children | 82387194519b | 
 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 an issue) to the proportional runoff system:
    36 struct candidate {
    37   char *key;              // identifier of the candidate, which is the "issue_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 issue #%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 issue_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 // determine candidate, which is assigned the next seat (starting with the worst rank):
    92 static struct candidate *loser(int round_number, struct ballot *ballots, int ballot_count) {
    93   int i, j;       // index variables for loops
    94   int remaining;  // remaining candidates to be seated
    95   // reset scores of all candidates:
    96   for (i=0; i<candidate_count; i++) {
    97     candidates[i].score = 0.0;
    98   }
    99   // calculate remaining candidates to be seated:
   100   remaining = candidate_count - round_number;
   101   // repeat following loop, as long as there is more than one remaining candidate:
   102   while (remaining > 1) {
   103     if (logging) printf("There are %i remaining candidates.\n", remaining);
   104     double scale;  // factor to be later multiplied with score_per_step:
   105     // reset score_per_step for all candidates:
   106     for (i=0; i<candidate_count; i++) {
   107       candidates[i].score_per_step = 0.0;
   108     }
   109     // calculate score_per_step for all candidates:
   110     for (i=0; i<ballot_count; i++) {
   111       int matches = 0;
   112       for (j=0; j<ballots[i].count; j++) {
   113         struct candidate *candidate;
   114         candidate = ballots[i].candidates[j];
   115         if (candidate->score < 1.0 && !candidate->seat) matches++;
   116       }
   117       if (matches) {
   118         double score_inc;
   119         score_inc = (double)ballots[i].weight / (double)matches;
   120         for (j=0; j<ballots[i].count; j++) {
   121           struct candidate *candidate;
   122           candidate = ballots[i].candidates[j];
   123           if (candidate->score < 1.0 && !candidate->seat) {
   124             candidate->score_per_step += score_inc;
   125           }
   126         }
   127       }
   128     }
   129     // calculate scale factor:
   130     scale = (double)0.0;  // 0.0 is used to indicate that there is no value yet
   131     for (i=0; i<candidate_count; i++) {
   132       double max_scale;
   133       if (candidates[i].score_per_step > 0.0) {
   134         max_scale = (1.0-candidates[i].score) / candidates[i].score_per_step;
   135         if (scale == 0.0 || max_scale <= scale) {
   136           scale = max_scale;
   137         }
   138       }
   139     }
   140     // add scale*score_per_step to each candidates score:
   141     for (i=0; i<candidate_count; i++) {
   142       int log_candidate = 0;
   143       if (logging && candidates[i].score < 1.0 && !candidates[i].seat) log_candidate = 1;
   144       if (log_candidate) printf("Score for issue #%s = %.4f+%.4f*%.4f", candidates[i].key, candidates[i].score, scale, candidates[i].score_per_step);
   145       if (candidates[i].score_per_step > 0.0) {
   146         double max_scale;
   147         max_scale = (1.0-candidates[i].score) / candidates[i].score_per_step;
   148         if (max_scale == scale) {
   149           // score of 1.0 should be reached, so we set score directly to avoid floating point errors:
   150           candidates[i].score = 1.0;
   151           remaining--;
   152         } else {
   153           candidates[i].score += scale * candidates[i].score_per_step;
   154           if (candidates[i].score >= 1.0) remaining--;
   155         }
   156       }
   157       if (log_candidate) {
   158         if (candidates[i].score >= 1.0) printf("=1\n");
   159         else printf("=%.4f\n", candidates[i].score);
   160       }
   161       // when there is only one candidate remaining, then break inner (and thus outer) loop:
   162       if (remaining <= 1) {
   163         break;
   164       }
   165     }
   166   }
   167   // return remaining candidate:
   168   for (i=0; i<candidate_count; i++) {
   169     if (candidates[i].score < 1.0 && !candidates[i].seat) return candidates+i;
   170   }
   171   // if there is no remaining candidate, then something went wrong:
   172   fprintf(stderr, "No remaining candidate (should not happen).");
   173   abort();
   174 }
   176 // write results to database:
   177 static int write_ranks(PGconn *db, char *escaped_area_or_unit_id, char *mode) {
   178   PGresult *res;
   179   char *cmd;
   180   int i;
   181   res = PQexec(db, "BEGIN");
   182   if (!res) {
   183     fprintf(stderr, "Error in pqlib while sending SQL command to initiate issue order update.\n");
   184     return 1;
   185   } else if (
   186     PQresultStatus(res) != PGRES_COMMAND_OK &&
   187     PQresultStatus(res) != PGRES_TUPLES_OK
   188   ) {
   189     fprintf(stderr, "Error while executing SQL command to initiate issue order update:\n%s", PQresultErrorMessage(res));
   190     PQclear(res);
   191     return 1;
   192   } else {
   193     PQclear(res);
   194   }
   195   for (i=0; i<candidate_count; i++) {
   196     char *escaped_issue_id;
   197     escaped_issue_id = escapeLiteral(db, candidates[i].key, strlen(candidates[i].key));
   198     if (!escaped_issue_id) {
   199       fprintf(stderr, "Could not escape literal in memory.\n");
   200       abort();
   201     }
   202     if (asprintf(&cmd, "UPDATE \"issue_order_in_admission_state\" SET \"order_in_%s\" = %i WHERE \"id\" = %s", mode, candidates[i].seat, escaped_issue_id) < 0) {
   203       fprintf(stderr, "Could not prepare query string in memory.\n");
   204       abort();
   205     }
   206     freemem(escaped_issue_id);
   207     res = PQexec(db, cmd);
   208     free(cmd);
   209     if (!res) {
   210       fprintf(stderr, "Error in pqlib while sending SQL command to update issue order.\n");
   211     } else if (
   212       PQresultStatus(res) != PGRES_COMMAND_OK &&
   213       PQresultStatus(res) != PGRES_TUPLES_OK
   214     ) {
   215       fprintf(stderr, "Error while executing SQL command to update issue order:\n%s", PQresultErrorMessage(res));
   216       PQclear(res);
   217     } else {
   218       PQclear(res);
   219       continue;
   220     }
   221     res = PQexec(db, "ROLLBACK");
   222     if (res) PQclear(res);
   223     return 1;
   224   }
   225   res = PQexec(db, "COMMIT");
   226   if (!res) {
   227     fprintf(stderr, "Error in pqlib while sending SQL command to commit transaction.\n");
   228     return 1;
   229   } else if (
   230     PQresultStatus(res) != PGRES_COMMAND_OK &&
   231     PQresultStatus(res) != PGRES_TUPLES_OK
   232   ) {
   233     fprintf(stderr, "Error while executing SQL command to commit transaction:\n%s", PQresultErrorMessage(res));
   234     PQclear(res);
   235     return 1;
   236   } else {
   237     PQclear(res);
   238   }
   239   return 0;
   240 }
   242 // calculate ordering of issues in admission state for an area and call write_ranks() to write it to database:
   243 static int process_area_or_unit(PGconn *db, PGresult *res, char *escaped_area_or_unit_id, char *mode) {
   244   int err;                 // variable to store an error condition (0 = success)
   245   int ballot_count = 1;    // number of ballots, must be initiatized to 1, due to loop below
   246   struct ballot *ballots;  // data structure containing the ballots
   247   int i;                   // index variable for loops
   248   // create candidates[] and ballots[] arrays:
   249   {
   250     void *candidate_tree = NULL;  // temporary structure to create a sorted unique list of all candidate keys
   251     int tuple_count;              // number of tuples returned from the database
   252     char *old_member_id = NULL;   // old member_id to be able to detect a new ballot in loops
   253     struct ballot *ballot;        // pointer to current ballot
   254     int candidates_in_ballot = 0; // number of candidates in ballot
   255     // reset candidate count:
   256     candidate_count = 0;
   257     // determine number of tuples:
   258     tuple_count = PQntuples(res);
   259     // trivial case, when there are no tuples:
   260     if (!tuple_count) {
   261       // write results to database:
   262       if (logging) printf("No supporters for any issue. Writing ranks to database.\n");
   263       err = write_ranks(db, escaped_area_or_unit_id, mode);
   264       if (logging) printf("Done.\n");
   265       return 0;
   266     }
   267     // calculate ballot_count and generate set of candidate keys (issue_id is used as key):
   268     for (i=0; i<tuple_count; i++) {
   269       char *member_id, *issue_id;
   270       member_id = PQgetvalue(res, i, COL_MEMBER_ID);
   271       issue_id = PQgetvalue(res, i, COL_ISSUE_ID);
   272       if (!candidate_tree || !tfind(issue_id, &candidate_tree, (void *)compare_id)) {
   273         candidate_count++;
   274         if (!tsearch(issue_id, &candidate_tree, (void *)compare_id)) {
   275           fprintf(stderr, "Insufficient memory while inserting into candidate tree.\n");
   276           abort();
   277         }
   278       }
   279       if (old_member_id && strcmp(old_member_id, member_id)) ballot_count++;
   280       old_member_id = member_id;
   281     }
   282     // allocate memory for candidates[] array:
   283     candidates = malloc(candidate_count * sizeof(struct candidate));
   284     if (!candidates) {
   285       fprintf(stderr, "Insufficient memory while creating candidate list.\n");
   286       abort();
   287     }
   288     // transform tree of candidate keys into sorted array:
   289     candidate_count = 0;  // needed by register_candidate()
   290     twalk(candidate_tree, (void *)register_candidate);
   291     // free memory of tree structure (tdestroy() is not available on all platforms):
   292     while (candidate_tree) tdelete(*(void **)candidate_tree, &candidate_tree, (void *)compare_id);
   293     // allocate memory for ballots[] array:
   294     ballots = calloc(ballot_count, sizeof(struct ballot));
   295     if (!ballots) {
   296       fprintf(stderr, "Insufficient memory while creating ballot list.\n");
   297       abort();
   298     }
   299     // set ballot weights, determine ballot section sizes, and verify preference values:
   300     ballot = ballots;
   301     old_member_id = NULL;
   302     for (i=0; i<tuple_count; i++) {
   303       char *member_id;
   304       int weight;
   305       member_id = PQgetvalue(res, i, COL_MEMBER_ID);
   306       weight = (int)strtol(PQgetvalue(res, i, COL_WEIGHT), (char **)NULL, 10);
   307       if (weight <= 0) {
   308         fprintf(stderr, "Unexpected weight value.\n");
   309         free(ballots);
   310         free(candidates);
   311         return 1;
   312       }
   313       if (old_member_id && strcmp(old_member_id, member_id)) ballot++;
   314       ballot->weight = weight;
   315       ballot->count++;
   316       old_member_id = member_id;
   317     }
   318     // allocate memory for ballot sections:
   319     for (i=0; i<ballot_count; i++) {
   320       if (ballots[i].count) {
   321         ballots[i].candidates = malloc(ballots[i].count * sizeof(struct candidate *));
   322         if (!ballots[i].candidates) {
   323           fprintf(stderr, "Insufficient memory while creating ballot section.\n");
   324           abort();
   325         }
   326       }
   327     }
   328     // fill ballot sections with candidate references:
   329     old_member_id = NULL;
   330     ballot = ballots;
   331     for (i=0; i<tuple_count; i++) {
   332       char *member_id, *issue_id;
   333       member_id = PQgetvalue(res, i, COL_MEMBER_ID);
   334       issue_id = PQgetvalue(res, i, COL_ISSUE_ID);
   335       if (old_member_id && strcmp(old_member_id, member_id)) {
   336         ballot++;
   337         candidates_in_ballot = 0;
   338       }
   339       ballot->candidates[candidates_in_ballot++] = candidate_by_key(issue_id);
   340       old_member_id = member_id;
   341     }
   342     // print ballots, if logging is enabled:
   343     if (logging) {
   344       for (i=0; i<ballot_count; i++) {
   345         int j;
   346         printf("Ballot #%i: ", i+1);
   347         for (j=0; j<ballots[i].count; j++) {
   348           if (!j) printf("issues ");
   349           else printf(", ");
   350           printf("#%s", ballots[i].candidates[j]->key);
   351         }
   352         // if (!j) printf("empty");  // should not happen
   353         printf(".\n");
   354       }
   355     }
   356   }
   358   // calculate ranks based on constructed data structures:
   359   for (i=0; i<candidate_count; i++) {
   360     struct candidate *candidate = loser(i, ballots, ballot_count);
   361     candidate->seat = candidate_count - i;
   362     if (logging) printf("Assigning rank #%i to issue #%s.\n", candidate_count-i, candidate->key);
   363   }
   365   // free ballots[] array:
   366   for (i=0; i<ballot_count; i++) {
   367     // if (ballots[i].count) {  // count should not be zero
   368       free(ballots[i].candidates);
   369     // }
   370   }
   371   free(ballots);
   373   // write results to database:
   374   if (logging) printf("Writing ranks to database.\n");
   375   err = write_ranks(db, escaped_area_or_unit_id, mode);
   376   if (logging) printf("Done.\n");
   378   // free candidates[] array:
   379   free(candidates);
   381   // return error code of write_ranks() call
   382   return err;
   383 }
   385 int main(int argc, char **argv) {
   387   // variable declarations:
   388   int err = 0;
   389   int i, count;
   390   char *conninfo;
   391   PGconn *db;
   392   PGresult *res;
   394   // parse command line:
   395   if (argc == 0) return 1;
   396   if (argc == 1 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
   397     FILE *out;
   398     out = argc == 1 ? stderr : stdout;
   399     fprintf(out, "\n");
   400     fprintf(out, "Usage: %s [-v|--verbose] <conninfo>\n", argv[0]);
   401     fprintf(out, "\n");
   402     fprintf(out, "<conninfo> is specified by PostgreSQL's libpq,\n");
   403     fprintf(out, "see http://www.postgresql.org/docs/9.1/static/libpq-connect.html\n");
   404     fprintf(out, "\n");
   405     fprintf(out, "Example: %s dbname=liquid_feedback\n", argv[0]);
   406     fprintf(out, "\n");
   407     return argc == 1 ? 1 : 0;
   408   }
   409   {
   410     size_t len = 0;
   411     int argb = 1;
   412     if (
   413       argc >= 2 &&
   414       (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--verbose"))
   415     ) {
   416       argb = 2;
   417       logging = 1;
   418     }
   419     for (i=argb; i<argc; i++) len += strlen(argv[i]) + 1;
   420     conninfo = malloc(len * sizeof(char));
   421     if (!conninfo) {
   422       fprintf(stderr, "Error: Could not allocate memory for conninfo string.\n");
   423       abort();
   424     }
   425     conninfo[0] = 0;
   426     for (i=argb; i<argc; i++) {
   427       if (i>argb) strcat(conninfo, " ");
   428       strcat(conninfo, argv[i]);
   429     }
   430   }
   432   // connect to database:
   433   db = PQconnectdb(conninfo);
   434   if (!db) {
   435     fprintf(stderr, "Error: Could not create database handle.\n");
   436     return 1;
   437   }
   438   if (PQstatus(db) != CONNECTION_OK) {
   439     fprintf(stderr, "Could not open connection:\n%s", PQerrorMessage(db));
   440     return 1;
   441   }
   443   // create missing "issue_order_in_admission_state" entries for issues
   444   res = PQexec(db, "INSERT INTO \"issue_order_in_admission_state\" (\"id\") SELECT \"issue\".\"id\" FROM \"issue\" NATURAL LEFT JOIN \"issue_order_in_admission_state\" WHERE \"issue\".\"state\" = 'admission'::\"issue_state\" AND \"issue_order_in_admission_state\".\"id\" ISNULL");
   445   if (!res) {
   446     fprintf(stderr, "Error in pqlib while sending SQL command creating new issue order entries.\n");
   447     err = 1;
   448   } else if (
   449     PQresultStatus(res) != PGRES_COMMAND_OK &&
   450     PQresultStatus(res) != PGRES_TUPLES_OK
   451   ) {
   452     fprintf(stderr, "Error while executing SQL command creating new issue order entries:\n%s", PQresultErrorMessage(res));
   453     err = 1;
   454     PQclear(res);
   455   } else {
   456     if (logging) printf("Created %s new issue order entries.\n", PQcmdTuples(res));
   457     PQclear(res);
   458   }
   460   // go through areas:
   461   res = PQexec(db, "SELECT \"id\" FROM \"area\"");
   462   if (!res) {
   463     fprintf(stderr, "Error in pqlib while sending SQL command selecting areas to process.\n");
   464     err = 1;
   465   } else if (PQresultStatus(res) != PGRES_TUPLES_OK) {
   466     fprintf(stderr, "Error while executing SQL command selecting areas to process:\n%s", PQresultErrorMessage(res));
   467     err = 1;
   468     PQclear(res);
   469   } else if (PQnfields(res) < 1) {
   470     fprintf(stderr, "Too few columns returned by SQL command selecting areas to process.\n");
   471     err = 1;
   472     PQclear(res);
   473   } else {
   474     count = PQntuples(res);
   475     if (logging) printf("Number of areas to process: %i\n", count);
   476     for (i=0; i<count; i++) {
   477       char *area_id, *escaped_area_id;
   478       char *cmd;
   479       PGresult *res2;
   480       area_id = PQgetvalue(res, i, 0);
   481       if (logging) printf("Processing area #%s:\n", area_id);
   482       escaped_area_id = escapeLiteral(db, area_id, strlen(area_id));
   483       if (!escaped_area_id) {
   484         fprintf(stderr, "Could not escape literal in memory.\n");
   485         abort();
   486       }
   487       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) {
   488         fprintf(stderr, "Could not prepare query string in memory.\n");
   489         abort();
   490       }
   491       res2 = PQexec(db, cmd);
   492       free(cmd);
   493       if (!res2) {
   494         fprintf(stderr, "Error in pqlib while sending SQL command selecting issue supporter in admission state.\n");
   495         err = 1;
   496       } else if (PQresultStatus(res2) != PGRES_TUPLES_OK) {
   497         fprintf(stderr, "Error while executing SQL command selecting issue supporter in admission state:\n%s", PQresultErrorMessage(res));
   498         err = 1;
   499         PQclear(res2);
   500       } else if (PQnfields(res2) < 3) {
   501         fprintf(stderr, "Too few columns returned by SQL command selecting issue supporter in admission state.\n");
   502         err = 1;
   503         PQclear(res2);
   504       } else {
   505         if (process_area_or_unit(db, res2, escaped_area_id, "area")) err = 1;
   506         PQclear(res2);
   507       }
   508       freemem(escaped_area_id);
   509     }
   510     PQclear(res);
   511   }
   513   // go through units:
   514   res = PQexec(db, "SELECT \"id\" FROM \"unit\"");
   515   if (!res) {
   516     fprintf(stderr, "Error in pqlib while sending SQL command selecting units to process.\n");
   517     err = 1;
   518   } else if (PQresultStatus(res) != PGRES_TUPLES_OK) {
   519     fprintf(stderr, "Error while executing SQL command selecting units to process:\n%s", PQresultErrorMessage(res));
   520     err = 1;
   521     PQclear(res);
   522   } else if (PQnfields(res) < 1) {
   523     fprintf(stderr, "Too few columns returned by SQL command selecting units to process.\n");
   524     err = 1;
   525     PQclear(res);
   526   } else {
   527     count = PQntuples(res);
   528     if (logging) printf("Number of units to process: %i\n", count);
   529     for (i=0; i<count; i++) {
   530       char *unit_id, *escaped_unit_id;
   531       char *cmd;
   532       PGresult *res2;
   533       unit_id = PQgetvalue(res, i, 0);
   534       if (logging) printf("Processing unit #%s:\n", unit_id);
   535       escaped_unit_id = escapeLiteral(db, unit_id, strlen(unit_id));
   536       if (!escaped_unit_id) {
   537         fprintf(stderr, "Could not escape literal in memory.\n");
   538         abort();
   539       }
   540       if (asprintf(&cmd, "SELECT \"member_id\", \"weight\", \"issue_id\" FROM \"issue_supporter_in_admission_state\" WHERE \"unit_id\" = %s ORDER BY \"member_id\"", escaped_unit_id) < 0) {
   541         fprintf(stderr, "Could not prepare query string in memory.\n");
   542         abort();
   543       }
   544       res2 = PQexec(db, cmd);
   545       free(cmd);
   546       if (!res2) {
   547         fprintf(stderr, "Error in pqlib while sending SQL command selecting issue supporter in admission state.\n");
   548         err = 1;
   549       } else if (PQresultStatus(res2) != PGRES_TUPLES_OK) {
   550         fprintf(stderr, "Error while executing SQL command selecting issue supporter in admission state:\n%s", PQresultErrorMessage(res));
   551         err = 1;
   552         PQclear(res2);
   553       } else if (PQnfields(res2) < 3) {
   554         fprintf(stderr, "Too few columns returned by SQL command selecting issue supporter in admission state.\n");
   555         err = 1;
   556         PQclear(res2);
   557       } else {
   558         if (process_area_or_unit(db, res2, escaped_unit_id, "unit")) err = 1;
   559         PQclear(res2);
   560       }
   561       freemem(escaped_unit_id);
   562     }
   563     PQclear(res);
   564   }
   566   // clean-up entries of deleted issues
   567   res = PQexec(db, "DELETE FROM \"issue_order_in_admission_state\" USING \"issue_order_in_admission_state\" AS \"self\" NATURAL LEFT JOIN \"issue\" WHERE \"issue_order_in_admission_state\".\"id\" = \"self\".\"id\" AND (\"issue\".\"id\" ISNULL OR \"issue\".\"state\" != 'admission'::\"issue_state\")");
   568   if (!res) {
   569     fprintf(stderr, "Error in pqlib while sending SQL command deleting ordering data of deleted issues.\n");
   570     err = 1;
   571   } else if (
   572     PQresultStatus(res) != PGRES_COMMAND_OK &&
   573     PQresultStatus(res) != PGRES_TUPLES_OK
   574   ) {
   575     fprintf(stderr, "Error while executing SQL command deleting ordering data of deleted issues:\n%s", PQresultErrorMessage(res));
   576     err = 1;
   577     PQclear(res);
   578   } else {
   579     if (logging) printf("Cleaned up ordering data of %s deleted issues.\n", PQcmdTuples(res));
   580     PQclear(res);
   581   }
   583   // cleanup and exit:
   584   PQfinish(db);
   585   if (!err) {
   586     if (logging) printf("Successfully terminated.\n");
   587   } else {
   588     fprintf(stderr, "Exiting with error code %i.\n", err);
   589   }
   590   return err;
   592 }
