LCOV - code coverage report
Current view: top level - src/sqlite - cloudsync_sqlite.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 81.0 % 852 690
Test Date: 2026-03-16 13:50:46 Functions: 94.3 % 53 50

            Line data    Source code
       1              : //
       2              : //  cloudsync_sqlite.c
       3              : //  cloudsync
       4              : //
       5              : //  Created by Marco Bambini on 05/12/25.
       6              : //
       7              : 
       8              : #include "cloudsync_sqlite.h"
       9              : #include "cloudsync_changes_sqlite.h"
      10              : #include "../pk.h"
      11              : #include "../cloudsync.h"
      12              : #include "../block.h"
      13              : #include "../database.h"
      14              : #include "../dbutils.h"
      15              : 
      16              : #ifndef CLOUDSYNC_OMIT_NETWORK
      17              : #include "../network/network.h"
      18              : #endif
      19              : 
      20              : #ifndef SQLITE_CORE
      21              : SQLITE_EXTENSION_INIT1
      22              : #endif
      23              : 
      24              : #ifndef UNUSED_PARAMETER
      25              : #define UNUSED_PARAMETER(X) (void)(X)
      26              : #endif
      27              : 
      28              : #ifdef _WIN32
      29              : #define APIEXPORT   __declspec(dllexport)
      30              : #else
      31              : #define APIEXPORT
      32              : #endif
      33              : 
      34              : typedef struct {
      35              :     sqlite3_context *context;
      36              :     int             index;
      37              : } cloudsync_pk_decode_context;
      38              : 
      39              : typedef struct {
      40              :     sqlite3_value   *table_name;
      41              :     sqlite3_value   **new_values;
      42              :     sqlite3_value   **old_values;
      43              :     int             count;
      44              :     int             capacity;
      45              : } cloudsync_update_payload;
      46              : 
      47            1 : void dbsync_set_error (sqlite3_context *context, const char *format, ...) {
      48              :     char buffer[2048];
      49              :     
      50              :     va_list arg;
      51            1 :     va_start (arg, format);
      52            1 :     vsnprintf(buffer, sizeof(buffer), format, arg);
      53            1 :     va_end (arg);
      54              :     
      55            1 :     if (context) sqlite3_result_error(context, buffer, -1);
      56            1 : }
      57              : 
      58              : // MARK: - Public -
      59              : 
      60            7 : void dbsync_version (sqlite3_context *context, int argc, sqlite3_value **argv) {
      61              :     DEBUG_FUNCTION("cloudsync_version");
      62            7 :     UNUSED_PARAMETER(argc);
      63            7 :     UNUSED_PARAMETER(argv);
      64            7 :     sqlite3_result_text(context, CLOUDSYNC_VERSION, -1, SQLITE_STATIC);
      65            7 : }
      66              : 
      67          511 : void dbsync_siteid (sqlite3_context *context, int argc, sqlite3_value **argv) {
      68              :     DEBUG_FUNCTION("cloudsync_siteid");
      69          511 :     UNUSED_PARAMETER(argc);
      70          511 :     UNUSED_PARAMETER(argv);
      71              :     
      72          511 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
      73          511 :     sqlite3_result_blob(context, cloudsync_siteid(data), UUID_LEN, SQLITE_STATIC);
      74          511 : }
      75              : 
      76            1 : void dbsync_db_version (sqlite3_context *context, int argc, sqlite3_value **argv) {
      77              :     DEBUG_FUNCTION("cloudsync_db_version");
      78            1 :     UNUSED_PARAMETER(argc);
      79            1 :     UNUSED_PARAMETER(argv);
      80              :     
      81              :     // retrieve context
      82            1 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
      83              :     
      84            1 :     int rc = cloudsync_dbversion_check_uptodate(data);
      85            1 :     if (rc != SQLITE_OK) {
      86            0 :         dbsync_set_error(context, "Unable to retrieve db_version (%s).", database_errmsg(data));
      87            0 :         return;
      88              :     }
      89              :     
      90            1 :     sqlite3_result_int64(context, cloudsync_dbversion(data));
      91            1 : }
      92              : 
      93        25073 : void dbsync_db_version_next (sqlite3_context *context, int argc, sqlite3_value **argv) {
      94              :     DEBUG_FUNCTION("cloudsync_db_version_next");
      95              :     
      96              :     // retrieve context
      97        25073 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
      98              :     
      99        25073 :     sqlite3_int64 merging_version = (argc == 1) ? database_value_int(argv[0]) : CLOUDSYNC_VALUE_NOTSET;
     100        25073 :     sqlite3_int64 value = cloudsync_dbversion_next(data, merging_version);
     101        25073 :     if (value == -1) {
     102            0 :         dbsync_set_error(context, "Unable to retrieve next_db_version (%s).", database_errmsg(data));
     103            0 :         return;
     104              :     }
     105              :     
     106        25073 :     sqlite3_result_int64(context, value);
     107        25073 : }
     108              : 
     109           64 : void dbsync_seq (sqlite3_context *context, int argc, sqlite3_value **argv) {
     110              :     DEBUG_FUNCTION("cloudsync_seq");
     111              :     
     112              :     // retrieve context
     113           64 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     114           64 :     sqlite3_result_int(context, cloudsync_bumpseq(data));
     115           64 : }
     116              : 
     117            5 : void dbsync_uuid (sqlite3_context *context, int argc, sqlite3_value **argv) {
     118              :     DEBUG_FUNCTION("cloudsync_uuid");
     119              :     
     120              :     char value[UUID_STR_MAXLEN];
     121            5 :     char *uuid = cloudsync_uuid_v7_string(value, true);
     122            5 :     sqlite3_result_text(context, uuid, -1, SQLITE_TRANSIENT);
     123            5 : }
     124              : 
     125              : // MARK: -
     126              : 
     127            2 : void dbsync_set (sqlite3_context *context, int argc, sqlite3_value **argv) {
     128              :     DEBUG_FUNCTION("cloudsync_set");
     129              :     
     130              :     // sanity check parameters
     131            2 :     const char *key = (const char *)database_value_text(argv[0]);
     132            2 :     const char *value = (const char *)database_value_text(argv[1]);
     133              :     
     134              :     // silently fails
     135            2 :     if (key == NULL) return;
     136              :     
     137            2 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     138            2 :     dbutils_settings_set_key_value(data, key, value);
     139            2 : }
     140              : 
     141           73 : void dbsync_set_column (sqlite3_context *context, int argc, sqlite3_value **argv) {
     142              :     DEBUG_FUNCTION("cloudsync_set_column");
     143              : 
     144           73 :     const char *tbl = (const char *)database_value_text(argv[0]);
     145           73 :     const char *col = (const char *)database_value_text(argv[1]);
     146           73 :     const char *key = (const char *)database_value_text(argv[2]);
     147           73 :     const char *value = (const char *)database_value_text(argv[3]);
     148              : 
     149           73 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     150              : 
     151              :     // Handle block column setup: cloudsync_set_column('tbl', 'col', 'algo', 'block')
     152           73 :     if (key && value && strcmp(key, "algo") == 0 && strcmp(value, "block") == 0) {
     153           69 :         int rc = cloudsync_setup_block_column(data, tbl, col, NULL);
     154           69 :         if (rc != DBRES_OK) {
     155            0 :             sqlite3_result_error(context, cloudsync_errmsg(data), -1);
     156            0 :         }
     157           69 :         return;
     158              :     }
     159              : 
     160              :     // Handle delimiter setting: cloudsync_set_column('tbl', 'col', 'delimiter', '\n\n')
     161            4 :     if (key && strcmp(key, "delimiter") == 0) {
     162            2 :         cloudsync_table_context *table = table_lookup(data, tbl);
     163            2 :         if (table) {
     164            2 :             int col_idx = table_col_index(table, col);
     165            2 :             if (col_idx >= 0 && table_col_algo(table, col_idx) == col_algo_block) {
     166            2 :                 table_set_col_delimiter(table, col_idx, value);
     167            2 :             }
     168            2 :         }
     169            2 :     }
     170              : 
     171            4 :     dbutils_table_settings_set_key_value(data, tbl, col, key, value);
     172           73 : }
     173              : 
     174            2 : void dbsync_set_table (sqlite3_context *context, int argc, sqlite3_value **argv) {
     175              :     DEBUG_FUNCTION("cloudsync_set_table");
     176              :     
     177            2 :     const char *tbl = (const char *)database_value_text(argv[0]);
     178            2 :     const char *key = (const char *)database_value_text(argv[1]);
     179            2 :     const char *value = (const char *)database_value_text(argv[2]);
     180              :     
     181            2 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     182            2 :     dbutils_table_settings_set_key_value(data, tbl, "*", key, value);
     183            2 : }
     184              : 
     185            2 : void dbsync_set_schema (sqlite3_context *context, int argc, sqlite3_value **argv) {
     186              :     DEBUG_FUNCTION("dbsync_set_schema");
     187              :     
     188            2 :     const char *schema = (const char *)database_value_text(argv[0]);
     189            2 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     190            2 :     cloudsync_set_schema(data, schema);
     191            2 : }
     192              : 
     193            2 : void dbsync_schema (sqlite3_context *context, int argc, sqlite3_value **argv) {
     194              :     DEBUG_FUNCTION("dbsync_schema");
     195              :     
     196            2 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     197            2 :     const char *schema = cloudsync_schema(data);
     198            2 :     (schema) ? sqlite3_result_text(context, schema, -1, NULL) : sqlite3_result_null(context);
     199            2 : }
     200              : 
     201            2 : void dbsync_table_schema (sqlite3_context *context, int argc, sqlite3_value **argv) {
     202              :     DEBUG_FUNCTION("dbsync_table_schema");
     203              :     
     204            2 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     205            2 :     const char *table_name = (const char *)database_value_text(argv[0]);
     206            2 :     const char *schema = cloudsync_table_schema(data, table_name);
     207            2 :     (schema) ? sqlite3_result_text(context, schema, -1, NULL) : sqlite3_result_null(context);
     208            2 : }
     209              : 
     210        12547 : void dbsync_is_sync (sqlite3_context *context, int argc, sqlite3_value **argv) {
     211              :     DEBUG_FUNCTION("cloudsync_is_sync");
     212              :     
     213        12547 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     214        12547 :     if (cloudsync_insync(data)) {
     215         8597 :         sqlite3_result_int(context, 1);
     216         8597 :         return;
     217              :     }
     218              :     
     219         3950 :     const char *table_name = (const char *)database_value_text(argv[0]);
     220         3950 :     cloudsync_table_context *table = table_lookup(data, table_name);
     221         3950 :     sqlite3_result_int(context, (table) ? (table_enabled(table) == 0) : 0);
     222        12547 : }
     223              : 
     224       126675 : void dbsync_col_value (sqlite3_context *context, int argc, sqlite3_value **argv) {
     225              :     // DEBUG_FUNCTION("cloudsync_col_value");
     226              :     
     227              :     // argv[0] -> table name
     228              :     // argv[1] -> column name
     229              :     // argv[2] -> encoded pk
     230              :     
     231              :     // retrieve column name
     232       126675 :     const char *col_name = (const char *)database_value_text(argv[1]);
     233       126675 :     if (!col_name) {
     234            0 :         dbsync_set_error(context, "Column name cannot be NULL");
     235            0 :         return;
     236              :     }
     237              :     
     238              :     // check for special tombstone value
     239       126675 :     if (strcmp(col_name, CLOUDSYNC_TOMBSTONE_VALUE) == 0) {
     240         4119 :         sqlite3_result_null(context);
     241         4119 :         return;
     242              :     }
     243              : 
     244              :     // lookup table
     245       122556 :     const char *table_name = (const char *)database_value_text(argv[0]);
     246       122556 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     247       122556 :     cloudsync_table_context *table = table_lookup(data, table_name);
     248       122556 :     if (!table) {
     249            0 :         dbsync_set_error(context, "Unable to retrieve table name %s in clousdsync_colvalue.", table_name);
     250            0 :         return;
     251              :     }
     252              : 
     253              :     // Block column: if col_name contains \x1F, read from blocks table
     254       122556 :     if (block_is_block_colname(col_name) && table_has_block_cols(table)) {
     255          981 :         dbvm_t *bvm = table_block_value_read_stmt(table);
     256          981 :         if (!bvm) {
     257            0 :             sqlite3_result_null(context);
     258            0 :             return;
     259              :         }
     260          981 :         int rc = databasevm_bind_blob(bvm, 1, database_value_blob(argv[2]), database_value_bytes(argv[2]));
     261          981 :         if (rc != DBRES_OK) { databasevm_reset(bvm); sqlite3_result_error(context, database_errmsg(data), -1); return; }
     262          981 :         rc = databasevm_bind_text(bvm, 2, col_name, -1);
     263          981 :         if (rc != DBRES_OK) { databasevm_reset(bvm); sqlite3_result_error(context, database_errmsg(data), -1); return; }
     264              : 
     265          981 :         rc = databasevm_step(bvm);
     266          981 :         if (rc == SQLITE_ROW) {
     267          902 :             sqlite3_result_value(context, database_column_value(bvm, 0));
     268          981 :         } else if (rc == SQLITE_DONE) {
     269           79 :             sqlite3_result_null(context);
     270           79 :         } else {
     271            0 :             sqlite3_result_error(context, database_errmsg(data), -1);
     272              :         }
     273          981 :         databasevm_reset(bvm);
     274          981 :         return;
     275              :     }
     276              : 
     277              :     // extract the right col_value vm associated to the column name
     278       121575 :     sqlite3_stmt *vm = table_column_lookup(table, col_name, false, NULL);
     279       121575 :     if (!vm) {
     280            0 :         sqlite3_result_error(context, "Unable to retrieve column value precompiled statement in clousdsync_colvalue.", -1);
     281            0 :         return;
     282              :     }
     283              : 
     284              :     // bind primary key values
     285       121575 :     int rc = pk_decode_prikey((char *)database_value_blob(argv[2]), (size_t)database_value_bytes(argv[2]), pk_decode_bind_callback, (void *)vm);
     286       121575 :     if (rc < 0) goto cleanup;
     287              : 
     288              :     // execute vm
     289       121575 :     rc = databasevm_step(vm);
     290       243150 :     if (rc == SQLITE_DONE) {
     291            0 :         rc = SQLITE_OK;
     292            0 :         sqlite3_result_text(context, CLOUDSYNC_RLS_RESTRICTED_VALUE, -1, SQLITE_STATIC);
     293       121575 :     } else if (rc == SQLITE_ROW) {
     294              :         // store value result
     295       121575 :         rc = SQLITE_OK;
     296       121575 :         sqlite3_result_value(context, database_column_value(vm, 0));
     297       121575 :     }
     298              : 
     299              : cleanup:
     300       121575 :     if (rc != SQLITE_OK) {
     301            0 :         sqlite3_result_error(context, database_errmsg(data), -1);
     302            0 :     }
     303       121575 :     databasevm_reset(vm);
     304       126675 : }
     305              : 
     306        10416 : void dbsync_pk_encode (sqlite3_context *context, int argc, sqlite3_value **argv) {
     307        10416 :     size_t bsize = 0;
     308        10416 :     char *buffer = pk_encode_prikey((dbvalue_t **)argv, argc, NULL, &bsize);
     309        10416 :     if (!buffer || buffer == PRIKEY_NULL_CONSTRAINT_ERROR) {
     310            1 :         sqlite3_result_null(context);
     311            1 :         return;
     312              :     }
     313        10415 :     sqlite3_result_blob(context, (const void *)buffer, (int)bsize, SQLITE_TRANSIENT);
     314        10415 :     cloudsync_memory_free(buffer);
     315        10416 : }
     316              : 
     317           19 : int dbsync_pk_decode_set_result_callback (void *xdata, int index, int type, int64_t ival, double dval, char *pval) {
     318           19 :     cloudsync_pk_decode_context *decode_context = (cloudsync_pk_decode_context *)xdata;
     319              :     // decode_context->index is 1 based
     320              :     // index is 0 based
     321           19 :     if (decode_context->index != index+1) return SQLITE_OK;
     322              :     
     323            7 :     int rc = 0;
     324            7 :     sqlite3_context *context = decode_context->context;
     325            7 :     switch (type) {
     326              :         case SQLITE_INTEGER:
     327            3 :             sqlite3_result_int64(context, ival);
     328            3 :             break;
     329              : 
     330              :         case SQLITE_FLOAT:
     331            2 :             sqlite3_result_double(context, dval);
     332            2 :             break;
     333              : 
     334              :         case SQLITE_NULL:
     335            0 :             sqlite3_result_null(context);
     336            0 :             break;
     337              : 
     338              :         case SQLITE_TEXT:
     339            1 :             sqlite3_result_text(context, pval, (int)ival, SQLITE_TRANSIENT);
     340            1 :             break;
     341              : 
     342              :         case SQLITE_BLOB:
     343            1 :             sqlite3_result_blob(context, pval, (int)ival, SQLITE_TRANSIENT);
     344            1 :             break;
     345              :     }
     346              :     
     347            7 :     return rc;
     348           19 : }
     349              : 
     350              : 
     351            7 : void dbsync_pk_decode (sqlite3_context *context, int argc, sqlite3_value **argv) {
     352            7 :     const char *pk = (const char *)database_value_blob(argv[0]);
     353            7 :     int pk_len = database_value_bytes(argv[0]);
     354            7 :     int i = (int)database_value_int(argv[1]);
     355              :     
     356            7 :     cloudsync_pk_decode_context xdata = {.context = context, .index = i};
     357            7 :     pk_decode_prikey((char *)pk, (size_t)pk_len, dbsync_pk_decode_set_result_callback, &xdata);
     358            7 : }
     359              : 
     360              : // MARK: -
     361              : 
     362         3514 : void dbsync_insert (sqlite3_context *context, int argc, sqlite3_value **argv) {
     363              :     DEBUG_FUNCTION("cloudsync_insert %s", database_value_text(argv[0]));
     364              :     // debug_values(argc-1, &argv[1]);
     365              :     
     366              :     // argv[0] is table name
     367              :     // argv[1]..[N] is primary key(s)
     368              :     
     369              :     // table_cloudsync
     370              :     // pk               -> encode(argc-1, &argv[1])
     371              :     // col_name         -> name
     372              :     // col_version      -> 0/1 +1
     373              :     // db_version       -> check
     374              :     // site_id          0
     375              :     // seq              -> sqlite_master
     376              :     
     377              :     // retrieve context
     378         3514 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     379              :     
     380              :     // lookup table
     381         3514 :     const char *table_name = (const char *)database_value_text(argv[0]);
     382         3514 :     cloudsync_table_context *table = table_lookup(data, table_name);
     383         3514 :     if (!table) {
     384            0 :         dbsync_set_error(context, "Unable to retrieve table name %s in cloudsync_insert.", table_name);
     385            0 :         return;
     386              :     }
     387              :     
     388              :     // encode the primary key values into a buffer
     389              :     char buffer[1024];
     390         3514 :     size_t pklen = sizeof(buffer);
     391         3514 :     char *pk = pk_encode_prikey((dbvalue_t **)&argv[1], table_count_pks(table), buffer, &pklen);
     392         3514 :     if (!pk) {
     393            0 :         sqlite3_result_error(context, "Not enough memory to encode the primary key(s).", -1);
     394            0 :         return;
     395              :     }
     396         3514 :     if (pk == PRIKEY_NULL_CONSTRAINT_ERROR) {
     397            1 :         dbsync_set_error(context, "Insert aborted because primary key in table %s contains NULL values.", table_name);
     398            1 :         return;
     399              :     }
     400              :     
     401              :     // compute the next database version for tracking changes
     402         3513 :     int64_t db_version = cloudsync_dbversion_next(data, CLOUDSYNC_VALUE_NOTSET);
     403              :     
     404              :     // check if a row with the same primary key already exists
     405              :     // if so, this means the row might have been previously deleted (sentinel)
     406         3513 :     bool pk_exists = table_pk_exists(table, pk, pklen);
     407         3513 :     int rc = SQLITE_OK;
     408              :     
     409         3513 :     if (table_count_cols(table) == 0) {
     410              :         // if there are no columns other than primary keys, insert a sentinel record
     411           97 :         rc = local_mark_insert_sentinel_meta(table, pk, pklen, db_version, cloudsync_bumpseq(data));
     412           97 :         if (rc != SQLITE_OK) goto cleanup;
     413         3513 :     } else if (pk_exists){
     414              :         // if a row with the same primary key already exists, update the sentinel record
     415            3 :         rc = local_update_sentinel(table, pk, pklen, db_version, cloudsync_bumpseq(data));
     416            3 :         if (rc != SQLITE_OK) goto cleanup;
     417            3 :     }
     418              :     
     419              :     // process each non-primary key column for insert or update
     420        14137 :     for (int i=0; i<table_count_cols(table); ++i) {
     421        10624 :         if (table_col_algo(table, i) == col_algo_block) {
     422              :             // Block column: read value from base table, split into blocks, store each block
     423           42 :             sqlite3_stmt *val_vm = (sqlite3_stmt *)table_column_lookup(table, table_colname(table, i), false, NULL);
     424           42 :             if (!val_vm) goto cleanup;
     425              : 
     426           42 :             rc = pk_decode_prikey(pk, pklen, pk_decode_bind_callback, (void *)val_vm);
     427           42 :             if (rc < 0) { databasevm_reset((dbvm_t *)val_vm); goto cleanup; }
     428              : 
     429           42 :             rc = databasevm_step((dbvm_t *)val_vm);
     430           42 :             if (rc == DBRES_ROW) {
     431           42 :                 const char *text = database_column_text((dbvm_t *)val_vm, 0);
     432           42 :                 const char *delim = table_col_delimiter(table, i);
     433           42 :                 const char *col = table_colname(table, i);
     434              : 
     435           42 :                 block_list_t *blocks = block_split(text ? text : "", delim);
     436           42 :                 if (blocks) {
     437           42 :                     char **positions = block_initial_positions(blocks->count);
     438           42 :                     if (positions) {
     439          352 :                         for (int b = 0; b < blocks->count; b++) {
     440          310 :                             char *block_cn = block_build_colname(col, positions[b]);
     441          310 :                             if (block_cn) {
     442          310 :                                 rc = local_mark_insert_or_update_meta(table, pk, pklen, block_cn, db_version, cloudsync_bumpseq(data));
     443              : 
     444              :                                 // Store block value in blocks table
     445          310 :                                 dbvm_t *wvm = table_block_value_write_stmt(table);
     446          310 :                                 if (wvm && rc == SQLITE_OK) {
     447          310 :                                     databasevm_bind_blob(wvm, 1, pk, (int)pklen);
     448          310 :                                     databasevm_bind_text(wvm, 2, block_cn, -1);
     449          310 :                                     databasevm_bind_text(wvm, 3, blocks->entries[b].content, -1);
     450          310 :                                     databasevm_step(wvm);
     451          310 :                                     databasevm_reset(wvm);
     452          310 :                                 }
     453              : 
     454          310 :                                 cloudsync_memory_free(block_cn);
     455          310 :                             }
     456          310 :                             cloudsync_memory_free(positions[b]);
     457          310 :                             if (rc != SQLITE_OK) break;
     458          310 :                         }
     459           42 :                         cloudsync_memory_free(positions);
     460           42 :                     }
     461           42 :                     block_list_free(blocks);
     462           42 :                 }
     463           42 :             }
     464           42 :             databasevm_reset((dbvm_t *)val_vm);
     465           42 :             if (rc == DBRES_ROW || rc == DBRES_DONE) rc = SQLITE_OK;
     466           42 :             if (rc != SQLITE_OK) goto cleanup;
     467           42 :         } else {
     468              :             // Regular column: mark as inserted or updated in the metadata
     469        10582 :             rc = local_mark_insert_or_update_meta(table, pk, pklen, table_colname(table, i), db_version, cloudsync_bumpseq(data));
     470        10582 :             if (rc != SQLITE_OK) goto cleanup;
     471              :         }
     472        14137 :     }
     473              : 
     474              : cleanup:
     475         3513 :     if (rc != SQLITE_OK) sqlite3_result_error(context, database_errmsg(data), -1);
     476              :     // free memory if the primary key was dynamically allocated
     477         3513 :     if (pk != buffer) cloudsync_memory_free(pk);
     478         3514 : }
     479              : 
     480           34 : void dbsync_delete (sqlite3_context *context, int argc, sqlite3_value **argv) {
     481              :     DEBUG_FUNCTION("cloudsync_delete %s", database_value_text(argv[0]));
     482              :     // debug_values(argc-1, &argv[1]);
     483              :     
     484              :     // retrieve context
     485           34 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     486              :     
     487              :     // lookup table
     488           34 :     const char *table_name = (const char *)database_value_text(argv[0]);
     489           34 :     cloudsync_table_context *table = table_lookup(data, table_name);
     490           34 :     if (!table) {
     491            0 :         dbsync_set_error(context, "Unable to retrieve table name %s in cloudsync_delete.", table_name);
     492            0 :         return;
     493              :     }
     494              :     
     495              :     // compute the next database version for tracking changes
     496           34 :     int64_t db_version = cloudsync_dbversion_next(data, CLOUDSYNC_VALUE_NOTSET);
     497           34 :     int rc = SQLITE_OK;
     498              :     
     499              :     // encode the primary key values into a buffer
     500              :     char buffer[1024];
     501           34 :     size_t pklen = sizeof(buffer);
     502           34 :     char *pk = pk_encode_prikey((dbvalue_t **)&argv[1], table_count_pks(table), buffer, &pklen);
     503           34 :     if (!pk) {
     504            0 :         sqlite3_result_error(context, "Not enough memory to encode the primary key(s).", -1);
     505            0 :         return;
     506              :     }
     507              :     
     508           34 :     if (pk == PRIKEY_NULL_CONSTRAINT_ERROR) {
     509            0 :         dbsync_set_error(context, "Delete aborted because primary key in table %s contains NULL values.", table_name);
     510            0 :         return;
     511              :     }
     512              :     
     513              :     // mark the row as deleted by inserting a delete sentinel into the metadata
     514           34 :     rc = local_mark_delete_meta(table, pk, pklen, db_version, cloudsync_bumpseq(data));
     515           34 :     if (rc != SQLITE_OK) goto cleanup;
     516              :     
     517              :     // remove any metadata related to the old rows associated with this primary key
     518           34 :     rc = local_drop_meta(table, pk, pklen);
     519           34 :     if (rc != SQLITE_OK) goto cleanup;
     520              :     
     521              : cleanup:
     522           34 :     if (rc != SQLITE_OK) sqlite3_result_error(context, database_errmsg(data), -1);
     523              :     // free memory if the primary key was dynamically allocated
     524           34 :     if (pk != buffer) cloudsync_memory_free(pk);
     525           34 : }
     526              : 
     527              : // MARK: -
     528              : 
     529          432 : void dbsync_update_payload_free (cloudsync_update_payload *payload) {
     530         2599 :     for (int i=0; i<payload->count; i++) {
     531         2167 :         database_value_free(payload->new_values[i]);
     532         2167 :         database_value_free(payload->old_values[i]);
     533         2167 :     }
     534          432 :     cloudsync_memory_free(payload->new_values);
     535          432 :     cloudsync_memory_free(payload->old_values);
     536          432 :     database_value_free(payload->table_name);
     537          432 :     payload->new_values = NULL;
     538          432 :     payload->old_values = NULL;
     539          432 :     payload->table_name = NULL;
     540          432 :     payload->count = 0;
     541          432 :     payload->capacity = 0;
     542          432 : }
     543              : 
     544         2167 : int dbsync_update_payload_append (cloudsync_update_payload *payload, sqlite3_value *v1, sqlite3_value *v2, sqlite3_value *v3) {
     545         2167 :     if (payload->count >= payload->capacity) {
     546          435 :         int newcap = payload->capacity ? payload->capacity * 2 : 128;
     547              : 
     548          435 :         sqlite3_value **new_values_2 = (sqlite3_value **)cloudsync_memory_realloc(payload->new_values, newcap * sizeof(*new_values_2));
     549          435 :         if (!new_values_2) return SQLITE_NOMEM;
     550              : 
     551          435 :         sqlite3_value **old_values_2 = (sqlite3_value **)cloudsync_memory_realloc(payload->old_values, newcap * sizeof(*old_values_2));
     552          435 :         if (!old_values_2) {
     553              :             // new_values_2 succeeded but old_values failed; keep new_values_2 pointer
     554              :             // (it's still valid, just larger) but don't update capacity
     555            0 :             payload->new_values = new_values_2;
     556            0 :             return SQLITE_NOMEM;
     557              :         }
     558              : 
     559          435 :         payload->new_values = new_values_2;
     560          435 :         payload->old_values = old_values_2;
     561          435 :         payload->capacity = newcap;
     562          435 :     }
     563              : 
     564         2167 :     int index = payload->count;
     565         2167 :     if (payload->table_name == NULL) payload->table_name = database_value_dup(v1);
     566         1735 :     else if (dbutils_value_compare(payload->table_name, v1) != 0) return SQLITE_NOMEM;
     567              : 
     568         2167 :     payload->new_values[index] = database_value_dup(v2);
     569         2167 :     payload->old_values[index] = database_value_dup(v3);
     570              : 
     571              :     // sanity check memory allocations before committing count
     572         2167 :     bool v1_can_be_null = (database_value_type(v1) == SQLITE_NULL);
     573         2167 :     bool v2_can_be_null = (database_value_type(v2) == SQLITE_NULL);
     574         2167 :     bool v3_can_be_null = (database_value_type(v3) == SQLITE_NULL);
     575              : 
     576         2167 :     bool oom = false;
     577         2167 :     if ((payload->table_name == NULL) && (!v1_can_be_null)) oom = true;
     578         2167 :     if ((payload->new_values[index] == NULL) && (!v2_can_be_null)) oom = true;
     579         2167 :     if ((payload->old_values[index] == NULL) && (!v3_can_be_null)) oom = true;
     580              : 
     581         2167 :     if (oom) {
     582              :         // clean up partial allocations at this index to prevent leaks
     583            0 :         if (payload->new_values[index]) { database_value_free(payload->new_values[index]); payload->new_values[index] = NULL; }
     584            0 :         if (payload->old_values[index]) { database_value_free(payload->old_values[index]); payload->old_values[index] = NULL; }
     585            0 :         return SQLITE_NOMEM;
     586              :     }
     587              : 
     588         2167 :     payload->count++;
     589         2167 :     return SQLITE_OK;
     590         2167 : }
     591              : 
     592         2167 : void dbsync_update_step (sqlite3_context *context, int argc, sqlite3_value **argv) {
     593              :     // argv[0] => table_name
     594              :     // argv[1] => new_column_value
     595              :     // argv[2] => old_column_value
     596              :     
     597              :     // allocate/get the update payload
     598         2167 :     cloudsync_update_payload *payload = (cloudsync_update_payload *)sqlite3_aggregate_context(context, sizeof(cloudsync_update_payload));
     599         2167 :     if (!payload) {sqlite3_result_error_nomem(context); return;}
     600              :     
     601         2167 :     if (dbsync_update_payload_append(payload, argv[0], argv[1], argv[2]) != SQLITE_OK) {
     602            0 :         sqlite3_result_error_nomem(context);
     603            0 :     }
     604         2167 : }
     605              : 
     606          432 : void dbsync_update_final (sqlite3_context *context) {
     607          432 :     cloudsync_update_payload *payload = (cloudsync_update_payload *)sqlite3_aggregate_context(context, sizeof(cloudsync_update_payload));
     608          432 :     if (!payload || payload->count == 0) return;
     609              :     
     610              :     // retrieve context
     611          432 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     612              :     
     613              :     // lookup table
     614          432 :     const char *table_name = (const char *)database_value_text(payload->table_name);
     615          432 :     cloudsync_table_context *table = table_lookup(data, table_name);
     616          432 :     if (!table) {
     617            0 :         dbsync_set_error(context, "Unable to retrieve table name %s in cloudsync_update.", table_name);
     618            0 :         dbsync_update_payload_free(payload);
     619            0 :         return;
     620              :     }
     621              : 
     622              :     // compute the next database version for tracking changes
     623          432 :     int64_t db_version = cloudsync_dbversion_next(data, CLOUDSYNC_VALUE_NOTSET);
     624          432 :     int rc = SQLITE_OK;
     625              :     
     626              :     // Check if the primary key(s) have changed
     627          432 :     bool prikey_changed = false;
     628         1136 :     for (int i=0; i<table_count_pks(table); ++i) {
     629          735 :         if (dbutils_value_compare(payload->old_values[i], payload->new_values[i]) != 0) {
     630           31 :             prikey_changed = true;
     631           31 :             break;
     632              :         }
     633          704 :     }
     634              : 
     635              :     // encode the NEW primary key values into a buffer (used later for indexing)
     636              :     char buffer[1024];
     637              :     char buffer2[1024];
     638          432 :     size_t pklen = sizeof(buffer);
     639          432 :     size_t oldpklen = sizeof(buffer2);
     640          432 :     char *oldpk = NULL;
     641              :     
     642          432 :     char *pk = pk_encode_prikey((dbvalue_t **)payload->new_values, table_count_pks(table), buffer, &pklen);
     643          432 :     if (!pk) {
     644            0 :         sqlite3_result_error(context, "Not enough memory to encode the primary key(s).", -1);
     645            0 :         dbsync_update_payload_free(payload);
     646            0 :         return;
     647              :     }
     648          432 :     if (pk == PRIKEY_NULL_CONSTRAINT_ERROR) {
     649            0 :         dbsync_set_error(context, "Update aborted because primary key in table %s contains NULL values.", table_name);
     650            0 :         dbsync_update_payload_free(payload);
     651            0 :         return;
     652              :     }
     653              :     
     654          432 :     if (prikey_changed) {
     655              :         // if the primary key has changed, we need to handle the row differently:
     656              :         // 1. mark the old row (OLD primary key) as deleted
     657              :         // 2. create a new row (NEW primary key)
     658              :         
     659              :         // encode the OLD primary key into a buffer
     660           31 :         oldpk = pk_encode_prikey((dbvalue_t **)payload->old_values, table_count_pks(table), buffer2, &oldpklen);
     661           31 :         if (!oldpk) {
     662              :             // no check here about PRIKEY_NULL_CONSTRAINT_ERROR because by design oldpk cannot contain NULL values
     663            0 :             if (pk != buffer) cloudsync_memory_free(pk);
     664            0 :             sqlite3_result_error(context, "Not enough memory to encode the primary key(s).", -1);
     665            0 :             dbsync_update_payload_free(payload);
     666            0 :             return;
     667              :         }
     668              :         
     669              :         // mark the rows with the old primary key as deleted in the metadata (old row handling)
     670           31 :         rc = local_mark_delete_meta(table, oldpk, oldpklen, db_version, cloudsync_bumpseq(data));
     671           31 :         if (rc != SQLITE_OK) goto cleanup;
     672              :         
     673              :         // move non-sentinel metadata entries from OLD primary key to NEW primary key
     674              :         // handles the case where some metadata is retained across primary key change
     675              :         // see https://github.com/sqliteai/sqlite-sync/blob/main/docs/PriKey.md for more details
     676           31 :         rc = local_update_move_meta(table, pk, pklen, oldpk, oldpklen, db_version);
     677           31 :         if (rc != SQLITE_OK) goto cleanup;
     678              :         
     679              :         // mark a new sentinel row with the new primary key in the metadata
     680           31 :         rc = local_mark_insert_sentinel_meta(table, pk, pklen, db_version, cloudsync_bumpseq(data));
     681           31 :         if (rc != SQLITE_OK) goto cleanup;
     682              :         
     683              :         // free memory if the OLD primary key was dynamically allocated
     684           31 :         if (oldpk != buffer2) cloudsync_memory_free(oldpk);
     685           31 :         oldpk = NULL;
     686           31 :     }
     687              :     
     688              :     // compare NEW and OLD values (excluding primary keys) to handle column updates
     689         1834 :     for (int i=0; i<table_count_cols(table); i++) {
     690         1402 :         int col_index = table_count_pks(table) + i;  // Regular columns start after primary keys
     691              : 
     692         1402 :         if (dbutils_value_compare(payload->old_values[col_index], payload->new_values[col_index]) != 0) {
     693          706 :             if (table_col_algo(table, i) == col_algo_block) {
     694              :                 // Block column: diff old and new text, emit per-block metadata changes
     695           91 :                 const char *new_text = (const char *)database_value_text(payload->new_values[col_index]);
     696           91 :                 const char *delim = table_col_delimiter(table, i);
     697           91 :                 const char *col = table_colname(table, i);
     698              : 
     699              :                 // Read existing blocks from blocks table
     700           91 :                 block_list_t *old_blocks = block_list_create_empty();
     701           91 :                 if (table_block_list_stmt(table)) {
     702           91 :                     char *like_pattern = block_build_colname(col, "%");
     703           91 :                     if (like_pattern) {
     704              :                         // Query blocks table directly for existing block names and values
     705           91 :                         char *list_sql = cloudsync_memory_mprintf(
     706              :                             "SELECT col_name, col_value FROM %s WHERE pk = ?1 AND col_name LIKE ?2 ORDER BY col_name",
     707           91 :                             table_blocks_ref(table));
     708           91 :                         if (list_sql) {
     709           91 :                             dbvm_t *list_vm = NULL;
     710           91 :                             if (databasevm_prepare(data, list_sql, &list_vm, 0) == DBRES_OK) {
     711           91 :                                 databasevm_bind_blob(list_vm, 1, pk, (int)pklen);
     712           91 :                                 databasevm_bind_text(list_vm, 2, like_pattern, -1);
     713         1439 :                                 while (databasevm_step(list_vm) == DBRES_ROW) {
     714         1348 :                                     const char *bcn = database_column_text(list_vm, 0);
     715         1348 :                                     const char *bval = database_column_text(list_vm, 1);
     716         1348 :                                     const char *pos = block_extract_position_id(bcn);
     717         1348 :                                     if (pos && old_blocks) {
     718         1348 :                                         block_list_add(old_blocks, bval ? bval : "", pos);
     719         1348 :                                     }
     720              :                                 }
     721           91 :                                 databasevm_finalize(list_vm);
     722           91 :                             }
     723           91 :                             cloudsync_memory_free(list_sql);
     724           91 :                         }
     725           91 :                         cloudsync_memory_free(like_pattern);
     726           91 :                     }
     727           91 :                 }
     728              : 
     729              :                 // Split new text into parts (NULL text = all blocks removed)
     730           91 :                 block_list_t *new_blocks = new_text ? block_split(new_text, delim) : block_list_create_empty();
     731           91 :                 if (new_blocks && old_blocks) {
     732              :                     // Build array of new content strings (NULL when count is 0)
     733           91 :                     const char **new_parts = NULL;
     734           91 :                     if (new_blocks->count > 0) {
     735           90 :                         new_parts = (const char **)cloudsync_memory_alloc(
     736           90 :                             (uint64_t)(new_blocks->count * sizeof(char *)));
     737           90 :                         if (new_parts) {
     738         1492 :                             for (int b = 0; b < new_blocks->count; b++) {
     739         1402 :                                 new_parts[b] = new_blocks->entries[b].content;
     740         1402 :                             }
     741           90 :                         }
     742           90 :                     }
     743              : 
     744           91 :                     if (new_parts || new_blocks->count == 0) {
     745          182 :                         block_diff_t *diff = block_diff(old_blocks->entries, old_blocks->count,
     746           91 :                                                          new_parts, new_blocks->count);
     747           91 :                         if (diff) {
     748          223 :                             for (int d = 0; d < diff->count; d++) {
     749          132 :                                 block_diff_entry_t *de = &diff->entries[d];
     750          132 :                                 char *block_cn = block_build_colname(col, de->position_id);
     751          132 :                                 if (!block_cn) continue;
     752              : 
     753          132 :                                 if (de->type == BLOCK_DIFF_ADDED || de->type == BLOCK_DIFF_MODIFIED) {
     754          186 :                                     rc = local_mark_insert_or_update_meta(table, pk, pklen, block_cn,
     755           93 :                                                                           db_version, cloudsync_bumpseq(data));
     756              :                                     // Store block value
     757           93 :                                     if (rc == SQLITE_OK && table_block_value_write_stmt(table)) {
     758           93 :                                         dbvm_t *wvm = table_block_value_write_stmt(table);
     759           93 :                                         databasevm_bind_blob(wvm, 1, pk, (int)pklen);
     760           93 :                                         databasevm_bind_text(wvm, 2, block_cn, -1);
     761           93 :                                         databasevm_bind_text(wvm, 3, de->content, -1);
     762           93 :                                         databasevm_step(wvm);
     763           93 :                                         databasevm_reset(wvm);
     764           93 :                                     }
     765          132 :                                 } else if (de->type == BLOCK_DIFF_REMOVED) {
     766              :                                     // Mark block as deleted in metadata (even col_version)
     767           78 :                                     rc = local_mark_delete_block_meta(table, pk, pklen, block_cn,
     768           39 :                                                                       db_version, cloudsync_bumpseq(data));
     769              :                                     // Remove from blocks table
     770           39 :                                     if (rc == SQLITE_OK) {
     771           39 :                                         block_delete_value_external(data, table, pk, pklen, block_cn);
     772           39 :                                     }
     773           39 :                                 }
     774          132 :                                 cloudsync_memory_free(block_cn);
     775          132 :                                 if (rc != SQLITE_OK) break;
     776          132 :                             }
     777           91 :                             block_diff_free(diff);
     778           91 :                         }
     779           91 :                         if (new_parts) cloudsync_memory_free((void *)new_parts);
     780           91 :                     }
     781           91 :                 }
     782           91 :                 if (new_blocks) block_list_free(new_blocks);
     783           91 :                 if (old_blocks) block_list_free(old_blocks);
     784           91 :                 if (rc != SQLITE_OK) goto cleanup;
     785           91 :             } else {
     786              :                 // Regular column: mark as updated in the metadata (columns are in cid order)
     787          615 :                 rc = local_mark_insert_or_update_meta(table, pk, pklen, table_colname(table, i), db_version, cloudsync_bumpseq(data));
     788          615 :                 if (rc != SQLITE_OK) goto cleanup;
     789              :             }
     790          706 :         }
     791         1834 :     }
     792              :     
     793              : cleanup:
     794          432 :     if (rc != SQLITE_OK) sqlite3_result_error(context, database_errmsg(data), -1);
     795          432 :     if (pk != buffer) cloudsync_memory_free(pk);
     796          432 :     if (oldpk && (oldpk != buffer2)) cloudsync_memory_free(oldpk);
     797              :     
     798          432 :     dbsync_update_payload_free(payload);
     799          432 : }
     800              : 
     801              : // MARK: -
     802              : 
     803            3 : void dbsync_cleanup (sqlite3_context *context, int argc, sqlite3_value **argv) {
     804              :     DEBUG_FUNCTION("cloudsync_cleanup");
     805              :     
     806            3 :     const char *table = (const char *)database_value_text(argv[0]);
     807            3 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     808              :     
     809            3 :     int rc = cloudsync_cleanup(data, table);
     810            3 :     if (rc != DBRES_OK) {
     811            0 :         sqlite3_result_error(context, cloudsync_errmsg(data), -1);
     812            0 :         sqlite3_result_error_code(context, rc);
     813            0 :     }
     814            3 : }
     815              : 
     816            6 : void dbsync_enable_disable (sqlite3_context *context, const char *table_name, bool value) {
     817              :     DEBUG_FUNCTION("cloudsync_enable_disable");
     818              :     
     819            6 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     820            6 :     cloudsync_table_context *table = table_lookup(data, table_name);
     821            6 :     if (!table) return;
     822              :     
     823            6 :     table_set_enabled(table, value);
     824            6 : }
     825              : 
     826            3 : void dbsync_enable (sqlite3_context *context, int argc, sqlite3_value **argv) {
     827              :     DEBUG_FUNCTION("cloudsync_enable");
     828              :     
     829            3 :     const char *table = (const char *)database_value_text(argv[0]);
     830            3 :     dbsync_enable_disable(context, table, true);
     831            3 : }
     832              : 
     833            3 : void dbsync_disable (sqlite3_context *context, int argc, sqlite3_value **argv) {
     834              :     DEBUG_FUNCTION("cloudsync_disable");
     835              :     
     836            3 :     const char *table = (const char *)database_value_text(argv[0]);
     837            3 :     dbsync_enable_disable(context, table, false);
     838            3 : }
     839              : 
     840         2858 : void dbsync_is_enabled (sqlite3_context *context, int argc, sqlite3_value **argv) {
     841              :     DEBUG_FUNCTION("cloudsync_is_enabled");
     842              :     
     843         2858 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     844         2858 :     const char *table_name = (const char *)database_value_text(argv[0]);
     845         2858 :     cloudsync_table_context *table = table_lookup(data, table_name);
     846              :     
     847         2858 :     int result = (table && table_enabled(table)) ? 1 : 0;
     848         2858 :     sqlite3_result_int(context, result);
     849         2858 : }
     850              : 
     851          195 : void dbsync_terminate (sqlite3_context *context, int argc, sqlite3_value **argv) {
     852              :     DEBUG_FUNCTION("cloudsync_terminate");
     853              :     
     854          195 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     855          195 :     int rc = cloudsync_terminate(data);
     856          195 :     sqlite3_result_int(context, rc);
     857          195 : }
     858              : 
     859              : // MARK: -
     860              : 
     861          223 : void dbsync_init (sqlite3_context *context, const char *table, const char *algo, bool skip_int_pk_check) {
     862          223 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     863              :     
     864          223 :     int rc = database_begin_savepoint(data, "cloudsync_init");
     865          223 :     if (rc != SQLITE_OK) {
     866            0 :         dbsync_set_error(context, "Unable to create cloudsync_init savepoint. %s", database_errmsg(data));
     867            0 :         sqlite3_result_error_code(context, rc);
     868            0 :         return;
     869              :     }
     870              :     
     871          223 :     rc = cloudsync_init_table(data, table, algo, skip_int_pk_check);
     872          223 :     if (rc == SQLITE_OK) {
     873          220 :         rc = database_commit_savepoint(data, "cloudsync_init");
     874          220 :         if (rc != SQLITE_OK) {
     875            0 :             dbsync_set_error(context, "Unable to release cloudsync_init savepoint. %s", database_errmsg(data));
     876            0 :             sqlite3_result_error_code(context, rc);
     877            0 :         }
     878          220 :     } else {
     879              :         // in case of error, rollback transaction
     880            3 :         sqlite3_result_error(context, cloudsync_errmsg(data), -1);
     881            3 :         sqlite3_result_error_code(context, rc);
     882            3 :         database_rollback_savepoint(data, "cloudsync_init");
     883            3 :         return;
     884              :     }
     885              :     
     886          220 :     cloudsync_update_schema_hash(data);
     887              :     
     888              :     // returns site_id as TEXT
     889              :     char buffer[UUID_STR_MAXLEN];
     890          220 :     cloudsync_uuid_v7_stringify(cloudsync_siteid(data), buffer, false);
     891          220 :     sqlite3_result_text(context, buffer, -1, SQLITE_TRANSIENT);
     892          223 : }
     893              : 
     894           44 : void dbsync_init3 (sqlite3_context *context, int argc, sqlite3_value **argv) {
     895              :     DEBUG_FUNCTION("cloudsync_init2");
     896              :     
     897           44 :     const char *table = (const char *)database_value_text(argv[0]);
     898           44 :     const char *algo = (const char *)database_value_text(argv[1]);
     899           44 :     bool skip_int_pk_check = (bool)database_value_int(argv[2]);
     900           44 :     dbsync_init(context, table, algo, skip_int_pk_check);
     901           44 : }
     902              : 
     903           83 : void dbsync_init2 (sqlite3_context *context, int argc, sqlite3_value **argv) {
     904              :     DEBUG_FUNCTION("cloudsync_init2");
     905              :     
     906           83 :     const char *table = (const char *)database_value_text(argv[0]);
     907           83 :     const char *algo = (const char *)database_value_text(argv[1]);
     908           83 :     dbsync_init(context, table, algo, false);
     909           83 : }
     910              : 
     911           96 : void dbsync_init1 (sqlite3_context *context, int argc, sqlite3_value **argv) {
     912              :     DEBUG_FUNCTION("cloudsync_init1");
     913              :     
     914           96 :     const char *table = (const char *)database_value_text(argv[0]);
     915           96 :     dbsync_init(context, table, NULL, false);
     916           96 : }
     917              : 
     918              : // MARK: -
     919              : 
     920           24 : void dbsync_begin_alter (sqlite3_context *context, int argc, sqlite3_value **argv) {
     921              :     DEBUG_FUNCTION("dbsync_begin_alter");
     922              :      
     923              :     //retrieve table argument
     924           24 :     const char *table_name = (const char *)database_value_text(argv[0]);
     925              :     
     926              :     // retrieve context
     927           24 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     928              :     
     929           24 :     int rc = cloudsync_begin_alter(data, table_name);
     930           24 :     if (rc != DBRES_OK) {
     931            1 :         sqlite3_result_error(context, cloudsync_errmsg(data), -1);
     932            1 :         sqlite3_result_error_code(context, rc);
     933            1 :     }
     934           24 : }
     935              : 
     936           24 : void dbsync_commit_alter (sqlite3_context *context, int argc, sqlite3_value **argv) {
     937              :     DEBUG_FUNCTION("cloudsync_commit_alter");
     938              :     
     939              :     //retrieve table argument
     940           24 :     const char *table_name = (const char *)database_value_text(argv[0]);
     941              :     
     942              :     // retrieve context
     943           24 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     944              :     
     945           24 :     int rc = cloudsync_commit_alter(data, table_name);
     946           24 :     if (rc != DBRES_OK) {
     947            1 :         sqlite3_result_error(context, cloudsync_errmsg(data), -1);
     948            1 :         sqlite3_result_error_code(context, rc);
     949            1 :     }
     950           24 : }
     951              : 
     952              : // MARK: - Payload -
     953              : 
     954        49096 : void dbsync_payload_encode_step (sqlite3_context *context, int argc, sqlite3_value **argv) {
     955              :     // allocate/get the session context
     956        49096 :     cloudsync_payload_context *payload = (cloudsync_payload_context *)sqlite3_aggregate_context(context, (int)cloudsync_payload_context_size(NULL));
     957        49096 :     if (!payload) {
     958            0 :         sqlite3_result_error(context, "Not enough memory to allocate payload session context", -1);
     959            0 :         sqlite3_result_error_code(context, SQLITE_NOMEM);
     960            0 :         return;
     961              :     }
     962              :     
     963              :     // retrieve context
     964        49096 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     965              :     
     966        49096 :     int rc = cloudsync_payload_encode_step(payload, data, argc, (dbvalue_t **)argv);
     967        49096 :     if (rc != SQLITE_OK) {
     968            0 :         sqlite3_result_error(context, cloudsync_errmsg(data), -1);
     969            0 :         sqlite3_result_error_code(context, rc);
     970            0 :     }
     971        49096 : }
     972              : 
     973          657 : void dbsync_payload_encode_final (sqlite3_context *context) {
     974              :     // get the session context
     975          657 :     cloudsync_payload_context *payload = (cloudsync_payload_context *)sqlite3_aggregate_context(context, (int)cloudsync_payload_context_size(NULL));
     976          657 :     if (!payload) {
     977            0 :         sqlite3_result_error(context, "Unable to extract payload session context", -1);
     978            0 :         sqlite3_result_error_code(context, SQLITE_NOMEM);
     979            0 :         return;
     980              :     }
     981              :     
     982              :     // retrieve context
     983          657 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     984              :     
     985          657 :     int rc = cloudsync_payload_encode_final(payload, data);
     986          657 :     if (rc != SQLITE_OK) {
     987            0 :         sqlite3_result_error(context, cloudsync_errmsg(data), -1);
     988            0 :         sqlite3_result_error_code(context, rc);
     989            0 :         return;
     990              :     }
     991              :     
     992              :     // result is OK so get BLOB and returns it
     993          657 :     int64_t blob_size = 0;
     994          657 :     char *blob = cloudsync_payload_blob (payload, &blob_size, NULL);
     995          657 :     if (!blob) {
     996            8 :         sqlite3_result_null(context);
     997            8 :     } else {
     998          649 :         sqlite3_result_blob64(context, blob, blob_size, SQLITE_TRANSIENT);
     999          649 :         cloudsync_memory_free(blob);
    1000              :     }
    1001              :     
    1002              :     // from: https://sqlite.org/c3ref/aggregate_context.html
    1003              :     // SQLite automatically frees the memory allocated by sqlite3_aggregate_context() when the aggregate query concludes.
    1004          657 : }
    1005              : 
    1006          645 : void dbsync_payload_decode (sqlite3_context *context, int argc, sqlite3_value **argv) {
    1007              :     DEBUG_FUNCTION("dbsync_payload_decode");
    1008              :     //debug_values(argc, argv);
    1009              :     
    1010              :     // sanity check payload type
    1011          645 :     if (database_value_type(argv[0]) != SQLITE_BLOB) {
    1012            0 :         sqlite3_result_error(context, "Error on cloudsync_payload_decode: value must be a BLOB.", -1);
    1013            0 :         sqlite3_result_error_code(context, SQLITE_MISUSE);
    1014            0 :         return;
    1015              :     }
    1016              :     
    1017              :     // sanity check payload size
    1018          645 :     int blen = database_value_bytes(argv[0]);
    1019          645 :     size_t header_size = 0;
    1020          645 :     cloudsync_payload_context_size(&header_size);
    1021          645 :     if (blen < (int)header_size) {
    1022            0 :         sqlite3_result_error(context, "Error on cloudsync_payload_decode: invalid input size.", -1);
    1023            0 :         sqlite3_result_error_code(context, SQLITE_MISUSE);
    1024            0 :         return;
    1025              :     }
    1026              :     
    1027              :     // obtain payload
    1028          645 :     const char *payload = (const char *)database_value_blob(argv[0]);
    1029              :     
    1030              :     // apply changes
    1031          645 :     int nrows = 0;
    1032          645 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
    1033          645 :     int rc = cloudsync_payload_apply(data, payload, blen, &nrows);
    1034          645 :     if (rc != SQLITE_OK) {
    1035            2 :         sqlite3_result_error(context, cloudsync_errmsg(data), -1);
    1036            2 :         sqlite3_result_error_code(context, rc);
    1037            2 :         return;
    1038              :     }
    1039              :     
    1040              :     // returns number of applied rows
    1041          643 :     sqlite3_result_int(context, nrows);
    1042          645 : }
    1043              : 
    1044              : #ifdef CLOUDSYNC_DESKTOP_OS
    1045            0 : void dbsync_payload_save (sqlite3_context *context, int argc, sqlite3_value **argv) {
    1046              :     DEBUG_FUNCTION("dbsync_payload_save");
    1047              :     
    1048              :     // sanity check argument
    1049            0 :     if (database_value_type(argv[0]) != SQLITE_TEXT) {
    1050            0 :         sqlite3_result_error(context, "Unable to retrieve file path.", -1);
    1051            0 :         return;
    1052              :     }
    1053              :     
    1054              :     // retrieve full path to file
    1055            0 :     const char *payload_path = (const char *)database_value_text(argv[0]);
    1056              :     
    1057              :     // retrieve global context
    1058            0 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
    1059              :     
    1060            0 :     int blob_size = 0;
    1061            0 :     int rc = cloudsync_payload_save(data, payload_path, &blob_size);
    1062            0 :     if (rc == SQLITE_OK) {
    1063              :         // if OK then returns blob size
    1064            0 :         sqlite3_result_int64(context, (sqlite3_int64)blob_size);
    1065            0 :         return;
    1066              :     }
    1067              :     
    1068            0 :     sqlite3_result_error(context, cloudsync_errmsg(data), -1);
    1069            0 :     sqlite3_result_error_code(context, rc);
    1070            0 : }
    1071              : 
    1072            0 : void dbsync_payload_load (sqlite3_context *context, int argc, sqlite3_value **argv) {
    1073              :     DEBUG_FUNCTION("dbsync_payload_load");
    1074              :     
    1075              :     // sanity check argument
    1076            0 :     if (database_value_type(argv[0]) != SQLITE_TEXT) {
    1077            0 :         sqlite3_result_error(context, "Unable to retrieve file path.", -1);
    1078            0 :         return;
    1079              :     }
    1080              :     
    1081              :     // retrieve full path to file
    1082            0 :     const char *path = (const char *)database_value_text(argv[0]);
    1083              :     
    1084            0 :     int64_t payload_size = 0;
    1085            0 :     char *payload = cloudsync_file_read(path, &payload_size);
    1086            0 :     if (!payload) {
    1087            0 :         if (payload_size < 0) {
    1088            0 :             sqlite3_result_error(context, "Unable to read payload from file path.", -1);
    1089            0 :             sqlite3_result_error_code(context, SQLITE_IOERR);
    1090            0 :             return;
    1091              :         }
    1092              :         // no rows affected but no error either
    1093            0 :         sqlite3_result_int(context, 0);
    1094            0 :         return;
    1095              :     }
    1096              :     
    1097            0 :     int nrows = 0;
    1098            0 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
    1099            0 :     int rc = cloudsync_payload_apply (data, payload, (int)payload_size, &nrows);
    1100            0 :     if (payload) cloudsync_memory_free(payload);
    1101              :     
    1102            0 :     if (rc != SQLITE_OK) {
    1103            0 :         sqlite3_result_error(context, cloudsync_errmsg(data), -1);
    1104            0 :         sqlite3_result_error_code(context, rc);
    1105            0 :         return;
    1106              :     }
    1107              :     
    1108              :     // returns number of applied rows
    1109            0 :     sqlite3_result_int(context, nrows);
    1110            0 : }
    1111              : #endif
    1112              : 
    1113              : // MARK: - Register -
    1114              : 
    1115         7334 : int dbsync_register_with_flags (sqlite3 *db, const char *name, void (*xfunc)(sqlite3_context*,int,sqlite3_value**), void (*xstep)(sqlite3_context*,int,sqlite3_value**), void (*xfinal)(sqlite3_context*), int nargs, int flags, char **pzErrMsg, void *ctx, void (*ctx_free)(void *)) {
    1116              : 
    1117         7334 :     int rc = sqlite3_create_function_v2(db, name, nargs, flags, ctx, xfunc, xstep, xfinal, ctx_free);
    1118              :     
    1119         7334 :     if (rc != SQLITE_OK) {
    1120            0 :         if (pzErrMsg) *pzErrMsg = sqlite3_mprintf("Error creating function %s: %s", name, sqlite3_errmsg(db));
    1121            0 :         return rc;
    1122              :     }
    1123         7334 :     return SQLITE_OK;
    1124         7334 : }
    1125              : 
    1126         5983 : int dbsync_register (sqlite3 *db, const char *name, void (*xfunc)(sqlite3_context*,int,sqlite3_value**), void (*xstep)(sqlite3_context*,int,sqlite3_value**), void (*xfinal)(sqlite3_context*), int nargs, char **pzErrMsg, void *ctx, void (*ctx_free)(void *)) {
    1127         5983 :     const int FLAGS_VOLATILE = SQLITE_UTF8;
    1128              :     DEBUG_DBFUNCTION("dbsync_register %s", name);
    1129         5983 :     return dbsync_register_with_flags(db, name, xfunc, xstep, xfinal, nargs, FLAGS_VOLATILE, pzErrMsg, ctx, ctx_free);
    1130              : }
    1131              : 
    1132         5790 : int dbsync_register_function (sqlite3 *db, const char *name, void (*xfunc)(sqlite3_context*,int,sqlite3_value**), int nargs, char **pzErrMsg, void *ctx, void (*ctx_free)(void *)) {
    1133              :     DEBUG_DBFUNCTION("dbsync_register_function %s", name);
    1134         5790 :     return dbsync_register(db, name, xfunc, NULL, NULL, nargs, pzErrMsg, ctx, ctx_free);
    1135              : }
    1136              : 
    1137          579 : int dbsync_register_pure_function (sqlite3 *db, const char *name, void (*xfunc)(sqlite3_context*,int,sqlite3_value**), int nargs, char **pzErrMsg, void *ctx, void (*ctx_free)(void *)) {
    1138          579 :     const int FLAGS_PURE = SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC;
    1139              :     DEBUG_DBFUNCTION("dbsync_register_pure_function %s", name);
    1140          579 :     return dbsync_register_with_flags(db, name, xfunc, NULL, NULL, nargs, FLAGS_PURE, pzErrMsg, ctx, ctx_free);
    1141              : }
    1142              : 
    1143          579 : int dbsync_register_trigger_function (sqlite3 *db, const char *name, void (*xfunc)(sqlite3_context*,int,sqlite3_value**), int nargs, char **pzErrMsg, void *ctx, void (*ctx_free)(void *)) {
    1144          579 :     const int FLAGS_TRIGGER = SQLITE_UTF8 | SQLITE_INNOCUOUS;
    1145              :     DEBUG_DBFUNCTION("dbsync_register_trigger_function %s", name);
    1146          579 :     return dbsync_register_with_flags(db, name, xfunc, NULL, NULL, nargs, FLAGS_TRIGGER, pzErrMsg, ctx, ctx_free);
    1147              : }
    1148              : 
    1149          193 : int dbsync_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 *)) {
    1150              :     DEBUG_DBFUNCTION("dbsync_register_aggregate %s", name);
    1151          193 :     return dbsync_register(db, name, NULL, xstep, xfinal, nargs, pzErrMsg, ctx, ctx_free);
    1152              : }
    1153              : 
    1154          193 : int dbsync_register_trigger_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 *)) {
    1155          193 :     const int FLAGS_TRIGGER = SQLITE_UTF8 | SQLITE_INNOCUOUS;
    1156              :     DEBUG_DBFUNCTION("dbsync_register_trigger_aggregate %s", name);
    1157          193 :     return dbsync_register_with_flags(db, name, NULL, xstep, xfinal, nargs, FLAGS_TRIGGER, pzErrMsg, ctx, ctx_free);
    1158              : }
    1159              : 
    1160              : // MARK: - Block-level LWW -
    1161              : 
    1162           50 : void dbsync_text_materialize (sqlite3_context *context, int argc, sqlite3_value **argv) {
    1163              :     DEBUG_FUNCTION("cloudsync_text_materialize");
    1164              : 
    1165              :     // argv[0] -> table name
    1166              :     // argv[1] -> column name
    1167              :     // argv[2..N] -> primary key values
    1168              : 
    1169           50 :     if (argc < 3) {
    1170            0 :         sqlite3_result_error(context, "cloudsync_text_materialize requires at least 3 arguments: table, column, pk...", -1);
    1171            0 :         return;
    1172              :     }
    1173              : 
    1174           50 :     const char *table_name = (const char *)database_value_text(argv[0]);
    1175           50 :     const char *col_name = (const char *)database_value_text(argv[1]);
    1176           50 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
    1177              : 
    1178           50 :     cloudsync_table_context *table = table_lookup(data, table_name);
    1179           50 :     if (!table) {
    1180            0 :         dbsync_set_error(context, "Unable to retrieve table name %s in cloudsync_text_materialize.", table_name);
    1181            0 :         return;
    1182              :     }
    1183              : 
    1184           50 :     int col_idx = table_col_index(table, col_name);
    1185           50 :     if (col_idx < 0 || table_col_algo(table, col_idx) != col_algo_block) {
    1186            0 :         dbsync_set_error(context, "Column %s in table %s is not configured as block-level.", col_name, table_name);
    1187            0 :         return;
    1188              :     }
    1189              : 
    1190              :     // Encode primary keys
    1191           50 :     int npks = table_count_pks(table);
    1192           50 :     if (argc - 2 != npks) {
    1193            0 :         sqlite3_result_error(context, "Wrong number of primary key values for cloudsync_text_materialize.", -1);
    1194            0 :         return;
    1195              :     }
    1196              : 
    1197              :     char buffer[1024];
    1198           50 :     size_t pklen = sizeof(buffer);
    1199           50 :     char *pk = pk_encode_prikey((dbvalue_t **)&argv[2], npks, buffer, &pklen);
    1200           50 :     if (!pk || pk == PRIKEY_NULL_CONSTRAINT_ERROR) {
    1201            0 :         sqlite3_result_error(context, "Failed to encode primary key(s).", -1);
    1202            0 :         return;
    1203              :     }
    1204              : 
    1205              :     // Materialize the column
    1206           50 :     int rc = block_materialize_column(data, table, pk, (int)pklen, col_name);
    1207           50 :     if (rc != DBRES_OK) {
    1208            0 :         sqlite3_result_error(context, cloudsync_errmsg(data), -1);
    1209            0 :     } else {
    1210           50 :         sqlite3_result_int(context, 1);
    1211              :     }
    1212              : 
    1213           50 :     if (pk != buffer) cloudsync_memory_free(pk);
    1214           50 : }
    1215              : 
    1216              : // MARK: - Row Filter -
    1217              : 
    1218            2 : void dbsync_set_filter (sqlite3_context *context, int argc, sqlite3_value **argv) {
    1219              :     DEBUG_FUNCTION("cloudsync_set_filter");
    1220              : 
    1221            2 :     const char *tbl = (const char *)database_value_text(argv[0]);
    1222            2 :     const char *filter_expr = (const char *)database_value_text(argv[1]);
    1223            2 :     if (!tbl || !filter_expr) {
    1224            0 :         dbsync_set_error(context, "cloudsync_set_filter: table and filter expression required");
    1225            0 :         return;
    1226              :     }
    1227              : 
    1228            2 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
    1229              : 
    1230              :     // Store filter in table settings
    1231            2 :     dbutils_table_settings_set_key_value(data, tbl, "*", "filter", filter_expr);
    1232              : 
    1233              :     // Read current algo
    1234            2 :     table_algo algo = dbutils_table_settings_get_algo(data, tbl);
    1235            2 :     if (algo == table_algo_none) algo = table_algo_crdt_cls;
    1236              : 
    1237              :     // Drop and recreate triggers with the filter
    1238            2 :     database_delete_triggers(data, tbl);
    1239            2 :     int rc = database_create_triggers(data, tbl, algo, filter_expr);
    1240            2 :     if (rc != DBRES_OK) {
    1241            0 :         dbsync_set_error(context, "cloudsync_set_filter: error recreating triggers");
    1242            0 :         sqlite3_result_error_code(context, rc);
    1243            0 :         return;
    1244              :     }
    1245              : 
    1246            2 :     sqlite3_result_int(context, 1);
    1247            2 : }
    1248              : 
    1249            0 : void dbsync_clear_filter (sqlite3_context *context, int argc, sqlite3_value **argv) {
    1250              :     DEBUG_FUNCTION("cloudsync_clear_filter");
    1251              : 
    1252            0 :     const char *tbl = (const char *)database_value_text(argv[0]);
    1253            0 :     if (!tbl) {
    1254            0 :         dbsync_set_error(context, "cloudsync_clear_filter: table name required");
    1255            0 :         return;
    1256              :     }
    1257              : 
    1258            0 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
    1259              : 
    1260              :     // Remove filter from table settings (set to NULL/empty)
    1261            0 :     dbutils_table_settings_set_key_value(data, tbl, "*", "filter", NULL);
    1262              : 
    1263              :     // Read current algo
    1264            0 :     table_algo algo = dbutils_table_settings_get_algo(data, tbl);
    1265            0 :     if (algo == table_algo_none) algo = table_algo_crdt_cls;
    1266              : 
    1267              :     // Drop and recreate triggers without filter
    1268            0 :     database_delete_triggers(data, tbl);
    1269            0 :     int rc = database_create_triggers(data, tbl, algo, NULL);
    1270            0 :     if (rc != DBRES_OK) {
    1271            0 :         dbsync_set_error(context, "cloudsync_clear_filter: error recreating triggers");
    1272            0 :         sqlite3_result_error_code(context, rc);
    1273            0 :         return;
    1274              :     }
    1275              : 
    1276            0 :     sqlite3_result_int(context, 1);
    1277            0 : }
    1278              : 
    1279          195 : int dbsync_register_functions (sqlite3 *db, char **pzErrMsg) {
    1280          195 :     int rc = SQLITE_OK;
    1281              :     
    1282              :     // there's no built-in way to verify if sqlite3_cloudsync_init has already been called
    1283              :     // for this specific database connection, we use a workaround: we attempt to retrieve the
    1284              :     // cloudsync_version and check for an error, an error indicates that initialization has not been performed
    1285          195 :     if (sqlite3_exec(db, "SELECT cloudsync_version();", NULL, NULL, NULL) == SQLITE_OK) return SQLITE_OK;
    1286              :     
    1287              :     // init memory debugger (NOOP in production)
    1288              :     cloudsync_memory_init(1);
    1289              : 
    1290              :     // set fractional-indexing allocator to use cloudsync memory
    1291          193 :     block_init_allocator();
    1292              : 
    1293              :     // init context
    1294          193 :     void *ctx = cloudsync_context_create(db);
    1295          193 :     if (!ctx) {
    1296            0 :         if (pzErrMsg) *pzErrMsg = sqlite3_mprintf("Not enought memory to create a database context");
    1297            0 :         return SQLITE_NOMEM;
    1298              :     }
    1299              :     
    1300              :     // register functions
    1301              :     
    1302              :     // PUBLIC functions
    1303          193 :     rc = dbsync_register_pure_function(db, "cloudsync_version", dbsync_version, 0, pzErrMsg, ctx, cloudsync_context_free);
    1304          193 :     if (rc != SQLITE_OK) return rc;
    1305              :     
    1306          193 :     rc = dbsync_register_function(db, "cloudsync_init", dbsync_init1, 1, pzErrMsg, ctx, NULL);
    1307          193 :     if (rc != SQLITE_OK) return rc;
    1308              :     
    1309          193 :     rc = dbsync_register_function(db, "cloudsync_init", dbsync_init2, 2, pzErrMsg, ctx, NULL);
    1310          193 :     if (rc != SQLITE_OK) return rc;
    1311              :     
    1312          193 :     rc = dbsync_register_function(db, "cloudsync_init", dbsync_init3, 3, pzErrMsg, ctx, NULL);
    1313          193 :     if (rc != SQLITE_OK) return rc;
    1314              : 
    1315          193 :     rc = dbsync_register_function(db, "cloudsync_enable", dbsync_enable, 1, pzErrMsg, ctx, NULL);
    1316          193 :     if (rc != SQLITE_OK) return rc;
    1317              :     
    1318          193 :     rc = dbsync_register_function(db, "cloudsync_disable", dbsync_disable, 1, pzErrMsg, ctx, NULL);
    1319          193 :     if (rc != SQLITE_OK) return rc;
    1320              :     
    1321          193 :     rc = dbsync_register_function(db, "cloudsync_is_enabled", dbsync_is_enabled, 1, pzErrMsg, ctx, NULL);
    1322          193 :     if (rc != SQLITE_OK) return rc;
    1323              :     
    1324          193 :     rc = dbsync_register_function(db, "cloudsync_cleanup", dbsync_cleanup, 1, pzErrMsg, ctx, NULL);
    1325          193 :     if (rc != SQLITE_OK) return rc;
    1326              :     
    1327          193 :     rc = dbsync_register_function(db, "cloudsync_terminate", dbsync_terminate, 0, pzErrMsg, ctx, NULL);
    1328          193 :     if (rc != SQLITE_OK) return rc;
    1329              :     
    1330          193 :     rc = dbsync_register_function(db, "cloudsync_set", dbsync_set, 2, pzErrMsg, ctx, NULL);
    1331          193 :     if (rc != SQLITE_OK) return rc;
    1332              :     
    1333          193 :     rc = dbsync_register_function(db, "cloudsync_set_table", dbsync_set_table, 3, pzErrMsg, ctx, NULL);
    1334          193 :     if (rc != SQLITE_OK) return rc;
    1335              : 
    1336          193 :     rc = dbsync_register_function(db, "cloudsync_set_filter", dbsync_set_filter, 2, pzErrMsg, ctx, NULL);
    1337          193 :     if (rc != SQLITE_OK) return rc;
    1338              : 
    1339          193 :     rc = dbsync_register_function(db, "cloudsync_clear_filter", dbsync_clear_filter, 1, pzErrMsg, ctx, NULL);
    1340          193 :     if (rc != SQLITE_OK) return rc;
    1341              : 
    1342          193 :     rc = dbsync_register_function(db, "cloudsync_set_schema", dbsync_set_schema, 1, pzErrMsg, ctx, NULL);
    1343          193 :     if (rc != SQLITE_OK) return rc;
    1344              :     
    1345          193 :     rc = dbsync_register_function(db, "cloudsync_schema", dbsync_schema, 0, pzErrMsg, ctx, NULL);
    1346          193 :     if (rc != SQLITE_OK) return rc;
    1347              :     
    1348          193 :     rc = dbsync_register_function(db, "cloudsync_table_schema", dbsync_table_schema, 1, pzErrMsg, ctx, NULL);
    1349          193 :     if (rc != SQLITE_OK) return rc;
    1350              : 
    1351          193 :     rc = dbsync_register_function(db, "cloudsync_set_column", dbsync_set_column, 4, pzErrMsg, ctx, NULL);
    1352          193 :     if (rc != SQLITE_OK) return rc;
    1353              :     
    1354          193 :     rc = dbsync_register_function(db, "cloudsync_siteid", dbsync_siteid, 0, pzErrMsg, ctx, NULL);
    1355          193 :     if (rc != SQLITE_OK) return rc;
    1356              :     
    1357          193 :     rc = dbsync_register_function(db, "cloudsync_db_version", dbsync_db_version, 0, pzErrMsg, ctx, NULL);
    1358          193 :     if (rc != SQLITE_OK) return rc;
    1359              :     
    1360          193 :     rc = dbsync_register_function(db, "cloudsync_db_version_next", dbsync_db_version_next, 0, pzErrMsg, ctx, NULL);
    1361          193 :     if (rc != SQLITE_OK) return rc;
    1362              :     
    1363          193 :     rc = dbsync_register_function(db, "cloudsync_db_version_next", dbsync_db_version_next, 1, pzErrMsg, ctx, NULL);
    1364          193 :     if (rc != SQLITE_OK) return rc;
    1365              :     
    1366          193 :     rc = dbsync_register_function(db, "cloudsync_begin_alter", dbsync_begin_alter, 1, pzErrMsg, ctx, NULL);
    1367          193 :     if (rc != SQLITE_OK) return rc;
    1368              :     
    1369          193 :     rc = dbsync_register_function(db, "cloudsync_commit_alter", dbsync_commit_alter, 1, pzErrMsg, ctx, NULL);
    1370          193 :     if (rc != SQLITE_OK) return rc;
    1371              :     
    1372          193 :     rc = dbsync_register_function(db, "cloudsync_uuid", dbsync_uuid, 0, pzErrMsg, ctx, NULL);
    1373          193 :     if (rc != SQLITE_OK) return rc;
    1374              :     
    1375              :     // PAYLOAD
    1376          193 :     rc = dbsync_register_aggregate(db, "cloudsync_payload_encode", dbsync_payload_encode_step, dbsync_payload_encode_final, -1, pzErrMsg, ctx, NULL);
    1377          193 :     if (rc != SQLITE_OK) return rc;
    1378              :     
    1379              :     // alias
    1380          193 :     rc = dbsync_register_function(db, "cloudsync_payload_decode", dbsync_payload_decode, -1, pzErrMsg, ctx, NULL);
    1381          193 :     if (rc != SQLITE_OK) return rc;
    1382          193 :     rc = dbsync_register_function(db, "cloudsync_payload_apply", dbsync_payload_decode, -1, pzErrMsg, ctx, NULL);
    1383          193 :     if (rc != SQLITE_OK) return rc;
    1384              :     
    1385              :     #ifdef CLOUDSYNC_DESKTOP_OS
    1386          193 :     rc = dbsync_register_function(db, "cloudsync_payload_save", dbsync_payload_save, 1, pzErrMsg, ctx, NULL);
    1387          193 :     if (rc != SQLITE_OK) return rc;
    1388              :     
    1389          193 :     rc = dbsync_register_function(db, "cloudsync_payload_load", dbsync_payload_load, 1, pzErrMsg, ctx, NULL);
    1390          193 :     if (rc != SQLITE_OK) return rc;
    1391              :     #endif
    1392              :     
    1393              :     // PRIVATE functions (used inside triggers — require SQLITE_INNOCUOUS)
    1394          193 :     rc = dbsync_register_trigger_function(db, "cloudsync_is_sync", dbsync_is_sync, 1, pzErrMsg, ctx, NULL);
    1395          193 :     if (rc != SQLITE_OK) return rc;
    1396              : 
    1397          193 :     rc = dbsync_register_trigger_function(db, "cloudsync_insert", dbsync_insert, -1, pzErrMsg, ctx, NULL);
    1398          193 :     if (rc != SQLITE_OK) return rc;
    1399              : 
    1400          193 :     rc = dbsync_register_trigger_aggregate(db, "cloudsync_update", dbsync_update_step, dbsync_update_final, 3, pzErrMsg, ctx, NULL);
    1401          193 :     if (rc != SQLITE_OK) return rc;
    1402              : 
    1403          193 :     rc = dbsync_register_trigger_function(db, "cloudsync_delete", dbsync_delete, -1, pzErrMsg, ctx, NULL);
    1404          193 :     if (rc != SQLITE_OK) return rc;
    1405              :     
    1406          193 :     rc = dbsync_register_function(db, "cloudsync_col_value", dbsync_col_value, 3, pzErrMsg, ctx, NULL);
    1407          193 :     if (rc != SQLITE_OK) return rc;
    1408              :     
    1409          193 :     rc = dbsync_register_pure_function(db, "cloudsync_pk_encode", dbsync_pk_encode, -1, pzErrMsg, ctx, NULL);
    1410          193 :     if (rc != SQLITE_OK) return rc;
    1411              : 
    1412          193 :     rc = dbsync_register_pure_function(db, "cloudsync_pk_decode", dbsync_pk_decode, 2, pzErrMsg, ctx, NULL);
    1413          193 :     if (rc != SQLITE_OK) return rc;
    1414              :     
    1415          193 :     rc = dbsync_register_function(db, "cloudsync_seq", dbsync_seq, 0, pzErrMsg, ctx, NULL);
    1416          193 :     if (rc != SQLITE_OK) return rc;
    1417              : 
    1418          193 :     rc = dbsync_register_function(db, "cloudsync_text_materialize", dbsync_text_materialize, -1, pzErrMsg, ctx, NULL);
    1419          193 :     if (rc != SQLITE_OK) return rc;
    1420              : 
    1421              :     // NETWORK LAYER
    1422              :     #ifndef CLOUDSYNC_OMIT_NETWORK
    1423              :     rc = cloudsync_network_register(db, pzErrMsg, ctx);
    1424              :     if (rc != SQLITE_OK) return rc;
    1425              :     #endif
    1426              :     
    1427          193 :     cloudsync_context *data = (cloudsync_context *)ctx;
    1428          193 :     sqlite3_commit_hook(db, cloudsync_commit_hook, ctx);
    1429          193 :     sqlite3_rollback_hook(db, cloudsync_rollback_hook, ctx);
    1430              :     
    1431              :     // register eponymous only changes virtual table
    1432          193 :     rc = cloudsync_vtab_register_changes (db, data);
    1433          193 :     if (rc != SQLITE_OK) {
    1434            0 :         if (pzErrMsg) *pzErrMsg = sqlite3_mprintf("Error creating changes virtual table: %s", sqlite3_errmsg(db));
    1435            0 :         return rc;
    1436              :     }
    1437              :     
    1438              :     // load config, if exists
    1439          193 :     if (cloudsync_config_exists(data)) {
    1440            0 :         if (cloudsync_context_init(data) == NULL) {
    1441            0 :             cloudsync_context_free(data);
    1442            0 :             if (pzErrMsg) *pzErrMsg = sqlite3_mprintf("An error occurred while trying to initialize context");
    1443            0 :             return SQLITE_ERROR;
    1444              :         }
    1445              :         
    1446              :         // make sure to update internal version to current version
    1447            0 :         dbutils_settings_set_key_value(data, CLOUDSYNC_KEY_LIBVERSION, CLOUDSYNC_VERSION);
    1448            0 :     }
    1449              :     
    1450          193 :     return SQLITE_OK;
    1451          195 : }
    1452              : 
    1453              : // MARK: - Main Entrypoint -
    1454              : 
    1455          195 : APIEXPORT int sqlite3_cloudsync_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi) {
    1456              :     DEBUG_FUNCTION("sqlite3_cloudsync_init");
    1457              :     
    1458              :     #ifndef SQLITE_CORE
    1459              :     SQLITE_EXTENSION_INIT2(pApi);
    1460              :     #endif
    1461              :     
    1462          195 :     return dbsync_register_functions(db, pzErrMsg);
    1463              : }
        

Generated by: LCOV version 2.4-0