diff --git a/sql/ckdb.sql b/sql/ckdb.sql index 7285972c..d03bd39d 100644 --- a/sql/ckdb.sql +++ b/sql/ckdb.sql @@ -253,9 +253,11 @@ CREATE TABLE sharesummary ( -- per workinfo for each user+worker CREATE TABLE workmarkers ( -- range of workinfo for share accounting markerid bigint NOT NULL, + poolinstance character varying(256) NOT NULL, workinfoidend bigint NOT NULL, workinfoidstart bigint NOT NULL, description character varying(256) DEFAULT ''::character varying NOT NULL, + status char NOT NULL, createdate timestamp with time zone NOT NULL, createby character varying(64) DEFAULT ''::character varying NOT NULL, createcode character varying(128) DEFAULT ''::character varying NOT NULL, @@ -283,7 +285,7 @@ CREATE TABLE markersummary ( -- sum of sharesummary for a workinfo range errorcount bigint NOT NULL, firstshare timestamp with time zone NOT NULL, lastshare timestamp with time zone NOT NULL, - complete char NOT NULL, + lastdiffacc float NOT NULL, createdate timestamp with time zone NOT NULL, createby character varying(64) NOT NULL, createcode character varying(128) NOT NULL, @@ -415,4 +417,4 @@ CREATE TABLE version ( PRIMARY KEY (vlock) ); -insert into version (vlock,version) values (1,'0.9.2'); +insert into version (vlock,version) values (1,'0.9.3'); diff --git a/sql/v0.9.2-v0.9.3.sql b/sql/v0.9.2-v0.9.3.sql new file mode 100644 index 00000000..9ca93b06 --- /dev/null +++ b/sql/v0.9.2-v0.9.3.sql @@ -0,0 +1,72 @@ +SET SESSION AUTHORIZATION 'postgres'; + +BEGIN transaction; + +DO $$ +DECLARE ver TEXT; +BEGIN + + UPDATE version set version='0.9.3' where vlock=1 and version='0.9.2'; + + IF found THEN + RETURN; + END IF; + + SELECT version into ver from version + WHERE vlock=1; + + RAISE EXCEPTION 'Wrong DB version - expect "0.9.2" - found "%"', ver; + +END $$; + +DROP table workmarkers; + +CREATE TABLE workmarkers ( -- range of workinfo for share accounting + markerid bigint NOT NULL, + poolinstance character varying(256) NOT NULL, + workinfoidend bigint NOT NULL, + workinfoidstart bigint NOT NULL, + description character varying(256) DEFAULT ''::character varying NOT NULL, + status char NOT NULL, + createdate timestamp with time zone NOT NULL, + createby character varying(64) DEFAULT ''::character varying NOT NULL, + createcode character varying(128) DEFAULT ''::character varying NOT NULL, + createinet character varying(128) DEFAULT ''::character varying NOT NULL, + expirydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', + PRIMARY KEY (markerid) +); + +DROP table markersummary; + +CREATE TABLE markersummary ( -- sum of sharesummary for a workinfo range + markerid bigint NOT NULL, + userid bigint NOT NULL, + workername character varying(256) NOT NULL, + diffacc float NOT NULL, + diffsta float NOT NULL, + diffdup float NOT NULL, + diffhi float NOT NULL, + diffrej float NOT NULL, + shareacc float NOT NULL, + sharesta float NOT NULL, + sharedup float NOT NULL, + sharehi float NOT NULL, + sharerej float NOT NULL, + sharecount bigint NOT NULL, + errorcount bigint NOT NULL, + firstshare timestamp with time zone NOT NULL, + lastshare timestamp with time zone NOT NULL, + lastdiffacc float NOT NULL, + createdate timestamp with time zone NOT NULL, + createby character varying(64) NOT NULL, + createcode character varying(128) NOT NULL, + createinet character varying(128) NOT NULL, + modifydate timestamp with time zone NOT NULL, + modifyby character varying(64) NOT NULL, + modifycode character varying(128) NOT NULL, + modifyinet character varying(128) NOT NULL, + PRIMARY KEY (markerid, userid, workername) +); + + +END transaction; diff --git a/src/ckdb.c b/src/ckdb.c index afe545cc..a167a231 100644 --- a/src/ckdb.c +++ b/src/ckdb.c @@ -435,6 +435,16 @@ K_TREE *workerstatus_root; K_LIST *workerstatus_free; K_STORE *workerstatus_store; +// MARKERSUMMARY +K_TREE *markersummary_root; +K_LIST *markersummary_free; +K_STORE *markersummary_store; + +// WORKMARKERS +K_TREE *workmarkers_root; +K_LIST *workmarkers_free; +K_STORE *workmarkers_store; + static char logname[512]; static char *dbcode; @@ -714,6 +724,10 @@ static bool getdata3() } if (!(ok = workinfo_fill(conn)) || everyone_die) goto sukamudai; + if (!(ok = workmarkers_fill(conn)) || everyone_die) + goto sukamudai; + if (!(ok = markersummary_fill(conn)) || everyone_die) + goto sukamudai; if (!(ok = sharesummary_fill(conn)) || everyone_die) goto sukamudai; if (!confirm_sharesummary) { @@ -998,6 +1012,16 @@ static void alloc_storage() ALLOC_WORKERSTATUS, LIMIT_WORKERSTATUS, true); workerstatus_store = k_new_store(workerstatus_free); workerstatus_root = new_ktree(); + + markersummary_free = k_new_list("MarkerSummary", sizeof(MARKERSUMMARY), + ALLOC_MARKERSUMMARY, LIMIT_MARKERSUMMARY, true); + markersummary_store = k_new_store(markersummary_free); + markersummary_root = new_ktree(); + + workmarkers_free = k_new_list("WorkMarkers", sizeof(WORKMARKERS), + ALLOC_WORKMARKERS, LIMIT_WORKMARKERS, true); + workmarkers_store = k_new_store(workmarkers_free); + workmarkers_root = new_ktree(); } static void free_workinfo_data(K_ITEM *item) @@ -1033,6 +1057,31 @@ static void free_optioncontrol_data(K_ITEM *item) free(optioncontrol->optionvalue); } +static void free_markersummary_data(K_ITEM *item) +{ + MARKERSUMMARY *markersummary; + + DATA_MARKERSUMMARY(markersummary, item); + if (markersummary->workername) + free(markersummary->workername); + SET_CREATEBY(markersummary_free, markersummary->createby, EMPTY); + SET_CREATECODE(markersummary_free, markersummary->createcode, EMPTY); + SET_CREATEINET(markersummary_free, markersummary->createinet, EMPTY); + SET_MODIFYBY(markersummary_free, markersummary->modifyby, EMPTY); + SET_MODIFYCODE(markersummary_free, markersummary->modifycode, EMPTY); + SET_MODIFYINET(markersummary_free, markersummary->modifyinet, EMPTY); +} + +static void free_workmarkers_data(K_ITEM *item) +{ + WORKMARKERS *workmarkers; + + DATA_WORKMARKERS(workmarkers, item); + if (workmarkers->poolinstance) + free(workmarkers->poolinstance); + if (workmarkers->description) + free(workmarkers->description); +} #define FREE_TREE(_tree) \ if (_tree ## _root) \ @@ -1046,15 +1095,42 @@ static void free_optioncontrol_data(K_ITEM *item) if (_list ## _free) \ _list ## _free = k_free_list(_list ## _free) \ -#define FREE_ALL(_list) FREE_TREE(_list); FREE_STORE(_list); FREE_LIST(_list) +#define FREE_STORE_DATA(_list) \ + if (_list ## _store) { \ + K_ITEM *_item = _list ## _store->head; \ + while (_item) { \ + free_ ## _list ## _data(_item); \ + _item = _item->next; \ + } \ + _list ## _store = k_free_store(_list ## _store); \ + } + +#define FREE_LIST_DATA(_list) \ + if (_list ## _free) { \ + K_ITEM *_item = _list ## _free->head; \ + while (_item) { \ + free_ ## _list ## _data(_item); \ + _item = _item->next; \ + } \ + _list ## _free = k_free_list(_list ## _free); \ + } #define FREE_LISTS(_list) FREE_STORE(_list); FREE_LIST(_list) +#define FREE_ALL(_list) FREE_TREE(_list); FREE_LISTS(_list) + static void dealloc_storage() { - K_ITEM *item; - FREE_LISTS(logqueue); + + FREE_TREE(workmarkers); + FREE_STORE_DATA(workmarkers); + FREE_LIST_DATA(workmarkers); + + FREE_TREE(markersummary); + FREE_STORE_DATA(markersummary); + FREE_LIST_DATA(markersummary); + FREE_ALL(workerstatus); FREE_TREE(userstats_workerstatus); @@ -1071,44 +1147,16 @@ static void dealloc_storage() FREE_TREE(sharesummary_workinfoid); FREE_TREE(sharesummary); - if (sharesummary_store) { - item = sharesummary_store->head; - while (item) { - free_sharesummary_data(item); - item = item->next; - } - FREE_STORE(sharesummary); - } - if (sharesummary_free) { - item = sharesummary_free->head; - while (item) { - free_sharesummary_data(item); - item = item->next; - } - FREE_LIST(sharesummary); - } + FREE_STORE_DATA(sharesummary); + FREE_LIST_DATA(sharesummary); FREE_ALL(shareerrors); FREE_ALL(shares); FREE_TREE(workinfo_height); FREE_TREE(workinfo); - if (workinfo_store) { - item = workinfo_store->head; - while (item) { - free_workinfo_data(item); - item = item->next; - } - FREE_STORE(workinfo); - } - if (workinfo_free) { - item = workinfo_free->head; - while (item) { - free_workinfo_data(item); - item = item->next; - } - FREE_LIST(workinfo); - } + FREE_STORE_DATA(workinfo); + FREE_LIST_DATA(workinfo); FREE_LISTS(idcontrol); FREE_ALL(payments); @@ -1116,22 +1164,8 @@ static void dealloc_storage() FREE_ALL(workers); FREE_TREE(optioncontrol); - if (optioncontrol_store) { - item = optioncontrol_store->head; - while (item) { - free_optioncontrol_data(item); - item = item->next; - } - FREE_STORE(optioncontrol); - } - if (optioncontrol_free) { - item = optioncontrol_free->head; - while (item) { - free_optioncontrol_data(item); - item = item->next; - } - FREE_LIST(optioncontrol); - } + FREE_STORE_DATA(optioncontrol); + FREE_LIST_DATA(optioncontrol); FREE_ALL(useratts); diff --git a/src/ckdb.h b/src/ckdb.h index 72aa2e6b..33227a3a 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -51,8 +51,8 @@ */ #define DB_VLOCK "1" -#define DB_VERSION "0.9.2" -#define CKDB_VERSION DB_VERSION"-0.602" +#define DB_VERSION "0.9.3" +#define CKDB_VERSION DB_VERSION"-0.610" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ @@ -186,6 +186,9 @@ enum data_type { TYPE_DOUBLE }; +// BLOB does what PTR needs +#define TXT_TO_PTR TXT_TO_BLOB + #define TXT_TO_STR(__nam, __fld, __data) txt_to_str(__nam, __fld, (__data), sizeof(__data)) #define TXT_TO_BIGINT(__nam, __fld, __data) txt_to_bigint(__nam, __fld, &(__data), sizeof(__data)) #define TXT_TO_INT(__nam, __fld, __data) txt_to_int(__nam, __fld, &(__data), sizeof(__data)) @@ -1261,6 +1264,62 @@ extern K_TREE *workerstatus_root; extern K_LIST *workerstatus_free; extern K_STORE *workerstatus_store; +// MARKERSUMMARY +typedef struct markersummary { + int64_t markerid; + int64_t userid; + char *workername; + double diffacc; + double diffsta; + double diffdup; + double diffhi; + double diffrej; + double shareacc; + double sharesta; + double sharedup; + double sharehi; + double sharerej; + int64_t sharecount; + int64_t errorcount; + tv_t firstshare; + tv_t lastshare; + double lastdiffacc; + MODIFYDATECONTROLPOINTERS; +} MARKERSUMMARY; + +#define ALLOC_MARKERSUMMARY 1000 +#define LIMIT_MARKERSUMMARY 0 +#define INIT_MARKERSUMMARY(_item) INIT_GENERIC(_item, markersummary) +#define DATA_MARKERSUMMARY(_var, _item) DATA_GENERIC(_var, _item, markersummary, true) +#define DATA_MARKERSUMMARY_NULL(_var, _item) DATA_GENERIC(_var, _item, markersummary, false) + +extern K_TREE *markersummary_root; +extern K_LIST *markersummary_free; +extern K_STORE *markersummary_store; + +// WORKMARKERS +typedef struct workmarkers { + int64_t markerid; + char *poolinstance; + int64_t workinfoidend; + int64_t workinfoidstart; + char *description; + char status[TXT_FLAG+1]; + HISTORYDATECONTROLFIELDS; +} WORKMARKERS; + +#define ALLOC_WORKMARKERS 100 +#define LIMIT_WORKMARKERS 0 +#define INIT_WORKMARKERS(_item) INIT_GENERIC(_item, workmarkers) +#define DATA_WORKMARKERS(_var, _item) DATA_GENERIC(_var, _item, workmarkers, true) +#define DATA_WORKMARKERS_NULL(_var, _item) DATA_GENERIC(_var, _item, workmarkers, false) + +extern K_TREE *workmarkers_root; +extern K_LIST *workmarkers_free; +extern K_STORE *workmarkers_store; + +#define MARKER_COMPLETE 'x' + extern void logmsg(int loglevel, const char *fmt, ...); extern void setnow(tv_t *now); extern void tick(); @@ -1413,6 +1472,8 @@ extern cmp_t cmp_userstats_workername(K_ITEM *a, K_ITEM *b); extern cmp_t cmp_userstats_statsdate(K_ITEM *a, K_ITEM *b); extern cmp_t cmp_userstats_workerstatus(K_ITEM *a, K_ITEM *b); extern bool userstats_starttimeband(USERSTATS *row, tv_t *statsdate); +extern cmp_t cmp_markersummary(K_ITEM *a, K_ITEM *b); +extern cmp_t cmp_workmarkers(K_ITEM *a, K_ITEM *b); // *** // *** PostgreSQL functions ckdb_dbio.c @@ -1554,6 +1615,8 @@ extern bool userstats_add(char *poolinstance, char *elapsed, char *username, bool eos, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root); extern bool userstats_fill(PGconn *conn); +extern bool markersummary_fill(PGconn *conn); +extern bool workmarkers_fill(PGconn *conn); extern bool check_db_version(PGconn *conn); // *** diff --git a/src/ckdb_cmd.c b/src/ckdb_cmd.c index d8e34e4e..fff59f2c 100644 --- a/src/ckdb_cmd.c +++ b/src/ckdb_cmd.c @@ -3341,6 +3341,8 @@ static char *cmd_stats(__maybe_unused PGconn *conn, char *cmd, char *id, USEINFO(shares, 1, 1); USEINFO(shareerrors, 1, 1); USEINFO(sharesummary, 1, 2); + USEINFO(workmarkers, 1, 1); + USEINFO(markersummary, 1, 1); USEINFO(blocks, 1, 1); USEINFO(miningpayouts, 1, 1); USEINFO(auths, 1, 1); diff --git a/src/ckdb_data.c b/src/ckdb_data.c index 9985c827..f786f78a 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -173,6 +173,8 @@ void _txt_to_data(enum data_type typ, char *nam, char *fld, void *data, size_t s quithere(1, "Field %s (%d) OOM" WHERE_FFL, nam, (int)strlen(fld), WHERE_FFL_PASS); } + // free() allows NULL + free(*((char **)data)); *((char **)data) = tmp; break; case TYPE_DOUBLE: @@ -2071,3 +2073,34 @@ bool userstats_starttimeband(USERSTATS *row, tv_t *statsdate) } return true; } + +// order by markerid asc,userid asc,workername asc +cmp_t cmp_markersummary(K_ITEM *a, K_ITEM *b) +{ + MARKERSUMMARY *ma, *mb; + DATA_MARKERSUMMARY(ma, a); + DATA_MARKERSUMMARY(mb, b); + cmp_t c = CMP_BIGINT(ma->markerid, mb->markerid); + if (c == 0) { + c = CMP_BIGINT(ma->userid, mb->userid); + if (c == 0) + c = CMP_STR(ma->workername, mb->workername); + } + return c; +} + +// order by markerid asc,workinfoidend asc,expirydate desc +cmp_t cmp_workmarkers(K_ITEM *a, K_ITEM *b) +{ + WORKMARKERS *wa, *wb; + DATA_WORKMARKERS(wa, a); + DATA_WORKMARKERS(wb, b); + cmp_t c = CMP_BIGINT(wa->markerid, wb->markerid); + if (c == 0) { + c = CMP_BIGINT(wa->workinfoidend, wb->workinfoidend); + if (c == 0) + c = CMP_TV(wb->expirydate, wa->expirydate); + } + return c; +} + diff --git a/src/ckdb_dbio.c b/src/ckdb_dbio.c index 5c645083..36b2e76d 100644 --- a/src/ckdb_dbio.c +++ b/src/ckdb_dbio.c @@ -4788,6 +4788,268 @@ bool userstats_fill(PGconn *conn) return ok; } +bool markersummary_fill(PGconn *conn) +{ + ExecStatusType rescode; + PGresult *res; + K_ITEM *item; + int n, i; + MARKERSUMMARY *row; + char *field; + char *sel; + int fields = 18; + bool ok; + + LOGDEBUG("%s(): select", __func__); + + // TODO: limit how far back + sel = "select " + "markerid,userid,workername,diffacc,diffsta,diffdup,diffhi," + "diffrej,shareacc,sharesta,sharedup,sharehi,sharerej," + "sharecount,errorcount,firstshare,lastshare," + "lastdiffacc" + MODIFYDATECONTROL + " from markersummary"; + res = PQexec(conn, sel, CKPQ_READ); + rescode = PQresultStatus(res); + if (!PGOK(rescode)) { + PGLOGERR("Select", rescode, conn); + PQclear(res); + return false; + } + + n = PQnfields(res); + if (n != (fields + MODIFYDATECOUNT)) { + LOGERR("%s(): Invalid field count - should be %d, but is %d", + __func__, fields + MODIFYDATECOUNT, n); + PQclear(res); + return false; + } + + n = PQntuples(res); + LOGDEBUG("%s(): tree build count %d", __func__, n); + ok = true; + for (i = 0; i < n; i++) { + item = k_unlink_head(markersummary_free); + DATA_MARKERSUMMARY(row, item); + + if (everyone_die) { + ok = false; + break; + } + + PQ_GET_FLD(res, i, "markerid", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("markerid", field, row->markerid); + + PQ_GET_FLD(res, i, "userid", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("userid", field, row->userid); + + PQ_GET_FLD(res, i, "workername", field, ok); + if (!ok) + break; + TXT_TO_PTR("workername", field, row->workername); + + PQ_GET_FLD(res, i, "diffacc", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("diffacc", field, row->diffacc); + + PQ_GET_FLD(res, i, "diffsta", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("diffsta", field, row->diffsta); + + PQ_GET_FLD(res, i, "diffdup", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("diffdup", field, row->diffdup); + + PQ_GET_FLD(res, i, "diffhi", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("diffhi", field, row->diffhi); + + PQ_GET_FLD(res, i, "diffrej", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("diffrej", field, row->diffrej); + + PQ_GET_FLD(res, i, "shareacc", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("shareacc", field, row->shareacc); + + PQ_GET_FLD(res, i, "sharesta", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("sharesta", field, row->sharesta); + + PQ_GET_FLD(res, i, "sharedup", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("sharedup", field, row->sharedup); + + PQ_GET_FLD(res, i, "sharehi", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("sharehi", field, row->sharehi); + + PQ_GET_FLD(res, i, "sharerej", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("sharerej", field, row->sharerej); + + PQ_GET_FLD(res, i, "sharecount", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("sharecount", field, row->sharecount); + + PQ_GET_FLD(res, i, "errorcount", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("errorcount", field, row->errorcount); + + PQ_GET_FLD(res, i, "firstshare", field, ok); + if (!ok) + break; + TXT_TO_TV("firstshare", field, row->firstshare); + + PQ_GET_FLD(res, i, "lastshare", field, ok); + if (!ok) + break; + TXT_TO_TV("lastshare", field, row->lastshare); + + PQ_GET_FLD(res, i, "lastdiffacc", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("lastdiffacc", field, row->lastdiffacc); + + MODIFYDATEFLDPOINTERS(markersummary_free, res, i, row, ok); + if (!ok) + break; + + markersummary_root = add_to_ktree(markersummary_root, item, cmp_markersummary); + k_add_head(markersummary_store, item); + + tick(); + } + if (!ok) + k_add_head(markersummary_free, item); + + PQclear(res); + + if (ok) { + LOGDEBUG("%s(): built", __func__); + LOGWARNING("%s(): loaded %d markersummary records", __func__, n); + } + + return ok; +} + +bool workmarkers_fill(PGconn *conn) +{ + ExecStatusType rescode; + PGresult *res; + K_ITEM *item; + int n, i; + WORKMARKERS *row; + char *field; + char *sel; + int fields = 6; + bool ok; + + LOGDEBUG("%s(): select", __func__); + + // TODO: limit how far back + sel = "select " + "markerid,poolinstance,workinfoidend,workinfoidstart," + "description,status" + HISTORYDATECONTROL + " from workmarkers"; + res = PQexec(conn, sel, CKPQ_READ); + rescode = PQresultStatus(res); + if (!PGOK(rescode)) { + PGLOGERR("Select", rescode, conn); + PQclear(res); + return false; + } + + n = PQnfields(res); + if (n != (fields + HISTORYDATECOUNT)) { + LOGERR("%s(): Invalid field count - should be %d, but is %d", + __func__, fields + HISTORYDATECOUNT, n); + PQclear(res); + return false; + } + + n = PQntuples(res); + LOGDEBUG("%s(): tree build count %d", __func__, n); + ok = true; + for (i = 0; i < n; i++) { + item = k_unlink_head(workmarkers_free); + DATA_WORKMARKERS(row, item); + + if (everyone_die) { + ok = false; + break; + } + + PQ_GET_FLD(res, i, "markerid", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("markerid", field, row->markerid); + + PQ_GET_FLD(res, i, "poolinstance", field, ok); + if (!ok) + break; + TXT_TO_PTR("poolinstance", field, row->poolinstance); + + PQ_GET_FLD(res, i, "workinfoidend", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("workinfoidend", field, row->workinfoidend); + + PQ_GET_FLD(res, i, "workinfoidstart", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("workinfoidstart", field, row->workinfoidstart); + + PQ_GET_FLD(res, i, "description", field, ok); + if (!ok) + break; + TXT_TO_PTR("description", field, row->description); + + PQ_GET_FLD(res, i, "status", field, ok); + if (!ok) + break; + TXT_TO_STR("status", field, row->status); + + HISTORYDATEFLDS(res, i, row, ok); + if (!ok) + break; + + workmarkers_root = add_to_ktree(workmarkers_root, item, cmp_workmarkers); + k_add_head(workmarkers_store, item); + + tick(); + } + if (!ok) + k_add_head(workmarkers_free, item); + + PQclear(res); + + if (ok) { + LOGDEBUG("%s(): built", __func__); + LOGWARNING("%s(): loaded %d workmarkers records", __func__, n); + } + + return ok; +} + bool check_db_version(PGconn *conn) { ExecStatusType rescode;