liquid_feedback_core
diff lf_update.c @ 532:5855ff9e5c8f
Several changes/additions for upcoming major release
- OAuth 2.0 support
- storing profiles as JSON document
- removed subject area membership
- revised snapshot system
- additional issue limiter (dynamic quorum in subject area)
- extended event logging in "event" table
- OAuth 2.0 support
- storing profiles as JSON document
- removed subject area membership
- revised snapshot system
- additional issue limiter (dynamic quorum in subject area)
- extended event logging in "event" table
| author | jbe |
|---|---|
| date | Thu Mar 30 19:42:38 2017 +0200 (2017-03-30) |
| parents | 3f7a89ad996d |
| children | 48ea1c309928 |
line diff
1.1 --- a/lf_update.c Sun Aug 21 17:31:44 2016 +0200 1.2 +++ b/lf_update.c Thu Mar 30 19:42:38 2017 +0200 1.3 @@ -1,33 +1,51 @@ 1.4 #include <stdlib.h> 1.5 #include <stdio.h> 1.6 #include <string.h> 1.7 +#include <stdint.h> 1.8 #include <libpq-fe.h> 1.9 1.10 -static char *escapeLiteral(PGconn *conn, const char *str, size_t len) { 1.11 - // provides compatibility for PostgreSQL versions prior 9.0 1.12 - // in future: return PQescapeLiteral(conn, str, len); 1.13 - char *res; 1.14 - size_t res_len; 1.15 - res = malloc(2*len+3); 1.16 - if (!res) return NULL; 1.17 - res[0] = '\''; 1.18 - res_len = PQescapeStringConn(conn, res+1, str, len, NULL); 1.19 - res[res_len+1] = '\''; 1.20 - res[res_len+2] = 0; 1.21 - return res; 1.22 -} 1.23 +#define exec_sql_error(message) do { \ 1.24 + fprintf(stderr, message ": %s\n%s", command, PQresultErrorMessage(res)); \ 1.25 + goto exec_sql_error_clear; \ 1.26 + } while (0) 1.27 1.28 -static void freemem(void *ptr) { 1.29 - // to be used for "escapeLiteral" function 1.30 - // provides compatibility for PostgreSQL versions prior 9.0 1.31 - // in future: PQfreemem(ptr); 1.32 - free(ptr); 1.33 +int exec_sql(PGconn *db, PGresult **resptr, int *errptr, int onerow, char *command) { 1.34 + int count = 0; 1.35 + PGresult *res = PQexec(db, command); 1.36 + if (!res) { 1.37 + fprintf(stderr, "Error in pqlib while sending the following SQL command: %s\n", command); 1.38 + goto exec_sql_error_exit; 1.39 + } 1.40 + if ( 1.41 + PQresultStatus(res) != PGRES_COMMAND_OK && 1.42 + PQresultStatus(res) != PGRES_TUPLES_OK 1.43 + ) exec_sql_error("Error while executing the following SQL command"); 1.44 + if (resptr) { 1.45 + if (PQresultStatus(res) != PGRES_TUPLES_OK) exec_sql_error("The following SQL command returned no result"); 1.46 + count = PQntuples(res); 1.47 + if (count < 0) exec_sql_error("The following SQL command returned too many rows"); 1.48 + if (onerow) { 1.49 + if (count < 1) exec_sql_error("The following SQL command returned less than one row"); 1.50 + else if (count > 1) exec_sql_error("The following SQL command returned more than one row"); 1.51 + } 1.52 + *resptr = res; 1.53 + } else { 1.54 + PQclear(res); 1.55 + } 1.56 + return count; 1.57 + exec_sql_error_clear: 1.58 + PQclear(res); 1.59 + exec_sql_error_exit: 1.60 + if (resptr) *resptr = NULL; 1.61 + if (errptr) *errptr = 1; 1.62 + return -1; 1.63 } 1.64 1.65 int main(int argc, char **argv) { 1.66 1.67 // variable declarations: 1.68 - int err = 0; 1.69 + int err = 0; /* set to 1 if any error occured */ 1.70 + int admission_failed = 0; /* set to 1 if error occurred during admission */ 1.71 int i, count; 1.72 char *conninfo; 1.73 PGconn *db; 1.74 @@ -42,15 +60,22 @@ 1.75 fprintf(out, "Usage: %s <conninfo>\n", argv[0]); 1.76 fprintf(out, "\n"); 1.77 fprintf(out, "<conninfo> is specified by PostgreSQL's libpq,\n"); 1.78 - fprintf(out, "see http://www.postgresql.org/docs/9.1/static/libpq-connect.html\n"); 1.79 + fprintf(out, "see http://www.postgresql.org/docs/9.6/static/libpq-connect.html\n"); 1.80 fprintf(out, "\n"); 1.81 fprintf(out, "Example: %s dbname=liquid_feedback\n", argv[0]); 1.82 fprintf(out, "\n"); 1.83 return argc == 1 ? 1 : 0; 1.84 } 1.85 { 1.86 - size_t len = 0; 1.87 - for (i=1; i<argc; i++) len += strlen(argv[i]) + 1; 1.88 + size_t len = 0, seglen; 1.89 + for (i=1; i<argc; i++) { 1.90 + seglen = strlen(argv[i]) + 1; 1.91 + if (seglen >= SIZE_MAX/2 || len >= SIZE_MAX/2) { 1.92 + fprintf(stderr, "Error: Command line arguments too long\n"); 1.93 + return 1; 1.94 + } 1.95 + len += seglen; 1.96 + } 1.97 conninfo = malloc(len * sizeof(char)); 1.98 if (!conninfo) { 1.99 fprintf(stderr, "Error: Could not allocate memory for conninfo string\n"); 1.100 @@ -75,139 +100,179 @@ 1.101 } 1.102 1.103 // delete expired sessions: 1.104 - res = PQexec(db, "DELETE FROM \"expired_session\""); 1.105 - if (!res) { 1.106 - fprintf(stderr, "Error in pqlib while sending SQL command deleting expired sessions\n"); 1.107 - err = 1; 1.108 - } else if ( 1.109 - PQresultStatus(res) != PGRES_COMMAND_OK && 1.110 - PQresultStatus(res) != PGRES_TUPLES_OK 1.111 - ) { 1.112 - fprintf(stderr, "Error while executing SQL command deleting expired sessions:\n%s", PQresultErrorMessage(res)); 1.113 - err = 1; 1.114 - PQclear(res); 1.115 - } else { 1.116 - PQclear(res); 1.117 - } 1.118 + exec_sql(db, NULL, &err, 0, "DELETE FROM \"expired_session\""); 1.119 + 1.120 + // delete expired tokens and authorization codes: 1.121 + exec_sql(db, NULL, &err, 0, "DELETE FROM \"expired_token\""); 1.122 + 1.123 + // delete expired snapshots: 1.124 + exec_sql(db, NULL, &err, 0, "DELETE FROM \"expired_snapshot\""); 1.125 1.126 // check member activity: 1.127 - res = PQexec(db, "SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT \"check_activity\"()"); 1.128 - if (!res) { 1.129 - fprintf(stderr, "Error in pqlib while sending SQL command checking member activity\n"); 1.130 - err = 1; 1.131 - } else if ( 1.132 - PQresultStatus(res) != PGRES_COMMAND_OK && 1.133 - PQresultStatus(res) != PGRES_TUPLES_OK 1.134 - ) { 1.135 - fprintf(stderr, "Error while executing SQL command checking member activity:\n%s", PQresultErrorMessage(res)); 1.136 - err = 1; 1.137 - PQclear(res); 1.138 - } else { 1.139 - PQclear(res); 1.140 - } 1.141 + exec_sql(db, NULL, &err, 0, "SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT \"check_activity\"()"); 1.142 1.143 // calculate member counts: 1.144 - res = PQexec(db, "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; SELECT \"calculate_member_counts\"()"); 1.145 - if (!res) { 1.146 - fprintf(stderr, "Error in pqlib while sending SQL command calculating member counts\n"); 1.147 - err = 1; 1.148 - } else if ( 1.149 - PQresultStatus(res) != PGRES_COMMAND_OK && 1.150 - PQresultStatus(res) != PGRES_TUPLES_OK 1.151 - ) { 1.152 - fprintf(stderr, "Error while executing SQL command calculating member counts:\n%s", PQresultErrorMessage(res)); 1.153 - err = 1; 1.154 - PQclear(res); 1.155 - } else { 1.156 + exec_sql(db, NULL, &err, 0, "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; SELECT \"calculate_member_counts\"()"); 1.157 + 1.158 + // issue admission: 1.159 + count = exec_sql(db, &res, &err, 0, "SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT \"id\" FROM \"area_with_unaccepted_issues\""); 1.160 + if (!res) admission_failed = 1; 1.161 + else { 1.162 + char *area_id, *escaped_area_id, *cmd; 1.163 + PGresult *res2; 1.164 + for (i=0; i<count; i++) { 1.165 + area_id = PQgetvalue(res, i, 0); 1.166 + escaped_area_id = PQescapeLiteral(db, area_id, strlen(area_id)); 1.167 + if (!escaped_area_id) { 1.168 + fprintf(stderr, "Could not escape literal in memory.\n"); 1.169 + err = admission_failed = 1; 1.170 + continue; 1.171 + } 1.172 + if (asprintf(&cmd, "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; SELECT \"take_snapshot\"(NULL, %s)", escaped_area_id) < 0) { 1.173 + fprintf(stderr, "Could not prepare query string in memory.\n"); 1.174 + err = admission_failed = 1; 1.175 + PQfreemem(escaped_area_id); 1.176 + continue; 1.177 + } 1.178 + exec_sql(db, &res2, &err, 1, cmd); 1.179 + free(cmd); 1.180 + if (!res2) admission_failed = 1; 1.181 + else { 1.182 + char *snapshot_id, *escaped_snapshot_id; 1.183 + int j, count2; 1.184 + snapshot_id = PQgetvalue(res, 0, 0); 1.185 + escaped_snapshot_id = PQescapeLiteral(db, snapshot_id, strlen(snapshot_id)); 1.186 + PQclear(res2); 1.187 + if (!escaped_snapshot_id) { 1.188 + fprintf(stderr, "Could not escape literal in memory.\n"); 1.189 + err = admission_failed = 1; 1.190 + goto area_admission_cleanup; 1.191 + } 1.192 + if (asprintf(&cmd, "SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT \"issue_id\" FROM \"snapshot_issue\" WHERE \"snapshot_id\" = %s", escaped_snapshot_id) < 0) { 1.193 + fprintf(stderr, "Could not prepare query string in memory.\n"); 1.194 + err = admission_failed = 1; 1.195 + PQfreemem(escaped_snapshot_id); 1.196 + goto area_admission_cleanup; 1.197 + } 1.198 + PQfreemem(escaped_snapshot_id); 1.199 + count2 = exec_sql(db, &res2, &err, 0, cmd); 1.200 + free(cmd); 1.201 + if (!res2) admission_failed = 1; 1.202 + else { 1.203 + char *issue_id, *escaped_issue_id; 1.204 + for (j=0; j<count2; j++) { 1.205 + issue_id = PQgetvalue(res2, j, 0); 1.206 + escaped_issue_id = PQescapeLiteral(db, issue_id, strlen(issue_id)); 1.207 + if (!escaped_issue_id) { 1.208 + fprintf(stderr, "Could not escape literal in memory.\n"); 1.209 + err = admission_failed = 1; 1.210 + continue; 1.211 + } 1.212 + if (asprintf(&cmd, "SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT \"finish_snapshot\"(%s)", escaped_issue_id) < 0) { 1.213 + fprintf(stderr, "Could not prepare query string in memory.\n"); 1.214 + err = admission_failed = 1; 1.215 + PQfreemem(escaped_issue_id); 1.216 + continue; 1.217 + } 1.218 + PQfreemem(escaped_issue_id); 1.219 + if (exec_sql(db, NULL, &err, 0, cmd) < 0) admission_failed = 1; 1.220 + free(cmd); 1.221 + } 1.222 + PQclear(res2); 1.223 + } 1.224 + if (!admission_failed) { 1.225 + if (asprintf(&cmd, "SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT \"issue_admission\"(%s)", escaped_area_id) < 0) { 1.226 + fprintf(stderr, "Could not prepare query string in memory.\n"); 1.227 + err = admission_failed = 1; 1.228 + goto area_admission_cleanup; 1.229 + } 1.230 + } 1.231 + while (1) { 1.232 + exec_sql(db, &res2, &err, 1, cmd); 1.233 + if (!res2) { 1.234 + admission_failed = 1; 1.235 + break; 1.236 + } 1.237 + if (PQgetvalue(res2, 0, 0)[0] != 't') { 1.238 + PQclear(res2); 1.239 + break; 1.240 + } 1.241 + PQclear(res2); 1.242 + } 1.243 + } 1.244 + area_admission_cleanup: 1.245 + PQfreemem(escaped_area_id); 1.246 + } 1.247 PQclear(res); 1.248 } 1.249 1.250 // update open issues: 1.251 - res = PQexec(db, "SELECT \"id\" FROM \"open_issue\""); 1.252 - if (!res) { 1.253 - fprintf(stderr, "Error in pqlib while sending SQL command selecting open issues\n"); 1.254 - err = 1; 1.255 - } else if (PQresultStatus(res) != PGRES_TUPLES_OK) { 1.256 - fprintf(stderr, "Error while executing SQL command selecting open issues:\n%s", PQresultErrorMessage(res)); 1.257 - err = 1; 1.258 - PQclear(res); 1.259 - } else { 1.260 - count = PQntuples(res); 1.261 - for (i=0; i<count; i++) { 1.262 - char *issue_id, *escaped_issue_id; 1.263 - PGresult *res2, *old_res2; 1.264 - int j; 1.265 - issue_id = PQgetvalue(res, i, 0); 1.266 - escaped_issue_id = escapeLiteral(db, issue_id, strlen(issue_id)); 1.267 - if (!escaped_issue_id) { 1.268 - fprintf(stderr, "Could not escape literal in memory.\n"); 1.269 + count = exec_sql( 1.270 + db, &res, &err, 0, 1.271 + admission_failed ? 1.272 + "SELECT \"id\" FROM \"open_issue\" WHERE \"state\" != 'admission'::\"issue_state\"" : 1.273 + "SELECT \"id\" FROM \"open_issue\"" 1.274 + ); 1.275 + for (i=0; i<count; i++) { 1.276 + char *issue_id, *escaped_issue_id; 1.277 + PGresult *res2, *old_res2; 1.278 + int j; 1.279 + issue_id = PQgetvalue(res, i, 0); 1.280 + escaped_issue_id = PQescapeLiteral(db, issue_id, strlen(issue_id)); 1.281 + if (!escaped_issue_id) { 1.282 + fprintf(stderr, "Could not escape literal in memory.\n"); 1.283 + err = 1; 1.284 + continue; 1.285 + } 1.286 + old_res2 = NULL; 1.287 + for (j=0; ; j++) { 1.288 + if (j >= 20) { // safety to avoid endless loops 1.289 + fprintf(stderr, "Function \"check_issue\"(...) returned non-null value too often.\n"); 1.290 err = 1; 1.291 + if (j > 0) PQclear(old_res2); 1.292 break; 1.293 } 1.294 - old_res2 = NULL; 1.295 - for (j=0; ; j++) { 1.296 - if (j >= 20) { // safety to avoid endless loops 1.297 - fprintf(stderr, "Function \"check_issue\"(...) returned non-null value too often.\n"); 1.298 + if (j == 0) { 1.299 + char *cmd; 1.300 + if (asprintf(&cmd, "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; SELECT \"check_issue\"(%s, NULL)", escaped_issue_id) < 0) { 1.301 + fprintf(stderr, "Could not prepare query string in memory.\n"); 1.302 err = 1; 1.303 - if (j > 0) PQclear(old_res2); 1.304 break; 1.305 } 1.306 - if (j == 0) { 1.307 - char *cmd; 1.308 - if (asprintf(&cmd, "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; SELECT \"check_issue\"(%s, NULL)", escaped_issue_id) < 0) { 1.309 - fprintf(stderr, "Could not prepare query string in memory.\n"); 1.310 - err = 1; 1.311 - break; 1.312 - } 1.313 - res2 = PQexec(db, cmd); 1.314 - free(cmd); 1.315 - } else { 1.316 - char *persist, *escaped_persist, *cmd; 1.317 - persist = PQgetvalue(old_res2, 0, 0); 1.318 - escaped_persist = escapeLiteral(db, persist, strlen(persist)); 1.319 - if (!escaped_persist) { 1.320 - fprintf(stderr, "Could not escape literal in memory.\n"); 1.321 - err = 1; 1.322 - PQclear(old_res2); 1.323 - break; 1.324 - } 1.325 - if (asprintf(&cmd, "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; SELECT \"check_issue\"(%s, %s::\"check_issue_persistence\")", escaped_issue_id, escaped_persist) < 0) { 1.326 - freemem(escaped_persist); 1.327 - fprintf(stderr, "Could not prepare query string in memory.\n"); 1.328 - err = 1; 1.329 - PQclear(old_res2); 1.330 - break; 1.331 - } 1.332 - freemem(escaped_persist); 1.333 - res2 = PQexec(db, cmd); 1.334 - free(cmd); 1.335 + exec_sql(db, &res2, &err, 1, cmd); 1.336 + free(cmd); 1.337 + } else { 1.338 + char *persist, *escaped_persist, *cmd; 1.339 + persist = PQgetvalue(old_res2, 0, 0); 1.340 + escaped_persist = PQescapeLiteral(db, persist, strlen(persist)); 1.341 + if (!escaped_persist) { 1.342 + fprintf(stderr, "Could not escape literal in memory.\n"); 1.343 + err = 1; 1.344 + PQclear(old_res2); 1.345 + break; 1.346 + } 1.347 + if (asprintf(&cmd, "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; SELECT \"check_issue\"(%s, %s::\"check_issue_persistence\")", escaped_issue_id, escaped_persist) < 0) { 1.348 + PQfreemem(escaped_persist); 1.349 + fprintf(stderr, "Could not prepare query string in memory.\n"); 1.350 + err = 1; 1.351 PQclear(old_res2); 1.352 + break; 1.353 } 1.354 - if (!res2) { 1.355 - fprintf(stderr, "Error in pqlib while sending SQL command to call function \"check_issue\"(...):\n"); 1.356 - err = 1; 1.357 - break; 1.358 - } else if ( 1.359 - PQresultStatus(res2) != PGRES_COMMAND_OK && 1.360 - PQresultStatus(res2) != PGRES_TUPLES_OK 1.361 - ) { 1.362 - fprintf(stderr, "Error while calling SQL function \"check_issue\"(...):\n%s", PQresultErrorMessage(res2)); 1.363 - err = 1; 1.364 - PQclear(res2); 1.365 - break; 1.366 - } else { 1.367 - if (PQntuples(res2) >= 1 && !PQgetisnull(res2, 0, 0)) { 1.368 - old_res2 = res2; 1.369 - } else { 1.370 - PQclear(res2); 1.371 - break; 1.372 - } 1.373 - } 1.374 + PQfreemem(escaped_persist); 1.375 + exec_sql(db, &res2, &err, 1, cmd); 1.376 + free(cmd); 1.377 + PQclear(old_res2); 1.378 } 1.379 - freemem(escaped_issue_id); 1.380 + if (!res2) break; 1.381 + if (PQgetisnull(res2, 0, 0)) { 1.382 + PQclear(res2); 1.383 + break; 1.384 + } 1.385 + old_res2 = res2; 1.386 } 1.387 - PQclear(res); 1.388 + PQfreemem(escaped_issue_id); 1.389 } 1.390 + if (res) PQclear(res); 1.391 1.392 // cleanup and exit: 1.393 PQfinish(db);