LCOV - code coverage report
Current view: top level - src/sqlite - cloudsync_sqlite.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 82.1 % 877 720
Test Date: 2026-04-24 14:45:44 Functions: 96.2 % 53 51

            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            8 : void dbsync_version (sqlite3_context *context, int argc, sqlite3_value **argv) {
      61              :     DEBUG_FUNCTION("cloudsync_version");
      62            8 :     UNUSED_PARAMETER(argc);
      63            8 :     UNUSED_PARAMETER(argv);
      64            8 :     sqlite3_result_text(context, CLOUDSYNC_VERSION, -1, SQLITE_STATIC);
      65            8 : }
      66              : 
      67          536 : void dbsync_siteid (sqlite3_context *context, int argc, sqlite3_value **argv) {
      68              :     DEBUG_FUNCTION("cloudsync_siteid");
      69          536 :     UNUSED_PARAMETER(argc);
      70          536 :     UNUSED_PARAMETER(argv);
      71              :     
      72          536 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
      73          536 :     sqlite3_result_blob(context, cloudsync_siteid(data), UUID_LEN, SQLITE_STATIC);
      74          536 : }
      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        25138 : 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        25138 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
      98              :     
      99        25138 :     sqlite3_int64 merging_version = (argc == 1) ? database_value_int(argv[0]) : CLOUDSYNC_VALUE_NOTSET;
     100        25138 :     sqlite3_int64 value = cloudsync_dbversion_next(data, merging_version);
     101        25138 :     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        25138 :     sqlite3_result_int64(context, value);
     107        25138 : }
     108              : 
     109           61 : void dbsync_seq (sqlite3_context *context, int argc, sqlite3_value **argv) {
     110              :     DEBUG_FUNCTION("cloudsync_seq");
     111              :     
     112              :     // retrieve context
     113           61 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     114           61 :     sqlite3_result_int(context, cloudsync_bumpseq(data));
     115           61 : }
     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           78 : void dbsync_set_column (sqlite3_context *context, int argc, sqlite3_value **argv) {
     142              :     DEBUG_FUNCTION("cloudsync_set_column");
     143              : 
     144           78 :     const char *tbl = (const char *)database_value_text(argv[0]);
     145           78 :     const char *col = (const char *)database_value_text(argv[1]);
     146           78 :     const char *key = (const char *)database_value_text(argv[2]);
     147           78 :     const char *value = (const char *)database_value_text(argv[3]);
     148              : 
     149           78 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     150              : 
     151              :     // Handle block column setup: cloudsync_set_column('tbl', 'col', 'algo', 'block')
     152           78 :     if (key && value && strcmp(key, "algo") == 0 && strcmp(value, "block") == 0) {
     153           73 :         int rc = cloudsync_setup_block_column(data, tbl, col, NULL, true);
     154           73 :         if (rc != DBRES_OK) {
     155            0 :             sqlite3_result_error(context, cloudsync_errmsg(data), -1);
     156            0 :         }
     157           73 :         return;
     158              :     }
     159              : 
     160              :     // Handle delimiter setting: cloudsync_set_column('tbl', 'col', 'delimiter', '\n\n')
     161            5 :     if (key && strcmp(key, "delimiter") == 0) {
     162            3 :         cloudsync_table_context *table = table_lookup(data, tbl);
     163            3 :         if (table) {
     164            3 :             int col_idx = table_col_index(table, col);
     165            3 :             if (col_idx >= 0 && table_col_algo(table, col_idx) == col_algo_block) {
     166            3 :                 table_set_col_delimiter(table, col_idx, value);
     167            3 :             }
     168            3 :         }
     169            3 :     }
     170              : 
     171            5 :     dbutils_table_settings_set_key_value(data, tbl, col, key, value);
     172           78 : }
     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        12665 : void dbsync_is_sync (sqlite3_context *context, int argc, sqlite3_value **argv) {
     211              :     DEBUG_FUNCTION("cloudsync_is_sync");
     212              :     
     213        12665 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     214        12665 :     if (cloudsync_insync(data)) {
     215         8645 :         sqlite3_result_int(context, 1);
     216         8645 :         return;
     217              :     }
     218              :     
     219         4020 :     const char *table_name = (const char *)database_value_text(argv[0]);
     220         4020 :     cloudsync_table_context *table = table_lookup(data, table_name);
     221         4020 :     sqlite3_result_int(context, (table) ? (table_enabled(table) == 0) : 0);
     222        12665 : }
     223              : 
     224       126832 : 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       126832 :     const char *col_name = (const char *)database_value_text(argv[1]);
     233       126832 :     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       126832 :     if (strcmp(col_name, CLOUDSYNC_TOMBSTONE_VALUE) == 0) {
     240         4109 :         sqlite3_result_null(context);
     241         4109 :         return;
     242              :     }
     243              : 
     244              :     // lookup table
     245       122723 :     const char *table_name = (const char *)database_value_text(argv[0]);
     246       122723 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     247       122723 :     cloudsync_table_context *table = table_lookup(data, table_name);
     248       122723 :     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       122723 :     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       121742 :     sqlite3_stmt *vm = table_column_lookup(table, col_name, false, NULL);
     279       121742 :     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       121742 :     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       121742 :     if (rc < 0) goto cleanup;
     287              : 
     288              :     // execute vm
     289       121742 :     rc = databasevm_step(vm);
     290       243484 :     if (rc == SQLITE_DONE) {
     291            0 :         rc = SQLITE_OK;
     292            0 :         sqlite3_result_text(context, CLOUDSYNC_RLS_RESTRICTED_VALUE, -1, SQLITE_STATIC);
     293       121742 :     } else if (rc == SQLITE_ROW) {
     294              :         // store value result
     295       121742 :         rc = SQLITE_OK;
     296       121742 :         sqlite3_result_value(context, database_column_value(vm, 0));
     297       121742 :     }
     298              : 
     299              : cleanup:
     300       121742 :     if (rc != SQLITE_OK) {
     301            0 :         sqlite3_result_error(context, database_errmsg(data), -1);
     302            0 :     }
     303       121742 :     databasevm_reset(vm);
     304       126832 : }
     305              : 
     306        10569 : void dbsync_pk_encode (sqlite3_context *context, int argc, sqlite3_value **argv) {
     307        10569 :     size_t bsize = 0;
     308        10569 :     char *buffer = pk_encode_prikey((dbvalue_t **)argv, argc, NULL, &bsize);
     309        10569 :     if (!buffer || buffer == PRIKEY_NULL_CONSTRAINT_ERROR) {
     310            1 :         sqlite3_result_null(context);
     311            1 :         return;
     312              :     }
     313        10568 :     sqlite3_result_blob(context, (const void *)buffer, (int)bsize, SQLITE_TRANSIENT);
     314        10568 :     cloudsync_memory_free(buffer);
     315        10569 : }
     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         3587 : 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         3587 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     379              :     
     380              :     // lookup table
     381         3587 :     const char *table_name = (const char *)database_value_text(argv[0]);
     382         3587 :     cloudsync_table_context *table = table_lookup(data, table_name);
     383         3587 :     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         3587 :     size_t pklen = sizeof(buffer);
     391         3587 :     char *pk = pk_encode_prikey((dbvalue_t **)&argv[1], table_count_pks(table), buffer, &pklen);
     392         3587 :     if (!pk) {
     393            0 :         sqlite3_result_error(context, "Not enough memory to encode the primary key(s).", -1);
     394            0 :         return;
     395              :     }
     396         3587 :     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         3586 :     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         3586 :     bool pk_exists = table_pk_exists(table, pk, pklen);
     407         3586 :     int rc = SQLITE_OK;
     408              :     
     409         3586 :     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         3586 :     } else if (pk_exists){
     414              :         // if a row with the same primary key already exists, update the sentinel record
     415            4 :         rc = local_update_sentinel(table, pk, pklen, db_version, cloudsync_bumpseq(data));
     416            4 :         if (rc != SQLITE_OK) goto cleanup;
     417            4 :     }
     418              :     
     419              :     // process each non-primary key column for insert or update
     420        14395 :     for (int i=0; i<table_count_cols(table); ++i) {
     421        10809 :         if (table_col_algo(table, i) == col_algo_block) {
     422              :             // Block column: read value from base table, split into blocks, store each block
     423           44 :             sqlite3_stmt *val_vm = (sqlite3_stmt *)table_column_lookup(table, table_colname(table, i), false, NULL);
     424           44 :             if (!val_vm) goto cleanup;
     425              : 
     426           44 :             rc = pk_decode_prikey(pk, pklen, pk_decode_bind_callback, (void *)val_vm);
     427           44 :             if (rc < 0) { databasevm_reset((dbvm_t *)val_vm); goto cleanup; }
     428              : 
     429           44 :             rc = databasevm_step((dbvm_t *)val_vm);
     430           44 :             if (rc == DBRES_ROW) {
     431           44 :                 const char *text = database_column_text((dbvm_t *)val_vm, 0);
     432           44 :                 const char *delim = table_col_delimiter(table, i);
     433           44 :                 const char *col = table_colname(table, i);
     434              : 
     435           44 :                 block_list_t *blocks = block_split(text ? text : "", delim);
     436           44 :                 if (blocks) {
     437           44 :                     char **positions = block_initial_positions(blocks->count);
     438           44 :                     if (positions) {
     439          360 :                         for (int b = 0; b < blocks->count; b++) {
     440          316 :                             char *block_cn = block_build_colname(col, positions[b]);
     441          316 :                             if (block_cn) {
     442          316 :                                 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          316 :                                 dbvm_t *wvm = table_block_value_write_stmt(table);
     446          316 :                                 if (wvm && rc == SQLITE_OK) {
     447          316 :                                     databasevm_bind_blob(wvm, 1, pk, (int)pklen);
     448          316 :                                     databasevm_bind_text(wvm, 2, block_cn, -1);
     449          316 :                                     databasevm_bind_text(wvm, 3, blocks->entries[b].content, -1);
     450          316 :                                     databasevm_step(wvm);
     451          316 :                                     databasevm_reset(wvm);
     452          316 :                                 }
     453              : 
     454          316 :                                 cloudsync_memory_free(block_cn);
     455          316 :                             }
     456          316 :                             cloudsync_memory_free(positions[b]);
     457          316 :                             if (rc != SQLITE_OK) break;
     458          316 :                         }
     459           44 :                         cloudsync_memory_free(positions);
     460           44 :                     }
     461           44 :                     block_list_free(blocks);
     462           44 :                 }
     463           44 :             }
     464           44 :             databasevm_reset((dbvm_t *)val_vm);
     465           44 :             if (rc == DBRES_ROW || rc == DBRES_DONE) rc = SQLITE_OK;
     466           44 :             if (rc != SQLITE_OK) goto cleanup;
     467           44 :         } else {
     468              :             // Regular column: mark as inserted or updated in the metadata
     469        10765 :             rc = local_mark_insert_or_update_meta(table, pk, pklen, table_colname(table, i), db_version, cloudsync_bumpseq(data));
     470        10765 :             if (rc != SQLITE_OK) goto cleanup;
     471              :         }
     472        14395 :     }
     473              : 
     474              : cleanup:
     475         3586 :     if (rc != SQLITE_OK) sqlite3_result_error(context, database_errmsg(data), -1);
     476              :     // free memory if the primary key was dynamically allocated
     477         3586 :     if (pk != buffer) cloudsync_memory_free(pk);
     478         3587 : }
     479              : 
     480           36 : 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           36 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     486              :     
     487              :     // lookup table
     488           36 :     const char *table_name = (const char *)database_value_text(argv[0]);
     489           36 :     cloudsync_table_context *table = table_lookup(data, table_name);
     490           36 :     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           36 :     int64_t db_version = cloudsync_dbversion_next(data, CLOUDSYNC_VALUE_NOTSET);
     497           36 :     int rc = SQLITE_OK;
     498              :     
     499              :     // encode the primary key values into a buffer
     500              :     char buffer[1024];
     501           36 :     size_t pklen = sizeof(buffer);
     502           36 :     char *pk = pk_encode_prikey((dbvalue_t **)&argv[1], table_count_pks(table), buffer, &pklen);
     503           36 :     if (!pk) {
     504            0 :         sqlite3_result_error(context, "Not enough memory to encode the primary key(s).", -1);
     505            0 :         return;
     506              :     }
     507              :     
     508           36 :     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           36 :     rc = local_mark_delete_meta(table, pk, pklen, db_version, cloudsync_bumpseq(data));
     515           36 :     if (rc != SQLITE_OK) goto cleanup;
     516              :     
     517              :     // remove any metadata related to the old rows associated with this primary key
     518           36 :     rc = local_drop_meta(table, pk, pklen);
     519           36 :     if (rc != SQLITE_OK) goto cleanup;
     520              :     
     521              : cleanup:
     522           36 :     if (rc != SQLITE_OK) sqlite3_result_error(context, database_errmsg(data), -1);
     523              :     // free memory if the primary key was dynamically allocated
     524           36 :     if (pk != buffer) cloudsync_memory_free(pk);
     525           36 : }
     526              : 
     527              : // MARK: -
     528              : 
     529          445 : void dbsync_update_payload_free (cloudsync_update_payload *payload) {
     530         2654 :     for (int i=0; i<payload->count; i++) {
     531         2209 :         database_value_free(payload->new_values[i]);
     532         2209 :         database_value_free(payload->old_values[i]);
     533         2209 :     }
     534          445 :     cloudsync_memory_free(payload->new_values);
     535          445 :     cloudsync_memory_free(payload->old_values);
     536          445 :     database_value_free(payload->table_name);
     537          445 :     payload->new_values = NULL;
     538          445 :     payload->old_values = NULL;
     539          445 :     payload->table_name = NULL;
     540          445 :     payload->count = 0;
     541          445 :     payload->capacity = 0;
     542          445 : }
     543              : 
     544         2209 : int dbsync_update_payload_append (cloudsync_update_payload *payload, sqlite3_value *v1, sqlite3_value *v2, sqlite3_value *v3) {
     545         2209 :     if (payload->count >= payload->capacity) {
     546          448 :         int newcap = payload->capacity ? payload->capacity * 2 : 128;
     547              : 
     548          448 :         sqlite3_value **new_values_2 = (sqlite3_value **)cloudsync_memory_realloc(payload->new_values, newcap * sizeof(*new_values_2));
     549          448 :         if (!new_values_2) return SQLITE_NOMEM;
     550              : 
     551          448 :         sqlite3_value **old_values_2 = (sqlite3_value **)cloudsync_memory_realloc(payload->old_values, newcap * sizeof(*old_values_2));
     552          448 :         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          448 :         payload->new_values = new_values_2;
     560          448 :         payload->old_values = old_values_2;
     561          448 :         payload->capacity = newcap;
     562          448 :     }
     563              : 
     564         2209 :     int index = payload->count;
     565         2209 :     if (payload->table_name == NULL) payload->table_name = database_value_dup(v1);
     566         1764 :     else if (dbutils_value_compare(payload->table_name, v1) != 0) return SQLITE_NOMEM;
     567              : 
     568         2209 :     payload->new_values[index] = database_value_dup(v2);
     569         2209 :     payload->old_values[index] = database_value_dup(v3);
     570              : 
     571              :     // sanity check memory allocations before committing count
     572         2209 :     bool v1_can_be_null = (database_value_type(v1) == SQLITE_NULL);
     573         2209 :     bool v2_can_be_null = (database_value_type(v2) == SQLITE_NULL);
     574         2209 :     bool v3_can_be_null = (database_value_type(v3) == SQLITE_NULL);
     575              : 
     576         2209 :     bool oom = false;
     577         2209 :     if ((payload->table_name == NULL) && (!v1_can_be_null)) oom = true;
     578         2209 :     if ((payload->new_values[index] == NULL) && (!v2_can_be_null)) oom = true;
     579         2209 :     if ((payload->old_values[index] == NULL) && (!v3_can_be_null)) oom = true;
     580              : 
     581         2209 :     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         2209 :     payload->count++;
     589         2209 :     return SQLITE_OK;
     590         2209 : }
     591              : 
     592         2209 : 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         2209 :     cloudsync_update_payload *payload = (cloudsync_update_payload *)sqlite3_aggregate_context(context, sizeof(cloudsync_update_payload));
     599         2209 :     if (!payload) {sqlite3_result_error_nomem(context); return;}
     600              :     
     601         2209 :     if (dbsync_update_payload_append(payload, argv[0], argv[1], argv[2]) != SQLITE_OK) {
     602            0 :         sqlite3_result_error_nomem(context);
     603            0 :     }
     604         2209 : }
     605              : 
     606          445 : void dbsync_update_final (sqlite3_context *context) {
     607          445 :     cloudsync_update_payload *payload = (cloudsync_update_payload *)sqlite3_aggregate_context(context, sizeof(cloudsync_update_payload));
     608          445 :     if (!payload || payload->count == 0) return;
     609              :     
     610              :     // retrieve context
     611          445 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     612              :     
     613              :     // lookup table
     614          445 :     const char *table_name = (const char *)database_value_text(payload->table_name);
     615          445 :     cloudsync_table_context *table = table_lookup(data, table_name);
     616          445 :     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          445 :     int64_t db_version = cloudsync_dbversion_next(data, CLOUDSYNC_VALUE_NOTSET);
     624          445 :     int rc = SQLITE_OK;
     625              :     
     626              :     // Check if the primary key(s) have changed
     627          445 :     bool prikey_changed = false;
     628         1171 :     for (int i=0; i<table_count_pks(table); ++i) {
     629          756 :         if (dbutils_value_compare(payload->old_values[i], payload->new_values[i]) != 0) {
     630           30 :             prikey_changed = true;
     631           30 :             break;
     632              :         }
     633          726 :     }
     634              : 
     635              :     // encode the NEW primary key values into a buffer (used later for indexing)
     636              :     char buffer[1024];
     637              :     char buffer2[1024];
     638          445 :     size_t pklen = sizeof(buffer);
     639          445 :     size_t oldpklen = sizeof(buffer2);
     640          445 :     char *oldpk = NULL;
     641              :     
     642          445 :     char *pk = pk_encode_prikey((dbvalue_t **)payload->new_values, table_count_pks(table), buffer, &pklen);
     643          445 :     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          445 :     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          445 :     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           30 :         oldpk = pk_encode_prikey((dbvalue_t **)payload->old_values, table_count_pks(table), buffer2, &oldpklen);
     661           30 :         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           30 :         rc = local_mark_delete_meta(table, oldpk, oldpklen, db_version, cloudsync_bumpseq(data));
     671           30 :         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           30 :         rc = local_update_move_meta(table, pk, pklen, oldpk, oldpklen, db_version);
     677           30 :         if (rc != SQLITE_OK) goto cleanup;
     678              :         
     679              :         // mark a new sentinel row with the new primary key in the metadata
     680           30 :         rc = local_mark_insert_sentinel_meta(table, pk, pklen, db_version, cloudsync_bumpseq(data));
     681           30 :         if (rc != SQLITE_OK) goto cleanup;
     682              :         
     683              :         // free memory if the OLD primary key was dynamically allocated
     684           30 :         if (oldpk != buffer2) cloudsync_memory_free(oldpk);
     685           30 :         oldpk = NULL;
     686           30 :     }
     687              :     
     688              :     // compare NEW and OLD values (excluding primary keys) to handle column updates
     689         1869 :     for (int i=0; i<table_count_cols(table); i++) {
     690         1424 :         int col_index = table_count_pks(table) + i;  // Regular columns start after primary keys
     691              : 
     692         1424 :         if (dbutils_value_compare(payload->old_values[col_index], payload->new_values[col_index]) != 0) {
     693          726 :             if (table_col_algo(table, i) == col_algo_block) {
     694              :                 // Block column: diff old and new text, emit per-block metadata changes
     695           93 :                 const char *new_text = (const char *)database_value_text(payload->new_values[col_index]);
     696           93 :                 const char *delim = table_col_delimiter(table, i);
     697           93 :                 const char *col = table_colname(table, i);
     698              : 
     699              :                 // Read existing blocks from blocks table
     700           93 :                 block_list_t *old_blocks = block_list_create_empty();
     701           93 :                 if (table_block_list_stmt(table)) {
     702           93 :                     char *like_pattern = block_build_colname(col, "%");
     703           93 :                     if (like_pattern) {
     704              :                         // Query blocks table directly for existing block names and values
     705           93 :                         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           93 :                             table_blocks_ref(table));
     708           93 :                         if (list_sql) {
     709           93 :                             dbvm_t *list_vm = NULL;
     710           93 :                             if (databasevm_prepare(data, list_sql, &list_vm, 0) == DBRES_OK) {
     711           93 :                                 databasevm_bind_blob(list_vm, 1, pk, (int)pklen);
     712           93 :                                 databasevm_bind_text(list_vm, 2, like_pattern, -1);
     713         1447 :                                 while (databasevm_step(list_vm) == DBRES_ROW) {
     714         1354 :                                     const char *bcn = database_column_text(list_vm, 0);
     715         1354 :                                     const char *bval = database_column_text(list_vm, 1);
     716         1354 :                                     const char *pos = block_extract_position_id(bcn);
     717         1354 :                                     if (pos && old_blocks) {
     718         1354 :                                         block_list_add(old_blocks, bval ? bval : "", pos);
     719         1354 :                                     }
     720              :                                 }
     721           93 :                                 databasevm_finalize(list_vm);
     722           93 :                             }
     723           93 :                             cloudsync_memory_free(list_sql);
     724           93 :                         }
     725           93 :                         cloudsync_memory_free(like_pattern);
     726           93 :                     }
     727           93 :                 }
     728              : 
     729              :                 // Split new text into parts (NULL text = all blocks removed)
     730           93 :                 block_list_t *new_blocks = new_text ? block_split(new_text, delim) : block_list_create_empty();
     731           93 :                 if (new_blocks && old_blocks) {
     732              :                     // Build array of new content strings (NULL when count is 0)
     733           93 :                     const char **new_parts = NULL;
     734           93 :                     if (new_blocks->count > 0) {
     735           92 :                         new_parts = (const char **)cloudsync_memory_alloc(
     736           92 :                             (uint64_t)(new_blocks->count * sizeof(char *)));
     737           92 :                         if (new_parts) {
     738         1500 :                             for (int b = 0; b < new_blocks->count; b++) {
     739         1408 :                                 new_parts[b] = new_blocks->entries[b].content;
     740         1408 :                             }
     741           92 :                         }
     742           92 :                     }
     743              : 
     744           93 :                     if (new_parts || new_blocks->count == 0) {
     745          186 :                         block_diff_t *diff = block_diff(old_blocks->entries, old_blocks->count,
     746           93 :                                                          new_parts, new_blocks->count);
     747           93 :                         if (diff) {
     748          229 :                             for (int d = 0; d < diff->count; d++) {
     749          136 :                                 block_diff_entry_t *de = &diff->entries[d];
     750          136 :                                 char *block_cn = block_build_colname(col, de->position_id);
     751          136 :                                 if (!block_cn) continue;
     752              : 
     753          136 :                                 if (de->type == BLOCK_DIFF_ADDED || de->type == BLOCK_DIFF_MODIFIED) {
     754          190 :                                     rc = local_mark_insert_or_update_meta(table, pk, pklen, block_cn,
     755           95 :                                                                           db_version, cloudsync_bumpseq(data));
     756              :                                     // Store block value
     757           95 :                                     if (rc == SQLITE_OK && table_block_value_write_stmt(table)) {
     758           95 :                                         dbvm_t *wvm = table_block_value_write_stmt(table);
     759           95 :                                         databasevm_bind_blob(wvm, 1, pk, (int)pklen);
     760           95 :                                         databasevm_bind_text(wvm, 2, block_cn, -1);
     761           95 :                                         databasevm_bind_text(wvm, 3, de->content, -1);
     762           95 :                                         databasevm_step(wvm);
     763           95 :                                         databasevm_reset(wvm);
     764           95 :                                     }
     765          136 :                                 } else if (de->type == BLOCK_DIFF_REMOVED) {
     766              :                                     // Mark block as deleted in metadata (even col_version)
     767           82 :                                     rc = local_mark_delete_block_meta(table, pk, pklen, block_cn,
     768           41 :                                                                       db_version, cloudsync_bumpseq(data));
     769              :                                     // Remove from blocks table
     770           41 :                                     if (rc == SQLITE_OK) {
     771           41 :                                         block_delete_value_external(data, table, pk, pklen, block_cn);
     772           41 :                                     }
     773           41 :                                 }
     774          136 :                                 cloudsync_memory_free(block_cn);
     775          136 :                                 if (rc != SQLITE_OK) break;
     776          136 :                             }
     777           93 :                             block_diff_free(diff);
     778           93 :                         }
     779           93 :                         if (new_parts) cloudsync_memory_free((void *)new_parts);
     780           93 :                     }
     781           93 :                 }
     782           93 :                 if (new_blocks) block_list_free(new_blocks);
     783           93 :                 if (old_blocks) block_list_free(old_blocks);
     784           93 :                 if (rc != SQLITE_OK) goto cleanup;
     785           93 :             } else {
     786              :                 // Regular column: mark as updated in the metadata (columns are in cid order)
     787          633 :                 rc = local_mark_insert_or_update_meta(table, pk, pklen, table_colname(table, i), db_version, cloudsync_bumpseq(data));
     788          633 :                 if (rc != SQLITE_OK) goto cleanup;
     789              :             }
     790          726 :         }
     791         1869 :     }
     792              :     
     793              : cleanup:
     794          445 :     if (rc != SQLITE_OK) sqlite3_result_error(context, database_errmsg(data), -1);
     795          445 :     if (pk != buffer) cloudsync_memory_free(pk);
     796          445 :     if (oldpk && (oldpk != buffer2)) cloudsync_memory_free(oldpk);
     797              :     
     798          445 :     dbsync_update_payload_free(payload);
     799          445 : }
     800              : 
     801              : // MARK: -
     802              : 
     803            4 : void dbsync_cleanup (sqlite3_context *context, int argc, sqlite3_value **argv) {
     804              :     DEBUG_FUNCTION("cloudsync_cleanup");
     805              :     
     806            4 :     const char *table = (const char *)database_value_text(argv[0]);
     807            4 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     808              :     
     809            4 :     int rc = cloudsync_cleanup(data, table);
     810            4 :     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            4 : }
     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          234 : void dbsync_terminate (sqlite3_context *context, int argc, sqlite3_value **argv) {
     852              :     DEBUG_FUNCTION("cloudsync_terminate");
     853              :     
     854          234 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     855          234 :     int rc = cloudsync_terminate(data);
     856          234 :     sqlite3_result_int(context, rc);
     857          234 : }
     858              : 
     859              : // MARK: -
     860              : 
     861          265 : void dbsync_init (sqlite3_context *context, const char *table, const char *algo, CLOUDSYNC_INIT_FLAG init_flags) {
     862          265 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     863              :     
     864          265 :     int rc = database_begin_savepoint(data, "cloudsync_init");
     865          265 :     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          265 :     rc = cloudsync_init_table(data, table, algo, init_flags);
     872          265 :     if (rc == SQLITE_OK) {
     873          260 :         rc = database_commit_savepoint(data, "cloudsync_init");
     874          260 :         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          260 :     } else {
     879              :         // in case of error, rollback transaction
     880            5 :         sqlite3_result_error(context, cloudsync_errmsg(data), -1);
     881            5 :         sqlite3_result_error_code(context, rc);
     882            5 :         database_rollback_savepoint(data, "cloudsync_init");
     883            5 :         return;
     884              :     }
     885              :     
     886          260 :     cloudsync_update_schema_hash(data);
     887              :     
     888              :     // returns site_id as TEXT
     889              :     char buffer[UUID_STR_MAXLEN];
     890          260 :     cloudsync_uuid_v7_stringify(cloudsync_siteid(data), buffer, false);
     891          260 :     sqlite3_result_text(context, buffer, -1, SQLITE_TRANSIENT);
     892          265 : }
     893              : 
     894           46 : void dbsync_init3 (sqlite3_context *context, int argc, sqlite3_value **argv) {
     895              :     DEBUG_FUNCTION("cloudsync_init2");
     896              :     
     897           46 :     const char *table = (const char *)database_value_text(argv[0]);
     898           46 :     const char *algo = (const char *)database_value_text(argv[1]);
     899           46 :     int init_flags = database_value_int(argv[2]);
     900           46 :     dbsync_init(context, table, algo, init_flags);
     901           46 : }
     902              : 
     903           86 : void dbsync_init2 (sqlite3_context *context, int argc, sqlite3_value **argv) {
     904              :     DEBUG_FUNCTION("cloudsync_init2");
     905              :     
     906           86 :     const char *table = (const char *)database_value_text(argv[0]);
     907           86 :     const char *algo = (const char *)database_value_text(argv[1]);
     908           86 :     dbsync_init(context, table, algo, CLOUDSYNC_INIT_FLAG_NONE);
     909           86 : }
     910              : 
     911          133 : void dbsync_init1 (sqlite3_context *context, int argc, sqlite3_value **argv) {
     912              :     DEBUG_FUNCTION("cloudsync_init1");
     913              :     
     914          133 :     const char *table = (const char *)database_value_text(argv[0]);
     915          133 :     dbsync_init(context, table, NULL, CLOUDSYNC_INIT_FLAG_NONE);
     916          133 : }
     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           24 :     const char *table_name = (const char *)database_value_text(argv[0]);
     924           24 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     925              : 
     926           24 :     int rc = database_begin_savepoint(data, "cloudsync_alter");
     927           24 :     if (rc != DBRES_OK) {
     928            0 :         sqlite3_result_error(context, "Unable to create cloudsync_alter savepoint", -1);
     929            0 :         sqlite3_result_error_code(context, rc);
     930            0 :         return;
     931              :     }
     932              : 
     933           24 :     rc = cloudsync_begin_alter(data, table_name);
     934           24 :     if (rc != DBRES_OK) {
     935            1 :         database_rollback_savepoint(data, "cloudsync_alter");
     936            1 :         sqlite3_result_error(context, cloudsync_errmsg(data), -1);
     937            1 :         sqlite3_result_error_code(context, rc);
     938            1 :     }
     939           24 : }
     940              : 
     941           24 : void dbsync_commit_alter (sqlite3_context *context, int argc, sqlite3_value **argv) {
     942              :     DEBUG_FUNCTION("cloudsync_commit_alter");
     943              :     
     944              :     //retrieve table argument
     945           24 :     const char *table_name = (const char *)database_value_text(argv[0]);
     946              :     
     947              :     // retrieve context
     948           24 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     949              :     
     950           24 :     int rc = cloudsync_commit_alter(data, table_name);
     951           24 :     if (rc != DBRES_OK) {
     952            1 :         database_rollback_savepoint(data, "cloudsync_alter");
     953            1 :         sqlite3_result_error(context, cloudsync_errmsg(data), -1);
     954            1 :         sqlite3_result_error_code(context, rc);
     955            1 :         return;
     956              :     }
     957              : 
     958           23 :     rc = database_commit_savepoint(data, "cloudsync_alter");
     959           23 :     if (rc != DBRES_OK) {
     960            0 :         sqlite3_result_error(context, database_errmsg(data), -1);
     961            0 :         sqlite3_result_error_code(context, rc);
     962            0 :         return;
     963              :     }
     964              : 
     965           23 :     cloudsync_update_schema_hash(data);
     966           24 : }
     967              : 
     968              : // MARK: - Payload -
     969              : 
     970        49172 : void dbsync_payload_encode_step (sqlite3_context *context, int argc, sqlite3_value **argv) {
     971              :     // allocate/get the session context
     972        49172 :     cloudsync_payload_context *payload = (cloudsync_payload_context *)sqlite3_aggregate_context(context, (int)cloudsync_payload_context_size(NULL));
     973        49172 :     if (!payload) {
     974            0 :         sqlite3_result_error(context, "Not enough memory to allocate payload session context", -1);
     975            0 :         sqlite3_result_error_code(context, SQLITE_NOMEM);
     976            0 :         return;
     977              :     }
     978              :     
     979              :     // retrieve context
     980        49172 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
     981              :     
     982        49172 :     int rc = cloudsync_payload_encode_step(payload, data, argc, (dbvalue_t **)argv);
     983        49172 :     if (rc != SQLITE_OK) {
     984            0 :         sqlite3_result_error(context, cloudsync_errmsg(data), -1);
     985            0 :         sqlite3_result_error_code(context, rc);
     986            0 :     }
     987        49172 : }
     988              : 
     989          695 : void dbsync_payload_encode_final (sqlite3_context *context) {
     990              :     // get the session context
     991          695 :     cloudsync_payload_context *payload = (cloudsync_payload_context *)sqlite3_aggregate_context(context, (int)cloudsync_payload_context_size(NULL));
     992          695 :     if (!payload) {
     993            0 :         sqlite3_result_error(context, "Unable to extract payload session context", -1);
     994            0 :         sqlite3_result_error_code(context, SQLITE_NOMEM);
     995            0 :         return;
     996              :     }
     997              :     
     998              :     // retrieve context
     999          695 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
    1000              :     
    1001          695 :     int rc = cloudsync_payload_encode_final(payload, data);
    1002          695 :     if (rc != SQLITE_OK) {
    1003            0 :         sqlite3_result_error(context, cloudsync_errmsg(data), -1);
    1004            0 :         sqlite3_result_error_code(context, rc);
    1005            0 :         return;
    1006              :     }
    1007              :     
    1008              :     // result is OK so get BLOB and returns it
    1009          695 :     int64_t blob_size = 0;
    1010          695 :     char *blob = cloudsync_payload_blob (payload, &blob_size, NULL);
    1011          695 :     if (!blob) {
    1012            8 :         sqlite3_result_null(context);
    1013            8 :     } else {
    1014          687 :         sqlite3_result_blob64(context, blob, blob_size, SQLITE_TRANSIENT);
    1015          687 :         cloudsync_memory_free(blob);
    1016              :     }
    1017              :     
    1018              :     // from: https://sqlite.org/c3ref/aggregate_context.html
    1019              :     // SQLite automatically frees the memory allocated by sqlite3_aggregate_context() when the aggregate query concludes.
    1020          695 : }
    1021              : 
    1022          687 : void dbsync_payload_decode (sqlite3_context *context, int argc, sqlite3_value **argv) {
    1023              :     DEBUG_FUNCTION("dbsync_payload_decode");
    1024              :     //debug_values(argc, argv);
    1025              :     
    1026              :     // sanity check payload type
    1027          687 :     if (database_value_type(argv[0]) != SQLITE_BLOB) {
    1028            0 :         sqlite3_result_error(context, "Error on cloudsync_payload_decode: value must be a BLOB.", -1);
    1029            0 :         sqlite3_result_error_code(context, SQLITE_MISUSE);
    1030            0 :         return;
    1031              :     }
    1032              :     
    1033              :     // sanity check payload size
    1034          687 :     int blen = database_value_bytes(argv[0]);
    1035          687 :     size_t header_size = 0;
    1036          687 :     cloudsync_payload_context_size(&header_size);
    1037          687 :     if (blen < (int)header_size) {
    1038            3 :         sqlite3_result_error(context, "Error on cloudsync_payload_decode: invalid input size.", -1);
    1039            3 :         sqlite3_result_error_code(context, SQLITE_MISUSE);
    1040            3 :         return;
    1041              :     }
    1042              :     
    1043              :     // obtain payload
    1044          684 :     const char *payload = (const char *)database_value_blob(argv[0]);
    1045              :     
    1046              :     // apply changes
    1047          684 :     int nrows = 0;
    1048          684 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
    1049          684 :     int rc = cloudsync_payload_apply(data, payload, blen, &nrows);
    1050          684 :     if (rc != SQLITE_OK) {
    1051            4 :         sqlite3_result_error(context, cloudsync_errmsg(data), -1);
    1052            4 :         sqlite3_result_error_code(context, rc);
    1053            4 :         return;
    1054              :     }
    1055              :     
    1056              :     // returns number of applied rows
    1057          680 :     sqlite3_result_int(context, nrows);
    1058          687 : }
    1059              : 
    1060              : #ifdef CLOUDSYNC_DESKTOP_OS
    1061            0 : void dbsync_payload_save (sqlite3_context *context, int argc, sqlite3_value **argv) {
    1062              :     DEBUG_FUNCTION("dbsync_payload_save");
    1063              :     
    1064              :     // sanity check argument
    1065            0 :     if (database_value_type(argv[0]) != SQLITE_TEXT) {
    1066            0 :         sqlite3_result_error(context, "Unable to retrieve file path.", -1);
    1067            0 :         return;
    1068              :     }
    1069              :     
    1070              :     // retrieve full path to file
    1071            0 :     const char *payload_path = (const char *)database_value_text(argv[0]);
    1072              :     
    1073              :     // retrieve global context
    1074            0 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
    1075              :     
    1076            0 :     int blob_size = 0;
    1077            0 :     int rc = cloudsync_payload_save(data, payload_path, &blob_size);
    1078            0 :     if (rc == SQLITE_OK) {
    1079              :         // if OK then returns blob size
    1080            0 :         sqlite3_result_int64(context, (sqlite3_int64)blob_size);
    1081            0 :         return;
    1082              :     }
    1083              :     
    1084            0 :     sqlite3_result_error(context, cloudsync_errmsg(data), -1);
    1085            0 :     sqlite3_result_error_code(context, rc);
    1086            0 : }
    1087              : 
    1088            0 : void dbsync_payload_load (sqlite3_context *context, int argc, sqlite3_value **argv) {
    1089              :     DEBUG_FUNCTION("dbsync_payload_load");
    1090              :     
    1091              :     // sanity check argument
    1092            0 :     if (database_value_type(argv[0]) != SQLITE_TEXT) {
    1093            0 :         sqlite3_result_error(context, "Unable to retrieve file path.", -1);
    1094            0 :         return;
    1095              :     }
    1096              :     
    1097              :     // retrieve full path to file
    1098            0 :     const char *path = (const char *)database_value_text(argv[0]);
    1099              :     
    1100            0 :     int64_t payload_size = 0;
    1101            0 :     char *payload = cloudsync_file_read(path, &payload_size);
    1102            0 :     if (!payload) {
    1103            0 :         if (payload_size < 0) {
    1104            0 :             sqlite3_result_error(context, "Unable to read payload from file path.", -1);
    1105            0 :             sqlite3_result_error_code(context, SQLITE_IOERR);
    1106            0 :             return;
    1107              :         }
    1108              :         // no rows affected but no error either
    1109            0 :         sqlite3_result_int(context, 0);
    1110            0 :         return;
    1111              :     }
    1112              :     
    1113            0 :     int nrows = 0;
    1114            0 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
    1115            0 :     int rc = cloudsync_payload_apply (data, payload, (int)payload_size, &nrows);
    1116            0 :     if (payload) cloudsync_memory_free(payload);
    1117              :     
    1118            0 :     if (rc != SQLITE_OK) {
    1119            0 :         sqlite3_result_error(context, cloudsync_errmsg(data), -1);
    1120            0 :         sqlite3_result_error_code(context, rc);
    1121            0 :         return;
    1122              :     }
    1123              :     
    1124              :     // returns number of applied rows
    1125            0 :     sqlite3_result_int(context, nrows);
    1126            0 : }
    1127              : #endif
    1128              : 
    1129              : // MARK: - Register -
    1130              : 
    1131         8816 : 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 *)) {
    1132              : 
    1133         8816 :     int rc = sqlite3_create_function_v2(db, name, nargs, flags, ctx, xfunc, xstep, xfinal, ctx_free);
    1134              :     
    1135         8816 :     if (rc != SQLITE_OK) {
    1136            0 :         if (pzErrMsg) *pzErrMsg = sqlite3_mprintf("Error creating function %s: %s", name, sqlite3_errmsg(db));
    1137            0 :         return rc;
    1138              :     }
    1139         8816 :     return SQLITE_OK;
    1140         8816 : }
    1141              : 
    1142         7192 : 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 *)) {
    1143         7192 :     const int FLAGS_VOLATILE = SQLITE_UTF8;
    1144              :     DEBUG_DBFUNCTION("dbsync_register %s", name);
    1145         7192 :     return dbsync_register_with_flags(db, name, xfunc, xstep, xfinal, nargs, FLAGS_VOLATILE, pzErrMsg, ctx, ctx_free);
    1146              : }
    1147              : 
    1148         6960 : 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 *)) {
    1149              :     DEBUG_DBFUNCTION("dbsync_register_function %s", name);
    1150         6960 :     return dbsync_register(db, name, xfunc, NULL, NULL, nargs, pzErrMsg, ctx, ctx_free);
    1151              : }
    1152              : 
    1153          696 : 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 *)) {
    1154          696 :     const int FLAGS_PURE = SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC;
    1155              :     DEBUG_DBFUNCTION("dbsync_register_pure_function %s", name);
    1156          696 :     return dbsync_register_with_flags(db, name, xfunc, NULL, NULL, nargs, FLAGS_PURE, pzErrMsg, ctx, ctx_free);
    1157              : }
    1158              : 
    1159          696 : 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 *)) {
    1160          696 :     const int FLAGS_TRIGGER = SQLITE_UTF8 | SQLITE_INNOCUOUS;
    1161              :     DEBUG_DBFUNCTION("dbsync_register_trigger_function %s", name);
    1162          696 :     return dbsync_register_with_flags(db, name, xfunc, NULL, NULL, nargs, FLAGS_TRIGGER, pzErrMsg, ctx, ctx_free);
    1163              : }
    1164              : 
    1165          232 : 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 *)) {
    1166              :     DEBUG_DBFUNCTION("dbsync_register_aggregate %s", name);
    1167          232 :     return dbsync_register(db, name, NULL, xstep, xfinal, nargs, pzErrMsg, ctx, ctx_free);
    1168              : }
    1169              : 
    1170          232 : 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 *)) {
    1171          232 :     const int FLAGS_TRIGGER = SQLITE_UTF8 | SQLITE_INNOCUOUS;
    1172              :     DEBUG_DBFUNCTION("dbsync_register_trigger_aggregate %s", name);
    1173          232 :     return dbsync_register_with_flags(db, name, NULL, xstep, xfinal, nargs, FLAGS_TRIGGER, pzErrMsg, ctx, ctx_free);
    1174              : }
    1175              : 
    1176              : // MARK: - Block-level LWW -
    1177              : 
    1178           50 : void dbsync_text_materialize (sqlite3_context *context, int argc, sqlite3_value **argv) {
    1179              :     DEBUG_FUNCTION("cloudsync_text_materialize");
    1180              : 
    1181              :     // argv[0] -> table name
    1182              :     // argv[1] -> column name
    1183              :     // argv[2..N] -> primary key values
    1184              : 
    1185           50 :     if (argc < 3) {
    1186            0 :         sqlite3_result_error(context, "cloudsync_text_materialize requires at least 3 arguments: table, column, pk...", -1);
    1187            0 :         return;
    1188              :     }
    1189              : 
    1190           50 :     const char *table_name = (const char *)database_value_text(argv[0]);
    1191           50 :     const char *col_name = (const char *)database_value_text(argv[1]);
    1192           50 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
    1193              : 
    1194           50 :     cloudsync_table_context *table = table_lookup(data, table_name);
    1195           50 :     if (!table) {
    1196            0 :         dbsync_set_error(context, "Unable to retrieve table name %s in cloudsync_text_materialize.", table_name);
    1197            0 :         return;
    1198              :     }
    1199              : 
    1200           50 :     int col_idx = table_col_index(table, col_name);
    1201           50 :     if (col_idx < 0 || table_col_algo(table, col_idx) != col_algo_block) {
    1202            0 :         dbsync_set_error(context, "Column %s in table %s is not configured as block-level.", col_name, table_name);
    1203            0 :         return;
    1204              :     }
    1205              : 
    1206              :     // Encode primary keys
    1207           50 :     int npks = table_count_pks(table);
    1208           50 :     if (argc - 2 != npks) {
    1209            0 :         sqlite3_result_error(context, "Wrong number of primary key values for cloudsync_text_materialize.", -1);
    1210            0 :         return;
    1211              :     }
    1212              : 
    1213              :     char buffer[1024];
    1214           50 :     size_t pklen = sizeof(buffer);
    1215           50 :     char *pk = pk_encode_prikey((dbvalue_t **)&argv[2], npks, buffer, &pklen);
    1216           50 :     if (!pk || pk == PRIKEY_NULL_CONSTRAINT_ERROR) {
    1217            0 :         sqlite3_result_error(context, "Failed to encode primary key(s).", -1);
    1218            0 :         return;
    1219              :     }
    1220              : 
    1221              :     // Materialize the column
    1222           50 :     int rc = block_materialize_column(data, table, pk, (int)pklen, col_name);
    1223           50 :     if (rc != DBRES_OK) {
    1224            0 :         sqlite3_result_error(context, cloudsync_errmsg(data), -1);
    1225            0 :     } else {
    1226           50 :         sqlite3_result_int(context, 1);
    1227              :     }
    1228              : 
    1229           50 :     if (pk != buffer) cloudsync_memory_free(pk);
    1230           50 : }
    1231              : 
    1232              : // MARK: - Row Filter -
    1233              : 
    1234           20 : void dbsync_set_filter (sqlite3_context *context, int argc, sqlite3_value **argv) {
    1235              :     DEBUG_FUNCTION("cloudsync_set_filter");
    1236              : 
    1237           20 :     const char *tbl = (const char *)database_value_text(argv[0]);
    1238           20 :     const char *filter_expr = (const char *)database_value_text(argv[1]);
    1239           20 :     if (!tbl || !filter_expr) {
    1240            0 :         dbsync_set_error(context, "cloudsync_set_filter: table and filter expression required");
    1241            0 :         return;
    1242              :     }
    1243              : 
    1244           20 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
    1245              : 
    1246              :     // Store filter in table settings
    1247           20 :     dbutils_table_settings_set_key_value(data, tbl, "*", "filter", filter_expr);
    1248              : 
    1249              :     // Read current algo
    1250           20 :     table_algo algo = dbutils_table_settings_get_algo(data, tbl);
    1251           20 :     if (algo == table_algo_none) algo = table_algo_crdt_cls;
    1252              : 
    1253              :     // Drop and recreate triggers with the filter
    1254           20 :     database_delete_triggers(data, tbl);
    1255           20 :     int rc = database_create_triggers(data, tbl, algo, filter_expr);
    1256           20 :     if (rc != DBRES_OK) {
    1257            0 :         dbsync_set_error(context, "cloudsync_set_filter: error recreating triggers");
    1258            0 :         sqlite3_result_error_code(context, rc);
    1259            0 :         return;
    1260              :     }
    1261              : 
    1262              :     // Clean and refill metatable with the new filter
    1263           20 :     rc = cloudsync_reset_metatable(data, tbl);
    1264           20 :     if (rc != DBRES_OK) {
    1265            0 :         dbsync_set_error(context, "cloudsync_set_filter: error resetting metatable");
    1266            0 :         sqlite3_result_error_code(context, rc);
    1267            0 :         return;
    1268              :     }
    1269              : 
    1270           20 :     sqlite3_result_int(context, 1);
    1271           20 : }
    1272              : 
    1273            4 : void dbsync_clear_filter (sqlite3_context *context, int argc, sqlite3_value **argv) {
    1274              :     DEBUG_FUNCTION("cloudsync_clear_filter");
    1275              : 
    1276            4 :     const char *tbl = (const char *)database_value_text(argv[0]);
    1277            4 :     if (!tbl) {
    1278            0 :         dbsync_set_error(context, "cloudsync_clear_filter: table name required");
    1279            0 :         return;
    1280              :     }
    1281              : 
    1282            4 :     cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
    1283              : 
    1284              :     // Remove filter from table settings (set to NULL/empty)
    1285            4 :     dbutils_table_settings_set_key_value(data, tbl, "*", "filter", NULL);
    1286              : 
    1287              :     // Read current algo
    1288            4 :     table_algo algo = dbutils_table_settings_get_algo(data, tbl);
    1289            4 :     if (algo == table_algo_none) algo = table_algo_crdt_cls;
    1290              : 
    1291              :     // Drop and recreate triggers without filter
    1292            4 :     database_delete_triggers(data, tbl);
    1293            4 :     int rc = database_create_triggers(data, tbl, algo, NULL);
    1294            4 :     if (rc != DBRES_OK) {
    1295            0 :         dbsync_set_error(context, "cloudsync_clear_filter: error recreating triggers");
    1296            0 :         sqlite3_result_error_code(context, rc);
    1297            0 :         return;
    1298              :     }
    1299              : 
    1300              :     // Clean and refill metatable without filter (all rows)
    1301            4 :     rc = cloudsync_reset_metatable(data, tbl);
    1302            4 :     if (rc != DBRES_OK) {
    1303            0 :         dbsync_set_error(context, "cloudsync_clear_filter: error resetting metatable");
    1304            0 :         sqlite3_result_error_code(context, rc);
    1305            0 :         return;
    1306              :     }
    1307              : 
    1308            4 :     sqlite3_result_int(context, 1);
    1309            4 : }
    1310              : 
    1311          234 : int dbsync_register_functions (sqlite3 *db, char **pzErrMsg) {
    1312          234 :     int rc = SQLITE_OK;
    1313              :     
    1314              :     // there's no built-in way to verify if sqlite3_cloudsync_init has already been called
    1315              :     // for this specific database connection, we use a workaround: we attempt to retrieve the
    1316              :     // cloudsync_version and check for an error, an error indicates that initialization has not been performed
    1317          234 :     if (sqlite3_exec(db, "SELECT cloudsync_version();", NULL, NULL, NULL) == SQLITE_OK) return SQLITE_OK;
    1318              :     
    1319              :     // init memory debugger (NOOP in production)
    1320              :     cloudsync_memory_init(1);
    1321              : 
    1322              :     // set fractional-indexing allocator to use cloudsync memory
    1323          232 :     block_init_allocator();
    1324              : 
    1325              :     // init context
    1326          232 :     void *ctx = cloudsync_context_create(db);
    1327          232 :     if (!ctx) {
    1328            0 :         if (pzErrMsg) *pzErrMsg = sqlite3_mprintf("Not enought memory to create a database context");
    1329            0 :         return SQLITE_NOMEM;
    1330              :     }
    1331              :     
    1332              :     // register functions
    1333              :     
    1334              :     // PUBLIC functions
    1335          232 :     rc = dbsync_register_pure_function(db, "cloudsync_version", dbsync_version, 0, pzErrMsg, ctx, cloudsync_context_free);
    1336          232 :     if (rc != SQLITE_OK) return rc;
    1337              :     
    1338          232 :     rc = dbsync_register_function(db, "cloudsync_init", dbsync_init1, 1, pzErrMsg, ctx, NULL);
    1339          232 :     if (rc != SQLITE_OK) return rc;
    1340              :     
    1341          232 :     rc = dbsync_register_function(db, "cloudsync_init", dbsync_init2, 2, pzErrMsg, ctx, NULL);
    1342          232 :     if (rc != SQLITE_OK) return rc;
    1343              :     
    1344          232 :     rc = dbsync_register_function(db, "cloudsync_init", dbsync_init3, 3, pzErrMsg, ctx, NULL);
    1345          232 :     if (rc != SQLITE_OK) return rc;
    1346              : 
    1347          232 :     rc = dbsync_register_function(db, "cloudsync_enable", dbsync_enable, 1, pzErrMsg, ctx, NULL);
    1348          232 :     if (rc != SQLITE_OK) return rc;
    1349              :     
    1350          232 :     rc = dbsync_register_function(db, "cloudsync_disable", dbsync_disable, 1, pzErrMsg, ctx, NULL);
    1351          232 :     if (rc != SQLITE_OK) return rc;
    1352              :     
    1353          232 :     rc = dbsync_register_function(db, "cloudsync_is_enabled", dbsync_is_enabled, 1, pzErrMsg, ctx, NULL);
    1354          232 :     if (rc != SQLITE_OK) return rc;
    1355              :     
    1356          232 :     rc = dbsync_register_function(db, "cloudsync_cleanup", dbsync_cleanup, 1, pzErrMsg, ctx, NULL);
    1357          232 :     if (rc != SQLITE_OK) return rc;
    1358              :     
    1359          232 :     rc = dbsync_register_function(db, "cloudsync_terminate", dbsync_terminate, 0, pzErrMsg, ctx, NULL);
    1360          232 :     if (rc != SQLITE_OK) return rc;
    1361              :     
    1362          232 :     rc = dbsync_register_function(db, "cloudsync_set", dbsync_set, 2, pzErrMsg, ctx, NULL);
    1363          232 :     if (rc != SQLITE_OK) return rc;
    1364              :     
    1365          232 :     rc = dbsync_register_function(db, "cloudsync_set_table", dbsync_set_table, 3, pzErrMsg, ctx, NULL);
    1366          232 :     if (rc != SQLITE_OK) return rc;
    1367              : 
    1368          232 :     rc = dbsync_register_function(db, "cloudsync_set_filter", dbsync_set_filter, 2, pzErrMsg, ctx, NULL);
    1369          232 :     if (rc != SQLITE_OK) return rc;
    1370              : 
    1371          232 :     rc = dbsync_register_function(db, "cloudsync_clear_filter", dbsync_clear_filter, 1, pzErrMsg, ctx, NULL);
    1372          232 :     if (rc != SQLITE_OK) return rc;
    1373              : 
    1374          232 :     rc = dbsync_register_function(db, "cloudsync_set_schema", dbsync_set_schema, 1, pzErrMsg, ctx, NULL);
    1375          232 :     if (rc != SQLITE_OK) return rc;
    1376              :     
    1377          232 :     rc = dbsync_register_function(db, "cloudsync_schema", dbsync_schema, 0, pzErrMsg, ctx, NULL);
    1378          232 :     if (rc != SQLITE_OK) return rc;
    1379              :     
    1380          232 :     rc = dbsync_register_function(db, "cloudsync_table_schema", dbsync_table_schema, 1, pzErrMsg, ctx, NULL);
    1381          232 :     if (rc != SQLITE_OK) return rc;
    1382              : 
    1383          232 :     rc = dbsync_register_function(db, "cloudsync_set_column", dbsync_set_column, 4, pzErrMsg, ctx, NULL);
    1384          232 :     if (rc != SQLITE_OK) return rc;
    1385              :     
    1386          232 :     rc = dbsync_register_function(db, "cloudsync_siteid", dbsync_siteid, 0, pzErrMsg, ctx, NULL);
    1387          232 :     if (rc != SQLITE_OK) return rc;
    1388              :     
    1389          232 :     rc = dbsync_register_function(db, "cloudsync_db_version", dbsync_db_version, 0, pzErrMsg, ctx, NULL);
    1390          232 :     if (rc != SQLITE_OK) return rc;
    1391              :     
    1392          232 :     rc = dbsync_register_function(db, "cloudsync_db_version_next", dbsync_db_version_next, 0, pzErrMsg, ctx, NULL);
    1393          232 :     if (rc != SQLITE_OK) return rc;
    1394              :     
    1395          232 :     rc = dbsync_register_function(db, "cloudsync_db_version_next", dbsync_db_version_next, 1, pzErrMsg, ctx, NULL);
    1396          232 :     if (rc != SQLITE_OK) return rc;
    1397              :     
    1398          232 :     rc = dbsync_register_function(db, "cloudsync_begin_alter", dbsync_begin_alter, 1, pzErrMsg, ctx, NULL);
    1399          232 :     if (rc != SQLITE_OK) return rc;
    1400              :     
    1401          232 :     rc = dbsync_register_function(db, "cloudsync_commit_alter", dbsync_commit_alter, 1, pzErrMsg, ctx, NULL);
    1402          232 :     if (rc != SQLITE_OK) return rc;
    1403              :     
    1404          232 :     rc = dbsync_register_function(db, "cloudsync_uuid", dbsync_uuid, 0, pzErrMsg, ctx, NULL);
    1405          232 :     if (rc != SQLITE_OK) return rc;
    1406              :     
    1407              :     // PAYLOAD
    1408          232 :     rc = dbsync_register_aggregate(db, "cloudsync_payload_encode", dbsync_payload_encode_step, dbsync_payload_encode_final, -1, pzErrMsg, ctx, NULL);
    1409          232 :     if (rc != SQLITE_OK) return rc;
    1410              :     
    1411              :     // alias
    1412          232 :     rc = dbsync_register_function(db, "cloudsync_payload_decode", dbsync_payload_decode, -1, pzErrMsg, ctx, NULL);
    1413          232 :     if (rc != SQLITE_OK) return rc;
    1414          232 :     rc = dbsync_register_function(db, "cloudsync_payload_apply", dbsync_payload_decode, -1, pzErrMsg, ctx, NULL);
    1415          232 :     if (rc != SQLITE_OK) return rc;
    1416              :     
    1417              :     #ifdef CLOUDSYNC_DESKTOP_OS
    1418          232 :     rc = dbsync_register_function(db, "cloudsync_payload_save", dbsync_payload_save, 1, pzErrMsg, ctx, NULL);
    1419          232 :     if (rc != SQLITE_OK) return rc;
    1420              :     
    1421          232 :     rc = dbsync_register_function(db, "cloudsync_payload_load", dbsync_payload_load, 1, pzErrMsg, ctx, NULL);
    1422          232 :     if (rc != SQLITE_OK) return rc;
    1423              :     #endif
    1424              :     
    1425              :     // PRIVATE functions (used inside triggers — require SQLITE_INNOCUOUS)
    1426          232 :     rc = dbsync_register_trigger_function(db, "cloudsync_is_sync", dbsync_is_sync, 1, pzErrMsg, ctx, NULL);
    1427          232 :     if (rc != SQLITE_OK) return rc;
    1428              : 
    1429          232 :     rc = dbsync_register_trigger_function(db, "cloudsync_insert", dbsync_insert, -1, pzErrMsg, ctx, NULL);
    1430          232 :     if (rc != SQLITE_OK) return rc;
    1431              : 
    1432          232 :     rc = dbsync_register_trigger_aggregate(db, "cloudsync_update", dbsync_update_step, dbsync_update_final, 3, pzErrMsg, ctx, NULL);
    1433          232 :     if (rc != SQLITE_OK) return rc;
    1434              : 
    1435          232 :     rc = dbsync_register_trigger_function(db, "cloudsync_delete", dbsync_delete, -1, pzErrMsg, ctx, NULL);
    1436          232 :     if (rc != SQLITE_OK) return rc;
    1437              :     
    1438          232 :     rc = dbsync_register_function(db, "cloudsync_col_value", dbsync_col_value, 3, pzErrMsg, ctx, NULL);
    1439          232 :     if (rc != SQLITE_OK) return rc;
    1440              :     
    1441          232 :     rc = dbsync_register_pure_function(db, "cloudsync_pk_encode", dbsync_pk_encode, -1, pzErrMsg, ctx, NULL);
    1442          232 :     if (rc != SQLITE_OK) return rc;
    1443              : 
    1444          232 :     rc = dbsync_register_pure_function(db, "cloudsync_pk_decode", dbsync_pk_decode, 2, pzErrMsg, ctx, NULL);
    1445          232 :     if (rc != SQLITE_OK) return rc;
    1446              :     
    1447          232 :     rc = dbsync_register_function(db, "cloudsync_seq", dbsync_seq, 0, pzErrMsg, ctx, NULL);
    1448          232 :     if (rc != SQLITE_OK) return rc;
    1449              : 
    1450          232 :     rc = dbsync_register_function(db, "cloudsync_text_materialize", dbsync_text_materialize, -1, pzErrMsg, ctx, NULL);
    1451          232 :     if (rc != SQLITE_OK) return rc;
    1452              : 
    1453              :     // NETWORK LAYER
    1454              :     #ifndef CLOUDSYNC_OMIT_NETWORK
    1455              :     rc = cloudsync_network_register(db, pzErrMsg, ctx);
    1456              :     if (rc != SQLITE_OK) return rc;
    1457              :     #endif
    1458              :     
    1459          232 :     cloudsync_context *data = (cloudsync_context *)ctx;
    1460          232 :     sqlite3_commit_hook(db, cloudsync_commit_hook, ctx);
    1461          232 :     sqlite3_rollback_hook(db, cloudsync_rollback_hook, ctx);
    1462              :     
    1463              :     // register eponymous only changes virtual table
    1464          232 :     rc = cloudsync_vtab_register_changes (db, data);
    1465          232 :     if (rc != SQLITE_OK) {
    1466            0 :         if (pzErrMsg) *pzErrMsg = sqlite3_mprintf("Error creating changes virtual table: %s", sqlite3_errmsg(db));
    1467            0 :         return rc;
    1468              :     }
    1469              :     
    1470              :     // load config, if exists
    1471          232 :     if (cloudsync_config_exists(data)) {
    1472            4 :         if (cloudsync_context_init(data) == NULL) {
    1473              :             // Do not free ctx here: it is already owned by the cloudsync_version
    1474              :             // function (registered above with cloudsync_context_free as its
    1475              :             // destructor). SQLite will release it when the connection is closed.
    1476              :             // Freeing it manually would cause a double-free on sqlite3_close.
    1477            0 :             if (pzErrMsg) *pzErrMsg = sqlite3_mprintf("An error occurred while trying to initialize context");
    1478            0 :             return SQLITE_ERROR;
    1479              :         }
    1480              :         
    1481              :         // update schema hash if upgrading from an older version
    1482            4 :         if (dbutils_settings_check_version(data, NULL) != 0) {
    1483            0 :             cloudsync_update_schema_hash(data);
    1484            0 :         }
    1485              : 
    1486              :         // make sure to update internal version to current version
    1487            4 :         dbutils_settings_set_key_value(data, CLOUDSYNC_KEY_LIBVERSION, CLOUDSYNC_VERSION);
    1488            4 :     }
    1489              :     
    1490          232 :     return SQLITE_OK;
    1491          234 : }
    1492              : 
    1493              : // MARK: - Main Entrypoint -
    1494              : 
    1495          234 : APIEXPORT int sqlite3_cloudsync_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi) {
    1496              :     DEBUG_FUNCTION("sqlite3_cloudsync_init");
    1497              :     
    1498              :     #ifndef SQLITE_CORE
    1499              :     SQLITE_EXTENSION_INIT2(pApi);
    1500              :     #endif
    1501              :     
    1502          234 :     return dbsync_register_functions(db, pzErrMsg);
    1503              : }
        

Generated by: LCOV version 2.4-0