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