LCOV - code coverage report
Current view: top level - src - dbutils.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 98.1 % 630 618
Test Date: 2025-09-30 09:59:00 Functions: 100.0 % 42 42

            Line data    Source code
       1              : //
       2              : //  dbutils.c
       3              : //  cloudsync
       4              : //
       5              : //  Created by Marco Bambini on 23/09/24.
       6              : //
       7              : 
       8              : #include <stdlib.h>
       9              : #include "utils.h"
      10              : #include "dbutils.h"
      11              : #include "cloudsync.h"
      12              : 
      13              : #ifndef SQLITE_CORE
      14              : SQLITE_EXTENSION_INIT3
      15              : #endif
      16              : 
      17              : #if CLOUDSYNC_UNITTEST
      18              : char *OUT_OF_MEMORY_BUFFER = "OUT_OF_MEMORY_BUFFER";
      19              : #ifndef SQLITE_MAX_ALLOCATION_SIZE
      20              : #define SQLITE_MAX_ALLOCATION_SIZE  2147483391
      21              : #endif
      22              : #endif
      23              : 
      24              : typedef struct {
      25              :     int type;
      26              :     int len;
      27              :     int rc;
      28              :     union {
      29              :         sqlite3_int64 intValue;
      30              :         double doubleValue;
      31              :         char *stringValue;
      32              :     } value;
      33              : } DATABASE_RESULT;
      34              : 
      35              : typedef struct {
      36              :     sqlite3             *db;
      37              :     cloudsync_context   *data;
      38              : } dbutils_settings_table_context;
      39              : 
      40              : int dbutils_settings_check_version (sqlite3 *db, const char *version);
      41              : 
      42              : // MARK: - General -
      43              : 
      44         5832 : DATABASE_RESULT dbutils_exec (sqlite3_context *context, sqlite3 *db, const char *sql, const char **values, int types[], int lens[], int count, DATABASE_RESULT results[], int expected_types[], int result_count) {
      45              :     DEBUG_DBFUNCTION("dbutils_exec %s", sql);
      46              :     
      47         5832 :     sqlite3_stmt *pstmt = NULL;
      48         5832 :     bool is_write = (result_count == 0);
      49              :     #ifdef CLOUDSYNC_UNITTEST
      50         5832 :     bool is_test = (result_count == 1 && expected_types[0] == SQLITE_NOMEM);
      51              :     #endif
      52         5832 :     int type = 0;
      53              :     
      54              :     // compile sql
      55         5832 :     int rc = sqlite3_prepare_v2(db, sql, -1, &pstmt, NULL);
      56         5832 :     if (rc != SQLITE_OK) goto dbutils_exec_finalize;
      57              :     
      58              :     // check bindings
      59         7144 :     for (int i=0; i<count; ++i) {
      60         1313 :         switch (types[i]) {
      61              :             case SQLITE_NULL:
      62            1 :                 rc = sqlite3_bind_null(pstmt, i+1);
      63            1 :                 break;
      64              :             case SQLITE_TEXT:
      65         1073 :                 rc = sqlite3_bind_text(pstmt, i+1, values[i], lens[i], SQLITE_STATIC);
      66         1073 :                 break;
      67              :             case SQLITE_BLOB:
      68           91 :                 rc = sqlite3_bind_blob(pstmt, i+1, values[i], lens[i], SQLITE_STATIC);
      69           91 :                 break;
      70              :             case SQLITE_INTEGER: {
      71          147 :                 sqlite3_int64 value = strtoll(values[i], NULL, 0);
      72          147 :                 rc = sqlite3_bind_int64(pstmt, i+1, value);
      73          147 :             }   break;
      74              :             case SQLITE_FLOAT: {
      75            1 :                 double value = strtod(values[i], NULL);
      76            1 :                 rc = sqlite3_bind_double(pstmt, i+1, value);
      77            1 :             }   break;
      78              :         }
      79         1313 :         if (rc != SQLITE_OK) goto dbutils_exec_finalize;
      80         1313 :     }
      81              :         
      82              :     // execute statement
      83         5831 :     rc = sqlite3_step(pstmt);
      84              :     
      85              :     // check return value based on pre-condition
      86         5831 :     if (rc != SQLITE_ROW) goto dbutils_exec_finalize;
      87         5233 :     if (is_write) goto dbutils_exec_finalize;
      88              :     
      89              :     // process result (if any)
      90        10467 :     for (int i=0; i<result_count; ++i) {
      91         5235 :         type = sqlite3_column_type(pstmt, i);
      92         5235 :         results[i].type = type;
      93              :         
      94         5235 :         if (type == SQLITE_NULL) {
      95           36 :             rc = SQLITE_OK;
      96           36 :             continue;
      97              :         }
      98              :         
      99              :         #ifdef CLOUDSYNC_UNITTEST
     100         5199 :         if (is_test) type = SQLITE_BLOB;
     101              :         #endif
     102         5199 :         if (type != expected_types[i]) {
     103            1 :             rc = SQLITE_ERROR;
     104            1 :             goto dbutils_exec_finalize;
     105              :         }
     106              :         #ifdef CLOUDSYNC_UNITTEST
     107              :         #endif
     108              :         
     109              :         // type == expected_type
     110         5198 :         if (type == SQLITE_INTEGER) results[i].value.intValue = sqlite3_column_int64(pstmt, i);
     111         3958 :         else if (type == SQLITE_FLOAT) results[i].value.doubleValue = sqlite3_column_double(pstmt, i);
     112              :         else {
     113              :             // TEXT or BLOB
     114         3958 :             int len = sqlite3_column_bytes(pstmt, i);
     115         3958 :             results[i].len = len;
     116              :             #if CLOUDSYNC_UNITTEST
     117         3958 :             if (is_test) len = SQLITE_MAX_ALLOCATION_SIZE + 1;
     118              :             #endif
     119              :             
     120         3958 :             char *buffer = NULL;
     121         3958 :             if (type == SQLITE_BLOB) {
     122           94 :                 const void *bvalue = sqlite3_column_blob(pstmt, i);
     123           94 :                 if (bvalue) {
     124           94 :                     buffer = (char *)cloudsync_memory_alloc(len);
     125           94 :                     if (!buffer) {rc = SQLITE_NOMEM; goto dbutils_exec_finalize;}
     126           94 :                     memcpy(buffer, bvalue, len);
     127           94 :                 }
     128           94 :             } else {
     129         3864 :                 const unsigned char *value = sqlite3_column_text(pstmt, i);
     130         3864 :                 if (value) buffer = cloudsync_string_dup((const char *)value, false);
     131              :             }
     132         3958 :             results[i].value.stringValue = buffer;
     133              :         }
     134         5198 :     }
     135              :     
     136         5232 :     rc = SQLITE_OK;
     137              : dbutils_exec_finalize:
     138         5832 :     if (rc == SQLITE_DONE) rc = SQLITE_OK;
     139         5832 :     if (rc != SQLITE_OK) {
     140              :         #ifdef CLOUDSYNC_UNITTEST
     141            2 :         if (is_test) count = -1;
     142              :         #endif
     143            2 :         if (count != -1) DEBUG_ALWAYS("Error executing %s in dbutils_exec (%s).", sql, sqlite3_errmsg(db));
     144            2 :         if (context) sqlite3_result_error(context, sqlite3_errmsg(db), -1);
     145            2 :     }
     146         5832 :     if (pstmt) sqlite3_finalize(pstmt);
     147              :     
     148         5832 :     if (is_write) {
     149          510 :         DATABASE_RESULT result = {0};
     150          510 :         result.rc = rc;
     151          510 :         return result;
     152              :     }
     153              :         
     154         5322 :     results[0].rc = rc;
     155         5322 :     return results[0];
     156         5832 : }
     157              : 
     158          509 : int dbutils_write (sqlite3 *db, sqlite3_context *context, const char *sql, const char **values, int types[], int lens[], int count) {
     159          509 :     DATABASE_RESULT result = dbutils_exec(context, db, sql, values, types, lens, count, NULL, NULL, 0);
     160          509 :     return result.rc;
     161              : }
     162              : 
     163            1 : int dbutils_write_simple (sqlite3 *db, const char *sql) {
     164            1 :     DATABASE_RESULT result = dbutils_exec(NULL, db, sql, NULL, NULL, NULL, 0, NULL, NULL, 0);
     165            1 :     return result.rc;
     166              : }
     167              : 
     168         1324 : sqlite3_int64 dbutils_int_select (sqlite3 *db, const char *sql) {
     169              :     // used only for cound(*), hash, or 1, so return -1 to signal an error
     170         1324 :     DATABASE_RESULT results[1] = {0};
     171         1324 :     int expected_types[1] = {SQLITE_INTEGER};
     172         1324 :     dbutils_exec(NULL, db, sql, NULL, NULL, NULL, 0, results, expected_types, 1);
     173         1324 :     return (results[0].rc == SQLITE_OK) ? results[0].value.intValue : -1;
     174              : }
     175              : 
     176         3899 : char *dbutils_text_select (sqlite3 *db, const char *sql) {
     177         3899 :     DATABASE_RESULT results[1] = {0};
     178         3899 :     int expected_types[1] = {SQLITE_TEXT};
     179         3899 :     dbutils_exec(NULL, db, sql, NULL, NULL, NULL, 0, results, expected_types, 1);
     180         3899 :     return results[0].value.stringValue;
     181              : }
     182              : 
     183           93 : char *dbutils_blob_select (sqlite3 *db, const char *sql, int *size, sqlite3_context *context, int *rc) {
     184           93 :     DATABASE_RESULT results[1] = {0};
     185           93 :     int expected_types[1] = {SQLITE_BLOB};
     186           93 :     dbutils_exec(context, db, sql, NULL, NULL, NULL, 0, results, expected_types, 1);
     187           93 :     *size = results[0].len;
     188           93 :     *rc = results[0].rc;
     189           93 :     return results[0].value.stringValue;
     190              : }
     191              : 
     192            1 : int dbutils_blob_int_int_select (sqlite3 *db, const char *sql, char **blob, int *size, sqlite3_int64 *int1, sqlite3_int64 *int2) {
     193            1 :     DATABASE_RESULT results[3] = {0};
     194            1 :     int expected_types[3] = {SQLITE_BLOB, SQLITE_INTEGER, SQLITE_INTEGER};
     195            1 :     dbutils_exec(NULL, db, sql, NULL, NULL, NULL, 0, results, expected_types, 3);
     196            1 :     *size = results[0].len;
     197            1 :     *blob = results[0].value.stringValue;
     198            1 :     *int1 = results[1].value.intValue;
     199            1 :     *int2 = results[2].value.intValue;
     200            1 :     return results[0].rc;
     201              : }
     202              : 
     203            5 : sqlite3_int64 dbutils_select (sqlite3 *db, const char *sql, const char **values, int types[], int lens[], int count, int expected_type) {
     204              :     // used only in unit-test
     205            5 :     DATABASE_RESULT results[1] = {0};
     206            5 :     int expected_types[1] = {expected_type};
     207            5 :     dbutils_exec(NULL, db, sql, values, types, lens, count, results, expected_types, 1);
     208            5 :     return results[0].value.intValue;
     209              : }
     210              : 
     211              : // MARK: -
     212              : 
     213              : // compares two SQLite values and returns an integer indicating the comparison result
     214        22714 : int dbutils_value_compare (sqlite3_value *lvalue, sqlite3_value *rvalue) {
     215        22714 :     if (lvalue == rvalue) return 0;
     216        22714 :     if (!lvalue) return -1;
     217        22714 :     if (!rvalue) return 1;
     218              :     
     219        22714 :     int l_type = (lvalue) ? sqlite3_value_type(lvalue) : SQLITE_NULL;
     220        22714 :     int r_type = sqlite3_value_type(rvalue);
     221              :     
     222              :     // early exit if types differ, null is less than all types
     223        22714 :     if (l_type != r_type) return (r_type - l_type);
     224              :     
     225              :     // at this point lvalue and rvalue are of the same type
     226        22709 :     switch (l_type) {
     227              :         case SQLITE_INTEGER: {
     228         6938 :             sqlite3_int64 l_int = sqlite3_value_int64(lvalue);
     229         6938 :             sqlite3_int64 r_int = sqlite3_value_int64(rvalue);
     230         6938 :             return (l_int < r_int) ? -1 : (l_int > r_int);
     231              :         } break;
     232              :             
     233              :         case SQLITE_FLOAT: {
     234            3 :             double l_double = sqlite3_value_double(lvalue);
     235            3 :             double r_double = sqlite3_value_double(rvalue);
     236            3 :             return (l_double < r_double) ? -1 : (l_double > r_double);
     237              :         } break;
     238              :             
     239              :         case SQLITE_NULL:
     240          634 :             break;
     241              :             
     242              :         case SQLITE_TEXT: {
     243        15127 :             const unsigned char *l_text = sqlite3_value_text(lvalue);
     244        15127 :             const unsigned char *r_text = sqlite3_value_text(rvalue);
     245        15127 :             return strcmp((const char *)l_text, (const char *)r_text);
     246              :         } break;
     247              :             
     248              :         case SQLITE_BLOB: {
     249            7 :             const void *l_blob = sqlite3_value_blob(lvalue);
     250            7 :             const void *r_blob = sqlite3_value_blob(rvalue);
     251            7 :             int l_size = sqlite3_value_bytes(lvalue);
     252            7 :             int r_size = sqlite3_value_bytes(rvalue);
     253            7 :             int cmp = memcmp(l_blob, r_blob, (l_size < r_size) ? l_size : r_size);
     254            7 :             return (cmp != 0) ? cmp : (l_size - r_size);
     255              :         } break;
     256              :     }
     257              :     
     258          634 :     return 0;
     259        22714 : }
     260              : 
     261           16 : void dbutils_context_result_error (sqlite3_context *context, const char *format, ...) {
     262              :     char buffer[4096];
     263              :     
     264              :     va_list arg;
     265           16 :     va_start (arg, format);
     266           16 :     vsnprintf(buffer, sizeof(buffer), format, arg);
     267           16 :     va_end (arg);
     268              :     
     269           16 :     if (context) sqlite3_result_error(context, buffer, -1);
     270           16 : }
     271              : 
     272              : // MARK: -
     273              : 
     274           28 : void dbutils_debug_value (sqlite3_value *value) {
     275           28 :     switch (sqlite3_value_type(value)) {
     276              :         case SQLITE_INTEGER:
     277            7 :             printf("\t\tINTEGER: %lld\n", sqlite3_value_int64(value));
     278            7 :             break;
     279              :         case SQLITE_FLOAT:
     280            7 :             printf("\t\tFLOAT: %f\n", sqlite3_value_double(value));
     281            7 :             break;
     282              :         case SQLITE_TEXT:
     283            6 :             printf("\t\tTEXT: %s (%d)\n", sqlite3_value_text(value), sqlite3_value_bytes(value));
     284            6 :             break;
     285              :         case SQLITE_BLOB:
     286            6 :             printf("\t\tBLOB: %p (%d)\n", (char *)sqlite3_value_blob(value), sqlite3_value_bytes(value));
     287            6 :             break;
     288              :         case SQLITE_NULL:
     289            2 :             printf("\t\tNULL\n");
     290            2 :             break;
     291              :     }
     292           28 : }
     293              : 
     294           14 : void dbutils_debug_values (int argc, sqlite3_value **argv) {
     295           42 :     for (int i = 0; i < argc; i++) {
     296           28 :         dbutils_debug_value(argv[i]);
     297           28 :     }
     298           14 : }
     299              : 
     300           97 : int dbutils_debug_stmt (sqlite3 *db, bool print_result) {
     301           97 :     sqlite3_stmt *stmt = NULL;
     302           97 :     int counter = 0;
     303           98 :     while ((stmt = sqlite3_next_stmt(db, stmt))) {
     304            1 :         ++counter;
     305            1 :         if (print_result) printf("Unfinalized stmt statement: %p\n", stmt);
     306              :     }
     307           97 :     return counter;
     308              : }
     309              : 
     310              : // MARK: -
     311              : 
     312         2495 : int dbutils_register_function (sqlite3 *db, const char *name, void (*ptr)(sqlite3_context*,int,sqlite3_value**), int nargs, char **pzErrMsg, void *ctx, void (*ctx_free)(void *)) {
     313              :     DEBUG_DBFUNCTION("dbutils_register_function %s", name);
     314              :     
     315         2495 :     const int DEFAULT_FLAGS = SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC;
     316         2495 :     int rc = sqlite3_create_function_v2(db, name, nargs, DEFAULT_FLAGS, ctx, ptr, NULL, NULL, ctx_free);
     317              :     
     318         2495 :     if (rc != SQLITE_OK) {
     319            1 :         if (pzErrMsg) *pzErrMsg = cloudsync_memory_mprintf("Error creating function %s: %s", name, sqlite3_errmsg(db));
     320            1 :         return rc;
     321              :     }
     322              :     
     323         2494 :     return SQLITE_OK;
     324         2495 : }
     325              : 
     326          173 : int dbutils_register_aggregate (sqlite3 *db, const char *name, void (*xstep)(sqlite3_context*,int,sqlite3_value**), void (*xfinal)(sqlite3_context*), int nargs, char **pzErrMsg, void *ctx, void (*ctx_free)(void *)) {
     327              :     DEBUG_DBFUNCTION("dbutils_register_aggregate %s", name);
     328              :     
     329          173 :     const int DEFAULT_FLAGS = SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC;
     330          173 :     int rc = sqlite3_create_function_v2(db, name, nargs, DEFAULT_FLAGS, ctx, NULL, xstep, xfinal, ctx_free);
     331              :     
     332          173 :     if (rc != SQLITE_OK) {
     333            1 :         if (pzErrMsg) *pzErrMsg = cloudsync_memory_mprintf("Error creating aggregate function %s: %s", name, sqlite3_errmsg(db));
     334            1 :         return rc;
     335              :     }
     336              :     
     337          172 :     return SQLITE_OK;
     338          173 : }
     339              : 
     340         1157 : bool dbutils_system_exists (sqlite3 *db, const char *name, const char *type) {
     341              :     DEBUG_DBFUNCTION("dbutils_system_exists %s: %s", type, name);
     342              :     
     343         1157 :     sqlite3_stmt *vm = NULL;
     344         1157 :     bool result = false;
     345              :     
     346              :     char sql[1024];
     347         1157 :     snprintf(sql, sizeof(sql), "SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE type='%s' AND name=?1 COLLATE NOCASE);", type);
     348         1157 :     int rc = sqlite3_prepare_v2(db, sql, -1, &vm, NULL);
     349         1157 :     if (rc != SQLITE_OK) goto finalize;
     350              :     
     351         1157 :     rc = sqlite3_bind_text(vm, 1, name, -1, SQLITE_STATIC);
     352         1157 :     if (rc != SQLITE_OK) goto finalize;
     353              :     
     354         1157 :     rc = sqlite3_step(vm);
     355         2314 :     if (rc == SQLITE_ROW) {
     356         1157 :         result = (bool)sqlite3_column_int(vm, 0);
     357         1157 :         rc = SQLITE_OK;
     358         1157 :     }
     359              :     
     360              : finalize:
     361         1157 :     if (rc != SQLITE_OK) DEBUG_ALWAYS("Error executing %s in dbutils_system_exists for type %s name %s (%s).", sql, type, name, sqlite3_errmsg(db));
     362         1157 :     if (vm) sqlite3_finalize(vm);
     363         1157 :     return result;
     364              : }
     365              : 
     366          715 : bool dbutils_table_exists (sqlite3 *db, const char *name) {
     367          715 :     return dbutils_system_exists(db, name, "table");
     368              : }
     369              : 
     370          441 : bool dbutils_trigger_exists (sqlite3 *db, const char *name) {
     371          441 :     return dbutils_system_exists(db, name, "trigger");
     372              : }
     373              : 
     374          160 : bool dbutils_table_sanity_check (sqlite3 *db, sqlite3_context *context, const char *name, bool skip_int_pk_check) {
     375              :     DEBUG_DBFUNCTION("dbutils_table_sanity_check %s", name);
     376              :     
     377              :     char buffer[2048];
     378          160 :     size_t blen = sizeof(buffer);
     379              :     
     380              :     // sanity check table name
     381          160 :     if (name == NULL) {
     382            1 :         dbutils_context_result_error(context, "%s", "cloudsync_init requires a non-null table parameter");
     383            1 :         return false;
     384              :     }
     385              :     
     386              :     // avoid allocating heap memory for SQL statements by setting a maximum length of 1900 characters
     387              :     // for table names. This limit is reasonable and helps prevent memory management issues.
     388          159 :     const size_t maxlen = blen - 148;
     389          159 :     if (strlen(name) > maxlen) {
     390            1 :         dbutils_context_result_error(context, "Table name cannot be longer than %d characters", maxlen);
     391            1 :         return false;
     392              :     }
     393              :     
     394              :     // check if table exists
     395          158 :     if (dbutils_table_exists(db, name) == false) {
     396            2 :         dbutils_context_result_error(context, "Table %s does not exist", name);
     397            2 :         return false;
     398              :     }
     399              :     
     400              :     // no more than 128 columns can be used as a composite primary key (SQLite hard limit)
     401          156 :     char *sql = sqlite3_snprintf((int)blen, buffer, "SELECT count(*) FROM pragma_table_info('%q') WHERE pk>0;", name);
     402          156 :     sqlite3_int64 count = dbutils_int_select(db, sql);
     403          156 :     if (count > 128) {
     404            1 :         dbutils_context_result_error(context, "No more than 128 columns can be used to form a composite primary key");
     405            1 :         return false;
     406          155 :     } else if (count == -1) {
     407            0 :         dbutils_context_result_error(context, "%s", sqlite3_errmsg(db));
     408            0 :         return false;
     409              :     }
     410              :     
     411              :     #if CLOUDSYNC_DISABLE_ROWIDONLY_TABLES
     412              :     // if count == 0 means that rowid will be used as primary key (BTW: very bad choice for the user)
     413          155 :     if (count == 0) {
     414            1 :         dbutils_context_result_error(context, "Rowid only tables are not supported, all primary keys must be explicitly set and declared as NOT NULL (table %s)", name);
     415            1 :         return false;
     416              :     }
     417              :     #endif
     418              :         
     419          154 :     if (!skip_int_pk_check) {
     420          103 :         if (count == 1) {
     421              :             // the affinity of a column is determined by the declared type of the column,
     422              :             // according to the following rules in the order shown:
     423              :             // 1. If the declared type contains the string "INT" then it is assigned INTEGER affinity.
     424           19 :             sql = sqlite3_snprintf((int)blen, buffer, "SELECT count(*) FROM pragma_table_info('%q') WHERE pk=1 AND \"type\" LIKE '%%INT%%';", name);
     425           19 :             sqlite3_int64 count2 = dbutils_int_select(db, sql);
     426           19 :             if (count == count2) {
     427            2 :                 dbutils_context_result_error(context, "Table %s uses an single-column INTEGER primary key. For CRDT replication, primary keys must be globally unique. Consider using a TEXT primary key with UUIDs or ULID to avoid conflicts across nodes. If you understand the risk and still want to use this INTEGER primary key, set the third argument of the cloudsync_init function to 1 to skip this check.", name);
     428            2 :                 return false;
     429              :             }
     430           17 :             if (count2 == -1) {
     431            0 :                 dbutils_context_result_error(context, "%s", sqlite3_errmsg(db));
     432            0 :                 return false;
     433              :             }
     434           17 :         }
     435          101 :     }
     436              :         
     437              :     // if user declared explicit primary key(s) then make sure they are all declared as NOT NULL
     438          152 :     if (count > 0) {
     439          152 :         sql = sqlite3_snprintf((int)blen, buffer, "SELECT count(*) FROM pragma_table_info('%q') WHERE pk>0 AND \"notnull\"=1;", name);
     440          152 :         sqlite3_int64 count2 = dbutils_int_select(db, sql);
     441          152 :         if (count2 == -1) {
     442            0 :             dbutils_context_result_error(context, "%s", sqlite3_errmsg(db));
     443            0 :             return false;
     444              :         }
     445          152 :         if (count != count2) {
     446            1 :             dbutils_context_result_error(context, "All primary keys must be explicitly declared as NOT NULL (table %s)", name);
     447            1 :             return false;
     448              :         }
     449          151 :     }
     450              :     
     451              :     // check for columns declared as NOT NULL without a DEFAULT value.
     452              :     // Otherwise, col_merge_stmt would fail if changes to other columns are inserted first.
     453          151 :     sql = sqlite3_snprintf((int)blen, buffer, "SELECT count(*) FROM pragma_table_info('%q') WHERE pk=0 AND \"notnull\"=1 AND \"dflt_value\" IS NULL;", name);
     454          151 :     sqlite3_int64 count3 = dbutils_int_select(db, sql);
     455          151 :     if (count3 == -1) {
     456            0 :         dbutils_context_result_error(context, "%s", sqlite3_errmsg(db));
     457            0 :         return false;
     458              :     }
     459          151 :     if (count3 > 0) {
     460            1 :         dbutils_context_result_error(context, "All non-primary key columns declared as NOT NULL must have a DEFAULT value. (table %s)", name);
     461            1 :         return false;
     462              :     }
     463              :     
     464          150 :     return true;
     465          160 : }
     466              : 
     467           29 : int dbutils_delete_triggers (sqlite3 *db, const char *table) {
     468              :     DEBUG_DBFUNCTION("dbutils_delete_triggers %s", table);
     469              :     
     470              :     // from dbutils_table_sanity_check we already know that 2048 is OK
     471              :     char buffer[2048];
     472           29 :     size_t blen = sizeof(buffer);
     473           29 :     int rc = SQLITE_ERROR;
     474              :     
     475           29 :     char *sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_before_update_%w\";", table);
     476           29 :     rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
     477           29 :     if (rc != SQLITE_OK) goto finalize;
     478              :     
     479           29 :     sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_before_delete_%w\";", table);
     480           29 :     rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
     481           29 :     if (rc != SQLITE_OK) goto finalize;
     482              :     
     483           29 :     sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_after_insert_%w\";", table);
     484           29 :     rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
     485           29 :     if (rc != SQLITE_OK) goto finalize;
     486              :     
     487           29 :     sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_after_update_%w\";", table);
     488           29 :     rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
     489           29 :     if (rc != SQLITE_OK) goto finalize;
     490              :     
     491           29 :     sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_after_delete_%w\";", table);
     492           29 :     rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
     493           29 :     if (rc != SQLITE_OK) goto finalize;
     494              :     
     495              : finalize:
     496           29 :     if (rc != SQLITE_OK) DEBUG_ALWAYS("dbutils_delete_triggers error %s (%s)", sqlite3_errmsg(db), sql);
     497           29 :     return rc;
     498              : }
     499              : 
     500          147 : int dbutils_check_triggers (sqlite3 *db, const char *table, table_algo algo) {
     501              :     DEBUG_DBFUNCTION("dbutils_check_triggers %s", table);
     502              :     
     503          147 :     if (dbutils_settings_check_version(db, "0.8.25") <= 0) {
     504            0 :         dbutils_delete_triggers(db, table);
     505            0 :     }
     506              :     
     507          147 :     char *trigger_name = NULL;
     508          147 :     int rc = SQLITE_NOMEM;
     509              :     
     510              :     // common part
     511          147 :     char *trigger_when = cloudsync_memory_mprintf("FOR EACH ROW WHEN cloudsync_is_sync('%q') = 0", table);
     512          147 :     if (!trigger_when) goto finalize;
     513              :     
     514              :     // INSERT TRIGGER
     515              :     // NEW.prikey1, NEW.prikey2...
     516          147 :     trigger_name = cloudsync_memory_mprintf("cloudsync_after_insert_%s", table);
     517          147 :     if (!trigger_name) goto finalize;
     518              :     
     519          147 :     if (!dbutils_trigger_exists(db, trigger_name)) {
     520          139 :         rc = SQLITE_NOMEM;
     521          139 :         char *sql = cloudsync_memory_mprintf("SELECT group_concat('NEW.\"' || format('%%w', name) || '\"', ',') FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk;", table);
     522          139 :         if (!sql) goto finalize;
     523              :         
     524          139 :         char *pkclause = dbutils_text_select(db, sql);
     525          139 :         char *pkvalues = (pkclause) ? pkclause : "NEW.rowid";
     526          139 :         cloudsync_memory_free(sql);
     527              :         
     528          139 :         sql = cloudsync_memory_mprintf("CREATE TRIGGER \"%w\" AFTER INSERT ON \"%w\" %s BEGIN SELECT cloudsync_insert('%q', %s); END", trigger_name, table, trigger_when, table, pkvalues);
     529          139 :         if (pkclause) cloudsync_memory_free(pkclause);
     530          139 :         if (!sql) goto finalize;
     531              :         
     532          139 :         rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
     533              :         DEBUG_SQL("\n%s", sql);
     534          139 :         cloudsync_memory_free(sql);
     535          139 :         if (rc != SQLITE_OK) goto finalize;
     536          139 :     }
     537          147 :     cloudsync_memory_free(trigger_name);
     538          147 :     trigger_name = NULL;
     539          147 :     rc = SQLITE_NOMEM;
     540              : 
     541          147 :     if (algo != table_algo_crdt_gos) {
     542          140 :         rc = SQLITE_NOMEM;
     543              :         
     544              :         // UPDATE TRIGGER
     545              :         // NEW.prikey1, NEW.prikey2, OLD.prikey1, OLD.prikey2, NEW.col1, OLD.col1, NEW.col2, OLD.col2...
     546          140 :         trigger_name = cloudsync_memory_mprintf("cloudsync_after_update_%s", table);
     547          140 :         if (!trigger_name) goto finalize;
     548              :         
     549          140 :         if (!dbutils_trigger_exists(db, trigger_name)) {
     550              :             // Generate VALUES clause for all columns using a CTE to avoid compound SELECT limits
     551              :             // First, get all primary key columns in order
     552          132 :             char *pk_values_sql = cloudsync_memory_mprintf(
     553              :                 "SELECT group_concat('('||quote('%q')||', NEW.\"' || format('%%w', name) || '\", OLD.\"' || format('%%w', name) || '\")', ', ') "
     554              :                 "FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk;", 
     555          132 :                 table, table);
     556          132 :             if (!pk_values_sql) goto finalize;
     557              :             
     558          132 :             char *pk_values_list = dbutils_text_select(db, pk_values_sql);
     559          132 :             cloudsync_memory_free(pk_values_sql);
     560              :             
     561              :             // Then get all regular columns in order
     562          132 :             char *col_values_sql = cloudsync_memory_mprintf(
     563              :                 "SELECT group_concat('('||quote('%q')||', NEW.\"' || format('%%w', name) || '\", OLD.\"' || format('%%w', name) || '\")', ', ') "
     564              :                 "FROM pragma_table_info('%q') WHERE pk=0 ORDER BY cid;", 
     565          132 :                 table, table);
     566          132 :             if (!col_values_sql) goto finalize;
     567              :             
     568          132 :             char *col_values_list = dbutils_text_select(db, col_values_sql);
     569          132 :             cloudsync_memory_free(col_values_sql);
     570              :             
     571              :             // Build the complete VALUES query
     572              :             char *values_query;
     573          132 :             if (col_values_list && strlen(col_values_list) > 0) {
     574              :                 // Table has both primary keys and regular columns
     575           97 :                 values_query = cloudsync_memory_mprintf(
     576              :                     "WITH column_data(table_name, new_value, old_value) AS (VALUES %s, %s) "
     577              :                     "SELECT table_name, new_value, old_value FROM column_data",
     578           97 :                     pk_values_list, col_values_list);
     579           97 :                 cloudsync_memory_free(col_values_list);
     580           97 :             } else {
     581              :                 // Table has only primary keys
     582           35 :                 values_query = cloudsync_memory_mprintf(
     583              :                     "WITH column_data(table_name, new_value, old_value) AS (VALUES %s) "
     584              :                     "SELECT table_name, new_value, old_value FROM column_data",
     585           35 :                     pk_values_list);
     586              :             }
     587              :             
     588          132 :             if (pk_values_list) cloudsync_memory_free(pk_values_list);
     589          132 :             if (!values_query) goto finalize;
     590              :             
     591              :             // Create the trigger with aggregate function
     592          132 :             char *sql = cloudsync_memory_mprintf(
     593              :                 "CREATE TRIGGER \"%w\" AFTER UPDATE ON \"%w\" %s BEGIN "
     594              :                 "SELECT cloudsync_update(table_name, new_value, old_value) FROM (%s); "
     595              :                 "END", 
     596          132 :                 trigger_name, table, trigger_when, values_query);
     597              :             
     598          132 :             cloudsync_memory_free(values_query);
     599          132 :             if (!sql) goto finalize;
     600              :             
     601          132 :             rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
     602              :             DEBUG_SQL("\n%s", sql);
     603          132 :             cloudsync_memory_free(sql);
     604          132 :             if (rc != SQLITE_OK) goto finalize;
     605          132 :         }
     606          140 :         cloudsync_memory_free(trigger_name);
     607          140 :         trigger_name = NULL;
     608          140 :     } else {
     609              :         // Grow Only Set
     610              :         // In a grow-only set, the update operation is not allowed.
     611              :         // A grow-only set is a type of CRDT (Conflict-free Replicated Data Type) where the only permissible operation is to add elements to the set,
     612              :         // without ever removing or modifying them.
     613              :         // Once an element is added to the set, it remains there permanently, which guarantees that the set only grows over time.
     614            7 :         trigger_name = cloudsync_memory_mprintf("cloudsync_before_update_%s", table);
     615            7 :         if (!trigger_name) goto finalize;
     616              :         
     617            7 :         if (!dbutils_trigger_exists(db, trigger_name)) {
     618            7 :             char *sql = cloudsync_memory_mprintf("CREATE TRIGGER \"%w\" BEFORE UPDATE ON \"%w\" FOR EACH ROW WHEN cloudsync_is_enabled('%q') = 1 BEGIN SELECT RAISE(ABORT, 'Error: UPDATE operation is not allowed on table %w.'); END", trigger_name, table, table, table);
     619            7 :             if (!sql) goto finalize;
     620              :             
     621            7 :             rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
     622              :             DEBUG_SQL("\n%s", sql);
     623            7 :             cloudsync_memory_free(sql);
     624            7 :             if (rc != SQLITE_OK) goto finalize;
     625            7 :         }
     626            7 :         cloudsync_memory_free(trigger_name);
     627            7 :         trigger_name = NULL;
     628              :     }
     629              :     
     630              :     // DELETE TRIGGER
     631              :     // OLD.prikey1, OLD.prikey2...
     632          147 :     if (algo != table_algo_crdt_gos) {
     633          140 :         trigger_name = cloudsync_memory_mprintf("cloudsync_after_delete_%s", table);
     634          140 :         if (!trigger_name) goto finalize;
     635              :         
     636          140 :         if (!dbutils_trigger_exists(db, trigger_name)) {
     637          132 :             char *sql = cloudsync_memory_mprintf("SELECT group_concat('OLD.\"' || format('%%w', name) || '\"', ',') FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk;", table);
     638          132 :             if (!sql) goto finalize;
     639              :             
     640          132 :             char *pkclause = dbutils_text_select(db, sql);
     641          132 :             char *pkvalues = (pkclause) ? pkclause : "OLD.rowid";
     642          132 :             cloudsync_memory_free(sql);
     643              :             
     644          132 :             sql = cloudsync_memory_mprintf("CREATE TRIGGER \"%w\" AFTER DELETE ON \"%w\" %s BEGIN SELECT cloudsync_delete('%q',%s); END", trigger_name, table, trigger_when, table, pkvalues);
     645          132 :             if (pkclause) cloudsync_memory_free(pkclause);
     646          132 :             if (!sql) goto finalize;
     647              :             
     648          132 :             rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
     649              :             DEBUG_SQL("\n%s", sql);
     650          132 :             cloudsync_memory_free(sql);
     651          132 :             if (rc != SQLITE_OK) goto finalize;
     652          132 :         }
     653              :         
     654          140 :         cloudsync_memory_free(trigger_name);
     655          140 :         trigger_name = NULL;
     656          140 :     } else {
     657              :         // Grow Only Set
     658              :         // In a grow-only set, the delete operation is not allowed.
     659            7 :         trigger_name = cloudsync_memory_mprintf("cloudsync_before_delete_%s", table);
     660            7 :         if (!trigger_name) goto finalize;
     661              :         
     662            7 :         if (!dbutils_trigger_exists(db, trigger_name)) {
     663            7 :             char *sql = cloudsync_memory_mprintf("CREATE TRIGGER \"%w\" BEFORE DELETE ON \"%w\" FOR EACH ROW WHEN cloudsync_is_enabled('%q') = 1 BEGIN SELECT RAISE(ABORT, 'Error: DELETE operation is not allowed on table %w.'); END", trigger_name, table, table, table);
     664            7 :             if (!sql) goto finalize;
     665              :             
     666            7 :             rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
     667              :             DEBUG_SQL("\n%s", sql);
     668            7 :             cloudsync_memory_free(sql);
     669            7 :             if (rc != SQLITE_OK) goto finalize;
     670            7 :         }
     671            7 :         cloudsync_memory_free(trigger_name);
     672            7 :         trigger_name = NULL;
     673              :     }
     674              :     
     675          147 :     rc = SQLITE_OK;
     676              :     
     677              : finalize:
     678          147 :     if (trigger_name) cloudsync_memory_free(trigger_name);
     679          147 :     if (trigger_when) cloudsync_memory_free(trigger_when);
     680          147 :     if (rc != SQLITE_OK) DEBUG_ALWAYS("dbutils_create_triggers error %s (%d)", sqlite3_errmsg(db), rc);
     681          147 :     return rc;
     682              : }
     683              : 
     684          145 : int dbutils_check_metatable (sqlite3 *db, const char *table, table_algo algo) {
     685              :     DEBUG_DBFUNCTION("dbutils_check_metatable %s", table);
     686              :         
     687              :     // WITHOUT ROWID is available starting from SQLite version 3.8.2 (2013-12-06) and later
     688          145 :     char *sql = cloudsync_memory_mprintf("CREATE TABLE IF NOT EXISTS \"%w_cloudsync\" (pk BLOB NOT NULL, col_name TEXT NOT NULL, col_version INTEGER, db_version INTEGER, site_id INTEGER DEFAULT 0, seq INTEGER, PRIMARY KEY (pk, col_name)) WITHOUT ROWID; CREATE INDEX IF NOT EXISTS \"%w_cloudsync_db_idx\" ON \"%w_cloudsync\" (db_version);", table, table, table);
     689          145 :     if (!sql) return SQLITE_NOMEM;
     690              :     
     691          145 :     int rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
     692              :     DEBUG_SQL("\n%s", sql);
     693          145 :     cloudsync_memory_free(sql);
     694              :     
     695          145 :     return rc;
     696          145 : }
     697              : 
     698              : 
     699           87 : sqlite3_int64 dbutils_schema_version (sqlite3 *db) {
     700              :     DEBUG_DBFUNCTION("dbutils_schema_version");
     701              :     
     702           87 :     return dbutils_int_select(db, "PRAGMA schema_version;");
     703              : }
     704              : 
     705         1141 : bool dbutils_is_star_table (const char *table_name) {
     706         1141 :     return (table_name && (strlen(table_name) == 1) && table_name[0] == '*');
     707              : }
     708              : 
     709              : // MARK: - Settings -
     710              : 
     711          452 : int binary_comparison (int x, int y) {
     712          452 :     if (x == y) return 0;
     713          151 :     if (x > y) return 1;
     714            2 :     return -1;
     715          452 : }
     716              : 
     717          419 : char *dbutils_settings_get_value (sqlite3 *db, const char *key, char *buffer, size_t blen) {
     718              :     DEBUG_SETTINGS("dbutils_settings_get_value key: %s", key);
     719              :     
     720              :     // check if heap allocation must be forced
     721          419 :     if (!buffer || blen == 0) blen = 0;
     722          419 :     size_t size = 0;
     723              :     
     724          419 :     sqlite3_stmt *vm = NULL;
     725          419 :     char *sql = "SELECT value FROM cloudsync_settings WHERE key=?1;";
     726          419 :     int rc = sqlite3_prepare(db, sql, -1, &vm, NULL);
     727          419 :     if (rc != SQLITE_OK) goto finalize_get_value;
     728              :     
     729          419 :     rc = sqlite3_bind_text(vm, 1, key, -1, SQLITE_STATIC);
     730          419 :     if (rc != SQLITE_OK) goto finalize_get_value;
     731              :     
     732          419 :     rc = sqlite3_step(vm);
     733          419 :     if (rc == SQLITE_DONE) rc = SQLITE_OK;
     734          262 :     else if (rc != SQLITE_ROW) goto finalize_get_value;
     735              :     
     736              :     // SQLITE_ROW case
     737          419 :     if (sqlite3_column_type(vm, 0) == SQLITE_NULL) {
     738          157 :         rc = SQLITE_OK;
     739          157 :         goto finalize_get_value;
     740              :     }
     741              :     
     742          262 :     const unsigned char *value = sqlite3_column_text(vm, 0);
     743              :     #if CLOUDSYNC_UNITTEST
     744          262 :     size = (buffer == OUT_OF_MEMORY_BUFFER) ? (SQLITE_MAX_ALLOCATION_SIZE + 1) :(size_t)sqlite3_column_bytes(vm, 0);
     745              :     #else
     746              :     size = (size_t)sqlite3_column_bytes(vm, 0);
     747              :     #endif
     748          262 :     if (size + 1 > blen) {
     749            2 :         buffer = cloudsync_memory_alloc((sqlite3_uint64)(size + 1));
     750            2 :         if (!buffer) {
     751            1 :             rc = SQLITE_NOMEM;
     752            1 :             goto finalize_get_value;
     753              :         }
     754            1 :     }
     755              :     
     756          261 :     memcpy(buffer, value, size+1);
     757          261 :     rc = SQLITE_OK;
     758              :     
     759              : finalize_get_value:
     760              :     #if CLOUDSYNC_UNITTEST
     761          419 :     if ((rc == SQLITE_NOMEM) && (size == SQLITE_MAX_ALLOCATION_SIZE + 1)) rc = SQLITE_OK;
     762              :     #endif
     763          419 :     if (rc != SQLITE_OK) DEBUG_ALWAYS("dbutils_settings_get_value error %s", sqlite3_errmsg(db));
     764          419 :     if (vm) sqlite3_finalize(vm);
     765              :     
     766          419 :     return buffer;
     767              : }
     768              : 
     769          232 : int dbutils_settings_set_key_value (sqlite3 *db, sqlite3_context *context, const char *key, const char *value) {
     770              :     DEBUG_SETTINGS("dbutils_settings_set_key_value key: %s value: %s", key, value);
     771              :     
     772          232 :     int rc = SQLITE_OK;
     773          232 :     if (db == NULL) db = sqlite3_context_db_handle(context);
     774              :     
     775          232 :     if (key && value) {
     776          231 :         char *sql = "REPLACE INTO cloudsync_settings (key, value) VALUES (?1, ?2);";
     777          231 :         const char *values[] = {key, value};
     778          231 :         int types[] = {SQLITE_TEXT, SQLITE_TEXT};
     779          231 :         int lens[] = {-1, -1};
     780          231 :         rc = dbutils_write(db, context, sql, values, types, lens, 2);
     781          231 :     }
     782              :     
     783          232 :     if (value == NULL) {
     784            1 :         char *sql = "DELETE FROM cloudsync_settings WHERE key = ?1;";
     785            1 :         const char *values[] = {key};
     786            1 :         int types[] = {SQLITE_TEXT};
     787            1 :         int lens[] = {-1};
     788            1 :         rc = dbutils_write(db, context, sql, values, types, lens, 1);
     789            1 :     }
     790              :     
     791          232 :     cloudsync_context *data = (context) ? (cloudsync_context *)sqlite3_user_data(context) : NULL;
     792          232 :     if (rc == SQLITE_OK && data) cloudsync_sync_key(data, key, value);
     793          232 :     return rc;
     794              : }
     795              : 
     796          266 : int dbutils_settings_get_int_value (sqlite3 *db, const char *key) {
     797              :     DEBUG_SETTINGS("dbutils_settings_get_int_value key: %s", key);
     798          266 :     char buffer[256] = {0};
     799          266 :     if (dbutils_settings_get_value(db, key, buffer, sizeof(buffer)) == NULL) return -1;
     800              :     
     801          266 :     return (int)strtol(buffer, NULL, 0);
     802          266 : }
     803              : 
     804          150 : int dbutils_settings_check_version (sqlite3 *db, const char *version) {
     805              :     DEBUG_SETTINGS("dbutils_settings_check_version");
     806              :     char buffer[256];
     807          150 :     if (dbutils_settings_get_value(db, CLOUDSYNC_KEY_LIBVERSION, buffer, sizeof(buffer)) == NULL) return -666;
     808              :     
     809              :     int major1, minor1, patch1;
     810              :     int major2, minor2, patch2;
     811          150 :     int count1 = sscanf(buffer, "%d.%d.%d", &major1, &minor1, &patch1);
     812          150 :     int count2 = sscanf((version == NULL ? CLOUDSYNC_VERSION : version), "%d.%d.%d", &major2, &minor2, &patch2);
     813              :     
     814          150 :     if (count1 != 3 || count2 != 3) return -666;
     815              :     
     816          150 :     int res = 0;
     817          150 :     if ((res = binary_comparison(major1, major2)) == 0) {
     818          150 :         if ((res = binary_comparison(minor1, minor2)) == 0) {
     819          149 :             return binary_comparison(patch1, patch2);
     820              :         }
     821            1 :     }
     822              :     
     823              :     DEBUG_SETTINGS(" %s %s (%d)", buffer, CLOUDSYNC_VERSION, res);
     824            1 :     return res;
     825          150 : }
     826              : 
     827          171 : char *dbutils_table_settings_get_value (sqlite3 *db, const char *table, const char *column, const char *key, char *buffer, size_t blen) {
     828              :     DEBUG_SETTINGS("dbutils_table_settings_get_value table: %s column: %s key: %s", table, column, key);
     829              :     
     830              :     // check if heap allocation must be forced
     831          171 :     if (!buffer || blen == 0) blen = 0;
     832          171 :     size_t size = 0;
     833              :     
     834          171 :     sqlite3_stmt *vm = NULL;
     835          171 :     char *sql = "SELECT value FROM cloudsync_table_settings WHERE (tbl_name=?1 AND col_name=?2 AND key=?3);";
     836          171 :     int rc = sqlite3_prepare(db, sql, -1, &vm, NULL);
     837          171 :     if (rc != SQLITE_OK) goto finalize_get_value;
     838              :     
     839          171 :     rc = sqlite3_bind_text(vm, 1, table, -1, SQLITE_STATIC);
     840          171 :     if (rc != SQLITE_OK) goto finalize_get_value;
     841              :     
     842          171 :     rc = sqlite3_bind_text(vm, 2, (column) ? column : "*", -1, SQLITE_STATIC);
     843          171 :     if (rc != SQLITE_OK) goto finalize_get_value;
     844              :     
     845          171 :     rc = sqlite3_bind_text(vm, 3, key, -1, SQLITE_STATIC);
     846          171 :     if (rc != SQLITE_OK) goto finalize_get_value;
     847              :     
     848          171 :     rc = sqlite3_step(vm);
     849          171 :     if (rc == SQLITE_DONE) rc = SQLITE_OK;
     850           54 :     else if (rc != SQLITE_ROW) goto finalize_get_value;
     851              :     
     852              :     // SQLITE_ROW case
     853          171 :     if (sqlite3_column_type(vm, 0) == SQLITE_NULL) {
     854          117 :         rc = SQLITE_OK;
     855          117 :         goto finalize_get_value;
     856              :     }
     857              :     
     858           54 :     const unsigned char *value = sqlite3_column_text(vm, 0);
     859              :     #if CLOUDSYNC_UNITTEST
     860           54 :     size = (buffer == OUT_OF_MEMORY_BUFFER) ? (SQLITE_MAX_ALLOCATION_SIZE + 1) :(size_t)sqlite3_column_bytes(vm, 0);
     861              :     #else
     862              :     size = (size_t)sqlite3_column_bytes(vm, 0);
     863              :     #endif
     864           54 :     if (size + 1 > blen) {
     865            2 :         buffer = cloudsync_memory_alloc((sqlite3_uint64)(size + 1));
     866            2 :         if (!buffer) {
     867            1 :             rc = SQLITE_NOMEM;
     868            1 :             goto finalize_get_value;
     869              :         }
     870            1 :     }
     871              :     
     872           53 :     memcpy(buffer, value, size+1);
     873           53 :     rc = SQLITE_OK;
     874              :     
     875              : finalize_get_value:
     876              :     #if CLOUDSYNC_UNITTEST
     877          171 :     if ((rc == SQLITE_NOMEM) && (size == SQLITE_MAX_ALLOCATION_SIZE + 1)) rc = SQLITE_OK;
     878              :     #endif
     879          171 :     if (rc != SQLITE_OK) {
     880            0 :         DEBUG_ALWAYS("cloudsync_table_settings error %s", sqlite3_errmsg(db));
     881            0 :     }
     882          171 :     if (vm) sqlite3_finalize(vm);
     883              :     
     884          171 :     return buffer;
     885              : }
     886              : 
     887          128 : int dbutils_table_settings_set_key_value (sqlite3 *db, sqlite3_context *context, const char *table, const char *column, const char *key, const char *value) {
     888              :     DEBUG_SETTINGS("dbutils_table_settings_set_key_value table: %s column: %s key: %s", table, column, key);
     889              :     
     890          128 :     int rc = SQLITE_OK;
     891          128 :     if (db == NULL) db = sqlite3_context_db_handle(context);
     892              :     
     893              :     // sanity check tbl_name
     894          128 :     if (table == NULL) {
     895            1 :         if (context) sqlite3_result_error(context, "cloudsync_set_table/set_column requires a non-null table parameter", -1);
     896            1 :         return SQLITE_ERROR;
     897              :     }
     898              :     
     899              :     // sanity check column name
     900          127 :     if (column == NULL) column = "*";
     901              :     
     902              :     // remove all table_name entries
     903          127 :     if (key == NULL) {
     904            6 :         char *sql = "DELETE FROM cloudsync_table_settings WHERE tbl_name=?1;";
     905            6 :         const char *values[] = {table};
     906            6 :         int types[] = {SQLITE_TEXT};
     907            6 :         int lens[] = {-1};
     908            6 :         rc = dbutils_write(db, context, sql, values, types, lens, 1);
     909            6 :         return rc;
     910              :     }
     911              :     
     912          121 :     if (key && value) {
     913          120 :         char *sql = "REPLACE INTO cloudsync_table_settings (tbl_name, col_name, key, value) VALUES (?1, ?2, ?3, ?4);";
     914          120 :         const char *values[] = {table, column, key, value};
     915          120 :         int types[] = {SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT};
     916          120 :         int lens[] = {-1, -1, -1, -1};
     917          120 :         rc = dbutils_write(db, context, sql, values, types, lens, 4);
     918          120 :     }
     919              :     
     920          121 :     if (value == NULL) {
     921            1 :         char *sql = "DELETE FROM cloudsync_table_settings WHERE (tbl_name=?1 AND col_name=?2 AND key=?3);";
     922            1 :         const char *values[] = {table, column, key};
     923            1 :         int types[] = {SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT};
     924            1 :         int lens[] = {-1, -1, -1};
     925            1 :         rc = dbutils_write(db, context, sql, values, types, lens, 3);
     926            1 :     }
     927              :     
     928              :     // unused in this version
     929              :     // cloudsync_context *data = (context) ? (cloudsync_context *)sqlite3_user_data(context) : NULL;
     930              :     // if (rc == SQLITE_OK && data) cloudsync_sync_table_key(data, table, column, key, value);
     931          121 :     return rc;
     932          128 : }
     933              : 
     934          377 : sqlite3_int64 dbutils_table_settings_count_tables (sqlite3 *db) {
     935              :     DEBUG_SETTINGS("dbutils_table_settings_count_tables");
     936          377 :     return dbutils_int_select(db, "SELECT count(*) FROM cloudsync_table_settings WHERE key='algo';");
     937              : }
     938              : 
     939          168 : table_algo dbutils_table_settings_get_algo (sqlite3 *db, const char *table_name) {
     940              :     DEBUG_SETTINGS("dbutils_table_settings_get_algo %s", table_name);
     941              :     
     942              :     char buffer[512];
     943          168 :     char *value = dbutils_table_settings_get_value(db, table_name, "*", "algo", buffer, sizeof(buffer));
     944          168 :     return (value) ? crdt_algo_from_name(value) : table_algo_none;
     945              : }
     946              : 
     947          178 : int dbutils_settings_load_callback (void *xdata, int ncols, char **values, char **names) {
     948          178 :     cloudsync_context *data = (cloudsync_context *)xdata;
     949              :     
     950          356 :     for (int i=0; i<ncols; i+=2) {
     951          178 :         const char *key = values[i];
     952          178 :         const char *value = values[i+1];
     953          178 :         cloudsync_sync_key(data, key, value);
     954              :         DEBUG_SETTINGS("key: %s value: %s", key, value);
     955          178 :     }
     956              :     
     957          178 :     return 0;
     958              : }
     959              : 
     960              : bool table_add_to_context (sqlite3 *db, cloudsync_context *data, table_algo algo, const char *table_name);
     961              : 
     962            2 : int dbutils_settings_table_load_callback (void *xdata, int ncols, char **values, char **names) {
     963            2 :     dbutils_settings_table_context *context = (dbutils_settings_table_context *)xdata;
     964            2 :     cloudsync_context *data = context->data;
     965            2 :     sqlite3 *db = context->db;
     966              : 
     967            4 :     for (int i=0; i<ncols; i+=4) {
     968            2 :         const char *table_name = values[i];
     969              :         // const char *col_name = values[i+1];
     970            2 :         const char *key = values[i+2];
     971            2 :         const char *value = values[i+3];
     972            2 :         if (strcmp(key, "algo")!=0) continue;
     973              :         
     974            2 :         if (dbutils_check_triggers(db, table_name, crdt_algo_from_name(value)) != SQLITE_OK) return SQLITE_MISUSE;
     975            2 :         if (table_add_to_context(db, data, crdt_algo_from_name(value), table_name)  == false) return SQLITE_MISUSE;
     976              :         
     977              :         DEBUG_SETTINGS("load tbl_name: %s value: %s", key, value);
     978            2 :     }
     979              :     
     980            2 :     return 0;
     981            2 : }
     982              : 
     983            1 : bool dbutils_migrate (sqlite3 *db) {
     984              :     // dbutils_settings_check_version comparison failed
     985              :     // so check for logic migration here (if necessary)
     986            1 :     return true;
     987              : }
     988              : 
     989           89 : int dbutils_settings_load (sqlite3 *db, cloudsync_context *data) {
     990              :     DEBUG_SETTINGS("dbutils_settings_load %p", data);
     991              :     
     992              :     // load global settings
     993           89 :     const char *sql = "SELECT key, value FROM cloudsync_settings;";
     994           89 :     int rc = sqlite3_exec(db, sql, dbutils_settings_load_callback, data, NULL);
     995           89 :     if (rc != SQLITE_OK) DEBUG_ALWAYS("cloudsync_load_settings error: %s", sqlite3_errmsg(db));
     996              :     
     997              :     // load table-specific settings
     998           89 :     dbutils_settings_table_context xdata = {.db = db, .data = data};
     999           89 :     sql = "SELECT lower(tbl_name), lower(col_name), key, value FROM cloudsync_table_settings ORDER BY tbl_name;";
    1000           89 :     rc = sqlite3_exec(db, sql, dbutils_settings_table_load_callback, &xdata, NULL);
    1001           89 :     if (rc != SQLITE_OK) DEBUG_ALWAYS("cloudsync_load_settings error: %s", sqlite3_errmsg(db));
    1002              :     
    1003           89 :     return SQLITE_OK;
    1004              : }
    1005              : 
    1006           89 : int dbutils_settings_init (sqlite3 *db, void *cloudsync_data, sqlite3_context *context) {
    1007              :     DEBUG_SETTINGS("dbutils_settings_init %p", context);
    1008              :         
    1009           89 :     cloudsync_context *data = (cloudsync_context *)cloudsync_data;
    1010           89 :     if (!data) data = (cloudsync_context *)sqlite3_user_data(context);
    1011              :     
    1012              :     // check if cloudsync_settings table exists
    1013           89 :     bool settings_exists = dbutils_table_exists(db, CLOUDSYNC_SETTINGS_NAME);
    1014           89 :     if (settings_exists == false) {
    1015              :         DEBUG_SETTINGS("cloudsync_settings does not exist (creating a new one)");
    1016              :         
    1017              :         char sql[1024];
    1018           87 :         int rc = SQLITE_OK;
    1019              :         
    1020              :         // create table and fill-in initial data
    1021           87 :         snprintf(sql, sizeof(sql), "CREATE TABLE IF NOT EXISTS cloudsync_settings (key TEXT PRIMARY KEY NOT NULL COLLATE NOCASE, value TEXT);");
    1022           87 :         rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
    1023           87 :         if (rc != SQLITE_OK) {if (context) sqlite3_result_error(context, sqlite3_errmsg(db), -1); return rc;}
    1024              :         
    1025              :         // library version
    1026           87 :         snprintf(sql, sizeof(sql), "INSERT INTO cloudsync_settings (key, value) VALUES ('%s', '%s');", CLOUDSYNC_KEY_LIBVERSION, CLOUDSYNC_VERSION);
    1027           87 :         rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
    1028           87 :         if (rc != SQLITE_OK) {if (context) sqlite3_result_error(context, sqlite3_errmsg(db), -1); return rc;}
    1029              :         
    1030              :         // schema version
    1031           87 :         snprintf(sql, sizeof(sql), "INSERT INTO cloudsync_settings (key, value) VALUES ('%s', %lld);", CLOUDSYNC_KEY_SCHEMAVERSION, (long long)dbutils_schema_version(db));
    1032           87 :         rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
    1033           87 :         if (rc != SQLITE_OK) {if (context) sqlite3_result_error(context, sqlite3_errmsg(db), -1); return rc;}
    1034           87 :     } 
    1035              :     
    1036           89 :     if (dbutils_table_exists(db, CLOUDSYNC_SITEID_NAME) == false) {
    1037              :         DEBUG_SETTINGS("cloudsync_site_id does not exist (creating a new one)");
    1038              :         
    1039              :         // create table and fill-in initial data
    1040              :         // site_id is implicitly indexed
    1041              :         // the rowid column is the primary key
    1042           87 :         char *sql = "CREATE TABLE IF NOT EXISTS cloudsync_site_id (site_id BLOB UNIQUE NOT NULL);";
    1043           87 :         int rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
    1044           87 :         if (rc != SQLITE_OK) {if (context) sqlite3_result_error(context, sqlite3_errmsg(db), -1); return rc;}
    1045              :         
    1046              :         // siteid (to uniquely identify this local copy of the database)
    1047              :         uint8_t site_id[UUID_LEN];
    1048           87 :         if (cloudsync_uuid_v7(site_id) == -1) {if (context) sqlite3_result_error(context, "Unable to create UUIDv7 site_id", -1); return SQLITE_ERROR;}
    1049              :         
    1050              :         // rowid 0 means local site_id
    1051           87 :         sql = "INSERT INTO cloudsync_site_id (rowid, site_id) VALUES (?, ?);";
    1052           87 :         const char *values[] = {"0", (const char *)&site_id};
    1053           87 :         int types[] = {SQLITE_INTEGER, SQLITE_BLOB};
    1054           87 :         int lens[] = {-1, UUID_LEN};
    1055           87 :         rc = dbutils_write(db, context, sql, values, types, lens, 2);
    1056           87 :         if (rc != SQLITE_OK) return rc;
    1057           87 :     }
    1058              :     
    1059              :     // check if cloudsync_table_settings table exists
    1060           89 :     if (dbutils_table_exists(db, CLOUDSYNC_TABLE_SETTINGS_NAME) == false) {
    1061              :         DEBUG_SETTINGS("cloudsync_table_settings does not exist (creating a new one)");
    1062              :         
    1063           87 :         char *sql = "CREATE TABLE IF NOT EXISTS cloudsync_table_settings (tbl_name TEXT NOT NULL COLLATE NOCASE, col_name TEXT NOT NULL COLLATE NOCASE, key TEXT, value TEXT, PRIMARY KEY(tbl_name,key));";
    1064           87 :         int rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
    1065           87 :         if (rc != SQLITE_OK) {if (context) sqlite3_result_error(context, sqlite3_errmsg(db), -1); return rc;}
    1066           87 :     }
    1067              :     
    1068              :     // check if cloudsync_settings table exists
    1069           89 :     bool schema_versions_exists = dbutils_table_exists(db, CLOUDSYNC_SCHEMA_VERSIONS_NAME);
    1070           89 :     if (schema_versions_exists == false) {
    1071              :         DEBUG_SETTINGS("cloudsync_schema_versions does not exist (creating a new one)");
    1072              :         
    1073           87 :         int rc = SQLITE_OK;
    1074              :         
    1075              :         // create table
    1076           87 :         char *sql = "CREATE TABLE IF NOT EXISTS cloudsync_schema_versions (hash INTEGER PRIMARY KEY, seq INTEGER NOT NULL)";
    1077           87 :         rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
    1078           87 :         if (rc != SQLITE_OK) {if (context) sqlite3_result_error(context, sqlite3_errmsg(db), -1); return rc;}
    1079           87 :     }
    1080              :     
    1081              :     // cloudsync_settings table exists so load it
    1082           89 :     dbutils_settings_load(db, data);
    1083              :     
    1084              :     // check if some process changed schema outside of the lib
    1085              :     /*
    1086              :     if ((settings_exists == true) && (data->schema_version != dbutils_schema_version(db))) {
    1087              :         // SOMEONE CHANGED SCHEMAs SO WE NEED TO RECHECK AUGMENTED TABLES and RELATED TRIGGERS
    1088              :         assert(0);
    1089              :     }
    1090              :      */
    1091              :     
    1092           89 :     return SQLITE_OK;
    1093           89 : }
    1094              : 
    1095          142 : int dbutils_update_schema_hash(sqlite3 *db, uint64_t *hash) {
    1096          142 :     char *schemasql = "SELECT group_concat(LOWER(sql)) FROM sqlite_master "
    1097              :             "WHERE type = 'table' AND name IN (SELECT tbl_name FROM cloudsync_table_settings ORDER BY tbl_name) "
    1098              :             "ORDER BY name;";
    1099          142 :     char *schema = dbutils_text_select(db, schemasql);
    1100          142 :     if (!schema) return SQLITE_ERROR;
    1101              :         
    1102          142 :     sqlite3_uint64 h = fnv1a_hash(schema, strlen(schema));
    1103          142 :     cloudsync_memory_free(schema);
    1104          142 :     if (hash && *hash == h) return SQLITE_CONSTRAINT;
    1105              :     
    1106              :     char sql[1024];
    1107          138 :     snprintf(sql, sizeof(sql), "INSERT INTO cloudsync_schema_versions (hash, seq) "
    1108              :                                "VALUES (%lld, COALESCE((SELECT MAX(seq) FROM cloudsync_schema_versions), 0) + 1) "
    1109              :                                "ON CONFLICT(hash) DO UPDATE SET "
    1110              :                                "  seq = (SELECT COALESCE(MAX(seq), 0) + 1 FROM cloudsync_schema_versions);", (sqlite3_int64)h);
    1111          138 :     int rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
    1112          138 :     if (rc == SQLITE_OK && hash) *hash = h;
    1113          138 :     return rc;
    1114          142 : }
    1115              : 
    1116           89 : sqlite3_uint64 dbutils_schema_hash (sqlite3 *db) {
    1117              :     DEBUG_DBFUNCTION("dbutils_schema_version");
    1118              :     
    1119           89 :     return (sqlite3_uint64)dbutils_int_select(db, "SELECT hash FROM cloudsync_schema_versions ORDER BY seq DESC limit 1;");
    1120              : }
    1121              : 
    1122            3 : bool dbutils_check_schema_hash (sqlite3 *db, sqlite3_uint64 hash) {
    1123              :     DEBUG_DBFUNCTION("dbutils_check_schema_hash");
    1124              :     
    1125              :     // a change from the current version of the schema or from previous known schema can be applied
    1126              :     // a change from a newer schema version not yet applied to this peer cannot be applied
    1127              :     // so a schema hash is valid if it exists in the cloudsync_schema_versions table
    1128              :     
    1129              :     // the idea is to allow changes on stale peers and to be able to apply these changes on peers with newer schema,
    1130              :     // but it requires alter table operation on augmented tables only add new columns and never drop columns for backward compatibility
    1131              :     char sql[1024];
    1132            3 :     snprintf(sql, sizeof(sql), "SELECT 1 FROM cloudsync_schema_versions WHERE hash = (%lld)", hash);
    1133              :     
    1134            3 :     return (dbutils_int_select(db, sql) == 1);
    1135              : }
    1136              : 
    1137              : 
    1138            2 : int dbutils_settings_cleanup (sqlite3 *db) {
    1139            2 :     const char *sql = "DROP TABLE IF EXISTS cloudsync_settings; DROP TABLE IF EXISTS cloudsync_site_id; DROP TABLE IF EXISTS cloudsync_table_settings; DROP TABLE IF EXISTS cloudsync_schema_versions; ";
    1140            2 :     return sqlite3_exec(db, sql, NULL, NULL, NULL);
    1141              : }
        

Generated by: LCOV version 2.3.2-1