liquid_feedback_core
changeset 394:326dc0c3b859
Calculation of "order_in_admission_state"
author | jbe |
---|---|
date | Fri Oct 11 12:57:03 2013 +0200 (2013-10-11) |
parents | 25fbce923cf2 |
children | d93428e4edad |
files | Makefile lf_update_issue_order.c |
line diff
1.1 --- a/Makefile Thu Oct 10 11:37:50 2013 +0200 1.2 +++ b/Makefile Fri Oct 11 12:57:03 2013 +0200 1.3 @@ -1,4 +1,4 @@ 1.4 -all:: lf_update lf_update_suggestion_order 1.5 +all:: lf_update lf_update_issue_order lf_update_suggestion_order 1.6 1.7 lf_update: lf_update.c 1.8 cc -Wall -O2 \ 1.9 @@ -6,6 +6,12 @@ 1.10 -L "`pg_config --libdir`" \ 1.11 -o lf_update lf_update.c -lpq 1.12 1.13 +lf_update_issue_order: lf_update_issue_order.c 1.14 + cc -Wall -O2 \ 1.15 + -I "`pg_config --includedir`" \ 1.16 + -L "`pg_config --libdir`" \ 1.17 + -o lf_update_issue_order lf_update_issue_order.c -lpq 1.18 + 1.19 lf_update_suggestion_order: lf_update_suggestion_order.c 1.20 cc -Wall -O2 \ 1.21 -I "`pg_config --includedir`" \ 1.22 @@ -13,4 +19,4 @@ 1.23 -o lf_update_suggestion_order lf_update_suggestion_order.c -lpq 1.24 1.25 clean:: 1.26 - rm -f lf_update lf_update_suggestion_order 1.27 + rm -f lf_update lf_update_issue_order lf_update_suggestion_order
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/lf_update_issue_order.c Fri Oct 11 12:57:03 2013 +0200 2.3 @@ -0,0 +1,507 @@ 2.4 +#include <stdlib.h> 2.5 +#include <stdio.h> 2.6 +#include <string.h> 2.7 +#include <libpq-fe.h> 2.8 +#include <search.h> 2.9 + 2.10 +static int logging = 0; 2.11 + 2.12 +static char *escapeLiteral(PGconn *conn, const char *str, size_t len) { 2.13 + // provides compatibility for PostgreSQL versions prior 9.0 2.14 + // in future: return PQescapeLiteral(conn, str, len); 2.15 + char *res; 2.16 + size_t res_len; 2.17 + res = malloc(2*len+3); 2.18 + if (!res) return NULL; 2.19 + res[0] = '\''; 2.20 + res_len = PQescapeStringConn(conn, res+1, str, len, NULL); 2.21 + res[res_len+1] = '\''; 2.22 + res[res_len+2] = 0; 2.23 + return res; 2.24 +} 2.25 + 2.26 +static void freemem(void *ptr) { 2.27 + // to be used for "escapeLiteral" function 2.28 + // provides compatibility for PostgreSQL versions prior 9.0 2.29 + // in future: PQfreemem(ptr); 2.30 + free(ptr); 2.31 +} 2.32 + 2.33 +// column numbers when querying "issue_supporter_in_admission_state" view in function main(): 2.34 +#define COL_MEMBER_ID 0 2.35 +#define COL_WEIGHT 1 2.36 +#define COL_ISSUE_ID 2 2.37 + 2.38 +// data structure for a candidate (in this case a suggestion) to the proportional runoff system: 2.39 +struct candidate { 2.40 + char *key; // identifier of the candidate, which is the "suggestion_id" string 2.41 + double score_per_step; // added score per step 2.42 + double score; // current score of candidate; a score of 1.0 is needed to survive a round 2.43 + int seat; // equals 0 for unseated candidates, or contains rank number 2.44 +}; 2.45 + 2.46 +// compare two integers stored as strings (invocation like strcmp): 2.47 +static int compare_id(char *id1, char *id2) { 2.48 + int ldiff; 2.49 + ldiff = strlen(id1) - strlen(id2); 2.50 + if (ldiff) return ldiff; 2.51 + else return strcmp(id1, id2); 2.52 +} 2.53 + 2.54 +// compare two candidates by their key (invocation like strcmp): 2.55 +static int compare_candidate(struct candidate *c1, struct candidate *c2) { 2.56 + return compare_id(c1->key, c2->key); 2.57 +} 2.58 + 2.59 +// candidates are stored as global variables due to the constrained twalk() interface: 2.60 +static int candidate_count; 2.61 +static struct candidate *candidates; 2.62 + 2.63 +// function to be passed to twalk() to store candidates ordered in candidates[] array: 2.64 +static void register_candidate(char **candidate_key, VISIT visit, int level) { 2.65 + if (visit == postorder || visit == leaf) { 2.66 + struct candidate *candidate; 2.67 + candidate = candidates + (candidate_count++); 2.68 + candidate->key = *candidate_key; 2.69 + candidate->seat = 0; 2.70 + if (logging) printf("Candidate #%i is suggestion #%s.\n", candidate_count, candidate->key); 2.71 + } 2.72 +} 2.73 + 2.74 +// performs a binary search in candidates[] array to lookup a candidate by its key (which is the suggestion_id): 2.75 +static struct candidate *candidate_by_key(char *candidate_key) { 2.76 + struct candidate *candidate; 2.77 + struct candidate compare; 2.78 + compare.key = candidate_key; 2.79 + candidate = bsearch(&compare, candidates, candidate_count, sizeof(struct candidate), (void *)compare_candidate); 2.80 + if (!candidate) { 2.81 + fprintf(stderr, "Candidate not found (should not happen).\n"); 2.82 + abort(); 2.83 + } 2.84 + return candidate; 2.85 +} 2.86 + 2.87 +// ballot of the proportional runoff system: 2.88 +struct ballot { 2.89 + int weight; // if weight is greater than 1, then the ballot is counted multiple times 2.90 + int count; 2.91 + struct candidate **candidates; 2.92 +}; 2.93 + 2.94 +// determine candidate, which is assigned the next seat (starting with the worst rank): 2.95 +static struct candidate *loser(int round_number, struct ballot *ballots, int ballot_count) { 2.96 + int i, j; // index variables for loops 2.97 + int remaining; // remaining candidates to be seated 2.98 + // reset scores of all candidates: 2.99 + for (i=0; i<candidate_count; i++) { 2.100 + candidates[i].score = 0.0; 2.101 + } 2.102 + // calculate remaining candidates to be seated: 2.103 + remaining = candidate_count - round_number; 2.104 + // repeat following loop, as long as there is more than one remaining candidate: 2.105 + while (remaining > 1) { 2.106 + if (logging) printf("There are %i remaining candidates.\n", remaining); 2.107 + double scale; // factor to be later multiplied with score_per_step: 2.108 + // reset score_per_step for all candidates: 2.109 + for (i=0; i<candidate_count; i++) { 2.110 + candidates[i].score_per_step = 0.0; 2.111 + } 2.112 + // calculate score_per_step for all candidates: 2.113 + for (i=0; i<ballot_count; i++) { 2.114 + int matches = 0; 2.115 + for (j=0; j<ballots[i].count; j++) { 2.116 + struct candidate *candidate; 2.117 + candidate = ballots[i].candidates[j]; 2.118 + if (candidate->score < 1.0 && !candidate->seat) matches++; 2.119 + } 2.120 + if (matches) { 2.121 + double score_inc; 2.122 + score_inc = (double)ballots[i].weight / (double)matches; 2.123 + for (j=0; j<ballots[i].count; j++) { 2.124 + struct candidate *candidate; 2.125 + candidate = ballots[i].candidates[j]; 2.126 + if (candidate->score < 1.0 && !candidate->seat) { 2.127 + candidate->score_per_step += score_inc; 2.128 + } 2.129 + } 2.130 + } 2.131 + } 2.132 + // calculate scale factor: 2.133 + scale = (double)0.0; // 0.0 is used to indicate that there is no value yet 2.134 + for (i=0; i<candidate_count; i++) { 2.135 + double max_scale; 2.136 + if (candidates[i].score_per_step > 0.0) { 2.137 + max_scale = (1.0-candidates[i].score) / candidates[i].score_per_step; 2.138 + if (scale == 0.0 || max_scale <= scale) { 2.139 + scale = max_scale; 2.140 + } 2.141 + } 2.142 + } 2.143 + // add scale*score_per_step to each candidates score: 2.144 + for (i=0; i<candidate_count; i++) { 2.145 + int log_candidate = 0; 2.146 + if (logging && candidates[i].score < 1.0 && !candidates[i].seat) log_candidate = 1; 2.147 + if (log_candidate) printf("Score for suggestion #%s = %.4f+%.4f*%.4f", candidates[i].key, candidates[i].score, scale, candidates[i].score_per_step); 2.148 + if (candidates[i].score_per_step > 0.0) { 2.149 + double max_scale; 2.150 + max_scale = (1.0-candidates[i].score) / candidates[i].score_per_step; 2.151 + if (max_scale == scale) { 2.152 + // score of 1.0 should be reached, so we set score directly to avoid floating point errors: 2.153 + candidates[i].score = 1.0; 2.154 + remaining--; 2.155 + } else { 2.156 + candidates[i].score += scale * candidates[i].score_per_step; 2.157 + if (candidates[i].score >= 1.0) remaining--; 2.158 + } 2.159 + } 2.160 + if (log_candidate) { 2.161 + if (candidates[i].score >= 1.0) printf("=1\n"); 2.162 + else printf("=%.4f\n", candidates[i].score); 2.163 + } 2.164 + // when there is only one candidate remaining, then break inner (and thus outer) loop: 2.165 + if (remaining <= 1) { 2.166 + break; 2.167 + } 2.168 + } 2.169 + } 2.170 + // return remaining candidate: 2.171 + for (i=0; i<candidate_count; i++) { 2.172 + if (candidates[i].score < 1.0 && !candidates[i].seat) return candidates+i; 2.173 + } 2.174 + // if there is no remaining candidate, then something went wrong: 2.175 + fprintf(stderr, "No remaining candidate (should not happen)."); 2.176 + abort(); 2.177 +} 2.178 + 2.179 +// write results to database: 2.180 +static int write_ranks(PGconn *db, char *escaped_area_id) { 2.181 + PGresult *res; 2.182 + char *cmd; 2.183 + int i; 2.184 + if (asprintf(&cmd, "BEGIN; UPDATE \"issue\" SET \"order_in_admission_state\" = NULL WHERE \"area_id\" = %s AND (\"state\" = 'admission' OR \"order_in_admission_state\" NOTNULL)", escaped_area_id) < 0) { 2.185 + fprintf(stderr, "Could not prepare query string in memory.\n"); 2.186 + abort(); 2.187 + } 2.188 + res = PQexec(db, cmd); 2.189 + free(cmd); 2.190 + if (!res) { 2.191 + fprintf(stderr, "Error in pqlib while sending SQL command to initiate issue update.\n"); 2.192 + return 1; 2.193 + } else if ( 2.194 + PQresultStatus(res) != PGRES_COMMAND_OK && 2.195 + PQresultStatus(res) != PGRES_TUPLES_OK 2.196 + ) { 2.197 + fprintf(stderr, "Error while executing SQL command to initiate issue update:\n%s", PQresultErrorMessage(res)); 2.198 + PQclear(res); 2.199 + return 1; 2.200 + } else { 2.201 + PQclear(res); 2.202 + } 2.203 + for (i=0; i<candidate_count; i++) { 2.204 + char *escaped_issue_id; 2.205 + escaped_issue_id = escapeLiteral(db, candidates[i].key, strlen(candidates[i].key)); 2.206 + if (!escaped_issue_id) { 2.207 + fprintf(stderr, "Could not escape literal in memory.\n"); 2.208 + abort(); 2.209 + } 2.210 + if (asprintf(&cmd, "UPDATE \"issue\" SET \"order_in_admission_state\" = %i WHERE \"id\" = %s", candidates[i].seat, escaped_issue_id) < 0) { 2.211 + fprintf(stderr, "Could not prepare query string in memory.\n"); 2.212 + abort(); 2.213 + } 2.214 + freemem(escaped_issue_id); 2.215 + res = PQexec(db, cmd); 2.216 + free(cmd); 2.217 + if (!res) { 2.218 + fprintf(stderr, "Error in pqlib while sending SQL command to update issue order.\n"); 2.219 + } else if ( 2.220 + PQresultStatus(res) != PGRES_COMMAND_OK && 2.221 + PQresultStatus(res) != PGRES_TUPLES_OK 2.222 + ) { 2.223 + fprintf(stderr, "Error while executing SQL command to update issue order:\n%s", PQresultErrorMessage(res)); 2.224 + PQclear(res); 2.225 + } else { 2.226 + PQclear(res); 2.227 + continue; 2.228 + } 2.229 + res = PQexec(db, "ROLLBACK"); 2.230 + if (res) PQclear(res); 2.231 + return 1; 2.232 + } 2.233 + res = PQexec(db, "COMMIT"); 2.234 + if (!res) { 2.235 + fprintf(stderr, "Error in pqlib while sending SQL command to commit transaction.\n"); 2.236 + return 1; 2.237 + } else if ( 2.238 + PQresultStatus(res) != PGRES_COMMAND_OK && 2.239 + PQresultStatus(res) != PGRES_TUPLES_OK 2.240 + ) { 2.241 + fprintf(stderr, "Error while executing SQL command to commit transaction:\n%s", PQresultErrorMessage(res)); 2.242 + PQclear(res); 2.243 + return 1; 2.244 + } else { 2.245 + PQclear(res); 2.246 + return 0; 2.247 + } 2.248 +} 2.249 + 2.250 +// calculate ordering of issues in admission state for an area and call write_ranks() to write it to database: 2.251 +static int process_area(PGconn *db, PGresult *res, char *escaped_area_id) { 2.252 + int err; // variable to store an error condition (0 = success) 2.253 + int ballot_count = 1; // number of ballots, must be initiatized to 1, due to loop below 2.254 + struct ballot *ballots; // data structure containing the ballots 2.255 + int i; // index variable for loops 2.256 + // create candidates[] and ballots[] arrays: 2.257 + { 2.258 + void *candidate_tree = NULL; // temporary structure to create a sorted unique list of all candidate keys 2.259 + int tuple_count; // number of tuples returned from the database 2.260 + char *old_member_id = NULL; // old member_id to be able to detect a new ballot in loops 2.261 + struct ballot *ballot; // pointer to current ballot 2.262 + int candidates_in_ballot = 0; // number of candidates in ballot 2.263 + // reset candidate count: 2.264 + candidate_count = 0; 2.265 + // determine number of tuples: 2.266 + tuple_count = PQntuples(res); 2.267 + // trivial case, when there are no tuples: 2.268 + if (!tuple_count) { 2.269 + if (logging) printf("Nothing to do.\n"); 2.270 + return 0; 2.271 + } 2.272 + // calculate ballot_count and generate set of candidate keys (suggestion_id is used as key): 2.273 + for (i=0; i<tuple_count; i++) { 2.274 + char *member_id, *issue_id; 2.275 + member_id = PQgetvalue(res, i, COL_MEMBER_ID); 2.276 + issue_id = PQgetvalue(res, i, COL_ISSUE_ID); 2.277 + if (!candidate_tree || !tfind(issue_id, &candidate_tree, (void *)compare_id)) { 2.278 + candidate_count++; 2.279 + if (!tsearch(issue_id, &candidate_tree, (void *)compare_id)) { 2.280 + fprintf(stderr, "Insufficient memory while inserting into candidate tree.\n"); 2.281 + abort(); 2.282 + } 2.283 + } 2.284 + if (old_member_id && strcmp(old_member_id, member_id)) ballot_count++; 2.285 + old_member_id = member_id; 2.286 + } 2.287 + // allocate memory for candidates[] array: 2.288 + candidates = malloc(candidate_count * sizeof(struct candidate)); 2.289 + if (!candidates) { 2.290 + fprintf(stderr, "Insufficient memory while creating candidate list.\n"); 2.291 + abort(); 2.292 + } 2.293 + // transform tree of candidate keys into sorted array: 2.294 + candidate_count = 0; // needed by register_candidate() 2.295 + twalk(candidate_tree, (void *)register_candidate); 2.296 + // free memory of tree structure (tdestroy() is not available on all platforms): 2.297 + while (candidate_tree) tdelete(*(void **)candidate_tree, &candidate_tree, (void *)compare_id); 2.298 + // allocate memory for ballots[] array: 2.299 + ballots = calloc(ballot_count, sizeof(struct ballot)); 2.300 + if (!ballots) { 2.301 + fprintf(stderr, "Insufficient memory while creating ballot list.\n"); 2.302 + abort(); 2.303 + } 2.304 + // set ballot weights, determine ballot section sizes, and verify preference values: 2.305 + ballot = ballots; 2.306 + old_member_id = NULL; 2.307 + for (i=0; i<tuple_count; i++) { 2.308 + char *member_id; 2.309 + int weight; 2.310 + member_id = PQgetvalue(res, i, COL_MEMBER_ID); 2.311 + weight = (int)strtol(PQgetvalue(res, i, COL_WEIGHT), (char **)NULL, 10); 2.312 + if (weight <= 0) { 2.313 + fprintf(stderr, "Unexpected weight value.\n"); 2.314 + free(ballots); 2.315 + free(candidates); 2.316 + return 1; 2.317 + } 2.318 + if (old_member_id && strcmp(old_member_id, member_id)) ballot++; 2.319 + ballot->weight = weight; 2.320 + ballot->count++; 2.321 + old_member_id = member_id; 2.322 + } 2.323 + // allocate memory for ballot sections: 2.324 + for (i=0; i<ballot_count; i++) { 2.325 + if (ballots[i].count) { 2.326 + ballots[i].candidates = malloc(ballots[i].count * sizeof(struct candidate *)); 2.327 + if (!ballots[i].candidates) { 2.328 + fprintf(stderr, "Insufficient memory while creating ballot section.\n"); 2.329 + abort(); 2.330 + } 2.331 + } 2.332 + } 2.333 + // fill ballot sections with candidate references: 2.334 + old_member_id = NULL; 2.335 + ballot = ballots; 2.336 + for (i=0; i<tuple_count; i++) { 2.337 + char *member_id, *issue_id; 2.338 + member_id = PQgetvalue(res, i, COL_MEMBER_ID); 2.339 + issue_id = PQgetvalue(res, i, COL_ISSUE_ID); 2.340 + if (old_member_id && strcmp(old_member_id, member_id)) { 2.341 + ballot++; 2.342 + candidates_in_ballot = 0; 2.343 + } 2.344 + ballot->candidates[candidates_in_ballot++] = candidate_by_key(issue_id); 2.345 + old_member_id = member_id; 2.346 + } 2.347 + // print ballots, if logging is enabled: 2.348 + if (logging) { 2.349 + for (i=0; i<ballot_count; i++) { 2.350 + int j; 2.351 + printf("Ballot #%i: ", i+1); 2.352 + for (j=0; j<ballots[i].count; j++) { 2.353 + if (!j) printf("issues "); 2.354 + else printf(", "); 2.355 + printf("#%s", ballots[i].candidates[j]->key); 2.356 + } 2.357 + // if (!j) printf("empty"); // should not happen 2.358 + printf(".\n"); 2.359 + } 2.360 + } 2.361 + } 2.362 + 2.363 + // calculate ranks based on constructed data structures: 2.364 + for (i=0; i<candidate_count; i++) { 2.365 + struct candidate *candidate = loser(i, ballots, ballot_count); 2.366 + candidate->seat = candidate_count - i; 2.367 + if (logging) printf("Assigning rank #%i to issue #%s.\n", candidate_count-i, candidate->key); 2.368 + } 2.369 + 2.370 + // free ballots[] array: 2.371 + for (i=0; i<ballot_count; i++) { 2.372 + // if (ballots[i].count) { // count should not be zero 2.373 + free(ballots[i].candidates); 2.374 + // } 2.375 + } 2.376 + free(ballots); 2.377 + 2.378 + // write results to database: 2.379 + if (logging) printf("Writing ranks to database.\n"); 2.380 + err = write_ranks(db, escaped_area_id); 2.381 + if (logging) printf("Done.\n"); 2.382 + 2.383 + // free candidates[] array: 2.384 + free(candidates); 2.385 + 2.386 + // return error code of write_ranks() call 2.387 + return err; 2.388 +} 2.389 + 2.390 +int main(int argc, char **argv) { 2.391 + 2.392 + // variable declarations: 2.393 + int err = 0; 2.394 + int i, count; 2.395 + char *conninfo; 2.396 + PGconn *db; 2.397 + PGresult *res; 2.398 + 2.399 + // parse command line: 2.400 + if (argc == 0) return 1; 2.401 + if (argc == 1 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { 2.402 + FILE *out; 2.403 + out = argc == 1 ? stderr : stdout; 2.404 + fprintf(out, "\n"); 2.405 + fprintf(out, "Usage: %s [-v|--verbose] <conninfo>\n", argv[0]); 2.406 + fprintf(out, "\n"); 2.407 + fprintf(out, "<conninfo> is specified by PostgreSQL's libpq,\n"); 2.408 + fprintf(out, "see http://www.postgresql.org/docs/9.1/static/libpq-connect.html\n"); 2.409 + fprintf(out, "\n"); 2.410 + fprintf(out, "Example: %s dbname=liquid_feedback\n", argv[0]); 2.411 + fprintf(out, "\n"); 2.412 + return argc == 1 ? 1 : 0; 2.413 + } 2.414 + { 2.415 + size_t len = 0; 2.416 + int argb = 1; 2.417 + if ( 2.418 + argc >= 2 && 2.419 + (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--verbose")) 2.420 + ) { 2.421 + argb = 2; 2.422 + logging = 1; 2.423 + } 2.424 + for (i=argb; i<argc; i++) len += strlen(argv[i]) + 1; 2.425 + conninfo = malloc(len * sizeof(char)); 2.426 + if (!conninfo) { 2.427 + fprintf(stderr, "Error: Could not allocate memory for conninfo string.\n"); 2.428 + abort(); 2.429 + } 2.430 + conninfo[0] = 0; 2.431 + for (i=argb; i<argc; i++) { 2.432 + if (i>argb) strcat(conninfo, " "); 2.433 + strcat(conninfo, argv[i]); 2.434 + } 2.435 + } 2.436 + 2.437 + // connect to database: 2.438 + db = PQconnectdb(conninfo); 2.439 + if (!db) { 2.440 + fprintf(stderr, "Error: Could not create database handle.\n"); 2.441 + return 1; 2.442 + } 2.443 + if (PQstatus(db) != CONNECTION_OK) { 2.444 + fprintf(stderr, "Could not open connection:\n%s", PQerrorMessage(db)); 2.445 + return 1; 2.446 + } 2.447 + 2.448 + // go through areas: 2.449 + res = PQexec(db, "SELECT \"id\" FROM \"area\""); 2.450 + if (!res) { 2.451 + fprintf(stderr, "Error in pqlib while sending SQL command selecting areas to process.\n"); 2.452 + err = 1; 2.453 + } else if (PQresultStatus(res) != PGRES_TUPLES_OK) { 2.454 + fprintf(stderr, "Error while executing SQL command selecting areas to process:\n%s", PQresultErrorMessage(res)); 2.455 + err = 1; 2.456 + PQclear(res); 2.457 + } else if (PQnfields(res) < 1) { 2.458 + fprintf(stderr, "Too few columns returned by SQL command selecting areas to process.\n"); 2.459 + err = 1; 2.460 + PQclear(res); 2.461 + } else { 2.462 + count = PQntuples(res); 2.463 + if (logging) printf("Number of areas to process: %i\n", count); 2.464 + for (i=0; i<count; i++) { 2.465 + char *area_id, *escaped_area_id; 2.466 + char *cmd; 2.467 + PGresult *res2; 2.468 + area_id = PQgetvalue(res, i, 0); 2.469 + if (logging) printf("Processing area #%s:\n", area_id); 2.470 + escaped_area_id = escapeLiteral(db, area_id, strlen(area_id)); 2.471 + if (!escaped_area_id) { 2.472 + fprintf(stderr, "Could not escape literal in memory.\n"); 2.473 + abort(); 2.474 + } 2.475 + 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) { 2.476 + fprintf(stderr, "Could not prepare query string in memory.\n"); 2.477 + abort(); 2.478 + } 2.479 + res2 = PQexec(db, cmd); 2.480 + free(cmd); 2.481 + if (!res2) { 2.482 + fprintf(stderr, "Error in pqlib while sending SQL command selecting issue supporter in admission state.\n"); 2.483 + err = 1; 2.484 + } else if (PQresultStatus(res2) != PGRES_TUPLES_OK) { 2.485 + fprintf(stderr, "Error while executing SQL command selecting issue supporter in admission state:\n%s", PQresultErrorMessage(res)); 2.486 + err = 1; 2.487 + PQclear(res2); 2.488 + } else if (PQnfields(res2) < 3) { 2.489 + fprintf(stderr, "Too few columns returned by SQL command selecting issue supporter in admission state.\n"); 2.490 + err = 1; 2.491 + PQclear(res2); 2.492 + } else { 2.493 + if (process_area(db, res2, escaped_area_id)) err = 1; 2.494 + PQclear(res2); 2.495 + } 2.496 + freemem(escaped_area_id); 2.497 + } 2.498 + PQclear(res); 2.499 + } 2.500 + 2.501 + // cleanup and exit: 2.502 + PQfinish(db); 2.503 + if (!err) { 2.504 + if (logging) printf("Successfully terminated.\n"); 2.505 + } else { 2.506 + fprintf(stderr, "Exiting with error code %i.\n", err); 2.507 + } 2.508 + return err; 2.509 + 2.510 +}