Line data Source code
1 : //
2 : // database_sqlite.c
3 : // cloudsync
4 : //
5 : // Created by Marco Bambini on 03/12/25.
6 : //
7 :
8 : #include "../cloudsync.h"
9 : #include "../database.h"
10 : #include "../dbutils.h"
11 : #include "../utils.h"
12 : #include "../sql.h"
13 :
14 : #include <inttypes.h>
15 : #include <string.h>
16 : #include <stdlib.h>
17 :
18 : #ifndef SQLITE_CORE
19 : #include "sqlite3ext.h"
20 : #else
21 : #include "sqlite3.h"
22 : #endif
23 :
24 : #ifndef SQLITE_CORE
25 : SQLITE_EXTENSION_INIT3
26 : #endif
27 :
28 : // MARK: - SQL -
29 :
30 0 : char *sql_build_drop_table (const char *table_name, char *buffer, int bsize, bool is_meta) {
31 0 : char *sql = NULL;
32 :
33 0 : if (is_meta) {
34 0 : sql = sqlite3_snprintf(bsize, buffer, "DROP TABLE IF EXISTS \"%w_cloudsync\";", table_name);
35 0 : } else {
36 0 : sql = sqlite3_snprintf(bsize, buffer, "DROP TABLE IF EXISTS \"%w\";", table_name);
37 : }
38 :
39 0 : return sql;
40 : }
41 :
42 5147 : char *sql_escape_identifier (const char *name, char *buffer, size_t bsize) {
43 5147 : return sqlite3_snprintf((int)bsize, buffer, "%q", name);
44 : }
45 :
46 244 : char *sql_build_select_nonpk_by_pk (cloudsync_context *data, const char *table_name, const char *schema) {
47 244 : UNUSED_PARAMETER(schema);
48 244 : char *sql = NULL;
49 :
50 : /*
51 : This SQL statement dynamically generates a SELECT query for a specified table.
52 : It uses Common Table Expressions (CTEs) to construct the column names and
53 : primary key conditions based on the table schema, which is obtained through
54 : the `pragma_table_info` function.
55 :
56 : 1. `col_names` CTE:
57 : - Retrieves a comma-separated list of non-primary key column names from
58 : the specified table's schema.
59 :
60 : 2. `pk_where` CTE:
61 : - Retrieves a condition string representing the primary key columns in the
62 : format: "column1=? AND column2=? AND ...", used to create the WHERE clause
63 : for selecting rows based on primary key values.
64 :
65 : 3. Final SELECT:
66 : - Constructs the complete SELECT statement as a string, combining:
67 : - Column names from `col_names`.
68 : - The target table name.
69 : - The WHERE clause conditions from `pk_where`.
70 :
71 : The resulting query can be used to select rows from the table based on primary
72 : key values, and can be executed within the application to retrieve data dynamically.
73 : */
74 :
75 : // Unfortunately in SQLite column names (or table names) cannot be bound parameters in a SELECT statement
76 : // otherwise we should have used something like SELECT 'SELECT ? FROM %w WHERE rowid=?';
77 : char buffer[1024];
78 244 : char *singlequote_escaped_table_name = sql_escape_identifier(table_name, buffer, sizeof(buffer));
79 :
80 : #if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES
81 : if (table->rowid_only) {
82 : sql = memory_mprintf(SQL_BUILD_SELECT_NONPK_COLS_BY_ROWID, table->name, table->name);
83 : goto process_process;
84 : }
85 : #endif
86 :
87 244 : sql = cloudsync_memory_mprintf(SQL_BUILD_SELECT_NONPK_COLS_BY_PK, table_name, table_name, singlequote_escaped_table_name);
88 :
89 : #if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES
90 : process_process:
91 : #endif
92 244 : if (!sql) return NULL;
93 :
94 244 : char *query = NULL;
95 244 : int rc = database_select_text(data, sql, &query);
96 244 : cloudsync_memory_free(sql);
97 :
98 244 : return (rc == DBRES_OK) ? query : NULL;
99 244 : }
100 :
101 283 : char *sql_build_delete_by_pk (cloudsync_context *data, const char *table_name, const char *schema) {
102 283 : UNUSED_PARAMETER(schema);
103 : char buffer[1024];
104 283 : char *singlequote_escaped_table_name = sql_escape_identifier(table_name, buffer, sizeof(buffer));
105 283 : char *sql = cloudsync_memory_mprintf(SQL_BUILD_DELETE_ROW_BY_PK, table_name, singlequote_escaped_table_name);
106 283 : if (!sql) return NULL;
107 :
108 283 : char *query = NULL;
109 283 : int rc = database_select_text(data, sql, &query);
110 283 : cloudsync_memory_free(sql);
111 :
112 283 : return (rc == DBRES_OK) ? query : NULL;
113 283 : }
114 :
115 283 : char *sql_build_insert_pk_ignore (cloudsync_context *data, const char *table_name, const char *schema) {
116 283 : UNUSED_PARAMETER(schema);
117 : char buffer[1024];
118 283 : char *singlequote_escaped_table_name = sql_escape_identifier(table_name, buffer, sizeof(buffer));
119 283 : char *sql = cloudsync_memory_mprintf(SQL_BUILD_INSERT_PK_IGNORE, table_name, table_name, singlequote_escaped_table_name);
120 283 : if (!sql) return NULL;
121 :
122 283 : char *query = NULL;
123 283 : int rc = database_select_text(data, sql, &query);
124 283 : cloudsync_memory_free(sql);
125 :
126 283 : return (rc == DBRES_OK) ? query : NULL;
127 283 : }
128 :
129 1082 : char *sql_build_upsert_pk_and_col (cloudsync_context *data, const char *table_name, const char *colname, const char *schema) {
130 1082 : UNUSED_PARAMETER(schema);
131 : char buffer[1024];
132 : char buffer2[1024];
133 1082 : char *singlequote_escaped_table_name = sql_escape_identifier(table_name, buffer, sizeof(buffer));
134 1082 : char *singlequote_escaped_col_name = sql_escape_identifier(colname, buffer2, sizeof(buffer2));
135 1082 : char *sql = cloudsync_memory_mprintf(
136 1082 : SQL_BUILD_UPSERT_PK_AND_COL,
137 1082 : table_name,
138 1082 : table_name,
139 1082 : singlequote_escaped_table_name,
140 1082 : singlequote_escaped_col_name,
141 1082 : singlequote_escaped_col_name
142 : );
143 1082 : if (!sql) return NULL;
144 :
145 1082 : char *query = NULL;
146 1082 : int rc = database_select_text(data, sql, &query);
147 1082 : cloudsync_memory_free(sql);
148 :
149 1082 : return (rc == DBRES_OK) ? query : NULL;
150 1082 : }
151 :
152 440 : char *sql_build_upsert_pk_and_multi_cols (cloudsync_context *data, const char *table_name, const char **colnames, int ncolnames, const char *schema) {
153 440 : UNUSED_PARAMETER(schema);
154 440 : if (ncolnames <= 0 || !colnames) return NULL;
155 :
156 : // Get PK column names via pragma_table_info (same approach as database_pk_names)
157 440 : char **pk_names = NULL;
158 440 : int npks = 0;
159 440 : int rc = database_pk_names(data, table_name, &pk_names, &npks);
160 440 : if (rc != DBRES_OK || npks <= 0 || !pk_names) return NULL;
161 :
162 : // Build column list: "pk1","pk2","col_a","col_b"
163 440 : char *col_list = cloudsync_memory_mprintf("\"%w\"", pk_names[0]);
164 440 : if (!col_list) goto fail;
165 833 : for (int i = 1; i < npks; i++) {
166 393 : char *prev = col_list;
167 393 : col_list = cloudsync_memory_mprintf("%s,\"%w\"", prev, pk_names[i]);
168 393 : cloudsync_memory_free(prev);
169 393 : if (!col_list) goto fail;
170 393 : }
171 1347 : for (int i = 0; i < ncolnames; i++) {
172 907 : char *prev = col_list;
173 907 : col_list = cloudsync_memory_mprintf("%s,\"%w\"", prev, colnames[i]);
174 907 : cloudsync_memory_free(prev);
175 907 : if (!col_list) goto fail;
176 907 : }
177 :
178 : // Build bind list: ?,?,?,?
179 440 : int total = npks + ncolnames;
180 440 : char *binds = (char *)cloudsync_memory_alloc(total * 2);
181 440 : if (!binds) { cloudsync_memory_free(col_list); goto fail; }
182 440 : int pos = 0;
183 2180 : for (int i = 0; i < total; i++) {
184 1740 : if (i > 0) binds[pos++] = ',';
185 1740 : binds[pos++] = '?';
186 1740 : }
187 440 : binds[pos] = '\0';
188 :
189 : // Build excluded set: "col_a"=EXCLUDED."col_a","col_b"=EXCLUDED."col_b"
190 440 : char *excl = cloudsync_memory_mprintf("\"%w\"=EXCLUDED.\"%w\"", colnames[0], colnames[0]);
191 440 : if (!excl) { cloudsync_memory_free(col_list); cloudsync_memory_free(binds); goto fail; }
192 907 : for (int i = 1; i < ncolnames; i++) {
193 467 : char *prev = excl;
194 467 : excl = cloudsync_memory_mprintf("%s,\"%w\"=EXCLUDED.\"%w\"", prev, colnames[i], colnames[i]);
195 467 : cloudsync_memory_free(prev);
196 467 : if (!excl) { cloudsync_memory_free(col_list); cloudsync_memory_free(binds); goto fail; }
197 467 : }
198 :
199 : // Assemble final SQL
200 440 : char *sql = cloudsync_memory_mprintf(
201 : "INSERT INTO \"%w\" (%s) VALUES (%s) ON CONFLICT DO UPDATE SET %s;",
202 440 : table_name, col_list, binds, excl
203 : );
204 :
205 440 : cloudsync_memory_free(col_list);
206 440 : cloudsync_memory_free(binds);
207 440 : cloudsync_memory_free(excl);
208 1273 : for (int i = 0; i < npks; i++) cloudsync_memory_free(pk_names[i]);
209 440 : cloudsync_memory_free(pk_names);
210 440 : return sql;
211 :
212 : fail:
213 0 : if (pk_names) {
214 0 : for (int i = 0; i < npks; i++) cloudsync_memory_free(pk_names[i]);
215 0 : cloudsync_memory_free(pk_names);
216 0 : }
217 0 : return NULL;
218 440 : }
219 :
220 417 : char *sql_build_update_pk_and_multi_cols (cloudsync_context *data, const char *table_name, const char **colnames, int ncolnames, const char *schema) {
221 417 : UNUSED_PARAMETER(schema);
222 417 : if (ncolnames <= 0 || !colnames) return NULL;
223 :
224 : // Get PK column names
225 417 : char **pk_names = NULL;
226 417 : int npks = 0;
227 417 : int rc = database_pk_names(data, table_name, &pk_names, &npks);
228 417 : if (rc != DBRES_OK || npks <= 0 || !pk_names) return NULL;
229 :
230 : // Build SET clause: "col_a"=?npks+1,"col_b"=?npks+2
231 : // Uses numbered parameters to match merge_flush_pending bind order:
232 : // positions 1..npks are PKs, npks+1..npks+ncolnames are column values.
233 417 : char *set_clause = cloudsync_memory_mprintf("\"%w\"=?%d", colnames[0], npks + 1);
234 417 : if (!set_clause) goto fail;
235 741 : for (int i = 1; i < ncolnames; i++) {
236 324 : char *prev = set_clause;
237 324 : set_clause = cloudsync_memory_mprintf("%s,\"%w\"=?%d", prev, colnames[i], npks + 1 + i);
238 324 : cloudsync_memory_free(prev);
239 324 : if (!set_clause) goto fail;
240 324 : }
241 :
242 : // Build WHERE clause: "pk1"=?1 AND "pk2"=?2
243 417 : char *where_clause = cloudsync_memory_mprintf("\"%w\"=?%d", pk_names[0], 1);
244 417 : if (!where_clause) { cloudsync_memory_free(set_clause); goto fail; }
245 824 : for (int i = 1; i < npks; i++) {
246 407 : char *prev = where_clause;
247 407 : where_clause = cloudsync_memory_mprintf("%s AND \"%w\"=?%d", prev, pk_names[i], 1 + i);
248 407 : cloudsync_memory_free(prev);
249 407 : if (!where_clause) { cloudsync_memory_free(set_clause); goto fail; }
250 407 : }
251 :
252 : // Assemble: UPDATE "table" SET ... WHERE ...
253 417 : char *sql = cloudsync_memory_mprintf(
254 : "UPDATE \"%w\" SET %s WHERE %s;",
255 417 : table_name, set_clause, where_clause
256 : );
257 :
258 417 : cloudsync_memory_free(set_clause);
259 417 : cloudsync_memory_free(where_clause);
260 1241 : for (int i = 0; i < npks; i++) cloudsync_memory_free(pk_names[i]);
261 417 : cloudsync_memory_free(pk_names);
262 417 : return sql;
263 :
264 : fail:
265 0 : if (pk_names) {
266 0 : for (int i = 0; i < npks; i++) cloudsync_memory_free(pk_names[i]);
267 0 : cloudsync_memory_free(pk_names);
268 0 : }
269 0 : return NULL;
270 417 : }
271 :
272 1081 : char *sql_build_select_cols_by_pk (cloudsync_context *data, const char *table_name, const char *colname, const char *schema) {
273 1081 : UNUSED_PARAMETER(schema);
274 1081 : char *colnamequote = "\"";
275 : char buffer[1024];
276 : char buffer2[1024];
277 1081 : char *singlequote_escaped_table_name = sql_escape_identifier(table_name, buffer, sizeof(buffer));
278 1081 : char *singlequote_escaped_col_name = sql_escape_identifier(colname, buffer2, sizeof(buffer2));
279 1081 : char *sql = cloudsync_memory_mprintf(
280 1081 : SQL_BUILD_SELECT_COLS_BY_PK_FMT,
281 1081 : table_name,
282 1081 : colnamequote,
283 1081 : singlequote_escaped_col_name,
284 1081 : colnamequote,
285 1081 : singlequote_escaped_table_name
286 : );
287 1081 : if (!sql) return NULL;
288 :
289 1081 : char *query = NULL;
290 1081 : int rc = database_select_text(data, sql, &query);
291 1081 : cloudsync_memory_free(sql);
292 :
293 1081 : return (rc == DBRES_OK) ? query : NULL;
294 1081 : }
295 :
296 283 : char *sql_build_rekey_pk_and_reset_version_except_col (cloudsync_context *data, const char *table_name, const char *except_col) {
297 283 : UNUSED_PARAMETER(data);
298 :
299 283 : char *meta_ref = database_build_meta_ref(NULL, table_name);
300 283 : if (!meta_ref) return NULL;
301 :
302 283 : char *result = cloudsync_memory_mprintf(SQL_CLOUDSYNC_REKEY_PK_AND_RESET_VERSION_EXCEPT_COL, meta_ref, except_col);
303 283 : cloudsync_memory_free(meta_ref);
304 283 : return result;
305 283 : }
306 :
307 283 : char *database_table_schema (const char *table_name) {
308 283 : return NULL;
309 : }
310 :
311 566 : char *database_build_meta_ref (const char *schema, const char *table_name) {
312 : // schema unused in SQLite
313 566 : return cloudsync_memory_mprintf("%s_cloudsync", table_name);
314 : }
315 :
316 283 : char *database_build_base_ref (const char *schema, const char *table_name) {
317 : // schema unused in SQLite
318 283 : return cloudsync_string_dup(table_name);
319 : }
320 :
321 71 : char *database_build_blocks_ref (const char *schema, const char *table_name) {
322 : // schema unused in SQLite
323 71 : return cloudsync_memory_mprintf("%s_cloudsync_blocks", table_name);
324 : }
325 :
326 : // SQLite version: schema parameter unused (SQLite has no schemas).
327 11 : char *sql_build_delete_cols_not_in_schema_query (const char *schema, const char *table_name, const char *meta_ref, const char *pkcol) {
328 11 : UNUSED_PARAMETER(schema);
329 11 : return cloudsync_memory_mprintf(
330 : "DELETE FROM \"%w\" WHERE col_name NOT IN ("
331 : "SELECT name FROM pragma_table_info('%q') "
332 : "UNION SELECT '%s'"
333 : ");",
334 11 : meta_ref, table_name, pkcol
335 : );
336 : }
337 :
338 307 : char *sql_build_pk_collist_query (const char *schema, const char *table_name) {
339 307 : UNUSED_PARAMETER(schema);
340 307 : return cloudsync_memory_mprintf(
341 : "SELECT group_concat('\"' || format('%%w', name) || '\"', ',') "
342 : "FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk;",
343 307 : table_name
344 : );
345 : }
346 :
347 0 : char *sql_build_pk_decode_selectlist_query (const char *schema, const char *table_name) {
348 0 : UNUSED_PARAMETER(schema);
349 0 : return cloudsync_memory_mprintf(
350 : "SELECT group_concat("
351 : "'cloudsync_pk_decode(pk, ' || pk || ') AS ' || '\"' || format('%%w', name) || '\"', ','"
352 : ") "
353 : "FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk;",
354 0 : table_name
355 : );
356 : }
357 :
358 11 : char *sql_build_pk_qualified_collist_query (const char *schema, const char *table_name) {
359 11 : UNUSED_PARAMETER(schema);
360 :
361 : char buffer[1024];
362 11 : char *singlequote_escaped_table_name = sql_escape_identifier(table_name, buffer, sizeof(buffer));
363 11 : if (!singlequote_escaped_table_name) return NULL;
364 :
365 11 : return cloudsync_memory_mprintf(
366 : "SELECT group_concat('\"%w\".\"' || format('%%w', name) || '\"', ',') "
367 11 : "FROM pragma_table_info('%s') WHERE pk>0 ORDER BY pk;", singlequote_escaped_table_name, singlequote_escaped_table_name
368 : );
369 11 : }
370 :
371 307 : char *sql_build_insert_missing_pks_query(const char *schema, const char *table_name,
372 : const char *pkvalues_identifiers,
373 : const char *base_ref, const char *meta_ref,
374 : const char *filter) {
375 307 : UNUSED_PARAMETER(schema);
376 :
377 : // SQLite: Use NOT EXISTS with cloudsync_pk_encode (same approach as PostgreSQL).
378 : // This avoids needing pk_decode select list which requires executing a query.
379 307 : if (filter) {
380 20 : return cloudsync_memory_mprintf(
381 : "SELECT cloudsync_insert('%q', %s) "
382 : "FROM \"%w\" "
383 : "WHERE (%s) AND NOT EXISTS ("
384 : " SELECT 1 FROM \"%w\" WHERE pk = cloudsync_pk_encode(%s)"
385 : ");",
386 20 : table_name, pkvalues_identifiers, base_ref, filter, meta_ref, pkvalues_identifiers
387 : );
388 : }
389 287 : return cloudsync_memory_mprintf(
390 : "SELECT cloudsync_insert('%q', %s) "
391 : "FROM \"%w\" "
392 : "WHERE NOT EXISTS ("
393 : " SELECT 1 FROM \"%w\" WHERE pk = cloudsync_pk_encode(%s)"
394 : ");",
395 287 : table_name, pkvalues_identifiers, base_ref, meta_ref, pkvalues_identifiers
396 : );
397 307 : }
398 :
399 : // MARK: - PRIVATE -
400 :
401 8764 : static int database_select1_value (cloudsync_context *data, const char *sql, char **ptr_value, int64_t *int_value, DBTYPE expected_type) {
402 8764 : sqlite3 *db = (sqlite3 *)cloudsync_db(data);
403 :
404 : // init values and sanity check expected_type
405 8764 : if (ptr_value) *ptr_value = NULL;
406 8764 : *int_value = 0;
407 8764 : if (expected_type != DBTYPE_INTEGER && expected_type != DBTYPE_TEXT && expected_type != DBTYPE_BLOB) return SQLITE_MISUSE;
408 :
409 8764 : sqlite3_stmt *vm = NULL;
410 8764 : int rc = sqlite3_prepare_v2((sqlite3 *)db, sql, -1, &vm, NULL);
411 8764 : if (rc != SQLITE_OK) goto cleanup_select;
412 :
413 : // ensure at least one column
414 8763 : if (sqlite3_column_count(vm) < 1) {rc = SQLITE_MISMATCH; goto cleanup_select;}
415 :
416 8763 : rc = sqlite3_step(vm);
417 8763 : if (rc == SQLITE_DONE) {rc = SQLITE_OK; goto cleanup_select;} // no rows OK
418 8539 : if (rc != SQLITE_ROW) goto cleanup_select;
419 :
420 : // sanity check column type
421 8539 : DBTYPE type = (DBTYPE)sqlite3_column_type(vm, 0);
422 8539 : if (type == SQLITE_NULL) {rc = SQLITE_OK; goto cleanup_select;}
423 8499 : if (type != expected_type) {rc = SQLITE_MISMATCH; goto cleanup_select;}
424 :
425 8499 : if (expected_type == DBTYPE_INTEGER) {
426 2394 : *int_value = (int64_t)sqlite3_column_int64(vm, 0);
427 2394 : } else {
428 6105 : const void *value = (expected_type == DBTYPE_TEXT) ? (const void *)sqlite3_column_text(vm, 0) : (const void *)sqlite3_column_blob(vm, 0);
429 6105 : int len = sqlite3_column_bytes(vm, 0);
430 6105 : if (len) {
431 6105 : char *ptr = cloudsync_memory_alloc(len + 1);
432 6105 : if (!ptr) {rc = SQLITE_NOMEM; goto cleanup_select;}
433 :
434 6105 : if (len > 0 && value) memcpy(ptr, value, len);
435 6105 : if (expected_type == DBTYPE_TEXT) ptr[len] = 0; // NULL terminate in case of TEXT
436 :
437 6105 : *ptr_value = ptr;
438 6105 : *int_value = len;
439 6105 : }
440 : }
441 8499 : rc = SQLITE_OK;
442 :
443 : cleanup_select:
444 8764 : if (vm) sqlite3_finalize(vm);
445 8764 : return rc;
446 8764 : }
447 :
448 0 : static int database_select2_values (cloudsync_context *data, const char *sql, char **value, int64_t *len, int64_t *value2) {
449 0 : sqlite3 *db = (sqlite3 *)cloudsync_db(data);
450 :
451 : // init values and sanity check expected_type
452 0 : *value = NULL;
453 0 : *value2 = 0;
454 0 : *len = 0;
455 :
456 0 : sqlite3_stmt *vm = NULL;
457 0 : int rc = sqlite3_prepare_v2((sqlite3 *)db, sql, -1, &vm, NULL);
458 0 : if (rc != SQLITE_OK) goto cleanup_select;
459 :
460 : // ensure column count
461 0 : if (sqlite3_column_count(vm) < 2) {rc = SQLITE_MISMATCH; goto cleanup_select;}
462 :
463 0 : rc = sqlite3_step(vm);
464 0 : if (rc == SQLITE_DONE) {rc = SQLITE_OK; goto cleanup_select;} // no rows OK
465 0 : if (rc != SQLITE_ROW) goto cleanup_select;
466 :
467 : // sanity check column types
468 0 : if (sqlite3_column_type(vm, 0) != SQLITE_BLOB) {rc = SQLITE_MISMATCH; goto cleanup_select;}
469 0 : if (sqlite3_column_type(vm, 1) != SQLITE_INTEGER) {rc = SQLITE_MISMATCH; goto cleanup_select;}
470 :
471 : // 1st column is BLOB
472 0 : const void *blob = (const void *)sqlite3_column_blob(vm, 0);
473 0 : int blob_len = sqlite3_column_bytes(vm, 0);
474 0 : if (blob_len) {
475 0 : char *ptr = cloudsync_memory_alloc(blob_len);
476 0 : if (!ptr) {rc = SQLITE_NOMEM; goto cleanup_select;}
477 :
478 0 : if (blob_len > 0 && blob) memcpy(ptr, blob, blob_len);
479 0 : *value = ptr;
480 0 : *len = blob_len;
481 0 : }
482 :
483 : // 2nd column is INTEGER
484 0 : *value2 = (int64_t)sqlite3_column_int64(vm, 1);
485 :
486 0 : rc = SQLITE_OK;
487 :
488 : cleanup_select:
489 0 : if (vm) sqlite3_finalize(vm);
490 0 : return rc;
491 : }
492 :
493 2490 : bool database_system_exists (cloudsync_context *data, const char *name, const char *type) {
494 2490 : sqlite3 *db = (sqlite3 *)cloudsync_db(data);
495 2490 : sqlite3_stmt *vm = NULL;
496 2490 : bool result = false;
497 :
498 : char sql[1024];
499 2490 : snprintf(sql, sizeof(sql), "SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE type='%s' AND name=?1 COLLATE NOCASE);", type);
500 2490 : int rc = sqlite3_prepare_v2(db, sql, -1, &vm, NULL);
501 2490 : if (rc != SQLITE_OK) goto finalize;
502 :
503 2490 : rc = sqlite3_bind_text(vm, 1, name, -1, SQLITE_STATIC);
504 2490 : if (rc != SQLITE_OK) goto finalize;
505 :
506 2490 : rc = sqlite3_step(vm);
507 4980 : if (rc == SQLITE_ROW) {
508 2490 : result = (bool)sqlite3_column_int(vm, 0);
509 2490 : rc = SQLITE_OK;
510 2490 : }
511 :
512 : finalize:
513 2490 : if (rc != SQLITE_OK) DEBUG_ALWAYS("Error executing %s in dbutils_system_exists for type %s name %s (%s).", sql, type, name, sqlite3_errmsg(db));
514 2490 : if (vm) sqlite3_finalize(vm);
515 2490 : return result;
516 : }
517 :
518 : // MARK: - GENERAL -
519 :
520 29197 : int database_exec (cloudsync_context *data, const char *sql) {
521 29197 : return sqlite3_exec((sqlite3 *)cloudsync_db(data), sql, NULL, NULL, NULL);
522 : }
523 :
524 700 : int database_exec_callback (cloudsync_context *data, const char *sql, int (*callback)(void *xdata, int argc, char **values, char **names), void *xdata) {
525 700 : return sqlite3_exec((sqlite3 *)cloudsync_db(data), sql, callback, xdata, NULL);
526 : }
527 :
528 1561 : int database_write (cloudsync_context *data, const char *sql, const char **bind_values, DBTYPE bind_types[], int bind_lens[], int bind_count) {
529 1561 : sqlite3 *db = (sqlite3 *)cloudsync_db(data);
530 1561 : sqlite3_stmt *vm = NULL;
531 1561 : int rc = sqlite3_prepare_v2((sqlite3 *)db, sql, -1, &vm, NULL);
532 1561 : if (rc != SQLITE_OK) goto cleanup_write;
533 :
534 5446 : for (int i=0; i<bind_count; ++i) {
535 3887 : switch (bind_types[i]) {
536 : case SQLITE_NULL:
537 1 : rc = sqlite3_bind_null(vm, i+1);
538 1 : break;
539 : case SQLITE_TEXT:
540 3381 : rc = sqlite3_bind_text(vm, i+1, bind_values[i], bind_lens[i], SQLITE_STATIC);
541 3381 : break;
542 : case SQLITE_BLOB:
543 222 : rc = sqlite3_bind_blob(vm, i+1, bind_values[i], bind_lens[i], SQLITE_STATIC);
544 222 : break;
545 : case SQLITE_INTEGER: {
546 282 : sqlite3_int64 value = strtoll(bind_values[i], NULL, 0);
547 282 : rc = sqlite3_bind_int64(vm, i+1, value);
548 282 : } break;
549 : case SQLITE_FLOAT: {
550 1 : double value = strtod(bind_values[i], NULL);
551 1 : rc = sqlite3_bind_double(vm, i+1, value);
552 1 : } break;
553 : }
554 3887 : if (rc != SQLITE_OK) goto cleanup_write;
555 3887 : }
556 :
557 : // execute statement
558 1559 : rc = sqlite3_step(vm);
559 1559 : if (rc == SQLITE_DONE) rc = SQLITE_OK;
560 :
561 : cleanup_write:
562 1561 : if (vm) sqlite3_finalize(vm);
563 1561 : return rc;
564 : }
565 :
566 2618 : int database_select_int (cloudsync_context *data, const char *sql, int64_t *value) {
567 2618 : return database_select1_value(data, sql, NULL, value, DBTYPE_INTEGER);
568 : }
569 :
570 5915 : int database_select_text (cloudsync_context *data, const char *sql, char **value) {
571 5915 : int64_t len = 0;
572 5915 : return database_select1_value(data, sql, value, &len, DBTYPE_TEXT);
573 : }
574 :
575 231 : int database_select_blob (cloudsync_context *data, const char *sql, char **value, int64_t *len) {
576 231 : return database_select1_value(data, sql, value, len, DBTYPE_BLOB);
577 : }
578 :
579 0 : int database_select_blob_int (cloudsync_context *data, const char *sql, char **value, int64_t *len, int64_t *value2) {
580 0 : return database_select2_values(data, sql, value, len, value2);
581 : }
582 :
583 22 : const char *database_errmsg (cloudsync_context *data) {
584 22 : return sqlite3_errmsg((sqlite3 *)cloudsync_db(data));
585 : }
586 :
587 23 : int database_errcode (cloudsync_context *data) {
588 23 : return sqlite3_errcode((sqlite3 *)cloudsync_db(data));
589 : }
590 :
591 4968 : bool database_in_transaction (cloudsync_context *data) {
592 4968 : sqlite3 *db = (sqlite3 *)cloudsync_db(data);
593 4968 : bool in_transaction = (sqlite3_get_autocommit(db) != true);
594 4968 : return in_transaction;
595 : }
596 :
597 1556 : bool database_table_exists (cloudsync_context *data, const char *name, const char *schema) {
598 1556 : UNUSED_PARAMETER(schema);
599 1556 : return database_system_exists(data, name, "table");
600 : }
601 :
602 1263 : bool database_internal_table_exists (cloudsync_context *data, const char *name) {
603 1263 : return database_table_exists(data, name, NULL);
604 : }
605 :
606 933 : bool database_trigger_exists (cloudsync_context *data, const char *name) {
607 933 : return database_system_exists(data, name, "trigger");
608 : }
609 :
610 568 : int database_count_pk (cloudsync_context *data, const char *table_name, bool not_null, const char *schema) {
611 568 : UNUSED_PARAMETER(schema);
612 : char buffer[1024];
613 568 : char *sql = NULL;
614 :
615 568 : if (not_null) {
616 0 : sql = sqlite3_snprintf(sizeof(buffer), buffer, "SELECT count(*) FROM pragma_table_info('%q') WHERE pk>0 AND \"notnull\"=1;", table_name);
617 0 : } else {
618 568 : sql = sqlite3_snprintf(sizeof(buffer), buffer, "SELECT count(*) FROM pragma_table_info('%q') WHERE pk>0;", table_name);
619 : }
620 :
621 568 : int64_t count = 0;
622 568 : int rc = database_select_int(data, sql, &count);
623 568 : if (rc != DBRES_OK) return -1;
624 568 : return (int)count;
625 568 : }
626 :
627 283 : int database_count_nonpk (cloudsync_context *data, const char *table_name, const char *schema) {
628 283 : UNUSED_PARAMETER(schema);
629 : char buffer[1024];
630 283 : char *sql = NULL;
631 :
632 283 : sql = sqlite3_snprintf(sizeof(buffer), buffer, "SELECT count(*) FROM pragma_table_info('%q') WHERE pk=0;", table_name);
633 283 : int64_t count = 0;
634 283 : int rc = database_select_int(data, sql, &count);
635 283 : if (rc != DBRES_OK) return -1;
636 283 : return (int)count;
637 283 : }
638 :
639 133 : int database_count_int_pk (cloudsync_context *data, const char *table_name, const char *schema) {
640 133 : UNUSED_PARAMETER(schema);
641 : char buffer[1024];
642 133 : char *sql = sqlite3_snprintf(sizeof(buffer), buffer, "SELECT count(*) FROM pragma_table_info('%q') WHERE pk=1 AND \"type\" LIKE '%%INT%%';", table_name);
643 :
644 133 : int64_t count = 0;
645 133 : int rc = database_select_int(data, sql, &count);
646 133 : if (rc != DBRES_OK) return -1;
647 133 : return (int)count;
648 133 : }
649 :
650 283 : int database_count_notnull_without_default (cloudsync_context *data, const char *table_name, const char *schema) {
651 283 : UNUSED_PARAMETER(schema);
652 : char buffer[1024];
653 283 : char *sql = sqlite3_snprintf(sizeof(buffer), buffer, "SELECT count(*) FROM pragma_table_info('%q') WHERE pk=0 AND \"notnull\"=1 AND \"dflt_value\" IS NULL;", table_name);
654 :
655 283 : int64_t count = 0;
656 283 : int rc = database_select_int(data, sql, &count);
657 283 : if (rc != DBRES_OK) return -1;
658 283 : return (int)count;
659 283 : }
660 :
661 0 : int database_cleanup (cloudsync_context *data) {
662 0 : char *sql = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'cloudsync_%' AND name NOT LIKE '%_cloudsync';";
663 0 : sqlite3 *db = (sqlite3 *)cloudsync_db(data);
664 :
665 0 : char **result = NULL;
666 0 : char *errmsg = NULL;
667 : int nrows, ncols;
668 0 : int rc = sqlite3_get_table(db, sql, &result, &nrows, &ncols, &errmsg);
669 0 : if (rc != SQLITE_OK) {
670 0 : cloudsync_set_error(data, (errmsg) ? errmsg : "Error retrieving augmented tables", rc);
671 0 : goto exit_cleanup;
672 : }
673 :
674 0 : for (int i = ncols; i < nrows+ncols; i+=ncols) {
675 0 : int rc2 = cloudsync_cleanup(data, result[i]);
676 0 : if (rc2 != SQLITE_OK) {rc = rc2; goto exit_cleanup;}
677 0 : }
678 :
679 : exit_cleanup:
680 0 : if (result) sqlite3_free_table(result);
681 0 : if (errmsg) sqlite3_free(errmsg);
682 0 : return rc;
683 : }
684 :
685 : // MARK: - TRIGGERS and META -
686 :
687 283 : int database_create_metatable (cloudsync_context *data, const char *table_name) {
688 : DEBUG_DBFUNCTION("database_create_metatable %s", table_name);
689 :
690 : // table_name cannot be longer than 512 characters so static buffer size is computed accordling to that value
691 : char buffer[2048];
692 :
693 : // WITHOUT ROWID is available starting from SQLite version 3.8.2 (2013-12-06) and later
694 283 : char *sql = sqlite3_snprintf(sizeof(buffer), buffer, "CREATE TABLE IF NOT EXISTS \"%w_cloudsync\" (pk BLOB NOT NULL, col_name TEXT NOT NULL, col_version INTEGER, db_version INTEGER, site_id INTEGER DEFAULT 0, seq INTEGER, PRIMARY KEY (pk, col_name)) WITHOUT ROWID; CREATE INDEX IF NOT EXISTS \"%w_cloudsync_db_idx\" ON \"%w_cloudsync\" (db_version);", table_name, table_name, table_name);
695 :
696 283 : int rc = database_exec(data, sql);
697 : DEBUG_SQL("\n%s", sql);
698 283 : return rc;
699 : }
700 :
701 311 : int database_create_insert_trigger (cloudsync_context *data, const char *table_name, char *trigger_when) {
702 : // NEW.prikey1, NEW.prikey2...
703 : char buffer[1024];
704 311 : char *trigger_name = sqlite3_snprintf(sizeof(buffer), buffer, "cloudsync_after_insert_%s", table_name);
705 311 : if (database_trigger_exists(data, trigger_name)) return SQLITE_OK;
706 :
707 : char buffer2[2048];
708 302 : char *sql2 = sqlite3_snprintf(sizeof(buffer2), buffer2, "SELECT group_concat('NEW.\"' || format('%%w', name) || '\"', ',') FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk;", table_name);
709 :
710 302 : char *pkclause = NULL;
711 302 : int rc = database_select_text(data, sql2, &pkclause);
712 302 : if (rc != SQLITE_OK) return rc;
713 302 : char *pkvalues = (pkclause) ? pkclause : "NEW.rowid";
714 :
715 302 : char *sql = cloudsync_memory_mprintf("CREATE TRIGGER \"%w\" AFTER INSERT ON \"%w\" %s BEGIN SELECT cloudsync_insert('%q', %s); END", trigger_name, table_name, trigger_when, table_name, pkvalues);
716 302 : if (pkclause) cloudsync_memory_free(pkclause);
717 302 : if (!sql) return SQLITE_NOMEM;
718 :
719 302 : rc = database_exec(data, sql);
720 : DEBUG_SQL("\n%s", sql);
721 302 : cloudsync_memory_free(sql);
722 302 : return rc;
723 311 : }
724 :
725 7 : int database_create_update_trigger_gos (cloudsync_context *data, const char *table_name) {
726 : // Grow Only Set
727 : // In a grow-only set, the update operation is not allowed.
728 : // A grow-only set is a type of CRDT (Conflict-free Replicated Data Type) where the only permissible operation is to add elements to the set,
729 : // without ever removing or modifying them.
730 : // Once an element is added to the set, it remains there permanently, which guarantees that the set only grows over time.
731 : char buffer[1024];
732 7 : char *trigger_name = sqlite3_snprintf(sizeof(buffer), buffer, "cloudsync_before_update_%s", table_name);
733 7 : if (database_trigger_exists(data, trigger_name)) return SQLITE_OK;
734 :
735 : char buffer2[2048+512];
736 7 : char *sql = sqlite3_snprintf(sizeof(buffer2), buffer2, "CREATE TRIGGER \"%w\" BEFORE UPDATE ON \"%w\" FOR EACH ROW WHEN cloudsync_is_enabled('%q') = 1 BEGIN SELECT RAISE(ABORT, 'Error: UPDATE operation is not allowed on table %w.'); END", trigger_name, table_name, table_name, table_name);
737 :
738 7 : int rc = database_exec(data, sql);
739 : DEBUG_SQL("\n%s", sql);
740 7 : return rc;
741 7 : }
742 :
743 304 : int database_create_update_trigger (cloudsync_context *data, const char *table_name, const char *trigger_when) {
744 : // NEW.prikey1, NEW.prikey2, OLD.prikey1, OLD.prikey2, NEW.col1, OLD.col1, NEW.col2, OLD.col2...
745 :
746 : char buffer[1024];
747 304 : char *trigger_name = sqlite3_snprintf(sizeof(buffer), buffer, "cloudsync_after_update_%s", table_name);
748 304 : if (database_trigger_exists(data, trigger_name)) return SQLITE_OK;
749 :
750 : // generate VALUES clause for all columns using a CTE to avoid compound SELECT limits
751 : // first, get all primary key columns in order
752 : char buffer2[2048];
753 295 : char *sql2 = sqlite3_snprintf(sizeof(buffer2), buffer2, "SELECT group_concat('('||quote('%q')||', NEW.\"' || format('%%w', name) || '\", OLD.\"' || format('%%w', name) || '\")', ', ') FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk;", table_name, table_name);
754 :
755 295 : char *pk_values_list = NULL;
756 295 : int rc = database_select_text(data, sql2, &pk_values_list);
757 295 : if (rc != SQLITE_OK) return rc;
758 :
759 : // then get all regular columns in order
760 295 : sql2 = sqlite3_snprintf(sizeof(buffer2), buffer2, "SELECT group_concat('('||quote('%q')||', NEW.\"' || format('%%w', name) || '\", OLD.\"' || format('%%w', name) || '\")', ', ') FROM pragma_table_info('%q') WHERE pk=0 ORDER BY cid;", table_name, table_name);
761 :
762 295 : char *col_values_list = NULL;
763 295 : rc = database_select_text(data, sql2, &col_values_list);
764 295 : if (rc != SQLITE_OK) {
765 0 : if (pk_values_list) cloudsync_memory_free(pk_values_list);
766 0 : return rc;
767 : }
768 :
769 : // build the complete VALUES query
770 295 : char *values_query = NULL;
771 295 : if (col_values_list && strlen(col_values_list) > 0) {
772 : // Table has both primary keys and regular columns
773 256 : values_query = cloudsync_memory_mprintf(
774 : "WITH column_data(table_name, new_value, old_value) AS (VALUES %s, %s) "
775 : "SELECT table_name, new_value, old_value FROM column_data",
776 256 : pk_values_list, col_values_list);
777 256 : } else {
778 : // Table has only primary keys
779 39 : values_query = cloudsync_memory_mprintf(
780 : "WITH column_data(table_name, new_value, old_value) AS (VALUES %s) "
781 : "SELECT table_name, new_value, old_value FROM column_data",
782 39 : pk_values_list);
783 : }
784 :
785 295 : if (pk_values_list) cloudsync_memory_free(pk_values_list);
786 295 : if (col_values_list) cloudsync_memory_free(col_values_list);
787 295 : if (!values_query) return SQLITE_NOMEM;
788 :
789 : // create the trigger with aggregate function
790 295 : char *sql = cloudsync_memory_mprintf(
791 : "CREATE TRIGGER \"%w\" AFTER UPDATE ON \"%w\" %s BEGIN "
792 : "SELECT cloudsync_update(table_name, new_value, old_value) FROM (%s); "
793 : "END",
794 295 : trigger_name, table_name, trigger_when, values_query);
795 :
796 295 : cloudsync_memory_free(values_query);
797 295 : if (!sql) return SQLITE_NOMEM;
798 :
799 295 : rc = database_exec(data, sql);
800 : DEBUG_SQL("\n%s", sql);
801 295 : cloudsync_memory_free(sql);
802 295 : return rc;
803 304 : }
804 :
805 7 : int database_create_delete_trigger_gos (cloudsync_context *data, const char *table_name) {
806 : // Grow Only Set
807 : // In a grow-only set, the delete operation is not allowed.
808 :
809 : char buffer[1024];
810 7 : char *trigger_name = sqlite3_snprintf(sizeof(buffer), buffer, "cloudsync_before_delete_%s", table_name);
811 7 : if (database_trigger_exists(data, trigger_name)) return SQLITE_OK;
812 :
813 : char buffer2[2048+512];
814 7 : char *sql = sqlite3_snprintf(sizeof(buffer2), buffer2, "CREATE TRIGGER \"%w\" BEFORE DELETE ON \"%w\" FOR EACH ROW WHEN cloudsync_is_enabled('%q') = 1 BEGIN SELECT RAISE(ABORT, 'Error: DELETE operation is not allowed on table %w.'); END", trigger_name, table_name, table_name, table_name);
815 :
816 7 : int rc = database_exec(data, sql);
817 : DEBUG_SQL("\n%s", sql);
818 7 : return rc;
819 7 : }
820 :
821 304 : int database_create_delete_trigger (cloudsync_context *data, const char *table_name, const char *trigger_when) {
822 : // OLD.prikey1, OLD.prikey2...
823 :
824 : char buffer[1024];
825 304 : char *trigger_name = sqlite3_snprintf(sizeof(buffer), buffer, "cloudsync_after_delete_%s", table_name);
826 304 : if (database_trigger_exists(data, trigger_name)) return SQLITE_OK;
827 :
828 : char buffer2[1024];
829 295 : char *sql2 = sqlite3_snprintf(sizeof(buffer2), buffer2, "SELECT group_concat('OLD.\"' || format('%%w', name) || '\"', ',') FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk;", table_name);
830 :
831 295 : char *pkclause = NULL;
832 295 : int rc = database_select_text(data, sql2, &pkclause);
833 295 : if (rc != SQLITE_OK) return rc;
834 295 : char *pkvalues = (pkclause) ? pkclause : "OLD.rowid";
835 :
836 295 : char *sql = cloudsync_memory_mprintf("CREATE TRIGGER \"%w\" AFTER DELETE ON \"%w\" %s BEGIN SELECT cloudsync_delete('%q',%s); END", trigger_name, table_name, trigger_when, table_name, pkvalues);
837 295 : if (pkclause) cloudsync_memory_free(pkclause);
838 295 : if (!sql) return SQLITE_NOMEM;
839 :
840 295 : rc = database_exec(data, sql);
841 : DEBUG_SQL("\n%s", sql);
842 295 : cloudsync_memory_free(sql);
843 295 : return rc;
844 304 : }
845 :
846 : // Build trigger WHEN clauses, optionally incorporating a row-level filter.
847 : // INSERT/UPDATE use NEW-prefixed filter, DELETE uses OLD-prefixed filter.
848 : // Returns dynamically-allocated strings that must be freed with cloudsync_memory_free.
849 311 : static void database_build_trigger_when(
850 : cloudsync_context *data, const char *table_name, const char *filter,
851 : char **when_new_out, char **when_old_out)
852 : {
853 311 : char *new_filter_str = NULL;
854 311 : char *old_filter_str = NULL;
855 :
856 311 : if (filter) {
857 : char sql_cols[1024];
858 40 : sqlite3_snprintf(sizeof(sql_cols), sql_cols,
859 20 : "SELECT name FROM pragma_table_info('%q') ORDER BY cid;", table_name);
860 :
861 : char *col_names[256];
862 20 : int ncols = 0;
863 :
864 20 : sqlite3_stmt *col_vm = NULL;
865 20 : int col_rc = sqlite3_prepare_v2((sqlite3 *)cloudsync_db(data), sql_cols, -1, &col_vm, NULL);
866 20 : if (col_rc == SQLITE_OK) {
867 90 : while (sqlite3_step(col_vm) == SQLITE_ROW && ncols < 256) {
868 70 : const char *name = (const char *)sqlite3_column_text(col_vm, 0);
869 70 : if (name) {
870 70 : char *dup = cloudsync_memory_mprintf("%s", name);
871 70 : if (!dup) break;
872 70 : col_names[ncols++] = dup;
873 70 : }
874 : }
875 20 : sqlite3_finalize(col_vm);
876 20 : }
877 :
878 20 : if (ncols > 0) {
879 20 : new_filter_str = cloudsync_filter_add_row_prefix(filter, "NEW", col_names, ncols);
880 20 : old_filter_str = cloudsync_filter_add_row_prefix(filter, "OLD", col_names, ncols);
881 90 : for (int i = 0; i < ncols; ++i) cloudsync_memory_free(col_names[i]);
882 20 : }
883 20 : }
884 :
885 311 : if (new_filter_str) {
886 20 : *when_new_out = cloudsync_memory_mprintf(
887 20 : "FOR EACH ROW WHEN cloudsync_is_sync('%q') = 0 AND (%s)", table_name, new_filter_str);
888 20 : } else {
889 291 : *when_new_out = cloudsync_memory_mprintf(
890 291 : "FOR EACH ROW WHEN cloudsync_is_sync('%q') = 0", table_name);
891 : }
892 :
893 311 : if (old_filter_str) {
894 20 : *when_old_out = cloudsync_memory_mprintf(
895 20 : "FOR EACH ROW WHEN cloudsync_is_sync('%q') = 0 AND (%s)", table_name, old_filter_str);
896 20 : } else {
897 291 : *when_old_out = cloudsync_memory_mprintf(
898 291 : "FOR EACH ROW WHEN cloudsync_is_sync('%q') = 0", table_name);
899 : }
900 :
901 311 : if (new_filter_str) cloudsync_memory_free(new_filter_str);
902 311 : if (old_filter_str) cloudsync_memory_free(old_filter_str);
903 311 : }
904 :
905 311 : int database_create_triggers (cloudsync_context *data, const char *table_name, table_algo algo, const char *filter) {
906 : DEBUG_DBFUNCTION("database_create_triggers %s", table_name);
907 :
908 311 : if (dbutils_settings_check_version(data, "0.8.25") <= 0) {
909 0 : database_delete_triggers(data, table_name);
910 0 : }
911 :
912 311 : char *trigger_when_new = NULL;
913 311 : char *trigger_when_old = NULL;
914 311 : database_build_trigger_when(data, table_name, filter,
915 : &trigger_when_new, &trigger_when_old);
916 :
917 311 : if (!trigger_when_new || !trigger_when_old) {
918 0 : if (trigger_when_new) cloudsync_memory_free(trigger_when_new);
919 0 : if (trigger_when_old) cloudsync_memory_free(trigger_when_old);
920 0 : return SQLITE_NOMEM;
921 : }
922 :
923 : // INSERT TRIGGER (uses NEW prefix)
924 311 : int rc = database_create_insert_trigger(data, table_name, trigger_when_new);
925 311 : if (rc != SQLITE_OK) goto done;
926 :
927 : // UPDATE TRIGGER (uses NEW prefix)
928 311 : if (algo == table_algo_crdt_gos) rc = database_create_update_trigger_gos(data, table_name);
929 304 : else rc = database_create_update_trigger(data, table_name, trigger_when_new);
930 311 : if (rc != SQLITE_OK) goto done;
931 :
932 : // DELETE TRIGGER (uses OLD prefix)
933 615 : if (algo == table_algo_crdt_gos) rc = database_create_delete_trigger_gos(data, table_name);
934 304 : else rc = database_create_delete_trigger(data, table_name, trigger_when_old);
935 :
936 : done:
937 311 : if (rc != SQLITE_OK) DEBUG_ALWAYS("database_create_triggers error %s (%d)", sqlite3_errmsg(cloudsync_db(data)), rc);
938 311 : cloudsync_memory_free(trigger_when_new);
939 311 : cloudsync_memory_free(trigger_when_old);
940 311 : return rc;
941 311 : }
942 :
943 51 : int database_delete_triggers (cloudsync_context *data, const char *table) {
944 : DEBUG_DBFUNCTION("database_delete_triggers %s", table);
945 :
946 : // from cloudsync_table_sanity_check we already know that 2048 is OK
947 : char buffer[2048];
948 51 : size_t blen = sizeof(buffer);
949 51 : int rc = SQLITE_ERROR;
950 :
951 51 : char *sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_before_update_%w\";", table);
952 51 : rc = database_exec(data, sql);
953 51 : if (rc != SQLITE_OK) goto finalize;
954 :
955 51 : sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_before_delete_%w\";", table);
956 51 : rc = database_exec(data, sql);
957 51 : if (rc != SQLITE_OK) goto finalize;
958 :
959 51 : sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_after_insert_%w\";", table);
960 51 : rc = database_exec(data, sql);
961 51 : if (rc != SQLITE_OK) goto finalize;
962 :
963 51 : sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_after_update_%w\";", table);
964 51 : rc = database_exec(data, sql);
965 51 : if (rc != SQLITE_OK) goto finalize;
966 :
967 51 : sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_after_delete_%w\";", table);
968 51 : rc = database_exec(data, sql);
969 51 : if (rc != SQLITE_OK) goto finalize;
970 :
971 : finalize:
972 51 : if (rc != SQLITE_OK) DEBUG_ALWAYS("dbutils_delete_triggers error %s (%s)", database_errmsg(data), sql);
973 51 : return rc;
974 : }
975 :
976 : // MARK: - SCHEMA -
977 :
978 222 : int64_t database_schema_version (cloudsync_context *data) {
979 222 : int64_t value = 0;
980 222 : int rc = database_select_int(data, SQL_SCHEMA_VERSION, &value);
981 222 : return (rc == DBRES_OK) ? value : 0;
982 : }
983 :
984 228 : uint64_t database_schema_hash (cloudsync_context *data) {
985 228 : int64_t value = 0;
986 228 : int rc = database_select_int(data, "SELECT hash FROM cloudsync_schema_versions ORDER BY seq DESC limit 1;", &value);
987 228 : return (rc == DBRES_OK) ? (uint64_t)value : 0;
988 : }
989 :
990 4 : bool database_check_schema_hash (cloudsync_context *data, uint64_t hash) {
991 : // a change from the current version of the schema or from previous known schema can be applied
992 : // a change from a newer schema version not yet applied to this peer cannot be applied
993 : // so a schema hash is valid if it exists in the cloudsync_schema_versions table
994 :
995 : // the idea is to allow changes on stale peers and to be able to apply these changes on peers with newer schema,
996 : // but it requires alter table operation on augmented tables only add new columns and never drop columns for backward compatibility
997 : char sql[1024];
998 4 : snprintf(sql, sizeof(sql), "SELECT 1 FROM cloudsync_schema_versions WHERE hash = (%" PRId64 ")", (int64_t)hash);
999 :
1000 4 : int64_t value = 0;
1001 4 : database_select_int(data, sql, &value);
1002 4 : return (value == 1);
1003 : }
1004 :
1005 285 : int database_update_schema_hash (cloudsync_context *data, uint64_t *hash) {
1006 : // Build normalized schema string using only: column name (lowercase), type (SQLite affinity), pk flag
1007 : // Format: tablename:colname:affinity:pk,... (ordered by table name, then column id)
1008 : // This makes the hash resilient to formatting, quoting, case differences and portable across databases
1009 : //
1010 : // Type mapping (simplified from SQLite affinity rules for cross-database compatibility):
1011 : // - Types containing 'INT' → 'integer'
1012 : // - Types containing 'CHAR', 'CLOB', 'TEXT' → 'text'
1013 : // - Types containing 'BLOB' or empty → 'blob'
1014 : // - Types containing 'REAL', 'FLOA', 'DOUB' → 'real'
1015 : // - Types exactly 'NUMERIC' or 'DECIMAL' → 'numeric'
1016 : // - Everything else → 'text' (default)
1017 : //
1018 : // NOTE: This deviates from SQLite's actual affinity rules where unknown types get NUMERIC affinity.
1019 : // We use 'text' as default to improve cross-database compatibility with PostgreSQL, where types
1020 : // like TIMESTAMPTZ, UUID, JSON, etc. are commonly used and map to 'text' in the PostgreSQL
1021 : // implementation. This ensures schemas with PostgreSQL-specific type names in SQLite DDL
1022 : // will hash consistently across both databases.
1023 285 : sqlite3 *db = (sqlite3 *)cloudsync_db(data);
1024 :
1025 285 : char **tables = NULL;
1026 : int ntables, tcols;
1027 285 : int rc = sqlite3_get_table(db, "SELECT DISTINCT tbl_name FROM cloudsync_table_settings ORDER BY tbl_name;",
1028 : &tables, &ntables, &tcols, NULL);
1029 285 : if (rc != SQLITE_OK || ntables == 0) {
1030 0 : if (tables) sqlite3_free_table(tables);
1031 0 : return SQLITE_ERROR;
1032 : }
1033 :
1034 285 : char *schema = NULL;
1035 285 : size_t schema_len = 0;
1036 285 : size_t schema_cap = 0;
1037 :
1038 645 : for (int t = 1; t <= ntables; t++) {
1039 361 : const char *tbl_name = tables[t];
1040 :
1041 : // Query pragma_table_info for this table with normalized type
1042 361 : char *col_sql = cloudsync_memory_mprintf(
1043 : "SELECT LOWER(name), "
1044 : "CASE "
1045 : " WHEN UPPER(type) LIKE '%%INT%%' THEN 'integer' "
1046 : " WHEN UPPER(type) LIKE '%%CHAR%%' OR UPPER(type) LIKE '%%CLOB%%' OR UPPER(type) LIKE '%%TEXT%%' THEN 'text' "
1047 : " WHEN UPPER(type) LIKE '%%BLOB%%' OR type = '' THEN 'blob' "
1048 : " WHEN UPPER(type) LIKE '%%REAL%%' OR UPPER(type) LIKE '%%FLOA%%' OR UPPER(type) LIKE '%%DOUB%%' THEN 'real' "
1049 : " WHEN UPPER(type) IN ('NUMERIC', 'DECIMAL') THEN 'numeric' "
1050 : " ELSE 'text' "
1051 : "END, "
1052 : "CASE WHEN pk > 0 THEN '1' ELSE '0' END "
1053 361 : "FROM pragma_table_info('%q') ORDER BY cid;", tbl_name);
1054 :
1055 361 : if (!col_sql) {
1056 0 : if (schema) cloudsync_memory_free(schema);
1057 0 : sqlite3_free_table(tables);
1058 0 : return SQLITE_NOMEM;
1059 : }
1060 :
1061 361 : char **cols = NULL;
1062 : int nrows, ncols;
1063 361 : rc = sqlite3_get_table(db, col_sql, &cols, &nrows, &ncols, NULL);
1064 361 : cloudsync_memory_free(col_sql);
1065 :
1066 361 : if (rc != SQLITE_OK || ncols != 3) {
1067 1 : if (cols) sqlite3_free_table(cols);
1068 1 : if (schema) cloudsync_memory_free(schema);
1069 1 : sqlite3_free_table(tables);
1070 1 : return SQLITE_ERROR;
1071 : }
1072 :
1073 : // Append each column: tablename:colname:affinity:pk
1074 2146 : for (int r = 1; r <= nrows; r++) {
1075 1786 : const char *col_name = cols[r * 3];
1076 1786 : const char *col_type = cols[r * 3 + 1];
1077 1786 : const char *col_pk = cols[r * 3 + 2];
1078 :
1079 : // Calculate required size: tbl_name:col_name:col_type:col_pk,
1080 1786 : size_t entry_len = strlen(tbl_name) + 1 + strlen(col_name) + 1 + strlen(col_type) + 1 + strlen(col_pk) + 1;
1081 :
1082 1786 : if (schema_len + entry_len + 1 > schema_cap) {
1083 289 : schema_cap = (schema_cap == 0) ? 1024 : schema_cap * 2;
1084 289 : if (schema_cap < schema_len + entry_len + 1) schema_cap = schema_len + entry_len + 1;
1085 289 : char *new_schema = cloudsync_memory_realloc(schema, schema_cap);
1086 289 : if (!new_schema) {
1087 0 : if (schema) cloudsync_memory_free(schema);
1088 0 : sqlite3_free_table(cols);
1089 0 : sqlite3_free_table(tables);
1090 0 : return SQLITE_NOMEM;
1091 : }
1092 289 : schema = new_schema;
1093 289 : }
1094 :
1095 1786 : int written = snprintf(schema + schema_len, schema_cap - schema_len, "%s:%s:%s:%s,",
1096 : tbl_name, col_name, col_type, col_pk);
1097 1786 : schema_len += written;
1098 1786 : }
1099 :
1100 360 : sqlite3_free_table(cols);
1101 360 : }
1102 :
1103 284 : sqlite3_free_table(tables);
1104 :
1105 284 : if (!schema || schema_len == 0) return SQLITE_ERROR;
1106 :
1107 : // Remove trailing comma
1108 284 : if (schema_len > 0 && schema[schema_len - 1] == ',') {
1109 284 : schema[schema_len - 1] = '\0';
1110 284 : schema_len--;
1111 284 : }
1112 :
1113 : DEBUG_MERGE("database_update_schema_hash len %zu schema %s", schema_len, schema);
1114 284 : sqlite3_uint64 h = fnv1a_hash(schema, schema_len);
1115 284 : cloudsync_memory_free(schema);
1116 284 : if (hash && *hash == h) return SQLITE_CONSTRAINT;
1117 :
1118 : char sql[1024];
1119 280 : snprintf(sql, sizeof(sql), "INSERT INTO cloudsync_schema_versions (hash, seq) "
1120 : "VALUES (%" PRId64 ", COALESCE((SELECT MAX(seq) FROM cloudsync_schema_versions), 0) + 1) "
1121 : "ON CONFLICT(hash) DO UPDATE SET "
1122 : " seq = (SELECT COALESCE(MAX(seq), 0) + 1 FROM cloudsync_schema_versions);", (int64_t)h);
1123 280 : rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
1124 280 : if (rc == SQLITE_OK && hash) *hash = h;
1125 280 : return rc;
1126 285 : }
1127 :
1128 : // MARK: - VM -
1129 :
1130 13301 : int databasevm_prepare (cloudsync_context *data, const char *sql, dbvm_t **vm, int flags) {
1131 13301 : return sqlite3_prepare_v3((sqlite3 *)cloudsync_db(data), sql, -1, flags, (sqlite3_stmt **)vm, NULL);
1132 : }
1133 :
1134 425285 : int databasevm_step (dbvm_t *vm) {
1135 425285 : return sqlite3_step((sqlite3_stmt *)vm);
1136 : }
1137 :
1138 13300 : void databasevm_finalize (dbvm_t *vm) {
1139 13300 : sqlite3_finalize((sqlite3_stmt *)vm);
1140 13300 : }
1141 :
1142 405852 : void databasevm_reset (dbvm_t *vm) {
1143 405852 : sqlite3_reset((sqlite3_stmt *)vm);
1144 405852 : }
1145 :
1146 232386 : void databasevm_clear_bindings (dbvm_t *vm) {
1147 232386 : sqlite3_clear_bindings((sqlite3_stmt *)vm);
1148 232386 : }
1149 :
1150 0 : const char *databasevm_sql (dbvm_t *vm) {
1151 0 : return sqlite3_sql((sqlite3_stmt *)vm);
1152 : // the following allocates memory that needs to be freed
1153 : // return sqlite3_expanded_sql((sqlite3_stmt *)vm);
1154 : }
1155 :
1156 0 : static int database_pk_rowid (sqlite3 *db, const char *table_name, char ***names, int *count) {
1157 : char buffer[2048];
1158 0 : char *sql = sqlite3_snprintf(sizeof(buffer), buffer, "SELECT rowid FROM %Q LIMIT 0;", table_name);
1159 0 : if (!sql) return SQLITE_NOMEM;
1160 :
1161 0 : sqlite3_stmt *vm = NULL;
1162 0 : int rc = sqlite3_prepare_v2(db, sql, -1, &vm, NULL);
1163 0 : if (rc != SQLITE_OK) goto cleanup;
1164 :
1165 : {
1166 0 : char **r = (char**)cloudsync_memory_alloc(sizeof(char*));
1167 0 : if (!r) {rc = SQLITE_NOMEM; goto cleanup;}
1168 0 : r[0] = cloudsync_string_dup("rowid");
1169 0 : if (!r[0]) {cloudsync_memory_free(r); rc = SQLITE_NOMEM; goto cleanup;}
1170 0 : *names = r;
1171 0 : *count = 1;
1172 0 : }
1173 :
1174 : cleanup:
1175 0 : if (vm) sqlite3_finalize(vm);
1176 0 : return rc;
1177 0 : }
1178 :
1179 903 : int database_pk_names (cloudsync_context *data, const char *table_name, char ***names, int *count) {
1180 : char buffer[2048];
1181 903 : char *sql = sqlite3_snprintf(sizeof(buffer), buffer, "SELECT name FROM pragma_table_info(%Q) WHERE pk > 0 ORDER BY pk;", table_name);
1182 903 : if (!sql) return SQLITE_NOMEM;
1183 :
1184 903 : sqlite3 *db = (sqlite3 *)cloudsync_db(data);
1185 903 : sqlite3_stmt *vm = NULL;
1186 :
1187 903 : int rc = sqlite3_prepare_v2(db, sql, -1, &vm, NULL);
1188 903 : if (rc != SQLITE_OK) goto cleanup;
1189 :
1190 : // count PK columns
1191 903 : int rows = 0;
1192 2646 : while ((rc = sqlite3_step(vm)) == SQLITE_ROW) rows++;
1193 903 : if (rc != SQLITE_DONE) goto cleanup;
1194 :
1195 903 : if (rows == 0) {
1196 0 : sqlite3_finalize(vm);
1197 : // no declared PKs so check for rowid availability
1198 0 : return database_pk_rowid(db, table_name, names, count);
1199 : }
1200 :
1201 : // reset vm to read PKs again
1202 903 : rc = sqlite3_reset(vm);
1203 903 : if (rc != SQLITE_OK) goto cleanup;
1204 :
1205 : // allocate array
1206 903 : char **r = (char**)cloudsync_memory_zeroalloc(sizeof(char*) * rows);
1207 903 : if (!r) {rc = SQLITE_NOMEM; goto cleanup;}
1208 :
1209 903 : int i = 0;
1210 2646 : while ((rc = sqlite3_step(vm)) == SQLITE_ROW) {
1211 1743 : const char *txt = (const char*)sqlite3_column_text(vm, 0);
1212 1743 : if (!txt) {rc = SQLITE_ERROR; goto cleanup_r;}
1213 1743 : r[i] = cloudsync_string_dup(txt);
1214 1743 : if (!r[i]) { rc = SQLITE_NOMEM; goto cleanup_r;}
1215 1743 : i++;
1216 : }
1217 903 : if (rc == SQLITE_DONE) rc = SQLITE_OK;
1218 :
1219 903 : *names = r;
1220 903 : *count = rows;
1221 903 : goto cleanup;
1222 :
1223 : cleanup_r:
1224 0 : for (int j = 0; j < i; j++) {
1225 0 : if (r[j]) cloudsync_memory_free(r[j]);
1226 0 : }
1227 0 : cloudsync_memory_free(r);
1228 :
1229 : cleanup:
1230 903 : if (vm) sqlite3_finalize(vm);
1231 903 : return rc;
1232 903 : }
1233 :
1234 : // MARK: - BINDING -
1235 :
1236 304580 : int databasevm_bind_blob (dbvm_t *vm, int index, const void *value, uint64_t size) {
1237 304580 : return sqlite3_bind_blob64((sqlite3_stmt *)vm, index, value, size, SQLITE_STATIC);
1238 : }
1239 :
1240 0 : int databasevm_bind_double (dbvm_t *vm, int index, double value) {
1241 0 : return sqlite3_bind_double((sqlite3_stmt *)vm, index, value);
1242 : }
1243 :
1244 419549 : int databasevm_bind_int (dbvm_t *vm, int index, int64_t value) {
1245 419549 : return sqlite3_bind_int64((sqlite3_stmt *)vm, index, value);
1246 : }
1247 :
1248 860 : int databasevm_bind_null (dbvm_t *vm, int index) {
1249 860 : return sqlite3_bind_null((sqlite3_stmt *)vm, index);
1250 : }
1251 :
1252 477457 : int databasevm_bind_text (dbvm_t *vm, int index, const char *value, int size) {
1253 477457 : return sqlite3_bind_text((sqlite3_stmt *)vm, index, value, size, SQLITE_STATIC);
1254 : }
1255 :
1256 28050 : int databasevm_bind_value (dbvm_t *vm, int index, dbvalue_t *value) {
1257 28050 : return sqlite3_bind_value((sqlite3_stmt *)vm, index, (const sqlite3_value *)value);
1258 : }
1259 :
1260 : // MARK: - VALUE -
1261 :
1262 677056 : const void *database_value_blob (dbvalue_t *value) {
1263 677056 : return sqlite3_value_blob((sqlite3_value *)value);
1264 : }
1265 :
1266 158206 : double database_value_double (dbvalue_t *value) {
1267 158206 : return sqlite3_value_double((sqlite3_value *)value);
1268 : }
1269 :
1270 1006612 : int64_t database_value_int (dbvalue_t *value) {
1271 1006612 : return (int64_t)sqlite3_value_int64((sqlite3_value *)value);
1272 : }
1273 :
1274 301577 : const char *database_value_text (dbvalue_t *value) {
1275 301577 : return (const char *)sqlite3_value_text((sqlite3_value *)value);
1276 : }
1277 :
1278 1458362 : int database_value_bytes (dbvalue_t *value) {
1279 1458362 : return sqlite3_value_bytes((sqlite3_value *)value);
1280 : }
1281 :
1282 3880804 : int database_value_type (dbvalue_t *value) {
1283 3880804 : return sqlite3_value_type((sqlite3_value *)value);
1284 : }
1285 :
1286 26200 : void database_value_free (dbvalue_t *value) {
1287 26200 : sqlite3_value_free((sqlite3_value *)value);
1288 26200 : }
1289 :
1290 26200 : void *database_value_dup (dbvalue_t *value) {
1291 26200 : return sqlite3_value_dup((const sqlite3_value *)value);
1292 : }
1293 :
1294 :
1295 : // MARK: - COLUMN -
1296 :
1297 40 : const void *database_column_blob (dbvm_t *vm, int index, size_t *len) {
1298 40 : if (len) *len = sqlite3_column_bytes((sqlite3_stmt *)vm, index);
1299 40 : return sqlite3_column_blob((sqlite3_stmt *)vm, index);
1300 : }
1301 :
1302 0 : double database_column_double (dbvm_t *vm, int index) {
1303 0 : return sqlite3_column_double((sqlite3_stmt *)vm, index);
1304 : }
1305 :
1306 157946 : int64_t database_column_int (dbvm_t *vm, int index) {
1307 157946 : return (int64_t)sqlite3_column_int64((sqlite3_stmt *)vm, index);
1308 : }
1309 :
1310 25398 : const char *database_column_text (dbvm_t *vm, int index) {
1311 25398 : return (const char *)sqlite3_column_text((sqlite3_stmt *)vm, index);
1312 : }
1313 :
1314 145590 : dbvalue_t *database_column_value (dbvm_t *vm, int index) {
1315 145590 : return (dbvalue_t *)sqlite3_column_value((sqlite3_stmt *)vm, index);
1316 : }
1317 :
1318 411 : int database_column_bytes (dbvm_t *vm, int index) {
1319 411 : return sqlite3_column_bytes((sqlite3_stmt *)vm, index);
1320 : }
1321 :
1322 1485 : int database_column_type (dbvm_t *vm, int index) {
1323 1485 : return sqlite3_column_type((sqlite3_stmt *)vm, index);
1324 : }
1325 :
1326 : // MARK: - SAVEPOINT -
1327 :
1328 12988 : int database_begin_savepoint (cloudsync_context *data, const char *savepoint_name) {
1329 : char sql[1024];
1330 12988 : snprintf(sql, sizeof(sql), "SAVEPOINT %s;", savepoint_name);
1331 12988 : return database_exec(data, sql);
1332 : }
1333 :
1334 12979 : int database_commit_savepoint (cloudsync_context *data, const char *savepoint_name) {
1335 : char sql[1024];
1336 12979 : snprintf(sql, sizeof(sql), "RELEASE %s;", savepoint_name);
1337 12979 : return database_exec(data, sql);
1338 : }
1339 :
1340 10 : int database_rollback_savepoint (cloudsync_context *data, const char *savepoint_name) {
1341 : char sql[1024];
1342 10 : snprintf(sql, sizeof(sql), "ROLLBACK TO %s; RELEASE %s;", savepoint_name, savepoint_name);
1343 10 : return database_exec(data, sql);
1344 : }
1345 :
1346 : // MARK: - MEMORY -
1347 :
1348 65049 : void *dbmem_alloc (uint64_t size) {
1349 65049 : return sqlite3_malloc64((sqlite3_uint64)size);
1350 : }
1351 :
1352 3633 : void *dbmem_zeroalloc (uint64_t size) {
1353 3633 : void *ptr = (void *)dbmem_alloc(size);
1354 3633 : if (!ptr) return NULL;
1355 :
1356 3633 : memset(ptr, 0, (size_t)size);
1357 3633 : return ptr;
1358 3633 : }
1359 :
1360 4750 : void *dbmem_realloc (void *ptr, uint64_t new_size) {
1361 4750 : return sqlite3_realloc64(ptr, (sqlite3_uint64)new_size);
1362 : }
1363 :
1364 16299 : char *dbmem_vmprintf (const char *format, va_list list) {
1365 16299 : return sqlite3_vmprintf(format, list);
1366 : }
1367 :
1368 16299 : char *dbmem_mprintf(const char *format, ...) {
1369 : va_list ap;
1370 : char *z;
1371 :
1372 16299 : va_start(ap, format);
1373 16299 : z = dbmem_vmprintf(format, ap);
1374 16299 : va_end(ap);
1375 :
1376 16299 : return z;
1377 : }
1378 :
1379 85149 : void dbmem_free (void *ptr) {
1380 85149 : sqlite3_free(ptr);
1381 85149 : }
1382 :
1383 0 : uint64_t dbmem_size (void *ptr) {
1384 0 : return (uint64_t)sqlite3_msize(ptr);
1385 : }
1386 :
1387 :
|