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