Line data Source code
1 : //
2 : // cloudsync.c
3 : // cloudsync
4 : //
5 : // Created by Marco Bambini on 16/05/24.
6 : //
7 :
8 : #include <inttypes.h>
9 : #include <stdbool.h>
10 : #include <limits.h>
11 : #include <stdint.h>
12 : #include <stdlib.h>
13 : #include <string.h>
14 : #include <assert.h>
15 : #include <stdio.h>
16 : #include <errno.h>
17 : #include <math.h>
18 :
19 : #include "cloudsync.h"
20 : #include "cloudsync_private.h"
21 : #include "lz4.h"
22 : #include "pk.h"
23 : #include "vtab.h"
24 : #include "utils.h"
25 : #include "dbutils.h"
26 :
27 : #ifndef CLOUDSYNC_OMIT_NETWORK
28 : #include "network.h"
29 : #endif
30 :
31 : #ifdef _WIN32
32 : #include <winsock2.h>
33 : #include <ws2tcpip.h>
34 : #else
35 : #include <arpa/inet.h> // for htonl, htons, ntohl, ntohs
36 : #include <netinet/in.h> // for struct sockaddr_in, INADDR_ANY, etc. (if needed)
37 : #endif
38 :
39 : #ifndef htonll
40 : #if __BIG_ENDIAN__
41 : #define htonll(x) (x)
42 : #define ntohll(x) (x)
43 : #else
44 : #ifndef htobe64
45 : #define htonll(x) ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32 | (uint64_t)htonl((x) >> 32))
46 : #define ntohll(x) ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32 | (uint64_t)ntohl((x) >> 32))
47 : #else
48 : #define htonll(x) htobe64(x)
49 : #define ntohll(x) be64toh(x)
50 : #endif
51 : #endif
52 : #endif
53 :
54 : #ifndef SQLITE_CORE
55 : SQLITE_EXTENSION_INIT1
56 : #endif
57 :
58 : #ifndef UNUSED_PARAMETER
59 : #define UNUSED_PARAMETER(X) (void)(X)
60 : #endif
61 :
62 : #ifdef _WIN32
63 : #define APIEXPORT __declspec(dllexport)
64 : #else
65 : #define APIEXPORT
66 : #endif
67 :
68 : #define CLOUDSYNC_DEFAULT_ALGO "cls"
69 : #define CLOUDSYNC_INIT_NTABLES 128
70 : #define CLOUDSYNC_VALUE_NOTSET -1
71 : #define CLOUDSYNC_MIN_DB_VERSION 0
72 :
73 : #define CLOUDSYNC_PAYLOAD_MINBUF_SIZE 512*1024
74 : #define CLOUDSYNC_PAYLOAD_VERSION 1
75 : #define CLOUDSYNC_PAYLOAD_SIGNATURE 'CLSY'
76 : #define CLOUDSYNC_PAYLOAD_APPLY_CALLBACK_KEY "cloudsync_payload_apply_callback"
77 :
78 : #ifndef MAX
79 : #define MAX(a, b) (((a)>(b))?(a):(b))
80 : #endif
81 :
82 : #define DEBUG_SQLITE_ERROR(_rc, _fn, _db) do {if (_rc != SQLITE_OK) printf("Error in %s: %s\n", _fn, sqlite3_errmsg(_db));} while (0)
83 :
84 : typedef enum {
85 : CLOUDSYNC_PK_INDEX_TBL = 0,
86 : CLOUDSYNC_PK_INDEX_PK = 1,
87 : CLOUDSYNC_PK_INDEX_COLNAME = 2,
88 : CLOUDSYNC_PK_INDEX_COLVALUE = 3,
89 : CLOUDSYNC_PK_INDEX_COLVERSION = 4,
90 : CLOUDSYNC_PK_INDEX_DBVERSION = 5,
91 : CLOUDSYNC_PK_INDEX_SITEID = 6,
92 : CLOUDSYNC_PK_INDEX_CL = 7,
93 : CLOUDSYNC_PK_INDEX_SEQ = 8
94 : } CLOUDSYNC_PK_INDEX;
95 :
96 : typedef enum {
97 : CLOUDSYNC_STMT_VALUE_ERROR = -1,
98 : CLOUDSYNC_STMT_VALUE_UNCHANGED = 0,
99 : CLOUDSYNC_STMT_VALUE_CHANGED = 1,
100 : } CLOUDSYNC_STMT_VALUE;
101 :
102 : typedef struct {
103 : sqlite3_context *context;
104 : int index;
105 : } cloudsync_pk_decode_context;
106 :
107 : #define SYNCBIT_SET(_data) _data->insync = 1
108 : #define SYNCBIT_RESET(_data) _data->insync = 0
109 : #define BUMP_SEQ(_data) ((_data)->seq += 1, (_data)->seq - 1)
110 :
111 : // MARK: -
112 :
113 : typedef struct {
114 : table_algo algo; // CRDT algoritm associated to the table
115 : char *name; // table name
116 : char **col_name; // array of column names
117 : sqlite3_stmt **col_merge_stmt; // array of merge insert stmt (indexed by col_name)
118 : sqlite3_stmt **col_value_stmt; // array of column value stmt (indexed by col_name)
119 : int *col_id; // array of column id
120 : int ncols; // number of non primary key cols
121 : int npks; // number of primary key cols
122 : bool enabled; // flag to check if a table is enabled or disabled
123 : #if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES
124 : bool rowid_only; // a table with no primary keys other than the implicit rowid
125 : #endif
126 :
127 : char **pk_name; // array of primary key names
128 :
129 : // precompiled statements
130 : sqlite3_stmt *meta_pkexists_stmt; // check if a primary key already exist in the augmented table
131 : sqlite3_stmt *meta_sentinel_update_stmt; // update a local sentinel row
132 : sqlite3_stmt *meta_sentinel_insert_stmt; // insert a local sentinel row
133 : sqlite3_stmt *meta_row_insert_update_stmt; // insert/update a local row
134 : sqlite3_stmt *meta_row_drop_stmt; // delete rows from meta
135 : sqlite3_stmt *meta_update_move_stmt; // update rows in meta when pk changes
136 : sqlite3_stmt *meta_local_cl_stmt; // compute local cl value
137 : sqlite3_stmt *meta_winner_clock_stmt; // get the rowid of the last inserted/updated row in the meta table
138 : sqlite3_stmt *meta_merge_delete_drop;
139 : sqlite3_stmt *meta_zero_clock_stmt;
140 : sqlite3_stmt *meta_col_version_stmt;
141 : sqlite3_stmt *meta_site_id_stmt;
142 :
143 : sqlite3_stmt *real_col_values_stmt; // retrieve all column values based on pk
144 : sqlite3_stmt *real_merge_delete_stmt;
145 : sqlite3_stmt *real_merge_sentinel_stmt;
146 :
147 : bool is_altering;
148 :
149 : } cloudsync_table_context;
150 :
151 : struct cloudsync_pk_decode_bind_context {
152 : sqlite3_stmt *vm;
153 : char *tbl;
154 : int64_t tbl_len;
155 : const void *pk;
156 : int64_t pk_len;
157 : char *col_name;
158 : int64_t col_name_len;
159 : int64_t col_version;
160 : int64_t db_version;
161 : const void *site_id;
162 : int64_t site_id_len;
163 : int64_t cl;
164 : int64_t seq;
165 : };
166 :
167 : struct cloudsync_context {
168 : sqlite3_context *sqlite_ctx;
169 :
170 : char *libversion;
171 : uint8_t site_id[UUID_LEN];
172 : int insync;
173 : int debug;
174 : bool merge_equal_values;
175 : bool temp_bool; // temporary value used in callback
176 : void *aux_data;
177 :
178 : // stmts and context values
179 : bool pragma_checked; // we need to check PRAGMAs only once per transaction
180 : sqlite3_stmt *schema_version_stmt;
181 : sqlite3_stmt *data_version_stmt;
182 : sqlite3_stmt *db_version_stmt;
183 : sqlite3_stmt *getset_siteid_stmt;
184 : int data_version;
185 : int schema_version;
186 : uint64_t schema_hash;
187 :
188 : // set at the start of each transaction on the first invocation and
189 : // re-set on transaction commit or rollback
190 : sqlite3_int64 db_version;
191 : // the version that the db will be set to at the end of the transaction
192 : // if that transaction were to commit at the time this value is checked
193 : sqlite3_int64 pending_db_version;
194 : // used to set an order inside each transaction
195 : int seq;
196 :
197 : // augmented tables are stored in-memory so we do not need to retrieve information about col names and cid
198 : // from the disk each time a write statement is performed
199 : // we do also not need to use an hash map here because for few tables the direct in-memory comparison with table name is faster
200 : cloudsync_table_context **tables;
201 : int tables_count;
202 : int tables_alloc;
203 : };
204 :
205 : typedef struct {
206 : char *buffer;
207 : size_t balloc;
208 : size_t bused;
209 : uint64_t nrows;
210 : uint16_t ncols;
211 : } cloudsync_data_payload;
212 :
213 : #ifdef _MSC_VER
214 : #pragma pack(push, 1) // For MSVC: pack struct with 1-byte alignment
215 : #define PACKED
216 : #else
217 : #define PACKED __attribute__((__packed__))
218 : #endif
219 :
220 : typedef struct PACKED {
221 : uint32_t signature; // 'CLSY'
222 : uint8_t version; // protocol version
223 : uint8_t libversion[3]; // major.minor.patch
224 : uint32_t expanded_size;
225 : uint16_t ncols;
226 : uint32_t nrows;
227 : uint64_t schema_hash;
228 : uint8_t unused[6]; // padding to ensure the struct is exactly 32 bytes
229 : } cloudsync_payload_header;
230 :
231 : typedef struct {
232 : sqlite3_value *table_name;
233 : sqlite3_value **new_values;
234 : sqlite3_value **old_values;
235 : int count;
236 : int capacity;
237 : } cloudsync_update_payload;
238 :
239 : #ifdef _MSC_VER
240 : #pragma pack(pop)
241 : #endif
242 :
243 : #if CLOUDSYNC_UNITTEST
244 : bool force_uncompressed_blob = false;
245 : #define CHECK_FORCE_UNCOMPRESSED_BUFFER() if (force_uncompressed_blob) use_uncompressed_buffer = true
246 : #else
247 : #define CHECK_FORCE_UNCOMPRESSED_BUFFER()
248 : #endif
249 :
250 : int db_version_rebuild_stmt (sqlite3 *db, cloudsync_context *data);
251 : int cloudsync_load_siteid (sqlite3 *db, cloudsync_context *data);
252 : int local_mark_insert_or_update_meta (sqlite3 *db, cloudsync_table_context *table, const char *pk, size_t pklen, const char *col_name, sqlite3_int64 db_version, int seq);
253 :
254 : // MARK: - STMT Utils -
255 :
256 30480 : CLOUDSYNC_STMT_VALUE stmt_execute (sqlite3_stmt *stmt, cloudsync_context *data) {
257 30480 : int rc = sqlite3_step(stmt);
258 30480 : if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
259 2 : if (data) DEBUG_SQLITE_ERROR(rc, "stmt_execute", sqlite3_db_handle(stmt));
260 2 : sqlite3_reset(stmt);
261 2 : return CLOUDSYNC_STMT_VALUE_ERROR;
262 : }
263 :
264 30478 : CLOUDSYNC_STMT_VALUE result = CLOUDSYNC_STMT_VALUE_CHANGED;
265 30478 : if (stmt == data->data_version_stmt) {
266 28660 : int version = sqlite3_column_int(stmt, 0);
267 28660 : if (version != data->data_version) {
268 108 : data->data_version = version;
269 108 : } else {
270 28552 : result = CLOUDSYNC_STMT_VALUE_UNCHANGED;
271 : }
272 30478 : } else if (stmt == data->schema_version_stmt) {
273 909 : int version = sqlite3_column_int(stmt, 0);
274 909 : if (version > data->schema_version) {
275 166 : data->schema_version = version;
276 166 : } else {
277 743 : result = CLOUDSYNC_STMT_VALUE_UNCHANGED;
278 : }
279 :
280 1818 : } else if (stmt == data->db_version_stmt) {
281 909 : data->db_version = (rc == SQLITE_DONE) ? CLOUDSYNC_MIN_DB_VERSION : sqlite3_column_int64(stmt, 0);
282 909 : }
283 :
284 30478 : sqlite3_reset(stmt);
285 30478 : return result;
286 30480 : }
287 :
288 3469 : int stmt_count (sqlite3_stmt *stmt, const char *value, size_t len, int type) {
289 3469 : int result = -1;
290 3469 : int rc = SQLITE_OK;
291 :
292 3469 : if (value) {
293 3468 : rc = (type == SQLITE_TEXT) ? sqlite3_bind_text(stmt, 1, value, (int)len, SQLITE_STATIC) : sqlite3_bind_blob(stmt, 1, value, (int)len, SQLITE_STATIC);
294 3468 : if (rc != SQLITE_OK) goto cleanup;
295 3468 : }
296 :
297 3469 : rc = sqlite3_step(stmt);
298 6938 : if (rc == SQLITE_DONE) {
299 1 : result = 0;
300 1 : rc = SQLITE_OK;
301 3469 : } else if (rc == SQLITE_ROW) {
302 3468 : result = sqlite3_column_int(stmt, 0);
303 3468 : rc = SQLITE_OK;
304 3468 : }
305 :
306 : cleanup:
307 3469 : DEBUG_SQLITE_ERROR(rc, "stmt_count", sqlite3_db_handle(stmt));
308 3469 : sqlite3_reset(stmt);
309 3469 : return result;
310 : }
311 :
312 236202 : sqlite3_stmt *stmt_reset (sqlite3_stmt *stmt) {
313 236202 : sqlite3_clear_bindings(stmt);
314 236202 : sqlite3_reset(stmt);
315 236202 : return NULL;
316 : }
317 :
318 279 : int stmts_add_tocontext (sqlite3 *db, cloudsync_context *data) {
319 : DEBUG_DBFUNCTION("cloudsync_add_stmts");
320 :
321 279 : if (data->data_version_stmt == NULL) {
322 109 : const char *sql = "PRAGMA data_version;";
323 109 : int rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &data->data_version_stmt, NULL);
324 : DEBUG_STMT("data_version_stmt %p", data->data_version_stmt);
325 109 : if (rc != SQLITE_OK) return rc;
326 : DEBUG_SQL("data_version_stmt: %s", sql);
327 109 : }
328 :
329 279 : if (data->schema_version_stmt == NULL) {
330 109 : const char *sql = "PRAGMA schema_version;";
331 109 : int rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &data->schema_version_stmt, NULL);
332 : DEBUG_STMT("schema_version_stmt %p", data->schema_version_stmt);
333 109 : if (rc != SQLITE_OK) return rc;
334 : DEBUG_SQL("schema_version_stmt: %s", sql);
335 109 : }
336 :
337 279 : if (data->getset_siteid_stmt == NULL) {
338 : // get and set index of the site_id
339 : // in SQLite, we can’t directly combine an INSERT and a SELECT to both insert a row and return an identifier (rowid) in a single statement,
340 : // however, we can use a workaround by leveraging the INSERT statement with ON CONFLICT DO UPDATE and then combining it with RETURNING rowid
341 109 : const char *sql = "INSERT INTO cloudsync_site_id (site_id) VALUES (?) ON CONFLICT(site_id) DO UPDATE SET site_id = site_id RETURNING rowid;";
342 109 : int rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &data->getset_siteid_stmt, NULL);
343 : DEBUG_STMT("getset_siteid_stmt %p", data->getset_siteid_stmt);
344 109 : if (rc != SQLITE_OK) return rc;
345 : DEBUG_SQL("getset_siteid_stmt: %s", sql);
346 109 : }
347 :
348 279 : return db_version_rebuild_stmt(db, data);
349 279 : }
350 :
351 : // MARK: - Database Version -
352 :
353 335 : char *db_version_build_query (sqlite3 *db) {
354 : // this function must be manually called each time tables changes
355 : // because the query plan changes too and it must be re-prepared
356 : // unfortunately there is no other way
357 :
358 : // we need to execute a query like:
359 : /*
360 : SELECT max(version) as version FROM (
361 : SELECT max(db_version) as version FROM "table1_cloudsync"
362 : UNION ALL
363 : SELECT max(db_version) as version FROM "table2_cloudsync"
364 : UNION ALL
365 : SELECT max(db_version) as version FROM "table3_cloudsync"
366 : UNION
367 : SELECT value as version FROM cloudsync_settings WHERE key = 'pre_alter_dbversion'
368 : )
369 : */
370 :
371 : // the good news is that the query can be computed in SQLite without the need to do any extra computation from the host language
372 335 : const char *sql = "WITH table_names AS ("
373 : "SELECT format('%w', name) as tbl_name "
374 : "FROM sqlite_master "
375 : "WHERE type='table' "
376 : "AND name LIKE '%_cloudsync'"
377 : "), "
378 : "query_parts AS ("
379 : "SELECT 'SELECT max(db_version) as version FROM \"' || tbl_name || '\"' as part FROM table_names"
380 : "), "
381 : "combined_query AS ("
382 : "SELECT GROUP_CONCAT(part, ' UNION ALL ') || ' UNION SELECT value as version FROM cloudsync_settings WHERE key = ''pre_alter_dbversion''' as full_query FROM query_parts"
383 : ") "
384 : "SELECT 'SELECT max(version) as version FROM (' || full_query || ');' FROM combined_query;";
385 335 : return dbutils_text_select(db, sql);
386 : }
387 :
388 445 : int db_version_rebuild_stmt (sqlite3 *db, cloudsync_context *data) {
389 445 : if (data->db_version_stmt) {
390 226 : sqlite3_finalize(data->db_version_stmt);
391 226 : data->db_version_stmt = NULL;
392 226 : }
393 :
394 445 : sqlite3_int64 count = dbutils_table_settings_count_tables(db);
395 445 : if (count == 0) return SQLITE_OK;
396 335 : else if (count == -1) {
397 0 : dbutils_context_result_error(data->sqlite_ctx, "%s", sqlite3_errmsg(db));
398 0 : return SQLITE_ERROR;
399 : }
400 :
401 335 : char *sql = db_version_build_query(db);
402 335 : if (!sql) return SQLITE_NOMEM;
403 : DEBUG_SQL("db_version_stmt: %s", sql);
404 :
405 335 : int rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &data->db_version_stmt, NULL);
406 : DEBUG_STMT("db_version_stmt %p", data->db_version_stmt);
407 335 : cloudsync_memory_free(sql);
408 335 : return rc;
409 445 : }
410 :
411 909 : int db_version_rerun (sqlite3 *db, cloudsync_context *data) {
412 909 : CLOUDSYNC_STMT_VALUE schema_changed = stmt_execute(data->schema_version_stmt, data);
413 909 : if (schema_changed == CLOUDSYNC_STMT_VALUE_ERROR) return -1;
414 :
415 909 : if (schema_changed == CLOUDSYNC_STMT_VALUE_CHANGED) {
416 166 : int rc = db_version_rebuild_stmt(db, data);
417 166 : if (rc != SQLITE_OK) return -1;
418 166 : }
419 :
420 909 : CLOUDSYNC_STMT_VALUE rc = stmt_execute(data->db_version_stmt, data);
421 909 : if (rc == CLOUDSYNC_STMT_VALUE_ERROR) return -1;
422 909 : return 0;
423 909 : }
424 :
425 28660 : int db_version_check_uptodate (sqlite3 *db, cloudsync_context *data) {
426 : // perform a PRAGMA data_version to check if some other process write any data
427 28660 : CLOUDSYNC_STMT_VALUE rc = stmt_execute(data->data_version_stmt, data);
428 28660 : if (rc == CLOUDSYNC_STMT_VALUE_ERROR) return -1;
429 :
430 : // db_version is already set and there is no need to update it
431 28660 : if (data->db_version != CLOUDSYNC_VALUE_NOTSET && rc == CLOUDSYNC_STMT_VALUE_UNCHANGED) return 0;
432 :
433 909 : return db_version_rerun(db, data);
434 28660 : }
435 :
436 28634 : sqlite3_int64 db_version_next (sqlite3 *db, cloudsync_context *data, sqlite3_int64 merging_version) {
437 28634 : int rc = db_version_check_uptodate(db, data);
438 28634 : if (rc != SQLITE_OK) return -1;
439 :
440 28634 : sqlite3_int64 result = data->db_version + 1;
441 28634 : if (result < data->pending_db_version) result = data->pending_db_version;
442 28634 : if (merging_version != CLOUDSYNC_VALUE_NOTSET && result < merging_version) result = merging_version;
443 28634 : data->pending_db_version = result;
444 :
445 28634 : return result;
446 28634 : }
447 :
448 : // MARK: -
449 :
450 0 : void *cloudsync_get_auxdata (sqlite3_context *context) {
451 0 : cloudsync_context *data = (context) ? (cloudsync_context *)sqlite3_user_data(context) : NULL;
452 0 : return (data) ? data->aux_data : NULL;
453 : }
454 :
455 0 : void cloudsync_set_auxdata (sqlite3_context *context, void *xdata) {
456 0 : cloudsync_context *data = (context) ? (cloudsync_context *)sqlite3_user_data(context) : NULL;
457 0 : if (data) data->aux_data = xdata;
458 0 : }
459 :
460 : // MARK: - PK Context -
461 :
462 16416 : char *cloudsync_pk_context_tbl (cloudsync_pk_decode_bind_context *ctx, int64_t *tbl_len) {
463 16416 : *tbl_len = ctx->tbl_len;
464 16416 : return ctx->tbl;
465 : }
466 :
467 16416 : void *cloudsync_pk_context_pk (cloudsync_pk_decode_bind_context *ctx, int64_t *pk_len) {
468 16416 : *pk_len = ctx->pk_len;
469 16416 : return (void *)ctx->pk;
470 : }
471 :
472 16416 : char *cloudsync_pk_context_colname (cloudsync_pk_decode_bind_context *ctx, int64_t *colname_len) {
473 16416 : *colname_len = ctx->col_name_len;
474 16416 : return ctx->col_name;
475 : }
476 :
477 16416 : int64_t cloudsync_pk_context_cl (cloudsync_pk_decode_bind_context *ctx) {
478 16416 : return ctx->cl;
479 : }
480 :
481 16416 : int64_t cloudsync_pk_context_dbversion (cloudsync_pk_decode_bind_context *ctx) {
482 16416 : return ctx->db_version;
483 : }
484 :
485 : // MARK: - Table Utils -
486 :
487 128 : char *table_build_values_sql (sqlite3 *db, cloudsync_table_context *table) {
488 128 : char *sql = NULL;
489 :
490 : /*
491 : This SQL statement dynamically generates a SELECT query for a specified table.
492 : It uses Common Table Expressions (CTEs) to construct the column names and
493 : primary key conditions based on the table schema, which is obtained through
494 : the `pragma_table_info` function.
495 :
496 : 1. `col_names` CTE:
497 : - Retrieves a comma-separated list of non-primary key column names from
498 : the specified table's schema.
499 :
500 : 2. `pk_where` CTE:
501 : - Retrieves a condition string representing the primary key columns in the
502 : format: "column1=? AND column2=? AND ...", used to create the WHERE clause
503 : for selecting rows based on primary key values.
504 :
505 : 3. Final SELECT:
506 : - Constructs the complete SELECT statement as a string, combining:
507 : - Column names from `col_names`.
508 : - The target table name.
509 : - The WHERE clause conditions from `pk_where`.
510 :
511 : The resulting query can be used to select rows from the table based on primary
512 : key values, and can be executed within the application to retrieve data dynamically.
513 : */
514 :
515 : // Unfortunately in SQLite column names (or table names) cannot be bound parameters in a SELECT statement
516 : // otherwise we should have used something like SELECT 'SELECT ? FROM %w WHERE rowid=?';
517 :
518 128 : char *singlequote_escaped_table_name = cloudsync_memory_mprintf("%q", table->name);
519 :
520 : #if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES
521 : if (table->rowid_only) {
522 : sql = memory_mprintf("WITH col_names AS (SELECT group_concat('\"' || format('%%w', name) || '\"', ',') AS cols FROM pragma_table_info('%q') WHERE pk=0 ORDER BY cid) SELECT 'SELECT ' || (SELECT cols FROM col_names) || ' FROM \"%w\" WHERE rowid=?;'", table->name, table->name);
523 : goto process_process;
524 : }
525 : #endif
526 :
527 128 : sql = cloudsync_memory_mprintf("WITH col_names AS (SELECT group_concat('\"' || format('%%w', name) || '\"', ',') AS cols FROM pragma_table_info('%q') WHERE pk=0 ORDER BY cid), pk_where AS (SELECT group_concat('\"' || format('%%w', name) || '\"', '=? AND ') || '=?' AS pk_clause FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk) SELECT 'SELECT ' || (SELECT cols FROM col_names) || ' FROM \"%w\" WHERE ' || (SELECT pk_clause FROM pk_where) || ';'", table->name, table->name, singlequote_escaped_table_name);
528 :
529 : #if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES
530 : process_process:
531 : #endif
532 128 : cloudsync_memory_free(singlequote_escaped_table_name);
533 128 : if (!sql) return NULL;
534 128 : char *query = dbutils_text_select(db, sql);
535 128 : cloudsync_memory_free(sql);
536 :
537 128 : return query;
538 128 : }
539 :
540 163 : char *table_build_mergedelete_sql (sqlite3 *db, cloudsync_table_context *table) {
541 : #if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES
542 : if (table->rowid_only) {
543 : char *sql = memory_mprintf("DELETE FROM \"%w\" WHERE rowid=?;", table->name);
544 : return sql;
545 : }
546 : #endif
547 :
548 163 : char *singlequote_escaped_table_name = cloudsync_memory_mprintf("%q", table->name);
549 163 : char *sql = cloudsync_memory_mprintf("WITH pk_where AS (SELECT group_concat('\"' || format('%%w', name) || '\"', '=? AND ') || '=?' AS pk_clause FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk) SELECT 'DELETE FROM \"%w\" WHERE ' || (SELECT pk_clause FROM pk_where) || ';'", table->name, singlequote_escaped_table_name);
550 163 : cloudsync_memory_free(singlequote_escaped_table_name);
551 163 : if (!sql) return NULL;
552 :
553 163 : char *query = dbutils_text_select(db, sql);
554 163 : cloudsync_memory_free(sql);
555 :
556 163 : return query;
557 163 : }
558 :
559 1100 : char *table_build_mergeinsert_sql (sqlite3 *db, cloudsync_table_context *table, const char *colname) {
560 1100 : char *sql = NULL;
561 :
562 : #if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES
563 : if (table->rowid_only) {
564 : if (colname == NULL) {
565 : // INSERT OR IGNORE INTO customers (first_name,last_name) VALUES (?,?);
566 : sql = memory_mprintf("INSERT OR IGNORE INTO \"%w\" (rowid) VALUES (?);", table->name);
567 : } else {
568 : // INSERT INTO customers (first_name,last_name,age) VALUES (?,?,?) ON CONFLICT DO UPDATE SET age=?;
569 : sql = memory_mprintf("INSERT INTO \"%w\" (rowid, \"%w\") VALUES (?, ?) ON CONFLICT DO UPDATE SET \"%w\"=?;", table->name, colname, colname);
570 : }
571 : return sql;
572 : }
573 : #endif
574 :
575 1100 : char *singlequote_escaped_table_name = cloudsync_memory_mprintf("%q", table->name);
576 :
577 1100 : if (colname == NULL) {
578 : // is sentinel insert
579 163 : sql = cloudsync_memory_mprintf("WITH pk_where AS (SELECT group_concat('\"' || format('%%w', name) || '\"') AS pk_clause FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk), pk_bind AS (SELECT group_concat('?') AS pk_binding FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk) SELECT 'INSERT OR IGNORE INTO \"%w\" (' || (SELECT pk_clause FROM pk_where) || ') VALUES (' || (SELECT pk_binding FROM pk_bind) || ');'", table->name, table->name, singlequote_escaped_table_name);
580 163 : } else {
581 937 : char *singlequote_escaped_col_name = cloudsync_memory_mprintf("%q", colname);
582 937 : sql = cloudsync_memory_mprintf("WITH pk_where AS (SELECT group_concat('\"' || format('%%w', name) || '\"') AS pk_clause FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk), pk_bind AS (SELECT group_concat('?') AS pk_binding FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk) SELECT 'INSERT INTO \"%w\" (' || (SELECT pk_clause FROM pk_where) || ',\"%w\") VALUES (' || (SELECT pk_binding FROM pk_bind) || ',?) ON CONFLICT DO UPDATE SET \"%w\"=?;'", table->name, table->name, singlequote_escaped_table_name, singlequote_escaped_col_name, singlequote_escaped_col_name);
583 937 : cloudsync_memory_free(singlequote_escaped_col_name);
584 :
585 : }
586 1100 : cloudsync_memory_free(singlequote_escaped_table_name);
587 1100 : if (!sql) return NULL;
588 :
589 1100 : char *query = dbutils_text_select(db, sql);
590 1100 : cloudsync_memory_free(sql);
591 :
592 1100 : return query;
593 1100 : }
594 :
595 937 : char *table_build_value_sql (sqlite3 *db, cloudsync_table_context *table, const char *colname) {
596 937 : char *colnamequote = dbutils_is_star_table(colname) ? "" : "\"";
597 :
598 : #if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES
599 : if (table->rowid_only) {
600 : char *sql = memory_mprintf("SELECT %s%w%s FROM \"%w\" WHERE rowid=?;", colnamequote, colname, colnamequote, table->name);
601 : return sql;
602 : }
603 : #endif
604 :
605 : // SELECT age FROM customers WHERE first_name=? AND last_name=?;
606 937 : char *singlequote_escaped_table_name = cloudsync_memory_mprintf("%q", table->name);
607 937 : char *singlequote_escaped_col_name = cloudsync_memory_mprintf("%q", colname);
608 937 : char *sql = cloudsync_memory_mprintf("WITH pk_where AS (SELECT group_concat('\"' || format('%%w', name) || '\"', '=? AND ') || '=?' AS pk_clause FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk) SELECT 'SELECT %s%w%s FROM \"%w\" WHERE ' || (SELECT pk_clause FROM pk_where) || ';'", table->name, colnamequote, singlequote_escaped_col_name, colnamequote, singlequote_escaped_table_name);
609 937 : cloudsync_memory_free(singlequote_escaped_col_name);
610 937 : cloudsync_memory_free(singlequote_escaped_table_name);
611 937 : if (!sql) return NULL;
612 :
613 937 : char *query = dbutils_text_select(db, sql);
614 937 : cloudsync_memory_free(sql);
615 :
616 937 : return query;
617 937 : }
618 :
619 163 : cloudsync_table_context *table_create (const char *name, table_algo algo) {
620 : DEBUG_DBFUNCTION("table_create %s", name);
621 :
622 163 : cloudsync_table_context *table = (cloudsync_table_context *)cloudsync_memory_zeroalloc(sizeof(cloudsync_table_context));
623 163 : if (!table) return NULL;
624 :
625 163 : table->algo = algo;
626 163 : table->name = cloudsync_string_dup(name, true);
627 163 : if (!table->name) {
628 0 : cloudsync_memory_free(table);
629 0 : return NULL;
630 : }
631 163 : table->enabled = true;
632 :
633 163 : return table;
634 163 : }
635 :
636 163 : void table_free (cloudsync_table_context *table) {
637 : DEBUG_DBFUNCTION("table_free %s", (table) ? (table->name) : "NULL");
638 163 : if (!table) return;
639 :
640 163 : if (table->ncols > 0) {
641 128 : if (table->col_name) {
642 1065 : for (int i=0; i<table->ncols; ++i) {
643 937 : cloudsync_memory_free(table->col_name[i]);
644 937 : }
645 128 : cloudsync_memory_free(table->col_name);
646 128 : }
647 128 : if (table->col_merge_stmt) {
648 1065 : for (int i=0; i<table->ncols; ++i) {
649 937 : sqlite3_finalize(table->col_merge_stmt[i]);
650 937 : }
651 128 : cloudsync_memory_free(table->col_merge_stmt);
652 128 : }
653 128 : if (table->col_value_stmt) {
654 1065 : for (int i=0; i<table->ncols; ++i) {
655 937 : sqlite3_finalize(table->col_value_stmt[i]);
656 937 : }
657 128 : cloudsync_memory_free(table->col_value_stmt);
658 128 : }
659 128 : if (table->col_id) {
660 128 : cloudsync_memory_free(table->col_id);
661 128 : }
662 128 : }
663 :
664 163 : if (table->pk_name) sqlite3_free_table(table->pk_name);
665 163 : if (table->name) cloudsync_memory_free(table->name);
666 163 : if (table->meta_pkexists_stmt) sqlite3_finalize(table->meta_pkexists_stmt);
667 163 : if (table->meta_sentinel_update_stmt) sqlite3_finalize(table->meta_sentinel_update_stmt);
668 163 : if (table->meta_sentinel_insert_stmt) sqlite3_finalize(table->meta_sentinel_insert_stmt);
669 163 : if (table->meta_row_insert_update_stmt) sqlite3_finalize(table->meta_row_insert_update_stmt);
670 163 : if (table->meta_row_drop_stmt) sqlite3_finalize(table->meta_row_drop_stmt);
671 163 : if (table->meta_update_move_stmt) sqlite3_finalize(table->meta_update_move_stmt);
672 163 : if (table->meta_local_cl_stmt) sqlite3_finalize(table->meta_local_cl_stmt);
673 163 : if (table->meta_winner_clock_stmt) sqlite3_finalize(table->meta_winner_clock_stmt);
674 163 : if (table->meta_merge_delete_drop) sqlite3_finalize(table->meta_merge_delete_drop);
675 163 : if (table->meta_zero_clock_stmt) sqlite3_finalize(table->meta_zero_clock_stmt);
676 163 : if (table->meta_col_version_stmt) sqlite3_finalize(table->meta_col_version_stmt);
677 163 : if (table->meta_site_id_stmt) sqlite3_finalize(table->meta_site_id_stmt);
678 :
679 163 : if (table->real_col_values_stmt) sqlite3_finalize(table->real_col_values_stmt);
680 163 : if (table->real_merge_delete_stmt) sqlite3_finalize(table->real_merge_delete_stmt);
681 163 : if (table->real_merge_sentinel_stmt) sqlite3_finalize(table->real_merge_sentinel_stmt);
682 :
683 163 : cloudsync_memory_free(table);
684 163 : }
685 :
686 163 : int table_add_stmts (sqlite3 *db, cloudsync_table_context *table, int ncols) {
687 163 : int rc = SQLITE_OK;
688 163 : char *sql = NULL;
689 :
690 : // META TABLE statements
691 :
692 : // CREATE TABLE IF NOT EXISTS \"%w_cloudsync\" (pk BLOB NOT NULL, col_name TEXT NOT NULL, col_version INTEGER, db_version INTEGER, site_id INTEGER DEFAULT 0, seq INTEGER, PRIMARY KEY (pk, col_name));
693 :
694 : // precompile the pk exists statement
695 : // we do not need an index on the pk column because it is already covered by the fact that it is part of the prikeys
696 : // EXPLAIN QUERY PLAN reports: SEARCH table_name USING PRIMARY KEY (pk=?)
697 163 : sql = cloudsync_memory_mprintf("SELECT EXISTS(SELECT 1 FROM \"%w_cloudsync\" WHERE pk = ? LIMIT 1);", table->name);
698 163 : if (!sql) {rc = SQLITE_NOMEM; goto cleanup;}
699 : DEBUG_SQL("meta_pkexists_stmt: %s", sql);
700 :
701 163 : rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &table->meta_pkexists_stmt, NULL);
702 :
703 163 : cloudsync_memory_free(sql);
704 163 : if (rc != SQLITE_OK) goto cleanup;
705 :
706 : // precompile the update local sentinel statement
707 163 : sql = cloudsync_memory_mprintf("UPDATE \"%w_cloudsync\" SET col_version = CASE col_version %% 2 WHEN 0 THEN col_version + 1 ELSE col_version + 2 END, db_version = ?, seq = ?, site_id = 0 WHERE pk = ? AND col_name = '%s';", table->name, CLOUDSYNC_TOMBSTONE_VALUE);
708 163 : if (!sql) {rc = SQLITE_NOMEM; goto cleanup;}
709 : DEBUG_SQL("meta_sentinel_update_stmt: %s", sql);
710 :
711 163 : rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &table->meta_sentinel_update_stmt, NULL);
712 163 : cloudsync_memory_free(sql);
713 163 : if (rc != SQLITE_OK) goto cleanup;
714 :
715 : // precompile the insert local sentinel statement
716 163 : sql = cloudsync_memory_mprintf("INSERT INTO \"%w_cloudsync\" (pk, col_name, col_version, db_version, seq, site_id) SELECT ?, '%s', 1, ?, ?, 0 WHERE 1 ON CONFLICT DO UPDATE SET col_version = CASE col_version %% 2 WHEN 0 THEN col_version + 1 ELSE col_version + 2 END, db_version = ?, seq = ?, site_id = 0;", table->name, CLOUDSYNC_TOMBSTONE_VALUE);
717 163 : if (!sql) {rc = SQLITE_NOMEM; goto cleanup;}
718 : DEBUG_SQL("meta_sentinel_insert_stmt: %s", sql);
719 :
720 163 : rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &table->meta_sentinel_insert_stmt, NULL);
721 163 : cloudsync_memory_free(sql);
722 163 : if (rc != SQLITE_OK) goto cleanup;
723 :
724 : // precompile the insert/update local row statement
725 163 : sql = cloudsync_memory_mprintf("INSERT INTO \"%w_cloudsync\" (pk, col_name, col_version, db_version, seq, site_id ) SELECT ?, ?, ?, ?, ?, 0 WHERE 1 ON CONFLICT DO UPDATE SET col_version = col_version + 1, db_version = ?, seq = ?, site_id = 0;", table->name);
726 163 : if (!sql) {rc = SQLITE_NOMEM; goto cleanup;}
727 : DEBUG_SQL("meta_row_insert_update_stmt: %s", sql);
728 :
729 163 : rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &table->meta_row_insert_update_stmt, NULL);
730 163 : cloudsync_memory_free(sql);
731 163 : if (rc != SQLITE_OK) goto cleanup;
732 :
733 : // precompile the delete rows from meta
734 163 : sql = cloudsync_memory_mprintf("DELETE FROM \"%w_cloudsync\" WHERE pk=? AND col_name!='%s';", table->name, CLOUDSYNC_TOMBSTONE_VALUE);
735 163 : if (!sql) {rc = SQLITE_NOMEM; goto cleanup;}
736 : DEBUG_SQL("meta_row_drop_stmt: %s", sql);
737 :
738 163 : rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &table->meta_row_drop_stmt, NULL);
739 163 : cloudsync_memory_free(sql);
740 163 : if (rc != SQLITE_OK) goto cleanup;
741 :
742 : // precompile the update rows from meta when pk changes
743 : // see https://github.com/sqliteai/sqlite-sync/blob/main/docs/PriKey.md for more details
744 163 : sql = cloudsync_memory_mprintf("UPDATE OR REPLACE \"%w_cloudsync\" SET pk=?, db_version=?, col_version=1, seq=cloudsync_seq(), site_id=0 WHERE (pk=? AND col_name!='%s');", table->name, CLOUDSYNC_TOMBSTONE_VALUE);
745 163 : if (!sql) {rc = SQLITE_NOMEM; goto cleanup;}
746 : DEBUG_SQL("meta_update_move_stmt: %s", sql);
747 :
748 163 : rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &table->meta_update_move_stmt, NULL);
749 163 : cloudsync_memory_free(sql);
750 163 : if (rc != SQLITE_OK) goto cleanup;
751 :
752 : // local cl
753 163 : sql = cloudsync_memory_mprintf("SELECT COALESCE((SELECT col_version FROM \"%w_cloudsync\" WHERE pk=? AND col_name='%s'), (SELECT 1 FROM \"%w_cloudsync\" WHERE pk=?));", table->name, CLOUDSYNC_TOMBSTONE_VALUE, table->name);
754 163 : if (!sql) {rc = SQLITE_NOMEM; goto cleanup;}
755 : DEBUG_SQL("meta_local_cl_stmt: %s", sql);
756 :
757 163 : rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &table->meta_local_cl_stmt, NULL);
758 163 : cloudsync_memory_free(sql);
759 163 : if (rc != SQLITE_OK) goto cleanup;
760 :
761 : // rowid of the last inserted/updated row in the meta table
762 163 : sql = cloudsync_memory_mprintf("INSERT OR REPLACE INTO \"%w_cloudsync\" (pk, col_name, col_version, db_version, seq, site_id) VALUES (?, ?, ?, cloudsync_db_version_next(?), ?, ?) RETURNING ((db_version << 30) | seq);", table->name);
763 163 : if (!sql) {rc = SQLITE_NOMEM; goto cleanup;}
764 : DEBUG_SQL("meta_winner_clock_stmt: %s", sql);
765 :
766 163 : rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &table->meta_winner_clock_stmt, NULL);
767 163 : cloudsync_memory_free(sql);
768 163 : if (rc != SQLITE_OK) goto cleanup;
769 :
770 163 : sql = cloudsync_memory_mprintf("DELETE FROM \"%w_cloudsync\" WHERE pk=? AND col_name!='%s';", table->name, CLOUDSYNC_TOMBSTONE_VALUE);
771 163 : if (!sql) {rc = SQLITE_NOMEM; goto cleanup;}
772 : DEBUG_SQL("meta_merge_delete_drop: %s", sql);
773 :
774 163 : rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &table->meta_merge_delete_drop, NULL);
775 163 : cloudsync_memory_free(sql);
776 163 : if (rc != SQLITE_OK) goto cleanup;
777 :
778 : // zero clock
779 163 : sql = cloudsync_memory_mprintf("UPDATE \"%w_cloudsync\" SET col_version = 0, db_version = cloudsync_db_version_next(?) WHERE pk=? AND col_name!='%s';", table->name, CLOUDSYNC_TOMBSTONE_VALUE);
780 163 : if (!sql) {rc = SQLITE_NOMEM; goto cleanup;}
781 : DEBUG_SQL("meta_zero_clock_stmt: %s", sql);
782 :
783 163 : rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &table->meta_zero_clock_stmt, NULL);
784 163 : cloudsync_memory_free(sql);
785 163 : if (rc != SQLITE_OK) goto cleanup;
786 :
787 : // col_version
788 163 : sql = cloudsync_memory_mprintf("SELECT col_version FROM \"%w_cloudsync\" WHERE pk=? AND col_name=?;", table->name);
789 163 : if (!sql) {rc = SQLITE_NOMEM; goto cleanup;}
790 : DEBUG_SQL("meta_col_version_stmt: %s", sql);
791 :
792 163 : rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &table->meta_col_version_stmt, NULL);
793 163 : cloudsync_memory_free(sql);
794 163 : if (rc != SQLITE_OK) goto cleanup;
795 :
796 : // site_id
797 163 : sql = cloudsync_memory_mprintf("SELECT site_id FROM \"%w_cloudsync\" WHERE pk=? AND col_name=?;", table->name);
798 163 : if (!sql) {rc = SQLITE_NOMEM; goto cleanup;}
799 : DEBUG_SQL("meta_site_id_stmt: %s", sql);
800 :
801 163 : rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &table->meta_site_id_stmt, NULL);
802 163 : cloudsync_memory_free(sql);
803 163 : if (rc != SQLITE_OK) goto cleanup;
804 :
805 : // REAL TABLE statements
806 :
807 : // precompile the get column value statement
808 163 : if (ncols > 0) {
809 128 : sql = table_build_values_sql(db, table);
810 128 : if (!sql) {rc = SQLITE_NOMEM; goto cleanup;}
811 : DEBUG_SQL("real_col_values_stmt: %s", sql);
812 :
813 128 : rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &table->real_col_values_stmt, NULL);
814 128 : cloudsync_memory_free(sql);
815 128 : if (rc != SQLITE_OK) goto cleanup;
816 128 : }
817 :
818 163 : sql = table_build_mergedelete_sql(db, table);
819 163 : if (!sql) {rc = SQLITE_NOMEM; goto cleanup;}
820 : DEBUG_SQL("real_merge_delete: %s", sql);
821 :
822 163 : rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &table->real_merge_delete_stmt, NULL);
823 163 : cloudsync_memory_free(sql);
824 163 : if (rc != SQLITE_OK) goto cleanup;
825 :
826 163 : sql = table_build_mergeinsert_sql(db, table, NULL);
827 163 : if (!sql) {rc = SQLITE_NOMEM; goto cleanup;}
828 : DEBUG_SQL("real_merge_sentinel: %s", sql);
829 :
830 163 : rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &table->real_merge_sentinel_stmt, NULL);
831 163 : cloudsync_memory_free(sql);
832 163 : if (rc != SQLITE_OK) goto cleanup;
833 :
834 : cleanup:
835 163 : if (rc != SQLITE_OK) printf("table_add_stmts error: %s\n", sqlite3_errmsg(db));
836 163 : return rc;
837 : }
838 :
839 188811 : cloudsync_table_context *table_lookup (cloudsync_context *data, const char *table_name) {
840 : DEBUG_DBFUNCTION("table_lookup %s", table_name);
841 :
842 189739 : for (int i=0; i<data->tables_count; ++i) {
843 189556 : const char *name = (data->tables[i]) ? data->tables[i]->name : NULL;
844 189556 : if ((name) && (strcasecmp(name, table_name) == 0)) {
845 188628 : return data->tables[i];
846 : }
847 928 : }
848 :
849 183 : return NULL;
850 188811 : }
851 :
852 172586 : sqlite3_stmt *table_column_lookup (cloudsync_table_context *table, const char *col_name, bool is_merge, int *index) {
853 : DEBUG_DBFUNCTION("table_column_lookup %s", col_name);
854 :
855 318374 : for (int i=0; i<table->ncols; ++i) {
856 318374 : if (strcasecmp(table->col_name[i], col_name) == 0) {
857 172586 : if (index) *index = i;
858 172586 : return (is_merge) ? table->col_merge_stmt[i] : table->col_value_stmt[i];
859 : }
860 145788 : }
861 :
862 0 : if (index) *index = -1;
863 0 : return NULL;
864 172586 : }
865 :
866 29 : int table_remove (cloudsync_context *data, const char *table_name) {
867 : DEBUG_DBFUNCTION("table_remove %s", table_name);
868 :
869 52 : for (int i=0; i<data->tables_count; ++i) {
870 52 : const char *name = (data->tables[i]) ? data->tables[i]->name : NULL;
871 52 : if ((name) && (strcasecmp(name, table_name) == 0)) {
872 29 : data->tables[i] = NULL;
873 29 : return i;
874 : }
875 23 : }
876 0 : return -1;
877 29 : }
878 :
879 937 : int table_add_to_context_cb (void *xdata, int ncols, char **values, char **names) {
880 937 : cloudsync_table_context *table = (cloudsync_table_context *)xdata;
881 :
882 937 : sqlite3 *db = sqlite3_db_handle(table->meta_pkexists_stmt);
883 937 : if (!db) return SQLITE_ERROR;
884 :
885 937 : int index = table->ncols;
886 1874 : for (int i=0; i<ncols; i+=2) {
887 937 : const char *name = values[i];
888 937 : int cid = (int)strtol(values[i+1], NULL, 0);
889 :
890 937 : table->col_id[index] = cid;
891 937 : table->col_name[index] = cloudsync_string_dup(name, true);
892 937 : if (!table->col_name[index]) return 1;
893 :
894 937 : char *sql = table_build_mergeinsert_sql(db, table, name);
895 937 : if (!sql) return SQLITE_NOMEM;
896 : DEBUG_SQL("col_merge_stmt[%d]: %s", index, sql);
897 :
898 937 : int rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &table->col_merge_stmt[index], NULL);
899 937 : cloudsync_memory_free(sql);
900 937 : if (rc != SQLITE_OK) return rc;
901 937 : if (!table->col_merge_stmt[index]) return SQLITE_MISUSE;
902 :
903 937 : sql = table_build_value_sql(db, table, name);
904 937 : if (!sql) return SQLITE_NOMEM;
905 : DEBUG_SQL("col_value_stmt[%d]: %s", index, sql);
906 :
907 937 : rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &table->col_value_stmt[index], NULL);
908 937 : cloudsync_memory_free(sql);
909 937 : if (rc != SQLITE_OK) return rc;
910 937 : if (!table->col_value_stmt[index]) return SQLITE_MISUSE;
911 937 : }
912 937 : table->ncols += 1;
913 :
914 937 : return 0;
915 937 : }
916 :
917 169 : bool table_add_to_context (sqlite3 *db, cloudsync_context *data, table_algo algo, const char *table_name) {
918 : DEBUG_DBFUNCTION("cloudsync_context_add_table %s", table_name);
919 :
920 : // check if table is already in the global context and in that case just return
921 169 : cloudsync_table_context *table = table_lookup(data, table_name);
922 169 : if (table) return true;
923 :
924 : // is there any space available?
925 163 : if (data->tables_alloc <= data->tables_count + 1) {
926 : // realloc tables
927 0 : cloudsync_table_context **clone = (cloudsync_table_context **)cloudsync_memory_realloc(data->tables, sizeof(cloudsync_table_context) * data->tables_alloc + CLOUDSYNC_INIT_NTABLES);
928 0 : if (!clone) goto abort_add_table;
929 :
930 : // reset new entries
931 0 : for (int i=data->tables_alloc; i<data->tables_alloc + CLOUDSYNC_INIT_NTABLES; ++i) {
932 0 : clone[i] = NULL;
933 0 : }
934 :
935 : // replace old ptr
936 0 : data->tables = clone;
937 0 : data->tables_alloc += CLOUDSYNC_INIT_NTABLES;
938 0 : }
939 :
940 : // setup a new table context
941 163 : table = table_create(table_name, algo);
942 163 : if (!table) return false;
943 :
944 : // fill remaining metadata in the table
945 163 : char *sql = cloudsync_memory_mprintf("SELECT count(*) FROM pragma_table_info('%q') WHERE pk>0;", table_name);
946 163 : if (!sql) goto abort_add_table;
947 163 : table->npks = (int)dbutils_int_select(db, sql);
948 163 : cloudsync_memory_free(sql);
949 163 : if (table->npks == -1) {
950 0 : dbutils_context_result_error(data->sqlite_ctx, "%s", sqlite3_errmsg(db));
951 0 : goto abort_add_table;
952 : }
953 :
954 163 : if (table->npks == 0) {
955 : #if CLOUDSYNC_DISABLE_ROWIDONLY_TABLES
956 0 : return false;
957 : #else
958 : table->rowid_only = true;
959 : table->npks = 1; // rowid
960 : #endif
961 : }
962 :
963 163 : sql = cloudsync_memory_mprintf("SELECT count(*) FROM pragma_table_info('%q') WHERE pk=0;", table_name);
964 163 : if (!sql) goto abort_add_table;
965 163 : int64_t ncols = (int64_t)dbutils_int_select(db, sql);
966 163 : cloudsync_memory_free(sql);
967 163 : if (ncols == -1) {
968 0 : dbutils_context_result_error(data->sqlite_ctx, "%s", sqlite3_errmsg(db));
969 0 : goto abort_add_table;
970 : }
971 :
972 163 : int rc = table_add_stmts(db, table, (int)ncols);
973 163 : if (rc != SQLITE_OK) goto abort_add_table;
974 :
975 : // a table with only pk(s) is totally legal
976 163 : if (ncols > 0) {
977 128 : table->col_name = (char **)cloudsync_memory_alloc((sqlite3_uint64)(sizeof(char *) * ncols));
978 128 : if (!table->col_name) goto abort_add_table;
979 :
980 128 : table->col_id = (int *)cloudsync_memory_alloc((sqlite3_uint64)(sizeof(int) * ncols));
981 128 : if (!table->col_id) goto abort_add_table;
982 :
983 128 : table->col_merge_stmt = (sqlite3_stmt **)cloudsync_memory_alloc((sqlite3_uint64)(sizeof(sqlite3_stmt *) * ncols));
984 128 : if (!table->col_merge_stmt) goto abort_add_table;
985 :
986 128 : table->col_value_stmt = (sqlite3_stmt **)cloudsync_memory_alloc((sqlite3_uint64)(sizeof(sqlite3_stmt *) * ncols));
987 128 : if (!table->col_value_stmt) goto abort_add_table;
988 :
989 128 : sql = cloudsync_memory_mprintf("SELECT name, cid FROM pragma_table_info('%q') WHERE pk=0 ORDER BY cid;", table_name);
990 128 : if (!sql) goto abort_add_table;
991 128 : int rc = sqlite3_exec(db, sql, table_add_to_context_cb, (void *)table, NULL);
992 128 : cloudsync_memory_free(sql);
993 128 : if (rc == SQLITE_ABORT) goto abort_add_table;
994 128 : }
995 :
996 : // lookup the first free slot
997 212 : for (int i=0; i<data->tables_alloc; ++i) {
998 212 : if (data->tables[i] == NULL) {
999 163 : data->tables[i] = table;
1000 163 : if (i > data->tables_count - 1) ++data->tables_count;
1001 163 : break;
1002 : }
1003 49 : }
1004 :
1005 163 : return true;
1006 :
1007 : abort_add_table:
1008 0 : table_free(table);
1009 0 : return false;
1010 169 : }
1011 :
1012 6 : bool table_remove_from_context (cloudsync_context *data, cloudsync_table_context *table) {
1013 6 : return (table_remove(data, table->name) != -1);
1014 : }
1015 :
1016 3709 : sqlite3_stmt *cloudsync_colvalue_stmt (sqlite3 *db, cloudsync_context *data, const char *tbl_name, bool *persistent) {
1017 3709 : sqlite3_stmt *vm = NULL;
1018 :
1019 3709 : cloudsync_table_context *table = table_lookup(data, tbl_name);
1020 3709 : if (table) {
1021 3709 : char *col_name = NULL;
1022 3709 : if (table->ncols > 0) {
1023 3709 : col_name = table->col_name[0];
1024 : // retrieve col_value precompiled statement
1025 3709 : vm = table_column_lookup(table, col_name, false, NULL);
1026 3709 : *persistent = true;
1027 3709 : } else {
1028 0 : char *sql = table_build_value_sql(db, table, "*");
1029 0 : sqlite3_prepare_v2(db, sql, -1, &vm, NULL);
1030 0 : cloudsync_memory_free(sql);
1031 0 : *persistent = false;
1032 : }
1033 3709 : }
1034 :
1035 3709 : return vm;
1036 : }
1037 :
1038 : // MARK: - Merge Insert -
1039 :
1040 45449 : sqlite3_int64 merge_get_local_cl (cloudsync_table_context *table, const char *pk, int pklen, const char **err) {
1041 45449 : sqlite3_stmt *vm = table->meta_local_cl_stmt;
1042 45449 : sqlite3_int64 result = -1;
1043 :
1044 45449 : int rc = sqlite3_bind_blob(vm, 1, (const void *)pk, pklen, SQLITE_STATIC);
1045 45449 : if (rc != SQLITE_OK) goto cleanup;
1046 :
1047 45449 : rc = sqlite3_bind_blob(vm, 2, (const void *)pk, pklen, SQLITE_STATIC);
1048 45449 : if (rc != SQLITE_OK) goto cleanup;
1049 :
1050 45449 : rc = sqlite3_step(vm);
1051 45449 : if (rc == SQLITE_ROW) result = sqlite3_column_int64(vm, 0);
1052 0 : else if (rc == SQLITE_DONE) result = 0;
1053 :
1054 : cleanup:
1055 45449 : if (result == -1) *err = sqlite3_errmsg(sqlite3_db_handle(vm));
1056 45449 : stmt_reset(vm);
1057 45449 : return result;
1058 : }
1059 :
1060 44464 : int merge_get_col_version (cloudsync_table_context *table, const char *col_name, const char *pk, int pklen, sqlite3_int64 *version, const char **err) {
1061 44464 : sqlite3_stmt *vm = table->meta_col_version_stmt;
1062 :
1063 44464 : int rc = sqlite3_bind_blob(vm, 1, (const void *)pk, pklen, SQLITE_STATIC);
1064 44464 : if (rc != SQLITE_OK) goto cleanup;
1065 :
1066 44464 : rc = sqlite3_bind_text(vm, 2, col_name, -1, SQLITE_STATIC);
1067 44464 : if (rc != SQLITE_OK) goto cleanup;
1068 :
1069 44464 : rc = sqlite3_step(vm);
1070 69228 : if (rc == SQLITE_ROW) {
1071 24764 : *version = sqlite3_column_int64(vm, 0);
1072 24764 : rc = SQLITE_OK;
1073 24764 : }
1074 :
1075 : cleanup:
1076 44464 : if ((rc != SQLITE_OK) && (rc != SQLITE_DONE)) *err = sqlite3_errmsg(sqlite3_db_handle(vm));
1077 44464 : stmt_reset(vm);
1078 44464 : return rc;
1079 : }
1080 :
1081 24632 : int merge_set_winner_clock (cloudsync_context *data, cloudsync_table_context *table, const char *pk, int pk_len, const char *colname, sqlite3_int64 col_version, sqlite3_int64 db_version, const char *site_id, int site_len, sqlite3_int64 seq, sqlite3_int64 *rowid, const char **err) {
1082 :
1083 : // get/set site_id
1084 24632 : sqlite3_stmt *vm = data->getset_siteid_stmt;
1085 24632 : int rc = sqlite3_bind_blob(vm, 1, (const void *)site_id, site_len, SQLITE_STATIC);
1086 24632 : if (rc != SQLITE_OK) goto cleanup_merge;
1087 :
1088 24632 : rc = sqlite3_step(vm);
1089 24632 : if (rc != SQLITE_ROW) goto cleanup_merge;
1090 :
1091 24632 : int64_t ord = sqlite3_column_int64(vm, 0);
1092 24632 : stmt_reset(vm);
1093 :
1094 24632 : vm = table->meta_winner_clock_stmt;
1095 24632 : rc = sqlite3_bind_blob(vm, 1, (const void *)pk, pk_len, SQLITE_STATIC);
1096 24632 : if (rc != SQLITE_OK) goto cleanup_merge;
1097 :
1098 24632 : rc = sqlite3_bind_text(vm, 2, (colname) ? colname : CLOUDSYNC_TOMBSTONE_VALUE, -1, SQLITE_STATIC);
1099 24632 : if (rc != SQLITE_OK) goto cleanup_merge;
1100 :
1101 24632 : rc = sqlite3_bind_int64(vm, 3, col_version);
1102 24632 : if (rc != SQLITE_OK) goto cleanup_merge;
1103 :
1104 24632 : rc = sqlite3_bind_int64(vm, 4, db_version);
1105 24632 : if (rc != SQLITE_OK) goto cleanup_merge;
1106 :
1107 24632 : rc = sqlite3_bind_int64(vm, 5, seq);
1108 24632 : if (rc != SQLITE_OK) goto cleanup_merge;
1109 :
1110 24632 : rc = sqlite3_bind_int64(vm, 6, ord);
1111 24632 : if (rc != SQLITE_OK) goto cleanup_merge;
1112 :
1113 24632 : rc = sqlite3_step(vm);
1114 49264 : if (rc == SQLITE_ROW) {
1115 24632 : *rowid = sqlite3_column_int64(vm, 0);
1116 24632 : rc = SQLITE_OK;
1117 24632 : }
1118 :
1119 : cleanup_merge:
1120 24632 : if (rc != SQLITE_OK) *err = sqlite3_errmsg(sqlite3_db_handle(vm));
1121 24632 : stmt_reset(vm);
1122 24632 : return rc;
1123 : }
1124 :
1125 24440 : int merge_insert_col (cloudsync_context *data, cloudsync_table_context *table, const char *pk, int pklen, const char *col_name, sqlite3_value *col_value, sqlite3_int64 col_version, sqlite3_int64 db_version, const char *site_id, int site_len, sqlite3_int64 seq, sqlite3_int64 *rowid, const char **err) {
1126 : int index;
1127 24440 : sqlite3_stmt *vm = table_column_lookup(table, col_name, true, &index);
1128 24440 : if (vm == NULL) {
1129 0 : *err = "Unable to retrieve column merge precompiled statement in merge_insert_col.";
1130 0 : return SQLITE_MISUSE;
1131 : }
1132 :
1133 : // INSERT INTO table (pk1, pk2, col_name) VALUES (?, ?, ?) ON CONFLICT DO UPDATE SET col_name=?;"
1134 :
1135 : // bind primary key(s)
1136 24440 : int rc = pk_decode_prikey((char *)pk, (size_t)pklen, pk_decode_bind_callback, vm);
1137 24440 : if (rc < 0) {
1138 0 : *err = sqlite3_errmsg(sqlite3_db_handle(vm));
1139 0 : rc = sqlite3_errcode(sqlite3_db_handle(vm));
1140 0 : stmt_reset(vm);
1141 0 : return rc;
1142 : }
1143 :
1144 : // bind value
1145 24440 : if (col_value) {
1146 24440 : rc = sqlite3_bind_value(vm, table->npks+1, col_value);
1147 24440 : if (rc == SQLITE_OK) rc = sqlite3_bind_value(vm, table->npks+2, col_value);
1148 24440 : if (rc != SQLITE_OK) {
1149 0 : *err = sqlite3_errmsg(sqlite3_db_handle(vm));
1150 0 : stmt_reset(vm);
1151 0 : return rc;
1152 : }
1153 :
1154 24440 : }
1155 :
1156 : // perform real operation and disable triggers
1157 :
1158 : // in case of GOS we reused the table->col_merge_stmt statement
1159 : // which looks like: INSERT INTO table (pk1, pk2, col_name) VALUES (?, ?, ?) ON CONFLICT DO UPDATE SET col_name=?;"
1160 : // but the UPDATE in the CONFLICT statement would return SQLITE_CONSTRAINT because the trigger raises the error
1161 : // the trick is to disable that trigger before executing the statement
1162 24440 : if (table->algo == table_algo_crdt_gos) table->enabled = 0;
1163 24440 : SYNCBIT_SET(data);
1164 24440 : rc = sqlite3_step(vm);
1165 : DEBUG_MERGE("merge_insert(%02x%02x): %s (%d)", data->site_id[UUID_LEN-2], data->site_id[UUID_LEN-1], sqlite3_expanded_sql(vm), rc);
1166 24440 : stmt_reset(vm);
1167 24440 : SYNCBIT_RESET(data);
1168 24440 : if (table->algo == table_algo_crdt_gos) table->enabled = 1;
1169 :
1170 24440 : if (rc != SQLITE_DONE) {
1171 0 : *err = sqlite3_errmsg(sqlite3_db_handle(vm));
1172 0 : return rc;
1173 : }
1174 :
1175 24440 : return merge_set_winner_clock(data, table, pk, pklen, col_name, col_version, db_version, site_id, site_len, seq, rowid, err);
1176 24440 : }
1177 :
1178 164 : int merge_delete (cloudsync_context *data, cloudsync_table_context *table, const char *pk, int pklen, const char *colname, sqlite3_int64 cl, sqlite3_int64 db_version, const char *site_id, int site_len, sqlite3_int64 seq, sqlite3_int64 *rowid, const char **err) {
1179 164 : int rc = SQLITE_OK;
1180 :
1181 : // reset return value
1182 164 : *rowid = 0;
1183 :
1184 : // bind pk
1185 164 : sqlite3_stmt *vm = table->real_merge_delete_stmt;
1186 164 : rc = pk_decode_prikey((char *)pk, (size_t)pklen, pk_decode_bind_callback, vm);
1187 164 : if (rc < 0) {
1188 0 : *err = sqlite3_errmsg(sqlite3_db_handle(vm));
1189 0 : rc = sqlite3_errcode(sqlite3_db_handle(vm));
1190 0 : stmt_reset(vm);
1191 0 : return rc;
1192 : }
1193 :
1194 : // perform real operation and disable triggers
1195 164 : SYNCBIT_SET(data);
1196 164 : rc = sqlite3_step(vm);
1197 : DEBUG_MERGE("merge_delete(%02x%02x): %s (%d)", data->site_id[UUID_LEN-2], data->site_id[UUID_LEN-1], sqlite3_expanded_sql(vm), rc);
1198 164 : stmt_reset(vm);
1199 164 : SYNCBIT_RESET(data);
1200 164 : if (rc == SQLITE_DONE) rc = SQLITE_OK;
1201 164 : if (rc != SQLITE_OK) {
1202 0 : *err = sqlite3_errmsg(sqlite3_db_handle(vm));
1203 0 : return rc;
1204 : }
1205 :
1206 164 : rc = merge_set_winner_clock(data, table, pk, pklen, colname, cl, db_version, site_id, site_len, seq, rowid, err);
1207 164 : if (rc != SQLITE_OK) return rc;
1208 :
1209 : // drop clocks _after_ setting the winner clock so we don't lose track of the max db_version!!
1210 : // this must never come before `set_winner_clock`
1211 164 : vm = table->meta_merge_delete_drop;
1212 164 : rc = sqlite3_bind_blob(vm, 1, (const void *)pk, pklen, SQLITE_STATIC);
1213 164 : if (rc == SQLITE_OK) rc = sqlite3_step(vm);
1214 164 : stmt_reset(vm);
1215 164 : if (rc == SQLITE_DONE) rc = SQLITE_OK;
1216 164 : if (rc != SQLITE_OK) {
1217 0 : *err = sqlite3_errmsg(sqlite3_db_handle(vm));
1218 0 : }
1219 :
1220 164 : return rc;
1221 164 : }
1222 :
1223 28 : int merge_zeroclock_on_resurrect(cloudsync_table_context *table, sqlite3_int64 db_version, const char *pk, int pklen, const char **err) {
1224 28 : sqlite3_stmt *vm = table->meta_zero_clock_stmt;
1225 :
1226 28 : int rc = sqlite3_bind_int64(vm, 1, db_version);
1227 28 : if (rc != SQLITE_OK) goto cleanup;
1228 :
1229 28 : rc = sqlite3_bind_blob(vm, 2, (const void *)pk, pklen, SQLITE_STATIC);
1230 28 : if (rc != SQLITE_OK) goto cleanup;
1231 :
1232 28 : rc = sqlite3_step(vm);
1233 28 : if (rc == SQLITE_DONE) rc = SQLITE_OK;
1234 :
1235 : cleanup:
1236 28 : if (rc != SQLITE_OK) *err = sqlite3_errmsg(sqlite3_db_handle(vm));
1237 28 : stmt_reset(vm);
1238 28 : return rc;
1239 : }
1240 :
1241 : // executed only if insert_cl == local_cl
1242 44464 : int merge_did_cid_win (cloudsync_context *data, cloudsync_table_context *table, const char *pk, int pklen, sqlite3_value *insert_value, const char *site_id, int site_len, const char *col_name, sqlite3_int64 col_version, bool *didwin_flag, const char **err) {
1243 :
1244 44464 : if (col_name == NULL) col_name = CLOUDSYNC_TOMBSTONE_VALUE;
1245 :
1246 : sqlite3_int64 local_version;
1247 44464 : int rc = merge_get_col_version(table, col_name, pk, pklen, &local_version, err);
1248 44464 : if (rc == SQLITE_DONE) {
1249 : // no rows returned, the incoming change wins if there's nothing there locally
1250 19700 : *didwin_flag = true;
1251 19700 : return SQLITE_OK;
1252 : }
1253 24764 : if (rc != SQLITE_OK) return rc;
1254 :
1255 : // rc == SQLITE_OK, means that a row with a version exists
1256 24764 : if (local_version != col_version) {
1257 1885 : if (col_version > local_version) {*didwin_flag = true; return SQLITE_OK;}
1258 708 : if (col_version < local_version) {*didwin_flag = false; return SQLITE_OK;}
1259 0 : }
1260 :
1261 : // rc == SQLITE_ROW and col_version == local_version, need to compare values
1262 :
1263 : // retrieve col_value precompiled statement
1264 22879 : sqlite3_stmt *vm = table_column_lookup(table, col_name, false, NULL);
1265 22879 : if (!vm) {
1266 0 : *err = "Unable to retrieve column value precompiled statement in merge_did_cid_win.";
1267 0 : return SQLITE_ERROR;
1268 : }
1269 :
1270 : // bind primary key values
1271 22879 : rc = pk_decode_prikey((char *)pk, (size_t)pklen, pk_decode_bind_callback, (void *)vm);
1272 22879 : if (rc < 0) {
1273 0 : *err = sqlite3_errmsg(sqlite3_db_handle(vm));
1274 0 : rc = sqlite3_errcode(sqlite3_db_handle(vm));
1275 0 : stmt_reset(vm);
1276 0 : return rc;
1277 : }
1278 :
1279 : // execute vm
1280 : sqlite3_value *local_value;
1281 22879 : rc = sqlite3_step(vm);
1282 22879 : if (rc == SQLITE_DONE) {
1283 : // meta entry exists but the actual value is missing
1284 : // we should allow the value_compare function to make a decision
1285 : // value_compare has been modified to handle the case where lvalue is NULL
1286 0 : local_value = NULL;
1287 0 : rc = SQLITE_OK;
1288 22879 : } else if (rc == SQLITE_ROW) {
1289 22879 : local_value = sqlite3_column_value(vm, 0);
1290 22879 : rc = SQLITE_OK;
1291 22879 : } else {
1292 0 : goto cleanup;
1293 : }
1294 :
1295 : // compare values
1296 22879 : int ret = dbutils_value_compare(insert_value, local_value);
1297 : // reset after compare, otherwise local value would be deallocated
1298 22879 : vm = stmt_reset(vm);
1299 :
1300 22879 : bool compare_site_id = (ret == 0 && data->merge_equal_values == true);
1301 22879 : if (!compare_site_id) {
1302 22879 : *didwin_flag = (ret > 0);
1303 22879 : goto cleanup;
1304 : }
1305 :
1306 : // values are the same and merge_equal_values is true
1307 0 : vm = table->meta_site_id_stmt;
1308 0 : rc = sqlite3_bind_blob(vm, 1, (const void *)pk, pklen, SQLITE_STATIC);
1309 0 : if (rc != SQLITE_OK) goto cleanup;
1310 :
1311 0 : rc = sqlite3_bind_text(vm, 2, col_name, -1, SQLITE_STATIC);
1312 0 : if (rc != SQLITE_OK) goto cleanup;
1313 :
1314 0 : rc = sqlite3_step(vm);
1315 0 : if (rc == SQLITE_ROW) {
1316 0 : const void *local_site_id = sqlite3_column_blob(vm, 0);
1317 0 : ret = memcmp(site_id, local_site_id, site_len);
1318 0 : *didwin_flag = (ret > 0);
1319 0 : stmt_reset(vm);
1320 0 : return SQLITE_OK;
1321 : }
1322 :
1323 : // handle error condition here
1324 0 : stmt_reset(vm);
1325 0 : *err = "Unable to find site_id for previous change. The cloudsync table is probably corrupted.";
1326 0 : return SQLITE_ERROR;
1327 :
1328 : cleanup:
1329 22879 : if (rc != SQLITE_OK) *err = sqlite3_errmsg(sqlite3_db_handle(vm));
1330 22879 : if (vm) stmt_reset(vm);
1331 22879 : return rc;
1332 44464 : }
1333 :
1334 28 : int merge_sentinel_only_insert (cloudsync_context *data, cloudsync_table_context *table, const char *pk, int pklen, sqlite3_int64 cl, sqlite3_int64 db_version, const char *site_id, int site_len, sqlite3_int64 seq, sqlite3_int64 *rowid, const char **err) {
1335 :
1336 : // reset return value
1337 28 : *rowid = 0;
1338 :
1339 : // bind pk
1340 28 : sqlite3_stmt *vm = table->real_merge_sentinel_stmt;
1341 28 : int rc = pk_decode_prikey((char *)pk, (size_t)pklen, pk_decode_bind_callback, vm);
1342 28 : if (rc < 0) {
1343 0 : *err = sqlite3_errmsg(sqlite3_db_handle(vm));
1344 0 : rc = sqlite3_errcode(sqlite3_db_handle(vm));
1345 0 : stmt_reset(vm);
1346 0 : return rc;
1347 : }
1348 :
1349 : // perform real operation and disable triggers
1350 28 : SYNCBIT_SET(data);
1351 28 : rc = sqlite3_step(vm);
1352 28 : stmt_reset(vm);
1353 28 : SYNCBIT_RESET(data);
1354 28 : if (rc == SQLITE_DONE) rc = SQLITE_OK;
1355 28 : if (rc != SQLITE_OK) {
1356 0 : *err = sqlite3_errmsg(sqlite3_db_handle(vm));
1357 0 : return rc;
1358 : }
1359 :
1360 28 : rc = merge_zeroclock_on_resurrect(table, db_version, pk, pklen, err);
1361 28 : if (rc != SQLITE_OK) return rc;
1362 :
1363 28 : return merge_set_winner_clock(data, table, pk, pklen, NULL, cl, db_version, site_id, site_len, seq, rowid, err);
1364 28 : }
1365 :
1366 3150 : int cloudsync_merge_insert_gos (sqlite3_vtab *vtab, cloudsync_context *data, cloudsync_table_context *table, const char *insert_pk, int insert_pk_len, const char *insert_name, sqlite3_value *insert_value, sqlite3_int64 insert_col_version, sqlite3_int64 insert_db_version, const char *insert_site_id, int insert_site_id_len, sqlite3_int64 insert_seq, sqlite3_int64 *rowid) {
1367 : // Grow-Only Set (GOS) Algorithm: Only insertions are allowed, deletions and updates are prevented from a trigger.
1368 :
1369 3150 : const char *err = NULL;
1370 6300 : int rc = merge_insert_col(data, table, insert_pk, insert_pk_len, insert_name, insert_value, insert_col_version, insert_db_version,
1371 3150 : insert_site_id, insert_site_id_len, insert_seq, rowid, &err);
1372 3150 : if (rc != SQLITE_OK) {
1373 0 : cloudsync_vtab_set_error(vtab, "Unable to perform GOS merge_insert_col: %s", err);
1374 0 : }
1375 :
1376 3150 : return rc;
1377 : }
1378 :
1379 48599 : int cloudsync_merge_insert (sqlite3_vtab *vtab, int argc, sqlite3_value **argv, sqlite3_int64 *rowid) {
1380 : // this function performs the merging logic for an insert in a cloud-synchronized table. It handles
1381 : // different scenarios including conflicts, causal lengths, delete operations, and resurrecting rows
1382 : // based on the incoming data (from remote nodes or clients) and the local database state
1383 :
1384 : // this function handles different CRDT algorithms (GOS, DWS, AWS, and CLS).
1385 : // the merging strategy is determined based on the table->algo value.
1386 :
1387 : // meta table declaration:
1388 : // tbl TEXT NOT NULL, pk BLOB NOT NULL, col_name TEXT NOT NULL,"
1389 : // "col_value ANY, col_version INTEGER NOT NULL, db_version INTEGER NOT NULL,"
1390 : // "site_id BLOB NOT NULL, cl INTEGER NOT NULL, seq INTEGER NOT NULL
1391 :
1392 : // meta information to retrieve from arguments:
1393 : // argv[0] -> table name (TEXT)
1394 : // argv[1] -> primary key (BLOB)
1395 : // argv[2] -> column name (TEXT or NULL if sentinel)
1396 : // argv[3] -> column value (ANY)
1397 : // argv[4] -> column version (INTEGER)
1398 : // argv[5] -> database version (INTEGER)
1399 : // argv[6] -> site ID (BLOB, identifies the origin of the update)
1400 : // argv[7] -> causal length (INTEGER, tracks the order of operations)
1401 : // argv[8] -> sequence number (INTEGER, unique per operation)
1402 :
1403 : // extract table name
1404 48599 : const char *insert_tbl = (const char *)sqlite3_value_text(argv[0]);
1405 :
1406 : // lookup table
1407 48599 : cloudsync_context *data = cloudsync_vtab_get_context(vtab);
1408 48599 : cloudsync_table_context *table = table_lookup(data, insert_tbl);
1409 48599 : if (!table) return cloudsync_vtab_set_error(vtab, "Unable to find table %s,", insert_tbl);
1410 :
1411 : // extract the remaining fields from the input values
1412 48599 : const char *insert_pk = (const char *)sqlite3_value_blob(argv[1]);
1413 48599 : int insert_pk_len = sqlite3_value_bytes(argv[1]);
1414 48599 : const char *insert_name = (sqlite3_value_type(argv[2]) == SQLITE_NULL) ? CLOUDSYNC_TOMBSTONE_VALUE : (const char *)sqlite3_value_text(argv[2]);
1415 48599 : sqlite3_value *insert_value = argv[3];
1416 48599 : sqlite3_int64 insert_col_version = sqlite3_value_int64(argv[4]);
1417 48599 : sqlite3_int64 insert_db_version = sqlite3_value_int64(argv[5]);
1418 48599 : const char *insert_site_id = (const char *)sqlite3_value_blob(argv[6]);
1419 48599 : int insert_site_id_len = sqlite3_value_bytes(argv[6]);
1420 48599 : sqlite3_int64 insert_cl = sqlite3_value_int64(argv[7]);
1421 48599 : sqlite3_int64 insert_seq = sqlite3_value_int64(argv[8]);
1422 48599 : const char *err = NULL;
1423 :
1424 : // perform different logic for each different table algorithm
1425 48599 : if (table->algo == table_algo_crdt_gos) return cloudsync_merge_insert_gos(vtab, data, table, insert_pk, insert_pk_len, insert_name, insert_value, insert_col_version, insert_db_version, insert_site_id, insert_site_id_len, insert_seq, rowid);
1426 :
1427 : // Handle DWS and AWS algorithms here
1428 : // Delete-Wins Set (DWS): table_algo_crdt_dws
1429 : // Add-Wins Set (AWS): table_algo_crdt_aws
1430 :
1431 : // Causal-Length Set (CLS) Algorithm (default)
1432 :
1433 : // compute the local causal length for the row based on the primary key
1434 : // the causal length is used to determine the order of operations and resolve conflicts.
1435 45449 : sqlite3_int64 local_cl = merge_get_local_cl(table, insert_pk, insert_pk_len, &err);
1436 45449 : if (local_cl < 0) {
1437 0 : return cloudsync_vtab_set_error(vtab, "Unable to compute local causal length: %s", err);
1438 : }
1439 :
1440 : // if the incoming causal length is older than the local causal length, we can safely ignore it
1441 : // because the local changes are more recent
1442 45449 : if (insert_cl < local_cl) return SQLITE_OK;
1443 :
1444 : // check if the operation is a delete by examining the causal length
1445 : // even causal lengths typically signify delete operations
1446 45232 : bool is_delete = (insert_cl % 2 == 0);
1447 45232 : if (is_delete) {
1448 : // if it's a delete, check if the local state is at the same causal length
1449 : // if it is, no further action is needed
1450 613 : if (local_cl == insert_cl) return SQLITE_OK;
1451 :
1452 : // perform a delete merge if the causal length is newer than the local one
1453 328 : int rc = merge_delete(data, table, insert_pk, insert_pk_len, insert_name, insert_col_version,
1454 164 : insert_db_version, insert_site_id, insert_site_id_len, insert_seq, rowid, &err);
1455 164 : if (rc != SQLITE_OK) cloudsync_vtab_set_error(vtab, "Unable to perform merge_delete: %s", err);
1456 164 : return rc;
1457 : }
1458 :
1459 : // if the operation is a sentinel-only insert (indicating a new row or resurrected row with no column update), handle it separately.
1460 44619 : bool is_sentinel_only = (strcmp(insert_name, CLOUDSYNC_TOMBSTONE_VALUE) == 0);
1461 44619 : if (is_sentinel_only) {
1462 155 : if (local_cl == insert_cl) return SQLITE_OK;
1463 :
1464 : // perform a sentinel-only insert to track the existence of the row
1465 56 : int rc = merge_sentinel_only_insert(data, table, insert_pk, insert_pk_len, insert_col_version,
1466 28 : insert_db_version, insert_site_id, insert_site_id_len, insert_seq, rowid, &err);
1467 28 : if (rc != SQLITE_OK) cloudsync_vtab_set_error(vtab, "Unable to perform merge_sentinel_only_insert: %s", err);
1468 28 : return rc;
1469 : }
1470 :
1471 : // from this point I can be sure that insert_name is not sentinel
1472 :
1473 : // handle the case where a row is being resurrected (e.g., after a delete, a new insert for the same row)
1474 : // odd causal lengths can "resurrect" rows
1475 44464 : bool needs_resurrect = (insert_cl > local_cl && insert_cl % 2 == 1);
1476 44464 : bool row_exists_locally = local_cl != 0;
1477 :
1478 : // if a resurrection is needed, insert a sentinel to mark the row as alive
1479 : // this handles out-of-order deliveries where the row was deleted and is now being re-inserted
1480 44464 : if (needs_resurrect && (row_exists_locally || (!row_exists_locally && insert_cl > 1))) {
1481 0 : int rc = merge_sentinel_only_insert(data, table, insert_pk, insert_pk_len, insert_cl,
1482 0 : insert_db_version, insert_site_id, insert_site_id_len, insert_seq, rowid, &err);
1483 0 : if (rc != SQLITE_OK) {
1484 0 : cloudsync_vtab_set_error(vtab, "Unable to perform merge_sentinel_only_insert: %s", err);
1485 0 : return rc;
1486 : }
1487 0 : }
1488 :
1489 : // at this point, we determine whether the incoming change wins based on causal length
1490 : // this can be due to a resurrection, a non-existent local row, or a conflict resolution
1491 44464 : bool flag = false;
1492 44464 : int rc = merge_did_cid_win(data, table, insert_pk, insert_pk_len, insert_value, insert_site_id, insert_site_id_len, insert_name, insert_col_version, &flag, &err);
1493 44464 : if (rc != SQLITE_OK) {
1494 0 : cloudsync_vtab_set_error(vtab, "Unable to perform merge_did_cid_win: %s", err);
1495 0 : return rc;
1496 : }
1497 :
1498 : // check if the incoming change wins and should be applied
1499 44464 : bool does_cid_win = ((needs_resurrect) || (!row_exists_locally) || (flag));
1500 44464 : if (!does_cid_win) return SQLITE_OK;
1501 :
1502 : // perform the final column insert or update if the incoming change wins
1503 21290 : rc = merge_insert_col(data, table, insert_pk, insert_pk_len, insert_name, insert_value, insert_col_version, insert_db_version, insert_site_id, insert_site_id_len, insert_seq, rowid, &err);
1504 21290 : if (rc != SQLITE_OK) cloudsync_vtab_set_error(vtab, "Unable to perform merge_insert_col: %s", err);
1505 21290 : return rc;
1506 48599 : }
1507 :
1508 : // MARK: - Private -
1509 :
1510 109 : bool cloudsync_config_exists (sqlite3 *db) {
1511 109 : return dbutils_table_exists(db, CLOUDSYNC_SITEID_NAME) == true;
1512 : }
1513 :
1514 109 : void *cloudsync_context_create (void) {
1515 109 : cloudsync_context *data = (cloudsync_context *)cloudsync_memory_zeroalloc((uint64_t)(sizeof(cloudsync_context)));
1516 : DEBUG_SETTINGS("cloudsync_context_create %p", data);
1517 :
1518 109 : data->libversion = CLOUDSYNC_VERSION;
1519 109 : data->pending_db_version = CLOUDSYNC_VALUE_NOTSET;
1520 : #if CLOUDSYNC_DEBUG
1521 : data->debug = 1;
1522 : #endif
1523 :
1524 : // allocate space for 128 tables (it can grow if needed)
1525 109 : data->tables = (cloudsync_table_context **)cloudsync_memory_zeroalloc((uint64_t)(CLOUDSYNC_INIT_NTABLES * sizeof(cloudsync_table_context *)));
1526 109 : if (!data->tables) {
1527 0 : cloudsync_memory_free(data);
1528 0 : return NULL;
1529 : }
1530 109 : data->tables_alloc = CLOUDSYNC_INIT_NTABLES;
1531 109 : data->tables_count = 0;
1532 :
1533 109 : return data;
1534 109 : }
1535 :
1536 109 : void cloudsync_context_free (void *ptr) {
1537 : DEBUG_SETTINGS("cloudsync_context_free %p", ptr);
1538 109 : if (!ptr) return;
1539 :
1540 109 : cloudsync_context *data = (cloudsync_context*)ptr;
1541 109 : cloudsync_memory_free(data->tables);
1542 109 : cloudsync_memory_free(data);
1543 109 : }
1544 :
1545 223 : const char *cloudsync_context_init (sqlite3 *db, cloudsync_context *data, sqlite3_context *context) {
1546 223 : if (!data && context) data = (cloudsync_context *)sqlite3_user_data(context);
1547 :
1548 : // perform init just the first time, if the site_id field is not set.
1549 : // The data->site_id value could exists while settings tables don't exists if the
1550 : // cloudsync_context_init was previously called in init transaction that was rolled back
1551 : // because of an error during the init process.
1552 223 : if (data->site_id[0] == 0 || !dbutils_table_exists(db, CLOUDSYNC_SITEID_NAME)) {
1553 111 : if (dbutils_settings_init(db, data, context) != SQLITE_OK) return NULL;
1554 111 : if (stmts_add_tocontext(db, data) != SQLITE_OK) return NULL;
1555 111 : if (cloudsync_load_siteid(db, data) != SQLITE_OK) return NULL;
1556 :
1557 111 : data->sqlite_ctx = context;
1558 111 : data->schema_hash = dbutils_schema_hash(db);
1559 111 : }
1560 :
1561 223 : return (const char *)data->site_id;
1562 223 : }
1563 :
1564 1029 : void cloudsync_sync_key(cloudsync_context *data, const char *key, const char *value) {
1565 : DEBUG_SETTINGS("cloudsync_sync_key key: %s value: %s", key, value);
1566 :
1567 : // sync data
1568 1029 : if (strcmp(key, CLOUDSYNC_KEY_SCHEMAVERSION) == 0) {
1569 111 : data->schema_version = (int)strtol(value, NULL, 0);
1570 111 : return;
1571 : }
1572 :
1573 918 : if (strcmp(key, CLOUDSYNC_KEY_DEBUG) == 0) {
1574 0 : data->debug = 0;
1575 0 : if (value && (value[0] != 0) && (value[0] != '0')) data->debug = 1;
1576 0 : return;
1577 : }
1578 1029 : }
1579 :
1580 : #if 0
1581 : void cloudsync_sync_table_key(cloudsync_context *data, const char *table, const char *column, const char *key, const char *value) {
1582 : DEBUG_SETTINGS("cloudsync_sync_table_key table: %s column: %s key: %s value: %s", table, column, key, value);
1583 : // Unused in this version
1584 : return;
1585 : }
1586 : #endif
1587 :
1588 6904 : int cloudsync_commit_hook (void *ctx) {
1589 6904 : cloudsync_context *data = (cloudsync_context *)ctx;
1590 :
1591 6904 : data->db_version = data->pending_db_version;
1592 6904 : data->pending_db_version = CLOUDSYNC_VALUE_NOTSET;
1593 6904 : data->seq = 0;
1594 :
1595 6904 : return SQLITE_OK;
1596 : }
1597 :
1598 2 : void cloudsync_rollback_hook (void *ctx) {
1599 2 : cloudsync_context *data = (cloudsync_context *)ctx;
1600 :
1601 2 : data->pending_db_version = CLOUDSYNC_VALUE_NOTSET;
1602 2 : data->seq = 0;
1603 2 : }
1604 :
1605 23 : int cloudsync_finalize_alter (sqlite3_context *context, cloudsync_context *data, cloudsync_table_context *table) {
1606 23 : int rc = SQLITE_OK;
1607 23 : sqlite3 *db = sqlite3_context_db_handle(context);
1608 :
1609 23 : db_version_check_uptodate(db, data);
1610 :
1611 : // If primary key columns change (in the schema)
1612 : // We need to drop, re-create and backfill
1613 : // the clock table.
1614 : // A change in pk columns means a change in all identities
1615 : // of all rows.
1616 : // We can determine this by comparing unique index on lookaside table vs
1617 : // pks on source table
1618 23 : char *errmsg = NULL;
1619 23 : char **result = NULL;
1620 : int nrows, ncols;
1621 23 : char *sql = cloudsync_memory_mprintf("SELECT name FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk;", table->name);
1622 23 : rc = sqlite3_get_table(db, sql, &result, &nrows, &ncols, NULL);
1623 23 : cloudsync_memory_free(sql);
1624 23 : if (rc != SQLITE_OK) {
1625 0 : DEBUG_SQLITE_ERROR(rc, "cloudsync_finalize_alter", db);
1626 0 : goto finalize;
1627 23 : } else if (errmsg || ncols != 1) {
1628 0 : rc = SQLITE_MISUSE;
1629 0 : goto finalize;
1630 : }
1631 :
1632 23 : bool pk_diff = false;
1633 23 : if (nrows != table->npks) {
1634 6 : pk_diff = true;
1635 6 : } else {
1636 51 : for (int i=0; i<nrows; ++i) {
1637 34 : if (strcmp(table->pk_name[i], result[i]) != 0) {
1638 0 : pk_diff = true;
1639 0 : break;
1640 : }
1641 34 : }
1642 : }
1643 :
1644 23 : if (pk_diff) {
1645 : // drop meta-table, it will be recreated
1646 6 : char *sql = cloudsync_memory_mprintf("DROP TABLE IF EXISTS \"%w_cloudsync\";", table->name);
1647 6 : rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
1648 6 : cloudsync_memory_free(sql);
1649 6 : if (rc != SQLITE_OK) {
1650 0 : DEBUG_SQLITE_ERROR(rc, "cloudsync_finalize_alter", db);
1651 0 : goto finalize;
1652 : }
1653 6 : } else {
1654 : // compact meta-table
1655 : // delete entries for removed columns
1656 17 : char *sql = cloudsync_memory_mprintf("DELETE FROM \"%w_cloudsync\" WHERE \"col_name\" NOT IN ("
1657 : "SELECT name FROM pragma_table_info('%q') UNION SELECT '%s'"
1658 17 : ")", table->name, table->name, CLOUDSYNC_TOMBSTONE_VALUE);
1659 17 : rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
1660 17 : cloudsync_memory_free(sql);
1661 17 : if (rc != SQLITE_OK) {
1662 0 : DEBUG_SQLITE_ERROR(rc, "cloudsync_finalize_alter", db);
1663 0 : goto finalize;
1664 : }
1665 :
1666 17 : char *singlequote_escaped_table_name = cloudsync_memory_mprintf("%q", table->name);
1667 17 : sql = cloudsync_memory_mprintf("SELECT group_concat('\"%w\".\"' || format('%%w', name) || '\"', ',') FROM pragma_table_info('%s') WHERE pk>0 ORDER BY pk;", singlequote_escaped_table_name, singlequote_escaped_table_name);
1668 17 : cloudsync_memory_free(singlequote_escaped_table_name);
1669 17 : if (!sql) {
1670 0 : rc = SQLITE_NOMEM;
1671 0 : goto finalize;
1672 : }
1673 17 : char *pkclause = dbutils_text_select(db, sql);
1674 17 : char *pkvalues = (pkclause) ? pkclause : "rowid";
1675 17 : cloudsync_memory_free(sql);
1676 :
1677 : // delete entries related to rows that no longer exist in the original table, but preserve tombstone
1678 17 : sql = cloudsync_memory_mprintf("DELETE FROM \"%w_cloudsync\" WHERE (\"col_name\" != '%s' OR (\"col_name\" = '%s' AND col_version %% 2 != 0)) AND NOT EXISTS (SELECT 1 FROM \"%w\" WHERE \"%w_cloudsync\".pk = cloudsync_pk_encode(%s) LIMIT 1);", table->name, CLOUDSYNC_TOMBSTONE_VALUE, CLOUDSYNC_TOMBSTONE_VALUE, table->name, table->name, pkvalues);
1679 17 : rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
1680 17 : if (pkclause) cloudsync_memory_free(pkclause);
1681 17 : cloudsync_memory_free(sql);
1682 17 : if (rc != SQLITE_OK) {
1683 0 : DEBUG_SQLITE_ERROR(rc, "cloudsync_finalize_alter", db);
1684 0 : goto finalize;
1685 : }
1686 :
1687 : }
1688 :
1689 : char buf[256];
1690 23 : snprintf(buf, sizeof(buf), "%lld", data->db_version);
1691 23 : dbutils_settings_set_key_value(db, context, "pre_alter_dbversion", buf);
1692 :
1693 : finalize:
1694 23 : sqlite3_free_table(result);
1695 23 : sqlite3_free(errmsg);
1696 :
1697 23 : return rc;
1698 : }
1699 :
1700 168 : int cloudsync_refill_metatable (sqlite3 *db, cloudsync_context *data, const char *table_name) {
1701 168 : cloudsync_table_context *table = table_lookup(data, table_name);
1702 168 : if (!table) return SQLITE_INTERNAL;
1703 :
1704 168 : sqlite3_stmt *vm = NULL;
1705 168 : sqlite3_int64 db_version = db_version_next(db, data, CLOUDSYNC_VALUE_NOTSET);
1706 :
1707 168 : char *sql = cloudsync_memory_mprintf("SELECT group_concat('\"' || format('%%w', name) || '\"', ',') FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk;", table_name);
1708 168 : char *pkclause_identifiers = dbutils_text_select(db, sql);
1709 168 : char *pkvalues_identifiers = (pkclause_identifiers) ? pkclause_identifiers : "rowid";
1710 168 : cloudsync_memory_free(sql);
1711 :
1712 168 : sql = cloudsync_memory_mprintf("SELECT group_concat('cloudsync_pk_decode(pk, ' || pk || ') AS ' || '\"' || format('%%w', name) || '\"', ',') FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk;", table_name);
1713 168 : char *pkdecode = dbutils_text_select(db, sql);
1714 168 : char *pkdecodeval = (pkdecode) ? pkdecode : "cloudsync_pk_decode(pk, 1) AS rowid";
1715 168 : cloudsync_memory_free(sql);
1716 :
1717 168 : sql = cloudsync_memory_mprintf("SELECT cloudsync_insert('%q', %s) FROM (SELECT %s FROM \"%w\" EXCEPT SELECT %s FROM \"%w_cloudsync\");", table_name, pkvalues_identifiers, pkvalues_identifiers, table_name, pkdecodeval, table_name);
1718 168 : int rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
1719 168 : cloudsync_memory_free(sql);
1720 168 : if (rc != SQLITE_OK) goto finalize;
1721 :
1722 : // fill missing colums
1723 : // for each non-pk column:
1724 : // The new query does 1 encode per source row and one indexed NOT-EXISTS probe.
1725 : // The old plan does many decodes per candidate and can’t use an index to rule out matches quickly—so it burns CPU and I/O.
1726 :
1727 168 : sql = cloudsync_memory_mprintf("WITH _cstemp1 AS (SELECT cloudsync_pk_encode(%s) AS pk FROM \"%w\") SELECT _cstemp1.pk FROM _cstemp1 WHERE NOT EXISTS (SELECT 1 FROM \"%w_cloudsync\" _cstemp2 WHERE _cstemp2.pk = _cstemp1.pk AND _cstemp2.col_name = ?);", pkvalues_identifiers, table_name, table_name);
1728 168 : rc = sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &vm, NULL);
1729 168 : cloudsync_memory_free(sql);
1730 168 : if (rc != SQLITE_OK) goto finalize;
1731 :
1732 1117 : for (int i=0; i<table->ncols; ++i) {
1733 949 : char *col_name = table->col_name[i];
1734 :
1735 949 : rc = sqlite3_bind_text(vm, 1, col_name, -1, SQLITE_STATIC);
1736 949 : if (rc != SQLITE_OK) goto finalize;
1737 :
1738 987 : while (1) {
1739 987 : rc = sqlite3_step(vm);
1740 987 : if (rc == SQLITE_ROW) {
1741 38 : const char *pk = (const char *)sqlite3_column_text(vm, 0);
1742 38 : size_t pklen = strlen(pk);
1743 38 : rc = local_mark_insert_or_update_meta(db, table, pk, pklen, col_name, db_version, BUMP_SEQ(data));
1744 987 : } else if (rc == SQLITE_DONE) {
1745 949 : rc = SQLITE_OK;
1746 949 : break;
1747 : } else {
1748 0 : break;
1749 : }
1750 : }
1751 949 : if (rc != SQLITE_OK) goto finalize;
1752 :
1753 949 : sqlite3_reset(vm);
1754 1117 : }
1755 :
1756 : finalize:
1757 168 : if (rc != SQLITE_OK) DEBUG_ALWAYS("cloudsync_refill_metatable error: %s", sqlite3_errmsg(db));
1758 168 : if (pkclause_identifiers) cloudsync_memory_free(pkclause_identifiers);
1759 168 : if (pkdecode) cloudsync_memory_free(pkdecode);
1760 168 : if (vm) sqlite3_finalize(vm);
1761 168 : return rc;
1762 168 : }
1763 :
1764 : // MARK: - Local -
1765 :
1766 1 : int local_update_sentinel (sqlite3 *db, cloudsync_table_context *table, const char *pk, size_t pklen, sqlite3_int64 db_version, int seq) {
1767 1 : sqlite3_stmt *vm = table->meta_sentinel_update_stmt;
1768 1 : if (!vm) return -1;
1769 :
1770 1 : int rc = sqlite3_bind_int64(vm, 1, db_version);
1771 1 : if (rc != SQLITE_OK) goto cleanup;
1772 :
1773 1 : rc = sqlite3_bind_int(vm, 2, seq);
1774 1 : if (rc != SQLITE_OK) goto cleanup;
1775 :
1776 1 : rc = sqlite3_bind_blob(vm, 3, pk, (int)pklen, SQLITE_STATIC);
1777 1 : if (rc != SQLITE_OK) goto cleanup;
1778 :
1779 1 : rc = sqlite3_step(vm);
1780 1 : if (rc == SQLITE_DONE) rc = SQLITE_OK;
1781 :
1782 : cleanup:
1783 1 : DEBUG_SQLITE_ERROR(rc, "local_update_sentinel", db);
1784 1 : sqlite3_reset(vm);
1785 1 : return rc;
1786 1 : }
1787 :
1788 121 : int local_mark_insert_sentinel_meta (sqlite3 *db, cloudsync_table_context *table, const char *pk, size_t pklen, sqlite3_int64 db_version, int seq) {
1789 121 : sqlite3_stmt *vm = table->meta_sentinel_insert_stmt;
1790 121 : if (!vm) return -1;
1791 :
1792 121 : int rc = sqlite3_bind_blob(vm, 1, pk, (int)pklen, SQLITE_STATIC);
1793 121 : if (rc != SQLITE_OK) goto cleanup;
1794 :
1795 121 : rc = sqlite3_bind_int64(vm, 2, db_version);
1796 121 : if (rc != SQLITE_OK) goto cleanup;
1797 :
1798 121 : rc = sqlite3_bind_int(vm, 3, seq);
1799 121 : if (rc != SQLITE_OK) goto cleanup;
1800 :
1801 121 : rc = sqlite3_bind_int64(vm, 4, db_version);
1802 121 : if (rc != SQLITE_OK) goto cleanup;
1803 :
1804 121 : rc = sqlite3_bind_int(vm, 5, seq);
1805 121 : if (rc != SQLITE_OK) goto cleanup;
1806 :
1807 121 : rc = sqlite3_step(vm);
1808 121 : if (rc == SQLITE_DONE) rc = SQLITE_OK;
1809 :
1810 : cleanup:
1811 121 : DEBUG_SQLITE_ERROR(rc, "local_insert_sentinel", db);
1812 121 : sqlite3_reset(vm);
1813 121 : return rc;
1814 121 : }
1815 :
1816 11286 : int local_mark_insert_or_update_meta_impl (sqlite3 *db, cloudsync_table_context *table, const char *pk, size_t pklen, const char *col_name, int col_version, sqlite3_int64 db_version, int seq) {
1817 :
1818 11286 : sqlite3_stmt *vm = table->meta_row_insert_update_stmt;
1819 11286 : if (!vm) return -1;
1820 :
1821 11286 : int rc = sqlite3_bind_blob(vm, 1, pk, (int)pklen, SQLITE_STATIC);
1822 11286 : if (rc != SQLITE_OK) goto cleanup;
1823 :
1824 11286 : rc = sqlite3_bind_text(vm, 2, (col_name) ? col_name : CLOUDSYNC_TOMBSTONE_VALUE, -1, SQLITE_STATIC);
1825 11286 : if (rc != SQLITE_OK) goto cleanup;
1826 :
1827 11286 : rc = sqlite3_bind_int(vm, 3, col_version);
1828 11286 : if (rc != SQLITE_OK) goto cleanup;
1829 :
1830 11286 : rc = sqlite3_bind_int64(vm, 4, db_version);
1831 11286 : if (rc != SQLITE_OK) goto cleanup;
1832 :
1833 11286 : rc = sqlite3_bind_int(vm, 5, seq);
1834 11286 : if (rc != SQLITE_OK) goto cleanup;
1835 :
1836 11286 : rc = sqlite3_bind_int64(vm, 6, db_version);
1837 11286 : if (rc != SQLITE_OK) goto cleanup;
1838 :
1839 11286 : rc = sqlite3_bind_int(vm, 7, seq);
1840 11286 : if (rc != SQLITE_OK) goto cleanup;
1841 :
1842 11286 : rc = sqlite3_step(vm);
1843 11286 : if (rc == SQLITE_DONE) rc = SQLITE_OK;
1844 :
1845 : cleanup:
1846 11286 : DEBUG_SQLITE_ERROR(rc, "local_insert_or_update", db);
1847 11286 : sqlite3_reset(vm);
1848 11286 : return rc;
1849 11286 : }
1850 :
1851 11228 : int local_mark_insert_or_update_meta (sqlite3 *db, cloudsync_table_context *table, const char *pk, size_t pklen, const char *col_name, sqlite3_int64 db_version, int seq) {
1852 11228 : return local_mark_insert_or_update_meta_impl(db, table, pk, pklen, col_name, 1, db_version, seq);
1853 : }
1854 :
1855 58 : int local_mark_delete_meta (sqlite3 *db, cloudsync_table_context *table, const char *pk, size_t pklen, sqlite3_int64 db_version, int seq) {
1856 58 : return local_mark_insert_or_update_meta_impl(db, table, pk, pklen, NULL, 2, db_version, seq);
1857 : }
1858 :
1859 28 : int local_drop_meta (sqlite3 *db, cloudsync_table_context *table, const char *pk, size_t pklen) {
1860 28 : sqlite3_stmt *vm = table->meta_row_drop_stmt;
1861 28 : if (!vm) return -1;
1862 :
1863 28 : int rc = sqlite3_bind_blob(vm, 1, pk, (int)pklen, SQLITE_STATIC);
1864 28 : if (rc != SQLITE_OK) goto cleanup;
1865 :
1866 28 : rc = sqlite3_step(vm);
1867 28 : if (rc == SQLITE_DONE) rc = SQLITE_OK;
1868 :
1869 : cleanup:
1870 28 : DEBUG_SQLITE_ERROR(rc, "local_drop_meta", db);
1871 28 : sqlite3_reset(vm);
1872 28 : return rc;
1873 28 : }
1874 :
1875 30 : int local_update_move_meta (sqlite3 *db, cloudsync_table_context *table, const char *pk, size_t pklen, const char *pk2, size_t pklen2, sqlite3_int64 db_version) {
1876 : /*
1877 : * This function moves non-sentinel metadata entries from an old primary key (OLD.pk)
1878 : * to a new primary key (NEW.pk) when a primary key change occurs.
1879 : *
1880 : * To ensure consistency and proper conflict resolution in a CRDT (Conflict-free Replicated Data Type) system,
1881 : * each non-sentinel metadata entry involved in the move must have a unique sequence value (seq).
1882 : *
1883 : * The `seq` is crucial for tracking the order of operations and for detecting and resolving conflicts
1884 : * during synchronization between replicas. Without a unique `seq` for each entry, concurrent updates
1885 : * may be applied incorrectly, leading to data inconsistency.
1886 : *
1887 : * When performing the update, a unique `seq` must be assigned to each metadata row. This can be achieved
1888 : * by either incrementing the maximum sequence value in the table or using a function (e.g., `bump_seq(data)`)
1889 : * that generates a unique sequence for each row. The update query should ensure that each row moved
1890 : * from OLD.pk to NEW.pk gets a distinct `seq` to maintain proper versioning and ordering of changes.
1891 : */
1892 :
1893 : // see https://github.com/sqliteai/sqlite-sync/blob/main/docs/PriKey.md for more details
1894 : // pk2 is the old pk
1895 :
1896 30 : sqlite3_stmt *vm = table->meta_update_move_stmt;
1897 30 : if (!vm) return -1;
1898 :
1899 : // new primary key
1900 30 : int rc = sqlite3_bind_blob(vm, 1, pk, (int)pklen, SQLITE_STATIC);
1901 30 : if (rc != SQLITE_OK) goto cleanup;
1902 :
1903 : // new db_version
1904 30 : rc = sqlite3_bind_int64(vm, 2, db_version);
1905 30 : if (rc != SQLITE_OK) goto cleanup;
1906 :
1907 : // old primary key
1908 30 : rc = sqlite3_bind_blob(vm, 3, pk2, (int)pklen2, SQLITE_STATIC);
1909 30 : if (rc != SQLITE_OK) goto cleanup;
1910 :
1911 30 : rc = sqlite3_step(vm);
1912 30 : if (rc == SQLITE_DONE) rc = SQLITE_OK;
1913 :
1914 : cleanup:
1915 30 : DEBUG_SQLITE_ERROR(rc, "local_update_move_meta", db);
1916 30 : sqlite3_reset(vm);
1917 30 : return rc;
1918 30 : }
1919 :
1920 : // MARK: - Payload Encode / Decode -
1921 :
1922 615 : bool cloudsync_buffer_free (cloudsync_data_payload *payload) {
1923 615 : if (payload) {
1924 615 : if (payload->buffer) cloudsync_memory_free(payload->buffer);
1925 615 : memset(payload, 0, sizeof(cloudsync_data_payload));
1926 615 : }
1927 :
1928 615 : return false;
1929 : }
1930 :
1931 48710 : bool cloudsync_buffer_check (cloudsync_data_payload *payload, size_t needed) {
1932 48710 : if (payload->nrows == 0) needed += sizeof(cloudsync_payload_header);
1933 :
1934 : // alloc/resize buffer
1935 48710 : if (payload->bused + needed > payload->balloc) {
1936 624 : if (needed < CLOUDSYNC_PAYLOAD_MINBUF_SIZE) needed = CLOUDSYNC_PAYLOAD_MINBUF_SIZE;
1937 624 : size_t balloc = payload->balloc + needed;
1938 :
1939 624 : char *buffer = cloudsync_memory_realloc(payload->buffer, balloc);
1940 624 : if (!buffer) return cloudsync_buffer_free(payload);
1941 :
1942 624 : payload->buffer = buffer;
1943 624 : payload->balloc = balloc;
1944 624 : if (payload->nrows == 0) payload->bused = sizeof(cloudsync_payload_header);
1945 624 : }
1946 :
1947 48710 : return true;
1948 48710 : }
1949 :
1950 615 : void cloudsync_payload_header_init (cloudsync_payload_header *header, uint32_t expanded_size, uint16_t ncols, uint32_t nrows, uint64_t hash) {
1951 615 : memset(header, 0, sizeof(cloudsync_payload_header));
1952 : assert(sizeof(cloudsync_payload_header)==32);
1953 :
1954 : int major, minor, patch;
1955 615 : sscanf(CLOUDSYNC_VERSION, "%d.%d.%d", &major, &minor, &patch);
1956 :
1957 615 : header->signature = htonl(CLOUDSYNC_PAYLOAD_SIGNATURE);
1958 615 : header->version = CLOUDSYNC_PAYLOAD_VERSION;
1959 615 : header->libversion[0] = major;
1960 615 : header->libversion[1] = minor;
1961 615 : header->libversion[2] = patch;
1962 615 : header->expanded_size = htonl(expanded_size);
1963 615 : header->ncols = htons(ncols);
1964 615 : header->nrows = htonl(nrows);
1965 615 : header->schema_hash = htonll(hash);
1966 615 : }
1967 :
1968 48710 : void cloudsync_payload_encode_step (sqlite3_context *context, int argc, sqlite3_value **argv) {
1969 : DEBUG_FUNCTION("cloudsync_payload_encode_step");
1970 : // debug_values(argc, argv);
1971 :
1972 : // allocate/get the session context
1973 48710 : cloudsync_data_payload *payload = (cloudsync_data_payload *)sqlite3_aggregate_context(context, sizeof(cloudsync_data_payload));
1974 48710 : if (!payload) return;
1975 :
1976 : // check if the step function is called for the first time
1977 48710 : if (payload->nrows == 0) payload->ncols = argc;
1978 :
1979 48710 : size_t breq = pk_encode_size(argv, argc, 0);
1980 48710 : if (cloudsync_buffer_check(payload, breq) == false) return;
1981 :
1982 48710 : char *buffer = payload->buffer + payload->bused;
1983 48710 : char *ptr = pk_encode(argv, argc, buffer, false, NULL);
1984 48710 : assert(buffer == ptr);
1985 :
1986 : // update buffer
1987 48710 : payload->bused += breq;
1988 :
1989 : // increment row counter
1990 48710 : ++payload->nrows;
1991 48710 : }
1992 :
1993 623 : void cloudsync_payload_encode_final (sqlite3_context *context) {
1994 : DEBUG_FUNCTION("cloudsync_payload_encode_final");
1995 :
1996 : // get the session context
1997 623 : cloudsync_data_payload *payload = (cloudsync_data_payload *)sqlite3_aggregate_context(context, sizeof(cloudsync_data_payload));
1998 623 : if (!payload) return;
1999 :
2000 623 : if (payload->nrows == 0) {
2001 8 : sqlite3_result_null(context);
2002 8 : return;
2003 : }
2004 :
2005 : // encode payload
2006 615 : int header_size = (int)sizeof(cloudsync_payload_header);
2007 615 : int real_buffer_size = (int)(payload->bused - header_size);
2008 615 : int zbound = LZ4_compressBound(real_buffer_size);
2009 615 : char *buffer = cloudsync_memory_alloc(zbound + header_size);
2010 615 : if (!buffer) {
2011 0 : cloudsync_buffer_free(payload);
2012 0 : sqlite3_result_error_code(context, SQLITE_NOMEM);
2013 0 : return;
2014 : }
2015 :
2016 : // adjust buffer to compress to skip the reserved header
2017 615 : char *src_buffer = payload->buffer + sizeof(cloudsync_payload_header);
2018 615 : int zused = LZ4_compress_default(src_buffer, buffer+header_size, real_buffer_size, zbound);
2019 615 : bool use_uncompressed_buffer = (!zused || zused > real_buffer_size);
2020 615 : CHECK_FORCE_UNCOMPRESSED_BUFFER();
2021 :
2022 : // setup payload header
2023 615 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
2024 : cloudsync_payload_header header;
2025 615 : cloudsync_payload_header_init(&header, (use_uncompressed_buffer) ? 0 : real_buffer_size, payload->ncols, (uint32_t)payload->nrows, data->schema_hash);
2026 :
2027 : // if compression fails or if compressed size is bigger than original buffer, then use the uncompressed buffer
2028 615 : if (use_uncompressed_buffer) {
2029 2 : cloudsync_memory_free(buffer);
2030 2 : buffer = payload->buffer;
2031 2 : zused = real_buffer_size;
2032 2 : }
2033 :
2034 : // copy header and data to SQLite BLOB
2035 615 : memcpy(buffer, &header, sizeof(cloudsync_payload_header));
2036 615 : int blob_size = zused+sizeof(cloudsync_payload_header);
2037 615 : sqlite3_result_blob(context, buffer, blob_size, SQLITE_TRANSIENT);
2038 :
2039 : // cleanup memory
2040 615 : cloudsync_buffer_free(payload);
2041 615 : if (!use_uncompressed_buffer) cloudsync_memory_free(buffer);
2042 623 : }
2043 :
2044 611 : cloudsync_payload_apply_callback_t cloudsync_get_payload_apply_callback(sqlite3 *db) {
2045 611 : return (sqlite3_libversion_number() >= 3044000) ? sqlite3_get_clientdata(db, CLOUDSYNC_PAYLOAD_APPLY_CALLBACK_KEY) : NULL;
2046 : }
2047 :
2048 22 : void cloudsync_set_payload_apply_callback(sqlite3 *db, cloudsync_payload_apply_callback_t callback) {
2049 22 : if (sqlite3_libversion_number() >= 3044000) {
2050 22 : sqlite3_set_clientdata(db, CLOUDSYNC_PAYLOAD_APPLY_CALLBACK_KEY, (void*)callback, NULL);
2051 22 : }
2052 22 : }
2053 :
2054 438318 : int cloudsync_pk_decode_bind_callback (void *xdata, int index, int type, int64_t ival, double dval, char *pval) {
2055 438318 : cloudsync_pk_decode_bind_context *decode_context = (cloudsync_pk_decode_bind_context*)xdata;
2056 438318 : int rc = pk_decode_bind_callback(decode_context->vm, index, type, ival, dval, pval);
2057 :
2058 438318 : if (rc == SQLITE_OK) {
2059 : // the dbversion index is smaller than seq index, so it is processed first
2060 : // when processing the dbversion column: save the value to the tmp_dbversion field
2061 : // when processing the seq column: update the dbversion and seq fields only if the current dbversion is greater than the last max value
2062 438318 : switch (index) {
2063 : case CLOUDSYNC_PK_INDEX_TBL:
2064 48702 : if (type == SQLITE_TEXT) {
2065 48702 : decode_context->tbl = pval;
2066 48702 : decode_context->tbl_len = ival;
2067 48702 : }
2068 48702 : break;
2069 : case CLOUDSYNC_PK_INDEX_PK:
2070 48702 : if (type == SQLITE_BLOB) {
2071 48702 : decode_context->pk = pval;
2072 48702 : decode_context->pk_len = ival;
2073 48702 : }
2074 48702 : break;
2075 : case CLOUDSYNC_PK_INDEX_COLNAME:
2076 48702 : if (type == SQLITE_TEXT) {
2077 48702 : decode_context->col_name = pval;
2078 48702 : decode_context->col_name_len = ival;
2079 48702 : }
2080 48702 : break;
2081 : case CLOUDSYNC_PK_INDEX_COLVERSION:
2082 48702 : if (type == SQLITE_INTEGER) decode_context->col_version = ival;
2083 48702 : break;
2084 : case CLOUDSYNC_PK_INDEX_DBVERSION:
2085 48702 : if (type == SQLITE_INTEGER) decode_context->db_version = ival;
2086 48702 : break;
2087 : case CLOUDSYNC_PK_INDEX_SITEID:
2088 48702 : if (type == SQLITE_BLOB) {
2089 48702 : decode_context->site_id = pval;
2090 48702 : decode_context->site_id_len = ival;
2091 48702 : }
2092 48702 : break;
2093 : case CLOUDSYNC_PK_INDEX_CL:
2094 48702 : if (type == SQLITE_INTEGER) decode_context->cl = ival;
2095 48702 : break;
2096 : case CLOUDSYNC_PK_INDEX_SEQ:
2097 48702 : if (type == SQLITE_INTEGER) decode_context->seq = ival;
2098 48702 : break;
2099 : }
2100 438318 : }
2101 :
2102 438318 : return rc;
2103 : }
2104 :
2105 : // #ifndef CLOUDSYNC_OMIT_RLS_VALIDATION
2106 :
2107 611 : int cloudsync_payload_apply (sqlite3_context *context, const char *payload, int blen) {
2108 : // decode header
2109 : cloudsync_payload_header header;
2110 611 : memcpy(&header, payload, sizeof(cloudsync_payload_header));
2111 :
2112 611 : header.signature = ntohl(header.signature);
2113 611 : header.expanded_size = ntohl(header.expanded_size);
2114 611 : header.ncols = ntohs(header.ncols);
2115 611 : header.nrows = ntohl(header.nrows);
2116 611 : header.schema_hash = ntohll(header.schema_hash);
2117 :
2118 611 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
2119 :
2120 : // sanity check header
2121 611 : if ((header.signature != CLOUDSYNC_PAYLOAD_SIGNATURE) || (header.ncols == 0)) {
2122 0 : dbutils_context_result_error(context, "Error on cloudsync_payload_apply: invalid signature or column size.");
2123 0 : sqlite3_result_error_code(context, SQLITE_MISUSE);
2124 0 : return -1;
2125 : }
2126 :
2127 611 : const char *buffer = payload + sizeof(cloudsync_payload_header);
2128 611 : blen -= sizeof(cloudsync_payload_header);
2129 :
2130 : // check if payload is compressed
2131 611 : char *clone = NULL;
2132 611 : if (header.expanded_size != 0) {
2133 609 : clone = (char *)cloudsync_memory_alloc(header.expanded_size);
2134 609 : if (!clone) {sqlite3_result_error_code(context, SQLITE_NOMEM); return -1;}
2135 :
2136 609 : uint32_t rc = LZ4_decompress_safe(buffer, clone, blen, header.expanded_size);
2137 609 : if (rc <= 0 || rc != header.expanded_size) {
2138 0 : dbutils_context_result_error(context, "Error on cloudsync_payload_apply: unable to decompress BLOB (%d).", rc);
2139 0 : sqlite3_result_error_code(context, SQLITE_MISUSE);
2140 0 : return -1;
2141 : }
2142 :
2143 609 : buffer = (const char *)clone;
2144 609 : }
2145 :
2146 611 : sqlite3 *db = sqlite3_context_db_handle(context);
2147 :
2148 : // precompile the insert statement
2149 611 : sqlite3_stmt *vm = NULL;
2150 611 : const char *sql = "INSERT INTO cloudsync_changes(tbl, pk, col_name, col_value, col_version, db_version, site_id, cl, seq) VALUES (?,?,?,?,?,?,?,?,?);";
2151 611 : int rc = sqlite3_prepare(db, sql, -1, &vm, NULL);
2152 611 : if (rc != SQLITE_OK) {
2153 0 : dbutils_context_result_error(context, "Error on cloudsync_payload_apply: error while compiling SQL statement (%s).", sqlite3_errmsg(db));
2154 0 : if (clone) cloudsync_memory_free(clone);
2155 0 : return -1;
2156 : }
2157 :
2158 : // process buffer, one row at a time
2159 611 : uint16_t ncols = header.ncols;
2160 611 : uint32_t nrows = header.nrows;
2161 611 : int64_t last_payload_db_version = -1;
2162 611 : bool in_savepoint = false;
2163 611 : int dbversion = dbutils_settings_get_int_value(db, CLOUDSYNC_KEY_CHECK_DBVERSION);
2164 611 : int seq = dbutils_settings_get_int_value(db, CLOUDSYNC_KEY_CHECK_SEQ);
2165 611 : cloudsync_pk_decode_bind_context decoded_context = {.vm = vm};
2166 611 : void *payload_apply_xdata = NULL;
2167 611 : cloudsync_payload_apply_callback_t payload_apply_callback = cloudsync_get_payload_apply_callback(db);
2168 :
2169 49313 : for (uint32_t i=0; i<nrows; ++i) {
2170 48702 : size_t seek = 0;
2171 48702 : pk_decode((char *)buffer, blen, ncols, &seek, cloudsync_pk_decode_bind_callback, &decoded_context);
2172 : // n is the pk_decode return value, I don't think I should assert here because in any case the next sqlite3_step would fail
2173 : // assert(n == ncols);
2174 :
2175 48702 : bool approved = true;
2176 48702 : if (payload_apply_callback) approved = payload_apply_callback(&payload_apply_xdata, &decoded_context, db, data, CLOUDSYNC_PAYLOAD_APPLY_WILL_APPLY, SQLITE_OK);
2177 :
2178 : // Apply consecutive rows with the same db_version inside a transaction if no
2179 : // transaction has already been opened.
2180 : // The user may have already opened a transaction before applying the payload,
2181 : // and the `payload_apply_callback` may have already opened a savepoint.
2182 : // Nested savepoints work, but overlapping savepoints could alter the expected behavior.
2183 : // This savepoint ensures that the db_version value remains consistent for all
2184 : // rows with the same original db_version in the payload.
2185 :
2186 48702 : bool db_version_changed = (last_payload_db_version != decoded_context.db_version);
2187 :
2188 : // Release existing savepoint if db_version changed
2189 48702 : if (in_savepoint && db_version_changed) {
2190 1539 : rc = sqlite3_exec(db, "RELEASE cloudsync_payload_apply;", NULL, NULL, NULL);
2191 1539 : if (rc != SQLITE_OK) {
2192 0 : dbutils_context_result_error(context, "Error on cloudsync_payload_apply: unable to release a savepoint (%s).", sqlite3_errmsg(db));
2193 0 : if (clone) cloudsync_memory_free(clone);
2194 0 : return -1;
2195 : }
2196 1539 : in_savepoint = false;
2197 1539 : }
2198 :
2199 : // Start new savepoint if needed
2200 48702 : bool in_transaction = sqlite3_get_autocommit(db) != true;
2201 48702 : if (!in_transaction && db_version_changed) {
2202 1682 : rc = sqlite3_exec(db, "SAVEPOINT cloudsync_payload_apply;", NULL, NULL, NULL);
2203 1682 : if (rc != SQLITE_OK) {
2204 0 : dbutils_context_result_error(context, "Error on cloudsync_payload_apply: unable to start a transaction (%s).", sqlite3_errmsg(db));
2205 0 : if (clone) cloudsync_memory_free(clone);
2206 0 : return -1;
2207 : }
2208 1682 : last_payload_db_version = decoded_context.db_version;
2209 1682 : in_savepoint = true;
2210 1682 : }
2211 :
2212 48702 : if (approved) {
2213 48581 : rc = sqlite3_step(vm);
2214 48581 : if (rc != SQLITE_DONE) {
2215 : // don't "break;", the error can be due to a RLS policy.
2216 : // in case of error we try to apply the following changes
2217 0 : printf("cloudsync_payload_apply error on db_version %lld/%lld: (%d) %s\n", decoded_context.db_version, decoded_context.seq, rc, sqlite3_errmsg(db));
2218 0 : }
2219 48581 : }
2220 :
2221 48702 : if (payload_apply_callback) payload_apply_callback(&payload_apply_xdata, &decoded_context, db, data, CLOUDSYNC_PAYLOAD_APPLY_DID_APPLY, rc);
2222 :
2223 48702 : buffer += seek;
2224 48702 : blen -= seek;
2225 48702 : stmt_reset(vm);
2226 48702 : }
2227 :
2228 611 : if (in_savepoint) {
2229 143 : sql = "RELEASE cloudsync_payload_apply;";
2230 143 : int rc1 = sqlite3_exec(db, sql, NULL, NULL, NULL);
2231 143 : if (rc1 != SQLITE_OK) rc = rc1;
2232 143 : }
2233 :
2234 611 : char *lasterr = (rc != SQLITE_OK && rc != SQLITE_DONE) ? cloudsync_string_dup(sqlite3_errmsg(db), false) : NULL;
2235 :
2236 611 : if (payload_apply_callback) {
2237 468 : payload_apply_callback(&payload_apply_xdata, &decoded_context, db, data, CLOUDSYNC_PAYLOAD_APPLY_CLEANUP, rc);
2238 468 : }
2239 :
2240 611 : if (rc == SQLITE_DONE) rc = SQLITE_OK;
2241 611 : if (rc == SQLITE_OK) {
2242 : char buf[256];
2243 611 : if (decoded_context.db_version >= dbversion) {
2244 479 : snprintf(buf, sizeof(buf), "%lld", decoded_context.db_version);
2245 479 : dbutils_settings_set_key_value(db, context, CLOUDSYNC_KEY_CHECK_DBVERSION, buf);
2246 :
2247 479 : if (decoded_context.seq != seq) {
2248 304 : snprintf(buf, sizeof(buf), "%lld", decoded_context.seq);
2249 304 : dbutils_settings_set_key_value(db, context, CLOUDSYNC_KEY_CHECK_SEQ, buf);
2250 304 : }
2251 479 : }
2252 611 : }
2253 :
2254 : // cleanup vm
2255 611 : if (vm) sqlite3_finalize(vm);
2256 :
2257 : // cleanup memory
2258 611 : if (clone) cloudsync_memory_free(clone);
2259 :
2260 611 : if (rc != SQLITE_OK) {
2261 0 : sqlite3_result_error(context, lasterr, -1);
2262 0 : sqlite3_result_error_code(context, SQLITE_MISUSE);
2263 0 : cloudsync_memory_free(lasterr);
2264 0 : return -1;
2265 : }
2266 :
2267 : // return the number of processed rows
2268 611 : sqlite3_result_int(context, nrows);
2269 611 : return nrows;
2270 611 : }
2271 :
2272 611 : void cloudsync_payload_decode (sqlite3_context *context, int argc, sqlite3_value **argv) {
2273 : DEBUG_FUNCTION("cloudsync_payload_decode");
2274 : //debug_values(argc, argv);
2275 :
2276 : // sanity check payload type
2277 611 : if (sqlite3_value_type(argv[0]) != SQLITE_BLOB) {
2278 0 : dbutils_context_result_error(context, "Error on cloudsync_payload_decode: value must be a BLOB.");
2279 0 : sqlite3_result_error_code(context, SQLITE_MISUSE);
2280 0 : return;
2281 : }
2282 :
2283 : // sanity check payload size
2284 611 : int blen = sqlite3_value_bytes(argv[0]);
2285 611 : if (blen < (int)sizeof(cloudsync_payload_header)) {
2286 0 : dbutils_context_result_error(context, "Error on cloudsync_payload_decode: invalid input size.");
2287 0 : sqlite3_result_error_code(context, SQLITE_MISUSE);
2288 0 : return;
2289 : }
2290 :
2291 : // obtain payload
2292 611 : const char *payload = (const char *)sqlite3_value_blob(argv[0]);
2293 :
2294 : // apply changes
2295 611 : cloudsync_payload_apply(context, payload, blen);
2296 611 : }
2297 :
2298 : // MARK: - Payload load/store -
2299 :
2300 0 : int cloudsync_payload_get (sqlite3_context *context, char **blob, int *blob_size, int *db_version, int *seq, sqlite3_int64 *new_db_version, sqlite3_int64 *new_seq) {
2301 0 : sqlite3 *db = sqlite3_context_db_handle(context);
2302 :
2303 0 : *db_version = dbutils_settings_get_int_value(db, CLOUDSYNC_KEY_SEND_DBVERSION);
2304 0 : if (*db_version < 0) {sqlite3_result_error(context, "Unable to retrieve db_version.", -1); return SQLITE_ERROR;}
2305 :
2306 0 : *seq = dbutils_settings_get_int_value(db, CLOUDSYNC_KEY_SEND_SEQ);
2307 0 : if (*seq < 0) {sqlite3_result_error(context, "Unable to retrieve seq.", -1); return SQLITE_ERROR;}
2308 :
2309 : // retrieve BLOB
2310 : char sql[1024];
2311 0 : snprintf(sql, sizeof(sql), "WITH max_db_version AS (SELECT MAX(db_version) AS max_db_version FROM cloudsync_changes) "
2312 : "SELECT cloudsync_payload_encode(tbl, pk, col_name, col_value, col_version, db_version, site_id, cl, seq), max_db_version AS max_db_version, MAX(IIF(db_version = max_db_version, seq, NULL)) FROM cloudsync_changes, max_db_version WHERE site_id=cloudsync_siteid() AND (db_version>%d OR (db_version=%d AND seq>%d))", *db_version, *db_version, *seq);
2313 :
2314 0 : int rc = dbutils_blob_int_int_select(db, sql, blob, blob_size, new_db_version, new_seq);
2315 0 : if (rc != SQLITE_OK) {
2316 0 : sqlite3_result_error(context, "cloudsync_network_send_changes unable to get changes", -1);
2317 0 : sqlite3_result_error_code(context, rc);
2318 0 : return rc;
2319 : }
2320 :
2321 : // exit if there is no data to send
2322 0 : if (blob == NULL || blob_size == 0) return SQLITE_OK;
2323 0 : return rc;
2324 0 : }
2325 :
2326 : #ifdef CLOUDSYNC_DESKTOP_OS
2327 :
2328 0 : void cloudsync_payload_save (sqlite3_context *context, int argc, sqlite3_value **argv) {
2329 : DEBUG_FUNCTION("cloudsync_payload_save");
2330 :
2331 : // sanity check argument
2332 0 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT) {
2333 0 : sqlite3_result_error(context, "Unable to retrieve file path.", -1);
2334 0 : return;
2335 : }
2336 :
2337 : // retrieve full path to file
2338 0 : const char *path = (const char *)sqlite3_value_text(argv[0]);
2339 0 : cloudsync_file_delete(path);
2340 :
2341 : // retrieve payload
2342 0 : char *blob = NULL;
2343 0 : int blob_size = 0, db_version = 0, seq = 0;
2344 0 : sqlite3_int64 new_db_version = 0, new_seq = 0;
2345 0 : int rc = cloudsync_payload_get(context, &blob, &blob_size, &db_version, &seq, &new_db_version, &new_seq);
2346 0 : if (rc != SQLITE_OK) return;
2347 :
2348 : // exit if there is no data to send
2349 0 : if (blob == NULL || blob_size == 0) return;
2350 :
2351 : // write payload to file
2352 0 : bool res = cloudsync_file_write(path, blob, (size_t)blob_size);
2353 0 : sqlite3_free(blob);
2354 :
2355 0 : if (res == false) {
2356 0 : sqlite3_result_error(context, "Unable to write payload to file path.", -1);
2357 0 : return;
2358 : }
2359 :
2360 : // update db_version and seq
2361 : char buf[256];
2362 0 : sqlite3 *db = sqlite3_context_db_handle(context);
2363 0 : if (new_db_version != db_version) {
2364 0 : snprintf(buf, sizeof(buf), "%lld", new_db_version);
2365 0 : dbutils_settings_set_key_value(db, context, CLOUDSYNC_KEY_SEND_DBVERSION, buf);
2366 0 : }
2367 0 : if (new_seq != seq) {
2368 0 : snprintf(buf, sizeof(buf), "%lld", new_seq);
2369 0 : dbutils_settings_set_key_value(db, context, CLOUDSYNC_KEY_SEND_SEQ, buf);
2370 0 : }
2371 :
2372 : // returns blob size
2373 0 : sqlite3_result_int64(context, (sqlite3_int64)blob_size);
2374 0 : }
2375 :
2376 0 : void cloudsync_payload_load (sqlite3_context *context, int argc, sqlite3_value **argv) {
2377 : DEBUG_FUNCTION("cloudsync_payload_load");
2378 :
2379 : // sanity check argument
2380 0 : if (sqlite3_value_type(argv[0]) != SQLITE_TEXT) {
2381 0 : sqlite3_result_error(context, "Unable to retrieve file path.", -1);
2382 0 : return;
2383 : }
2384 :
2385 : // retrieve full path to file
2386 0 : const char *path = (const char *)sqlite3_value_text(argv[0]);
2387 :
2388 0 : sqlite3_int64 payload_size = 0;
2389 0 : char *payload = cloudsync_file_read(path, &payload_size);
2390 0 : if (!payload) {
2391 0 : if (payload_size == -1) sqlite3_result_error(context, "Unable to read payload from file path.", -1);
2392 0 : if (payload) cloudsync_memory_free(payload);
2393 0 : return;
2394 : }
2395 :
2396 0 : int nrows = (payload_size) ? cloudsync_payload_apply (context, payload, (int)payload_size) : 0;
2397 0 : if (payload) cloudsync_memory_free(payload);
2398 :
2399 : // returns number of applied rows
2400 0 : if (nrows != -1) sqlite3_result_int(context, nrows);
2401 0 : }
2402 :
2403 : #endif
2404 :
2405 : // MARK: - Public -
2406 :
2407 7 : void cloudsync_version (sqlite3_context *context, int argc, sqlite3_value **argv) {
2408 : DEBUG_FUNCTION("cloudsync_version");
2409 7 : UNUSED_PARAMETER(argc);
2410 7 : UNUSED_PARAMETER(argv);
2411 7 : sqlite3_result_text(context, CLOUDSYNC_VERSION, -1, SQLITE_STATIC);
2412 7 : }
2413 :
2414 471 : void cloudsync_siteid (sqlite3_context *context, int argc, sqlite3_value **argv) {
2415 : DEBUG_FUNCTION("cloudsync_siteid");
2416 471 : UNUSED_PARAMETER(argc);
2417 471 : UNUSED_PARAMETER(argv);
2418 :
2419 471 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
2420 471 : sqlite3_result_blob(context, data->site_id, UUID_LEN, SQLITE_STATIC);
2421 471 : }
2422 :
2423 3 : void cloudsync_db_version (sqlite3_context *context, int argc, sqlite3_value **argv) {
2424 : DEBUG_FUNCTION("cloudsync_db_version");
2425 3 : UNUSED_PARAMETER(argc);
2426 3 : UNUSED_PARAMETER(argv);
2427 :
2428 : // retrieve context
2429 3 : sqlite3 *db = sqlite3_context_db_handle(context);
2430 3 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
2431 :
2432 3 : int rc = db_version_check_uptodate(db, data);
2433 3 : if (rc != SQLITE_OK) {
2434 0 : dbutils_context_result_error(context, "Unable to retrieve db_version (%s).", sqlite3_errmsg(db));
2435 0 : return;
2436 : }
2437 :
2438 3 : sqlite3_result_int64(context, data->db_version);
2439 3 : }
2440 :
2441 24633 : void cloudsync_db_version_next (sqlite3_context *context, int argc, sqlite3_value **argv) {
2442 : DEBUG_FUNCTION("cloudsync_db_version_next");
2443 :
2444 : // retrieve context
2445 24633 : sqlite3 *db = sqlite3_context_db_handle(context);
2446 24633 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
2447 :
2448 24633 : sqlite3_int64 merging_version = (argc == 1) ? sqlite3_value_int64(argv[0]) : CLOUDSYNC_VALUE_NOTSET;
2449 24633 : sqlite3_int64 value = db_version_next(db, data, merging_version);
2450 24633 : if (value == -1) {
2451 0 : dbutils_context_result_error(context, "Unable to retrieve next_db_version (%s).", sqlite3_errmsg(db));
2452 0 : return;
2453 : }
2454 :
2455 24633 : sqlite3_result_int64(context, value);
2456 24633 : }
2457 :
2458 22 : void cloudsync_seq (sqlite3_context *context, int argc, sqlite3_value **argv) {
2459 : DEBUG_FUNCTION("cloudsync_seq");
2460 :
2461 : // retrieve context
2462 22 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
2463 22 : sqlite3_result_int(context, BUMP_SEQ(data));
2464 22 : }
2465 :
2466 1 : void cloudsync_uuid (sqlite3_context *context, int argc, sqlite3_value **argv) {
2467 : DEBUG_FUNCTION("cloudsync_uuid");
2468 :
2469 : char value[UUID_STR_MAXLEN];
2470 1 : char *uuid = cloudsync_uuid_v7_string(value, true);
2471 1 : sqlite3_result_text(context, uuid, -1, SQLITE_TRANSIENT);
2472 1 : }
2473 :
2474 : // MARK: -
2475 :
2476 1 : void cloudsync_set (sqlite3_context *context, int argc, sqlite3_value **argv) {
2477 : DEBUG_FUNCTION("cloudsync_set");
2478 :
2479 : // sanity check parameters
2480 1 : const char *key = (const char *)sqlite3_value_text(argv[0]);
2481 1 : const char *value = (const char *)sqlite3_value_text(argv[1]);
2482 :
2483 : // silently fails
2484 1 : if (key == NULL) return;
2485 :
2486 1 : sqlite3 *db = sqlite3_context_db_handle(context);
2487 1 : dbutils_settings_set_key_value(db, context, key, value);
2488 1 : }
2489 :
2490 1 : void cloudsync_set_column (sqlite3_context *context, int argc, sqlite3_value **argv) {
2491 : DEBUG_FUNCTION("cloudsync_set_column");
2492 :
2493 1 : const char *tbl = (const char *)sqlite3_value_text(argv[0]);
2494 1 : const char *col = (const char *)sqlite3_value_text(argv[1]);
2495 1 : const char *key = (const char *)sqlite3_value_text(argv[2]);
2496 1 : const char *value = (const char *)sqlite3_value_text(argv[3]);
2497 1 : dbutils_table_settings_set_key_value(NULL, context, tbl, col, key, value);
2498 1 : }
2499 :
2500 1 : void cloudsync_set_table (sqlite3_context *context, int argc, sqlite3_value **argv) {
2501 : DEBUG_FUNCTION("cloudsync_set_table");
2502 :
2503 1 : const char *tbl = (const char *)sqlite3_value_text(argv[0]);
2504 1 : const char *key = (const char *)sqlite3_value_text(argv[1]);
2505 1 : const char *value = (const char *)sqlite3_value_text(argv[2]);
2506 1 : dbutils_table_settings_set_key_value(NULL, context, tbl, "*", key, value);
2507 1 : }
2508 :
2509 25559 : void cloudsync_is_sync (sqlite3_context *context, int argc, sqlite3_value **argv) {
2510 : DEBUG_FUNCTION("cloudsync_is_sync");
2511 :
2512 25559 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
2513 25559 : if (data->insync) {
2514 21756 : sqlite3_result_int(context, 1);
2515 21756 : return;
2516 : }
2517 :
2518 3803 : const char *table_name = (const char *)sqlite3_value_text(argv[0]);
2519 3803 : cloudsync_table_context *table = table_lookup(data, table_name);
2520 3803 : sqlite3_result_int(context, (table) ? (table->enabled == 0) : 0);
2521 25559 : }
2522 :
2523 125592 : void cloudsync_col_value (sqlite3_context *context, int argc, sqlite3_value **argv) {
2524 : // DEBUG_FUNCTION("cloudsync_col_value");
2525 :
2526 : // argv[0] -> table name
2527 : // argv[1] -> column name
2528 : // argv[2] -> encoded pk
2529 :
2530 : // lookup table
2531 125592 : const char *table_name = (const char *)sqlite3_value_text(argv[0]);
2532 125592 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
2533 125592 : cloudsync_table_context *table = table_lookup(data, table_name);
2534 125592 : if (!table) {
2535 0 : dbutils_context_result_error(context, "Unable to retrieve table name %s in clousdsync_colvalue.", table_name);
2536 0 : return;
2537 : }
2538 :
2539 : // retrieve column name
2540 125592 : const char *col_name = (const char *)sqlite3_value_text(argv[1]);
2541 :
2542 : // check for special tombstone value
2543 125592 : if (strcmp(col_name, CLOUDSYNC_TOMBSTONE_VALUE) == 0) {
2544 4034 : sqlite3_result_null(context);
2545 4034 : return;
2546 : }
2547 :
2548 : // extract the right col_value vm associated to the column name
2549 121558 : sqlite3_stmt *vm = table_column_lookup(table, col_name, false, NULL);
2550 121558 : if (!vm) {
2551 0 : sqlite3_result_error(context, "Unable to retrieve column value precompiled statement in clousdsync_colvalue.", -1);
2552 0 : return;
2553 : }
2554 :
2555 : // bind primary key values
2556 121558 : int rc = pk_decode_prikey((char *)sqlite3_value_blob(argv[2]), (size_t)sqlite3_value_bytes(argv[2]), pk_decode_bind_callback, (void *)vm);
2557 121558 : if (rc < 0) goto cleanup;
2558 :
2559 : // execute vm
2560 121558 : rc = sqlite3_step(vm);
2561 243116 : if (rc == SQLITE_DONE) {
2562 0 : rc = SQLITE_OK;
2563 0 : sqlite3_result_text(context, CLOUDSYNC_RLS_RESTRICTED_VALUE, -1, SQLITE_STATIC);
2564 121558 : } else if (rc == SQLITE_ROW) {
2565 : // store value result
2566 121558 : rc = SQLITE_OK;
2567 121558 : sqlite3_result_value(context, sqlite3_column_value(vm, 0));
2568 121558 : }
2569 :
2570 : cleanup:
2571 121558 : if (rc != SQLITE_OK) {
2572 0 : sqlite3 *db = sqlite3_context_db_handle(context);
2573 0 : sqlite3_result_error(context, sqlite3_errmsg(db), -1);
2574 0 : }
2575 121558 : sqlite3_reset(vm);
2576 125592 : }
2577 :
2578 10365 : void cloudsync_pk_encode (sqlite3_context *context, int argc, sqlite3_value **argv) {
2579 10365 : size_t bsize = 0;
2580 10365 : char *buffer = pk_encode_prikey(argv, argc, NULL, &bsize);
2581 10365 : if (!buffer) {
2582 0 : sqlite3_result_null(context);
2583 0 : return;
2584 : }
2585 10365 : sqlite3_result_blob(context, (const void *)buffer, (int)bsize, SQLITE_TRANSIENT);
2586 10365 : cloudsync_memory_free(buffer);
2587 10365 : }
2588 :
2589 380 : int cloudsync_pk_decode_set_result_callback (void *xdata, int index, int type, int64_t ival, double dval, char *pval) {
2590 380 : cloudsync_pk_decode_context *decode_context = (cloudsync_pk_decode_context *)xdata;
2591 : // decode_context->index is 1 based
2592 : // index is 0 based
2593 380 : if (decode_context->index != index+1) return SQLITE_OK;
2594 :
2595 190 : int rc = 0;
2596 190 : sqlite3_context *context = decode_context->context;
2597 190 : switch (type) {
2598 : case SQLITE_INTEGER:
2599 0 : sqlite3_result_int64(context, ival);
2600 0 : break;
2601 :
2602 : case SQLITE_FLOAT:
2603 0 : sqlite3_result_double(context, dval);
2604 0 : break;
2605 :
2606 : case SQLITE_NULL:
2607 0 : sqlite3_result_null(context);
2608 0 : break;
2609 :
2610 : case SQLITE_TEXT:
2611 190 : sqlite3_result_text(context, pval, (int)ival, SQLITE_TRANSIENT);
2612 190 : break;
2613 :
2614 : case SQLITE_BLOB:
2615 0 : sqlite3_result_blob(context, pval, (int)ival, SQLITE_TRANSIENT);
2616 0 : break;
2617 : }
2618 :
2619 190 : return rc;
2620 380 : }
2621 :
2622 :
2623 190 : void cloudsync_pk_decode (sqlite3_context *context, int argc, sqlite3_value **argv) {
2624 190 : const char *pk = (const char *)sqlite3_value_text(argv[0]);
2625 190 : int i = sqlite3_value_int(argv[1]);
2626 :
2627 190 : cloudsync_pk_decode_context xdata = {.context = context, .index = i};
2628 190 : pk_decode_prikey((char *)pk, strlen(pk), cloudsync_pk_decode_set_result_callback, &xdata);
2629 190 : }
2630 :
2631 : // MARK: -
2632 :
2633 3468 : void cloudsync_insert (sqlite3_context *context, int argc, sqlite3_value **argv) {
2634 : DEBUG_FUNCTION("cloudsync_insert %s", sqlite3_value_text(argv[0]));
2635 : // debug_values(argc-1, &argv[1]);
2636 :
2637 : // argv[0] is table name
2638 : // argv[1]..[N] is primary key(s)
2639 :
2640 : // table_cloudsync
2641 : // pk -> encode(argc-1, &argv[1])
2642 : // col_name -> name
2643 : // col_version -> 0/1 +1
2644 : // db_version -> check
2645 : // site_id 0
2646 : // seq -> sqlite_master
2647 :
2648 : // retrieve context
2649 3468 : sqlite3 *db = sqlite3_context_db_handle(context);
2650 3468 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
2651 :
2652 : // lookup table
2653 3468 : const char *table_name = (const char *)sqlite3_value_text(argv[0]);
2654 3468 : cloudsync_table_context *table = table_lookup(data, table_name);
2655 3468 : if (!table) {
2656 0 : dbutils_context_result_error(context, "Unable to retrieve table name %s in cloudsync_insert.", table_name);
2657 0 : return;
2658 : }
2659 :
2660 : // encode the primary key values into a buffer
2661 : char buffer[1024];
2662 3468 : size_t pklen = sizeof(buffer);
2663 3468 : char *pk = pk_encode_prikey(&argv[1], table->npks, buffer, &pklen);
2664 3468 : if (!pk) {
2665 0 : sqlite3_result_error(context, "Not enough memory to encode the primary key(s).", -1);
2666 0 : return;
2667 : }
2668 :
2669 : // compute the next database version for tracking changes
2670 3468 : sqlite3_int64 db_version = db_version_next(db, data, CLOUDSYNC_VALUE_NOTSET);
2671 :
2672 : // check if a row with the same primary key already exists
2673 : // if so, this means the row might have been previously deleted (sentinel)
2674 3468 : bool pk_exists = (bool)stmt_count(table->meta_pkexists_stmt, pk, pklen, SQLITE_BLOB);
2675 3468 : int rc = SQLITE_OK;
2676 :
2677 3468 : if (table->ncols == 0) {
2678 : // if there are no columns other than primary keys, insert a sentinel record
2679 91 : rc = local_mark_insert_sentinel_meta(db, table, pk, pklen, db_version, BUMP_SEQ(data));
2680 91 : if (rc != SQLITE_OK) goto cleanup;
2681 3468 : } else if (pk_exists){
2682 : // if a row with the same primary key already exists, update the sentinel record
2683 1 : rc = local_update_sentinel(db, table, pk, pklen, db_version, BUMP_SEQ(data));
2684 1 : if (rc != SQLITE_OK) goto cleanup;
2685 1 : }
2686 :
2687 : // process each non-primary key column for insert or update
2688 14049 : for (int i=0; i<table->ncols; ++i) {
2689 : // mark the column as inserted or updated in the metadata
2690 10581 : rc = local_mark_insert_or_update_meta(db, table, pk, pklen, table->col_name[i], db_version, BUMP_SEQ(data));
2691 10581 : if (rc != SQLITE_OK) goto cleanup;
2692 14049 : }
2693 :
2694 : cleanup:
2695 3468 : if (rc != SQLITE_OK) sqlite3_result_error(context, sqlite3_errmsg(db), -1);
2696 : // free memory if the primary key was dynamically allocated
2697 3468 : if (pk != buffer) cloudsync_memory_free(pk);
2698 3468 : }
2699 :
2700 28 : void cloudsync_delete (sqlite3_context *context, int argc, sqlite3_value **argv) {
2701 : DEBUG_FUNCTION("cloudsync_delete %s", sqlite3_value_text(argv[0]));
2702 : // debug_values(argc-1, &argv[1]);
2703 :
2704 : // retrieve context
2705 28 : sqlite3 *db = sqlite3_context_db_handle(context);
2706 28 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
2707 :
2708 : // lookup table
2709 28 : const char *table_name = (const char *)sqlite3_value_text(argv[0]);
2710 28 : cloudsync_table_context *table = table_lookup(data, table_name);
2711 28 : if (!table) {
2712 0 : dbutils_context_result_error(context, "Unable to retrieve table name %s in cloudsync_delete.", table_name);
2713 0 : return;
2714 : }
2715 :
2716 : // compute the next database version for tracking changes
2717 28 : sqlite3_int64 db_version = db_version_next(db, data, CLOUDSYNC_VALUE_NOTSET);
2718 28 : int rc = SQLITE_OK;
2719 :
2720 : // encode the primary key values into a buffer
2721 : char buffer[1024];
2722 28 : size_t pklen = sizeof(buffer);
2723 28 : char *pk = pk_encode_prikey(&argv[1], table->npks, buffer, &pklen);
2724 28 : if (!pk) {
2725 0 : sqlite3_result_error(context, "Not enough memory to encode the primary key(s).", -1);
2726 0 : return;
2727 : }
2728 :
2729 : // mark the row as deleted by inserting a delete sentinel into the metadata
2730 28 : rc = local_mark_delete_meta(db, table, pk, pklen, db_version, BUMP_SEQ(data));
2731 28 : if (rc != SQLITE_OK) goto cleanup;
2732 :
2733 : // remove any metadata related to the old rows associated with this primary key
2734 28 : rc = local_drop_meta(db, table, pk, pklen);
2735 28 : if (rc != SQLITE_OK) goto cleanup;
2736 :
2737 : cleanup:
2738 28 : if (rc != SQLITE_OK) sqlite3_result_error(context, sqlite3_errmsg(db), -1);
2739 : // free memory if the primary key was dynamically allocated
2740 28 : if (pk != buffer) cloudsync_memory_free(pk);
2741 28 : }
2742 :
2743 : // MARK: -
2744 :
2745 337 : void cloudsync_update_payload_free (cloudsync_update_payload *payload) {
2746 2303 : for (int i=0; i<payload->count; i++) {
2747 1966 : sqlite3_value_free(payload->new_values[i]);
2748 1966 : sqlite3_value_free(payload->old_values[i]);
2749 1966 : }
2750 337 : cloudsync_memory_free(payload->new_values);
2751 337 : cloudsync_memory_free(payload->old_values);
2752 337 : sqlite3_value_free(payload->table_name);
2753 337 : payload->new_values = NULL;
2754 337 : payload->old_values = NULL;
2755 337 : payload->table_name = NULL;
2756 337 : payload->count = 0;
2757 337 : payload->capacity = 0;
2758 337 : }
2759 :
2760 1966 : int cloudsync_update_payload_append (cloudsync_update_payload *payload, sqlite3_value *v1, sqlite3_value *v2, sqlite3_value *v3) {
2761 1966 : if (payload->count >= payload->capacity) {
2762 340 : int newcap = payload->capacity ? payload->capacity * 2 : 128;
2763 :
2764 340 : sqlite3_value **new_values_2 = (sqlite3_value **)cloudsync_memory_realloc(payload->new_values, newcap * sizeof(*new_values_2));
2765 340 : if (!new_values_2) return SQLITE_NOMEM;
2766 340 : payload->new_values = new_values_2;
2767 :
2768 340 : sqlite3_value **old_values_2 = (sqlite3_value **)cloudsync_memory_realloc(payload->old_values, newcap * sizeof(*old_values_2));
2769 340 : if (!old_values_2) return SQLITE_NOMEM;
2770 340 : payload->old_values = old_values_2;
2771 :
2772 340 : payload->capacity = newcap;
2773 340 : }
2774 :
2775 1966 : int index = payload->count;
2776 1966 : if (payload->table_name == NULL) payload->table_name = sqlite3_value_dup(v1);
2777 1629 : else if (dbutils_value_compare(payload->table_name, v1) != 0) return SQLITE_NOMEM;
2778 1966 : payload->new_values[index] = sqlite3_value_dup(v2);
2779 1966 : payload->old_values[index] = sqlite3_value_dup(v3);
2780 1966 : payload->count++;
2781 :
2782 : // sanity check memory allocations
2783 1966 : bool v1_can_be_null = (sqlite3_value_type(v1) == SQLITE_NULL);
2784 1966 : bool v2_can_be_null = (sqlite3_value_type(v2) == SQLITE_NULL);
2785 1966 : bool v3_can_be_null = (sqlite3_value_type(v3) == SQLITE_NULL);
2786 :
2787 1966 : if ((payload->table_name == NULL) && (!v1_can_be_null)) return SQLITE_NOMEM;
2788 1966 : if ((payload->old_values[index] == NULL) && (!v2_can_be_null)) return SQLITE_NOMEM;
2789 1966 : if ((payload->new_values[index] == NULL) && (!v3_can_be_null)) return SQLITE_NOMEM;
2790 :
2791 1966 : return SQLITE_OK;
2792 1966 : }
2793 :
2794 1966 : void cloudsync_update_step (sqlite3_context *context, int argc, sqlite3_value **argv) {
2795 : // argv[0] => table_name
2796 : // argv[1] => new_column_value
2797 : // argv[2] => old_column_value
2798 :
2799 : // allocate/get the update payload
2800 1966 : cloudsync_update_payload *payload = (cloudsync_update_payload *)sqlite3_aggregate_context(context, sizeof(cloudsync_update_payload));
2801 1966 : if (!payload) {sqlite3_result_error_nomem(context); return;}
2802 :
2803 1966 : if (cloudsync_update_payload_append(payload, argv[0], argv[1], argv[2]) != SQLITE_OK) {
2804 0 : sqlite3_result_error_nomem(context);
2805 0 : }
2806 1966 : }
2807 :
2808 337 : void cloudsync_update_final (sqlite3_context *context) {
2809 337 : cloudsync_update_payload *payload = (cloudsync_update_payload *)sqlite3_aggregate_context(context, sizeof(cloudsync_update_payload));
2810 337 : if (!payload || payload->count == 0) return;
2811 :
2812 : // retrieve context
2813 337 : sqlite3 *db = sqlite3_context_db_handle(context);
2814 337 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
2815 :
2816 : // lookup table
2817 337 : const char *table_name = (const char *)sqlite3_value_text(payload->table_name);
2818 337 : cloudsync_table_context *table = table_lookup(data, table_name);
2819 337 : if (!table) {
2820 0 : dbutils_context_result_error(context, "Unable to retrieve table name %s in cloudsync_update.", table_name);
2821 0 : return;
2822 : }
2823 :
2824 : // compute the next database version for tracking changes
2825 337 : sqlite3_int64 db_version = db_version_next(db, data, CLOUDSYNC_VALUE_NOTSET);
2826 337 : int rc = SQLITE_OK;
2827 :
2828 : // Check if the primary key(s) have changed
2829 337 : bool prikey_changed = false;
2830 947 : for (int i=0; i<table->npks; ++i) {
2831 640 : if (dbutils_value_compare(payload->old_values[i], payload->new_values[i]) != 0) {
2832 30 : prikey_changed = true;
2833 30 : break;
2834 : }
2835 610 : }
2836 :
2837 : // encode the NEW primary key values into a buffer (used later for indexing)
2838 : char buffer[1024];
2839 : char buffer2[1024];
2840 337 : size_t pklen = sizeof(buffer);
2841 337 : size_t oldpklen = sizeof(buffer2);
2842 337 : char *oldpk = NULL;
2843 :
2844 337 : char *pk = pk_encode_prikey(payload->new_values, table->npks, buffer, &pklen);
2845 337 : if (!pk) {
2846 0 : sqlite3_result_error(context, "Not enough memory to encode the primary key(s).", -1);
2847 0 : return;
2848 : }
2849 :
2850 337 : if (prikey_changed) {
2851 : // if the primary key has changed, we need to handle the row differently:
2852 : // 1. mark the old row (OLD primary key) as deleted
2853 : // 2. create a new row (NEW primary key)
2854 :
2855 : // encode the OLD primary key into a buffer
2856 30 : oldpk = pk_encode_prikey(payload->old_values, table->npks, buffer2, &oldpklen);
2857 30 : if (!oldpk) {
2858 0 : if (pk != buffer) cloudsync_memory_free(pk);
2859 0 : sqlite3_result_error(context, "Not enough memory to encode the primary key(s).", -1);
2860 0 : return;
2861 : }
2862 :
2863 : // mark the rows with the old primary key as deleted in the metadata (old row handling)
2864 30 : rc = local_mark_delete_meta(db, table, oldpk, oldpklen, db_version, BUMP_SEQ(data));
2865 30 : if (rc != SQLITE_OK) goto cleanup;
2866 :
2867 : // move non-sentinel metadata entries from OLD primary key to NEW primary key
2868 : // handles the case where some metadata is retained across primary key change
2869 : // see https://github.com/sqliteai/sqlite-sync/blob/main/docs/PriKey.md for more details
2870 30 : rc = local_update_move_meta(db, table, pk, pklen, oldpk, oldpklen, db_version);
2871 30 : if (rc != SQLITE_OK) goto cleanup;
2872 :
2873 : // mark a new sentinel row with the new primary key in the metadata
2874 30 : rc = local_mark_insert_sentinel_meta(db, table, pk, pklen, db_version, BUMP_SEQ(data));
2875 30 : if (rc != SQLITE_OK) goto cleanup;
2876 :
2877 : // free memory if the OLD primary key was dynamically allocated
2878 30 : if (oldpk != buffer2) cloudsync_memory_free(oldpk);
2879 30 : oldpk = NULL;
2880 30 : }
2881 :
2882 : // compare NEW and OLD values (excluding primary keys) to handle column updates
2883 1634 : for (int i=0; i<table->ncols; i++) {
2884 1297 : int col_index = table->npks + i; // Regular columns start after primary keys
2885 :
2886 1297 : if (dbutils_value_compare(payload->old_values[col_index], payload->new_values[col_index]) != 0) {
2887 : // if a column value has changed, mark it as updated in the metadata
2888 : // columns are in cid order
2889 609 : rc = local_mark_insert_or_update_meta(db, table, pk, pklen, table->col_name[i], db_version, BUMP_SEQ(data));
2890 609 : if (rc != SQLITE_OK) goto cleanup;
2891 609 : }
2892 1634 : }
2893 :
2894 : cleanup:
2895 337 : if (rc != SQLITE_OK) sqlite3_result_error(context, sqlite3_errmsg(db), -1);
2896 337 : if (pk != buffer) cloudsync_memory_free(pk);
2897 337 : if (oldpk && (oldpk != buffer2)) cloudsync_memory_free(oldpk);
2898 :
2899 337 : cloudsync_update_payload_free(payload);
2900 337 : }
2901 :
2902 : // MARK: -
2903 :
2904 6 : int cloudsync_cleanup_internal (sqlite3_context *context, const char *table_name) {
2905 6 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
2906 :
2907 : // get database reference
2908 6 : sqlite3 *db = sqlite3_context_db_handle(context);
2909 :
2910 : // init cloudsync_settings
2911 6 : if (cloudsync_context_init(db, data, context) == NULL) return SQLITE_MISUSE;
2912 :
2913 6 : cloudsync_table_context *table = table_lookup(data, table_name);
2914 6 : if (!table) return SQLITE_OK;
2915 :
2916 6 : table_remove_from_context(data, table);
2917 6 : table_free(table);
2918 :
2919 : // drop meta-table
2920 6 : char *sql = cloudsync_memory_mprintf("DROP TABLE IF EXISTS \"%w_cloudsync\";", table_name);
2921 6 : int rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
2922 6 : cloudsync_memory_free(sql);
2923 6 : if (rc != SQLITE_OK) {
2924 0 : dbutils_context_result_error(context, "Unable to drop cloudsync table %s_cloudsync in cloudsync_cleanup.", table_name);
2925 0 : sqlite3_result_error_code(context, rc);
2926 0 : return rc;
2927 : }
2928 :
2929 : // drop original triggers
2930 6 : dbutils_delete_triggers(db, table_name);
2931 6 : if (rc != SQLITE_OK) {
2932 0 : dbutils_context_result_error(context, "Unable to drop cloudsync table %s_cloudsync in cloudsync_cleanup.", table_name);
2933 0 : sqlite3_result_error_code(context, rc);
2934 0 : return rc;
2935 : }
2936 :
2937 : // remove all table related settings
2938 6 : dbutils_table_settings_set_key_value(db, context, table_name, NULL, NULL, NULL);
2939 :
2940 6 : return SQLITE_OK;
2941 6 : }
2942 :
2943 1 : void cloudsync_cleanup_all (sqlite3_context *context) {
2944 1 : char *sql = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'cloudsync_%' AND name NOT LIKE '%_cloudsync';";
2945 :
2946 1 : sqlite3 *db = sqlite3_context_db_handle(context);
2947 1 : char **result = NULL;
2948 : int nrows, ncols;
2949 : char *errmsg;
2950 1 : int rc = sqlite3_get_table(db, sql, &result, &nrows, &ncols, &errmsg);
2951 1 : if (errmsg || ncols != 1) {
2952 0 : printf("cloudsync_cleanup_all error: %s\n", errmsg ? errmsg : "invalid table");
2953 0 : goto cleanup;
2954 : }
2955 :
2956 1 : rc = SQLITE_OK;
2957 6 : for (int i = ncols; i < nrows+ncols; i+=ncols) {
2958 5 : int rc2 = cloudsync_cleanup_internal(context, result[i]);
2959 5 : if (rc2 != SQLITE_OK) rc = rc2;
2960 5 : }
2961 :
2962 2 : if (rc == SQLITE_OK) {
2963 1 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
2964 1 : data->site_id[0] = 0;
2965 1 : dbutils_settings_cleanup(db);
2966 1 : }
2967 :
2968 : cleanup:
2969 1 : sqlite3_free_table(result);
2970 1 : sqlite3_free(errmsg);
2971 1 : }
2972 :
2973 2 : void cloudsync_cleanup (sqlite3_context *context, int argc, sqlite3_value **argv) {
2974 : DEBUG_FUNCTION("cloudsync_cleanup");
2975 :
2976 2 : const char *table = (const char *)sqlite3_value_text(argv[0]);
2977 2 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
2978 2 : sqlite3 *db = sqlite3_context_db_handle(context);
2979 :
2980 2 : if (dbutils_is_star_table(table)) cloudsync_cleanup_all(context);
2981 1 : else cloudsync_cleanup_internal(context, table);
2982 :
2983 2 : if (dbutils_table_exists(db, CLOUDSYNC_TABLE_SETTINGS_NAME) == true) dbutils_update_schema_hash(db, &data->schema_hash);
2984 2 : }
2985 :
2986 2 : void cloudsync_enable_disable (sqlite3_context *context, const char *table_name, bool value) {
2987 : DEBUG_FUNCTION("cloudsync_enable_disable");
2988 :
2989 2 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
2990 2 : cloudsync_table_context *table = table_lookup(data, table_name);
2991 2 : if (!table) return;
2992 :
2993 2 : table->enabled = value;
2994 2 : }
2995 :
2996 28 : int cloudsync_enable_disable_all_callback (void *xdata, int ncols, char **values, char **names) {
2997 28 : sqlite3_context *context = (sqlite3_context *)xdata;
2998 28 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
2999 28 : bool value = data->temp_bool;
3000 :
3001 56 : for (int i=0; i<ncols; i++) {
3002 28 : const char *table_name = values[i];
3003 28 : cloudsync_table_context *table = table_lookup(data, table_name);
3004 28 : if (!table) continue;
3005 10 : table->enabled = value;
3006 10 : }
3007 :
3008 28 : return SQLITE_OK;
3009 : }
3010 :
3011 2 : void cloudsync_enable_disable_all (sqlite3_context *context, bool value) {
3012 : DEBUG_FUNCTION("cloudsync_enable_disable_all");
3013 :
3014 2 : char *sql = "SELECT name FROM sqlite_master WHERE type='table';";
3015 :
3016 2 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
3017 2 : data->temp_bool = value;
3018 2 : sqlite3 *db = sqlite3_context_db_handle(context);
3019 2 : sqlite3_exec(db, sql, cloudsync_enable_disable_all_callback, context, NULL);
3020 2 : }
3021 :
3022 2 : void cloudsync_enable (sqlite3_context *context, int argc, sqlite3_value **argv) {
3023 : DEBUG_FUNCTION("cloudsync_enable");
3024 :
3025 2 : const char *table = (const char *)sqlite3_value_text(argv[0]);
3026 2 : if (dbutils_is_star_table(table)) cloudsync_enable_disable_all(context, true);
3027 1 : else cloudsync_enable_disable(context, table, true);
3028 2 : }
3029 :
3030 2 : void cloudsync_disable (sqlite3_context *context, int argc, sqlite3_value **argv) {
3031 : DEBUG_FUNCTION("cloudsync_disable");
3032 :
3033 2 : const char *table = (const char *)sqlite3_value_text(argv[0]);
3034 2 : if (dbutils_is_star_table(table)) cloudsync_enable_disable_all(context, false);
3035 1 : else cloudsync_enable_disable(context, table, false);
3036 2 : }
3037 :
3038 2854 : void cloudsync_is_enabled (sqlite3_context *context, int argc, sqlite3_value **argv) {
3039 : DEBUG_FUNCTION("cloudsync_is_enabled");
3040 :
3041 2854 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
3042 2854 : const char *table_name = (const char *)sqlite3_value_text(argv[0]);
3043 2854 : cloudsync_table_context *table = table_lookup(data, table_name);
3044 :
3045 2854 : int result = (table && table->enabled) ? 1 : 0;
3046 2854 : sqlite3_result_int(context, result);
3047 2854 : }
3048 :
3049 111 : void cloudsync_terminate (sqlite3_context *context, int argc, sqlite3_value **argv) {
3050 : DEBUG_FUNCTION("cloudsync_terminate");
3051 :
3052 111 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
3053 :
3054 251 : for (int i=0; i<data->tables_count; ++i) {
3055 140 : if (data->tables[i]) table_free(data->tables[i]);
3056 140 : data->tables[i] = NULL;
3057 140 : }
3058 :
3059 111 : if (data->schema_version_stmt) sqlite3_finalize(data->schema_version_stmt);
3060 111 : if (data->data_version_stmt) sqlite3_finalize(data->data_version_stmt);
3061 111 : if (data->db_version_stmt) sqlite3_finalize(data->db_version_stmt);
3062 111 : if (data->getset_siteid_stmt) sqlite3_finalize(data->getset_siteid_stmt);
3063 :
3064 111 : data->schema_version_stmt = NULL;
3065 111 : data->data_version_stmt = NULL;
3066 111 : data->db_version_stmt = NULL;
3067 111 : data->getset_siteid_stmt = NULL;
3068 :
3069 : // reset the site_id so the cloudsync_context_init will be executed again
3070 : // if any other cloudsync function is called after terminate
3071 111 : data->site_id[0] = 0;
3072 :
3073 111 : sqlite3_result_int(context, 1);
3074 111 : }
3075 :
3076 : // MARK: -
3077 :
3078 111 : int cloudsync_load_siteid (sqlite3 *db, cloudsync_context *data) {
3079 : // check if site_id was already loaded
3080 111 : if (data->site_id[0] != 0) return SQLITE_OK;
3081 :
3082 : // load site_id
3083 : int size, rc;
3084 110 : char *buffer = dbutils_blob_select(db, "SELECT site_id FROM cloudsync_site_id WHERE rowid=0;", &size, data->sqlite_ctx, &rc);
3085 110 : if (!buffer) return rc;
3086 110 : if (size != UUID_LEN) return SQLITE_MISUSE;
3087 :
3088 110 : memcpy(data->site_id, buffer, UUID_LEN);
3089 110 : cloudsync_memory_free(buffer);
3090 :
3091 110 : return SQLITE_OK;
3092 111 : }
3093 :
3094 170 : int cloudsync_init_internal (sqlite3_context *context, const char *table_name, const char *algo_name, bool skip_int_pk_check) {
3095 : DEBUG_FUNCTION("cloudsync_init_internal");
3096 :
3097 : // get database reference
3098 170 : sqlite3 *db = sqlite3_context_db_handle(context);
3099 :
3100 : // retrieve global context
3101 170 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
3102 :
3103 : // sanity check table and its primary key(s)
3104 170 : if (dbutils_table_sanity_check(db, context, table_name, skip_int_pk_check) == false) {
3105 1 : return SQLITE_MISUSE;
3106 : }
3107 :
3108 : // init cloudsync_settings
3109 169 : if (cloudsync_context_init(db, data, context) == NULL) return SQLITE_MISUSE;
3110 :
3111 : // sanity check algo name (if exists)
3112 169 : table_algo algo_new = table_algo_none;
3113 169 : if (!algo_name) {
3114 17 : algo_name = CLOUDSYNC_DEFAULT_ALGO;
3115 17 : }
3116 :
3117 169 : algo_new = crdt_algo_from_name(algo_name);
3118 169 : if (algo_new == table_algo_none) {
3119 1 : dbutils_context_result_error(context, "algo name %s does not exist", crdt_algo_name);
3120 1 : return SQLITE_MISUSE;
3121 : }
3122 :
3123 : // check if table name was already augmented
3124 168 : table_algo algo_current = dbutils_table_settings_get_algo(db, table_name);
3125 :
3126 : // sanity check algorithm
3127 168 : if ((algo_new == algo_current) && (algo_current != table_algo_none)) {
3128 : // if table algorithms and the same and not none, do nothing
3129 168 : } else if ((algo_new == table_algo_none) && (algo_current == table_algo_none)) {
3130 : // nothing is written into settings because the default table_algo_crdt_cls will be used
3131 0 : algo_new = algo_current = table_algo_crdt_cls;
3132 139 : } else if ((algo_new == table_algo_none) && (algo_current != table_algo_none)) {
3133 : // algo is already written into settins so just use it
3134 0 : algo_new = algo_current;
3135 139 : } else if ((algo_new != table_algo_none) && (algo_current == table_algo_none)) {
3136 : // write table algo name in settings
3137 139 : dbutils_table_settings_set_key_value(NULL, context, table_name, "*", "algo", algo_name);
3138 139 : } else {
3139 : // error condition
3140 0 : dbutils_context_result_error(context, "%s", "Before changing a table algorithm you must call cloudsync_cleanup(table_name)");
3141 0 : return SQLITE_MISUSE;
3142 : }
3143 :
3144 : // Run the following function even if table was already augmented.
3145 : // It is safe to call the following function multiple times, if there is nothing to update nothing will be changed.
3146 : // After an alter table, in contrast, all the cloudsync triggers, tables and stmts must be recreated.
3147 :
3148 : // sync algo with table (unused in this version)
3149 : // cloudsync_sync_table_key(data, table_name, "*", CLOUDSYNC_KEY_ALGO, crdt_algo_name(algo_new));
3150 :
3151 : // check triggers
3152 168 : int rc = dbutils_check_triggers(db, table_name, algo_new);
3153 168 : if (rc != SQLITE_OK) {
3154 0 : dbutils_context_result_error(context, "An error occurred while creating triggers: %s (%d)", sqlite3_errmsg(db), rc);
3155 0 : return SQLITE_MISUSE;
3156 : }
3157 :
3158 : // check meta-table
3159 168 : rc = dbutils_check_metatable(db, table_name, algo_new);
3160 168 : if (rc != SQLITE_OK) {
3161 0 : dbutils_context_result_error(context, "An error occurred while creating metatable: %s (%d)", sqlite3_errmsg(db), rc);
3162 0 : return SQLITE_MISUSE;
3163 : }
3164 :
3165 : // add prepared statements
3166 168 : if (stmts_add_tocontext(db, data) != SQLITE_OK) {
3167 0 : dbutils_context_result_error(context, "%s", "An error occurred while trying to compile prepared SQL statements.");
3168 0 : return SQLITE_MISUSE;
3169 : }
3170 :
3171 : // add table to in-memory data context
3172 168 : if (table_add_to_context(db, data, algo_new, table_name) == false) {
3173 0 : dbutils_context_result_error(context, "An error occurred while adding %s table information to global context", table_name);
3174 0 : return SQLITE_MISUSE;
3175 : }
3176 :
3177 168 : if (cloudsync_refill_metatable(db, data, table_name) != SQLITE_OK) {
3178 0 : dbutils_context_result_error(context, "%s", "An error occurred while trying to fill the augmented table.");
3179 0 : return SQLITE_MISUSE;
3180 : }
3181 :
3182 168 : return SQLITE_OK;
3183 170 : }
3184 :
3185 1 : int cloudsync_init_all (sqlite3_context *context, const char *algo_name, bool skip_int_pk_check) {
3186 : char sql[1024];
3187 1 : snprintf(sql, sizeof(sql), "SELECT name, '%s' FROM sqlite_master WHERE type='table' and name NOT LIKE 'sqlite_%%' AND name NOT LIKE 'cloudsync_%%' AND name NOT LIKE '%%_cloudsync';", (algo_name) ? algo_name : CLOUDSYNC_DEFAULT_ALGO);
3188 :
3189 1 : sqlite3 *db = sqlite3_context_db_handle(context);
3190 1 : sqlite3_stmt *vm = NULL;
3191 1 : int rc = sqlite3_prepare_v2(db, sql, -1, &vm, NULL);
3192 1 : if (rc != SQLITE_OK) goto abort_init_all;
3193 :
3194 6 : while (1) {
3195 6 : rc = sqlite3_step(vm);
3196 6 : if (rc == SQLITE_DONE) break;
3197 5 : else if (rc != SQLITE_ROW) goto abort_init_all;
3198 :
3199 5 : const char *table = (const char *)sqlite3_column_text(vm, 0);
3200 5 : const char *algo = (const char *)sqlite3_column_text(vm, 1);
3201 5 : rc = cloudsync_init_internal(context, table, algo, skip_int_pk_check);
3202 5 : if (rc != SQLITE_OK) {cloudsync_cleanup_internal(context, table); goto abort_init_all;}
3203 : }
3204 1 : rc = SQLITE_OK;
3205 :
3206 : abort_init_all:
3207 1 : if (vm) sqlite3_finalize(vm);
3208 1 : return rc;
3209 : }
3210 :
3211 143 : void cloudsync_init (sqlite3_context *context, const char *table, const char *algo, bool skip_int_pk_check) {
3212 143 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
3213 143 : data->sqlite_ctx = context;
3214 :
3215 143 : sqlite3 *db = sqlite3_context_db_handle(context);
3216 143 : int rc = sqlite3_exec(db, "SAVEPOINT cloudsync_init;", NULL, NULL, NULL);
3217 143 : if (rc != SQLITE_OK) {
3218 0 : dbutils_context_result_error(context, "Unable to create cloudsync_init savepoint. %s", sqlite3_errmsg(db));
3219 0 : sqlite3_result_error_code(context, rc);
3220 0 : return;
3221 : }
3222 :
3223 143 : if (dbutils_is_star_table(table)) rc = cloudsync_init_all(context, algo, skip_int_pk_check);
3224 142 : else rc = cloudsync_init_internal(context, table, algo, skip_int_pk_check);
3225 :
3226 143 : if (rc == SQLITE_OK) {
3227 141 : rc = sqlite3_exec(db, "RELEASE cloudsync_init", NULL, NULL, NULL);
3228 141 : if (rc != SQLITE_OK) {
3229 0 : dbutils_context_result_error(context, "Unable to release cloudsync_init savepoint. %s", sqlite3_errmsg(db));
3230 0 : sqlite3_result_error_code(context, rc);
3231 0 : }
3232 141 : }
3233 :
3234 : // in case of error, rollback transaction
3235 143 : if (rc != SQLITE_OK) {
3236 2 : sqlite3_exec(db, "ROLLBACK TO cloudsync_init; RELEASE cloudsync_init", NULL, NULL, NULL);
3237 2 : return;
3238 : }
3239 :
3240 141 : dbutils_update_schema_hash(db, &data->schema_hash);
3241 :
3242 : // returns site_id as TEXT
3243 : char buffer[UUID_STR_MAXLEN];
3244 141 : cloudsync_uuid_v7_stringify(data->site_id, buffer, false);
3245 141 : sqlite3_result_text(context, buffer, -1, NULL);
3246 143 : }
3247 :
3248 41 : void cloudsync_init3 (sqlite3_context *context, int argc, sqlite3_value **argv) {
3249 : DEBUG_FUNCTION("cloudsync_init2");
3250 :
3251 41 : const char *table = (const char *)sqlite3_value_text(argv[0]);
3252 41 : const char *algo = (const char *)sqlite3_value_text(argv[1]);
3253 41 : bool skip_int_pk_check = (bool)sqlite3_value_int(argv[2]);
3254 :
3255 41 : cloudsync_init(context, table, algo, skip_int_pk_check);
3256 41 : }
3257 :
3258 83 : void cloudsync_init2 (sqlite3_context *context, int argc, sqlite3_value **argv) {
3259 : DEBUG_FUNCTION("cloudsync_init2");
3260 :
3261 83 : const char *table = (const char *)sqlite3_value_text(argv[0]);
3262 83 : const char *algo = (const char *)sqlite3_value_text(argv[1]);
3263 :
3264 83 : cloudsync_init(context, table, algo, false);
3265 83 : }
3266 :
3267 19 : void cloudsync_init1 (sqlite3_context *context, int argc, sqlite3_value **argv) {
3268 : DEBUG_FUNCTION("cloudsync_init1");
3269 :
3270 19 : const char *table = (const char *)sqlite3_value_text(argv[0]);
3271 :
3272 19 : cloudsync_init(context, table, NULL, false);
3273 19 : }
3274 :
3275 : // MARK: -
3276 :
3277 24 : void cloudsync_begin_alter (sqlite3_context *context, int argc, sqlite3_value **argv) {
3278 : DEBUG_FUNCTION("cloudsync_begin_alter");
3279 24 : char *errmsg = NULL;
3280 24 : char **result = NULL;
3281 :
3282 24 : const char *table_name = (const char *)sqlite3_value_text(argv[0]);
3283 :
3284 : // get database reference
3285 24 : sqlite3 *db = sqlite3_context_db_handle(context);
3286 :
3287 : // retrieve global context
3288 24 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
3289 :
3290 : // init cloudsync_settings
3291 24 : if (cloudsync_context_init(db, data, context) == NULL) {
3292 0 : sqlite3_result_error(context, "Unable to init the cloudsync context.", -1);
3293 0 : sqlite3_result_error_code(context, SQLITE_MISUSE);
3294 0 : return;
3295 : }
3296 :
3297 24 : cloudsync_table_context *table = table_lookup(data, table_name);
3298 24 : if (!table) {
3299 1 : dbutils_context_result_error(context, "Unable to find table %s", table_name);
3300 1 : sqlite3_result_error_code(context, SQLITE_MISUSE);
3301 1 : return;
3302 : }
3303 :
3304 23 : if (table->is_altering) return;
3305 :
3306 : // create a savepoint to manage the alter operations as a transaction
3307 23 : int rc = sqlite3_exec(db, "SAVEPOINT cloudsync_alter", NULL, NULL, NULL);
3308 23 : if (rc != SQLITE_OK) {
3309 0 : sqlite3_result_error(context, "Unable to create cloudsync_alter savepoint.", -1);
3310 0 : sqlite3_result_error_code(context, rc);
3311 0 : goto rollback_begin_alter;
3312 : }
3313 :
3314 : int nrows, ncols;
3315 23 : char *sql = cloudsync_memory_mprintf("SELECT name FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk;", table_name);
3316 23 : rc = sqlite3_get_table(db, sql, &result, &nrows, &ncols, &errmsg);
3317 23 : cloudsync_memory_free(sql);
3318 23 : if (errmsg || ncols != 1 || nrows != table->npks) {
3319 0 : dbutils_context_result_error(context, "Unable to get primary keys for table %s (%s)", table_name, errmsg);
3320 0 : sqlite3_result_error_code(context, SQLITE_MISUSE);
3321 0 : goto rollback_begin_alter;
3322 : }
3323 :
3324 : // drop original triggers
3325 23 : dbutils_delete_triggers(db, table_name);
3326 23 : if (rc != SQLITE_OK) {
3327 0 : dbutils_context_result_error(context, "Unable to delete triggers for table %s in cloudsync_begin_alter.", table_name);
3328 0 : sqlite3_result_error_code(context, rc);
3329 0 : goto rollback_begin_alter;
3330 : }
3331 :
3332 23 : if (table->pk_name) sqlite3_free_table(table->pk_name);
3333 23 : table->pk_name = result;
3334 23 : table->is_altering = true;
3335 23 : return;
3336 :
3337 : rollback_begin_alter:
3338 0 : sqlite3_exec(db, "ROLLBACK TO cloudsync_alter; RELEASE cloudsync_alter;", NULL, NULL, NULL);
3339 :
3340 : cleanup_begin_alter:
3341 0 : sqlite3_free_table(result);
3342 0 : sqlite3_free(errmsg);
3343 24 : }
3344 :
3345 24 : void cloudsync_commit_alter (sqlite3_context *context, int argc, sqlite3_value **argv) {
3346 : DEBUG_FUNCTION("cloudsync_commit_alter");
3347 :
3348 24 : const char *table_name = (const char *)sqlite3_value_text(argv[0]);
3349 24 : cloudsync_table_context *table = NULL;
3350 :
3351 : // get database reference
3352 24 : sqlite3 *db = sqlite3_context_db_handle(context);
3353 :
3354 : // retrieve global context
3355 24 : cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
3356 :
3357 : // init cloudsync_settings
3358 24 : if (cloudsync_context_init(db, data, context) == NULL) {
3359 0 : dbutils_context_result_error(context, "Unable to init the cloudsync context.");
3360 0 : sqlite3_result_error_code(context, SQLITE_MISUSE);
3361 0 : goto rollback_finalize_alter;
3362 : }
3363 :
3364 24 : table = table_lookup(data, table_name);
3365 24 : if (!table) {
3366 1 : dbutils_context_result_error(context, "Unable to find table context.");
3367 1 : sqlite3_result_error_code(context, SQLITE_MISUSE);
3368 1 : goto rollback_finalize_alter;
3369 : }
3370 :
3371 23 : if (!table->is_altering) return;
3372 :
3373 23 : if (!table->pk_name) {
3374 0 : dbutils_context_result_error(context, "Unable to find table context.");
3375 0 : sqlite3_result_error_code(context, SQLITE_MISUSE);
3376 0 : goto rollback_finalize_alter;
3377 : }
3378 :
3379 23 : int rc = cloudsync_finalize_alter(context, data, table);
3380 23 : if (rc != SQLITE_OK) goto rollback_finalize_alter;
3381 :
3382 : // the table is outdated, delete it and it will be reloaded in the cloudsync_init_internal
3383 23 : table_remove(data, table_name);
3384 23 : table_free(table);
3385 23 : table = NULL;
3386 :
3387 : // init again cloudsync for the table
3388 23 : table_algo algo_current = dbutils_table_settings_get_algo(db, table_name);
3389 23 : if (algo_current == table_algo_none) algo_current = dbutils_table_settings_get_algo(db, "*");
3390 23 : rc = cloudsync_init_internal(context, table_name, crdt_algo_name(algo_current), true);
3391 23 : if (rc != SQLITE_OK) goto rollback_finalize_alter;
3392 :
3393 : // release savepoint
3394 23 : rc = sqlite3_exec(db, "RELEASE cloudsync_alter", NULL, NULL, NULL);
3395 23 : if (rc != SQLITE_OK) {
3396 0 : dbutils_context_result_error(context, sqlite3_errmsg(db));
3397 0 : sqlite3_result_error_code(context, rc);
3398 0 : goto rollback_finalize_alter;
3399 : }
3400 :
3401 23 : dbutils_update_schema_hash(db, &data->schema_hash);
3402 :
3403 : // flag is reset here; note that table_remove + table_free destroyed the old table,
3404 : // and cloudsync_init_internal created a fresh one with is_altering = false (zero-alloc).
3405 :
3406 23 : return;
3407 :
3408 : rollback_finalize_alter:
3409 1 : sqlite3_exec(db, "ROLLBACK TO cloudsync_alter; RELEASE cloudsync_alter;", NULL, NULL, NULL);
3410 1 : if (table) {
3411 0 : sqlite3_free_table(table->pk_name);
3412 0 : table->pk_name = NULL;
3413 0 : table->is_altering = false;
3414 0 : }
3415 24 : }
3416 :
3417 : // MARK: - Main Entrypoint -
3418 :
3419 111 : int cloudsync_register (sqlite3 *db, char **pzErrMsg) {
3420 111 : int rc = SQLITE_OK;
3421 :
3422 : // there's no built-in way to verify if sqlite3_cloudsync_init has already been called
3423 : // for this specific database connection, we use a workaround: we attempt to retrieve the
3424 : // cloudsync_version and check for an error, an error indicates that initialization has not been performed
3425 111 : if (sqlite3_exec(db, "SELECT cloudsync_version();", NULL, NULL, NULL) == SQLITE_OK) return SQLITE_OK;
3426 :
3427 : // init memory debugger (NOOP in production)
3428 : cloudsync_memory_init(1);
3429 :
3430 : // init context
3431 109 : void *ctx = cloudsync_context_create();
3432 109 : if (!ctx) {
3433 0 : if (pzErrMsg) *pzErrMsg = "Not enought memory to create a database context";
3434 0 : return SQLITE_NOMEM;
3435 : }
3436 :
3437 : // register functions
3438 :
3439 : // PUBLIC functions
3440 109 : rc = dbutils_register_function(db, "cloudsync_version", cloudsync_version, 0, pzErrMsg, ctx, cloudsync_context_free);
3441 109 : if (rc != SQLITE_OK) return rc;
3442 :
3443 109 : rc = dbutils_register_function(db, "cloudsync_init", cloudsync_init1, 1, pzErrMsg, ctx, NULL);
3444 109 : if (rc != SQLITE_OK) return rc;
3445 :
3446 109 : rc = dbutils_register_function(db, "cloudsync_init", cloudsync_init2, 2, pzErrMsg, ctx, NULL);
3447 109 : if (rc != SQLITE_OK) return rc;
3448 :
3449 109 : rc = dbutils_register_function(db, "cloudsync_init", cloudsync_init3, 3, pzErrMsg, ctx, NULL);
3450 109 : if (rc != SQLITE_OK) return rc;
3451 :
3452 :
3453 109 : rc = dbutils_register_function(db, "cloudsync_enable", cloudsync_enable, 1, pzErrMsg, ctx, NULL);
3454 109 : if (rc != SQLITE_OK) return rc;
3455 :
3456 109 : rc = dbutils_register_function(db, "cloudsync_disable", cloudsync_disable, 1, pzErrMsg, ctx, NULL);
3457 109 : if (rc != SQLITE_OK) return rc;
3458 :
3459 109 : rc = dbutils_register_function(db, "cloudsync_is_enabled", cloudsync_is_enabled, 1, pzErrMsg, ctx, NULL);
3460 109 : if (rc != SQLITE_OK) return rc;
3461 :
3462 109 : rc = dbutils_register_function(db, "cloudsync_cleanup", cloudsync_cleanup, 1, pzErrMsg, ctx, NULL);
3463 109 : if (rc != SQLITE_OK) return rc;
3464 :
3465 109 : rc = dbutils_register_function(db, "cloudsync_terminate", cloudsync_terminate, 0, pzErrMsg, ctx, NULL);
3466 109 : if (rc != SQLITE_OK) return rc;
3467 :
3468 109 : rc = dbutils_register_function(db, "cloudsync_set", cloudsync_set, 2, pzErrMsg, ctx, NULL);
3469 109 : if (rc != SQLITE_OK) return rc;
3470 :
3471 109 : rc = dbutils_register_function(db, "cloudsync_set_table", cloudsync_set_table, 3, pzErrMsg, ctx, NULL);
3472 109 : if (rc != SQLITE_OK) return rc;
3473 :
3474 109 : rc = dbutils_register_function(db, "cloudsync_set_column", cloudsync_set_column, 4, pzErrMsg, ctx, NULL);
3475 109 : if (rc != SQLITE_OK) return rc;
3476 :
3477 109 : rc = dbutils_register_function(db, "cloudsync_siteid", cloudsync_siteid, 0, pzErrMsg, ctx, NULL);
3478 109 : if (rc != SQLITE_OK) return rc;
3479 :
3480 109 : rc = dbutils_register_function(db, "cloudsync_db_version", cloudsync_db_version, 0, pzErrMsg, ctx, NULL);
3481 109 : if (rc != SQLITE_OK) return rc;
3482 :
3483 109 : rc = dbutils_register_function(db, "cloudsync_db_version_next", cloudsync_db_version_next, 0, pzErrMsg, ctx, NULL);
3484 109 : if (rc != SQLITE_OK) return rc;
3485 :
3486 109 : rc = dbutils_register_function(db, "cloudsync_db_version_next", cloudsync_db_version_next, 1, pzErrMsg, ctx, NULL);
3487 109 : if (rc != SQLITE_OK) return rc;
3488 :
3489 109 : rc = dbutils_register_function(db, "cloudsync_begin_alter", cloudsync_begin_alter, 1, pzErrMsg, ctx, NULL);
3490 109 : if (rc != SQLITE_OK) return rc;
3491 :
3492 109 : rc = dbutils_register_function(db, "cloudsync_commit_alter", cloudsync_commit_alter, 1, pzErrMsg, ctx, NULL);
3493 109 : if (rc != SQLITE_OK) return rc;
3494 :
3495 109 : rc = dbutils_register_function(db, "cloudsync_uuid", cloudsync_uuid, 0, pzErrMsg, ctx, NULL);
3496 109 : if (rc != SQLITE_OK) return rc;
3497 :
3498 : // PAYLOAD
3499 109 : rc = dbutils_register_aggregate(db, "cloudsync_payload_encode", cloudsync_payload_encode_step, cloudsync_payload_encode_final, -1, pzErrMsg, ctx, NULL);
3500 109 : if (rc != SQLITE_OK) return rc;
3501 :
3502 109 : rc = dbutils_register_function(db, "cloudsync_payload_decode", cloudsync_payload_decode, -1, pzErrMsg, ctx, NULL);
3503 109 : if (rc != SQLITE_OK) return rc;
3504 :
3505 : #ifdef CLOUDSYNC_DESKTOP_OS
3506 109 : rc = dbutils_register_function(db, "cloudsync_payload_save", cloudsync_payload_save, 1, pzErrMsg, ctx, NULL);
3507 109 : if (rc != SQLITE_OK) return rc;
3508 :
3509 109 : rc = dbutils_register_function(db, "cloudsync_payload_load", cloudsync_payload_load, 1, pzErrMsg, ctx, NULL);
3510 109 : if (rc != SQLITE_OK) return rc;
3511 : #endif
3512 :
3513 : // PRIVATE functions
3514 109 : rc = dbutils_register_function(db, "cloudsync_is_sync", cloudsync_is_sync, 1, pzErrMsg, ctx, NULL);
3515 109 : if (rc != SQLITE_OK) return rc;
3516 :
3517 109 : rc = dbutils_register_function(db, "cloudsync_insert", cloudsync_insert, -1, pzErrMsg, ctx, NULL);
3518 109 : if (rc != SQLITE_OK) return rc;
3519 :
3520 109 : rc = dbutils_register_aggregate(db, "cloudsync_update", cloudsync_update_step, cloudsync_update_final, 3, pzErrMsg, ctx, NULL);
3521 109 : if (rc != SQLITE_OK) return rc;
3522 :
3523 109 : rc = dbutils_register_function(db, "cloudsync_delete", cloudsync_delete, -1, pzErrMsg, ctx, NULL);
3524 109 : if (rc != SQLITE_OK) return rc;
3525 :
3526 109 : rc = dbutils_register_function(db, "cloudsync_col_value", cloudsync_col_value, 3, pzErrMsg, ctx, NULL);
3527 109 : if (rc != SQLITE_OK) return rc;
3528 :
3529 109 : rc = dbutils_register_function(db, "cloudsync_pk_encode", cloudsync_pk_encode, -1, pzErrMsg, ctx, NULL);
3530 109 : if (rc != SQLITE_OK) return rc;
3531 :
3532 109 : rc = dbutils_register_function(db, "cloudsync_pk_decode", cloudsync_pk_decode, 2, pzErrMsg, ctx, NULL);
3533 109 : if (rc != SQLITE_OK) return rc;
3534 :
3535 109 : rc = dbutils_register_function(db, "cloudsync_seq", cloudsync_seq, 0, pzErrMsg, ctx, NULL);
3536 109 : if (rc != SQLITE_OK) return rc;
3537 :
3538 : // NETWORK LAYER
3539 : #ifndef CLOUDSYNC_OMIT_NETWORK
3540 : rc = cloudsync_network_register(db, pzErrMsg, ctx);
3541 : if (rc != SQLITE_OK) return rc;
3542 : #endif
3543 :
3544 109 : cloudsync_context *data = (cloudsync_context *)ctx;
3545 109 : sqlite3_commit_hook(db, cloudsync_commit_hook, ctx);
3546 109 : sqlite3_rollback_hook(db, cloudsync_rollback_hook, ctx);
3547 :
3548 : // register eponymous only changes virtual table
3549 109 : rc = cloudsync_vtab_register_changes (db, data);
3550 109 : if (rc != SQLITE_OK) return rc;
3551 :
3552 : // load config, if exists
3553 109 : if (cloudsync_config_exists(db)) {
3554 0 : cloudsync_context_init(db, ctx, NULL);
3555 :
3556 : // make sure to update internal version to current version
3557 0 : dbutils_settings_set_key_value(db, NULL, CLOUDSYNC_KEY_LIBVERSION, CLOUDSYNC_VERSION);
3558 0 : }
3559 :
3560 109 : return SQLITE_OK;
3561 111 : }
3562 :
3563 111 : APIEXPORT int sqlite3_cloudsync_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi) {
3564 : DEBUG_FUNCTION("sqlite3_cloudsync_init");
3565 :
3566 : #ifndef SQLITE_CORE
3567 : SQLITE_EXTENSION_INIT2(pApi);
3568 : #endif
3569 :
3570 111 : return cloudsync_register(db, pzErrMsg);
3571 : }
|