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