LCOV - code coverage report
Current view: top level - src/sqlite - database_sqlite.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 84.3 % 765 645
Test Date: 2026-03-16 13:50:46 Functions: 89.1 % 92 82

            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         4764 : char *sql_escape_identifier (const char *name, char *buffer, size_t bsize) {
      43         4764 :     return sqlite3_snprintf((int)bsize, buffer, "%q", name);
      44              : }
      45              : 
      46          201 : char *sql_build_select_nonpk_by_pk (cloudsync_context *data, const char *table_name, const char *schema) {
      47          201 :     UNUSED_PARAMETER(schema);
      48          201 :     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          201 :     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          201 :     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          201 :     if (!sql) return NULL;
      93              :     
      94          201 :     char *query = NULL;
      95          201 :     int rc = database_select_text(data, sql, &query);
      96          201 :     cloudsync_memory_free(sql);
      97              :     
      98          201 :     return (rc == DBRES_OK) ? query : NULL;
      99          201 : }
     100              : 
     101          240 : char *sql_build_delete_by_pk (cloudsync_context *data, const char *table_name, const char *schema) {
     102          240 :     UNUSED_PARAMETER(schema);
     103              :     char buffer[1024];
     104          240 :     char *singlequote_escaped_table_name = sql_escape_identifier(table_name, buffer, sizeof(buffer));
     105          240 :     char *sql = cloudsync_memory_mprintf(SQL_BUILD_DELETE_ROW_BY_PK, table_name, singlequote_escaped_table_name);
     106          240 :     if (!sql) return NULL;
     107              : 
     108          240 :     char *query = NULL;
     109          240 :     int rc = database_select_text(data, sql, &query);
     110          240 :     cloudsync_memory_free(sql);
     111              : 
     112          240 :     return (rc == DBRES_OK) ? query : NULL;
     113          240 : }
     114              : 
     115          240 : char *sql_build_insert_pk_ignore (cloudsync_context *data, const char *table_name, const char *schema) {
     116          240 :     UNUSED_PARAMETER(schema);
     117              :     char buffer[1024];
     118          240 :     char *singlequote_escaped_table_name = sql_escape_identifier(table_name, buffer, sizeof(buffer));
     119          240 :     char *sql = cloudsync_memory_mprintf(SQL_BUILD_INSERT_PK_IGNORE, table_name, table_name, singlequote_escaped_table_name);
     120          240 :     if (!sql) return NULL;
     121              : 
     122          240 :     char *query = NULL;
     123          240 :     int rc = database_select_text(data, sql, &query);
     124          240 :     cloudsync_memory_free(sql);
     125              : 
     126          240 :     return (rc == DBRES_OK) ? query : NULL;
     127          240 : }
     128              : 
     129         1018 : char *sql_build_upsert_pk_and_col (cloudsync_context *data, const char *table_name, const char *colname, const char *schema) {
     130         1018 :     UNUSED_PARAMETER(schema);
     131              :     char buffer[1024];
     132              :     char buffer2[1024];
     133         1018 :     char *singlequote_escaped_table_name = sql_escape_identifier(table_name, buffer, sizeof(buffer));
     134         1018 :     char *singlequote_escaped_col_name = sql_escape_identifier(colname, buffer2, sizeof(buffer2));
     135         1018 :     char *sql = cloudsync_memory_mprintf(
     136         1018 :         SQL_BUILD_UPSERT_PK_AND_COL,
     137         1018 :         table_name,
     138         1018 :         table_name,
     139         1018 :         singlequote_escaped_table_name,
     140         1018 :         singlequote_escaped_col_name,
     141         1018 :         singlequote_escaped_col_name
     142              :     );
     143         1018 :     if (!sql) return NULL;
     144              : 
     145         1018 :     char *query = NULL;
     146         1018 :     int rc = database_select_text(data, sql, &query);
     147         1018 :     cloudsync_memory_free(sql);
     148              : 
     149         1018 :     return (rc == DBRES_OK) ? query : NULL;
     150         1018 : }
     151              : 
     152          413 : char *sql_build_upsert_pk_and_multi_cols (cloudsync_context *data, const char *table_name, const char **colnames, int ncolnames, const char *schema) {
     153          413 :     UNUSED_PARAMETER(schema);
     154          413 :     if (ncolnames <= 0 || !colnames) return NULL;
     155              : 
     156              :     // Get PK column names via pragma_table_info (same approach as database_pk_names)
     157          413 :     char **pk_names = NULL;
     158          413 :     int npks = 0;
     159          413 :     int rc = database_pk_names(data, table_name, &pk_names, &npks);
     160          413 :     if (rc != DBRES_OK || npks <= 0 || !pk_names) return NULL;
     161              : 
     162              :     // Build column list: "pk1","pk2","col_a","col_b"
     163          413 :     char *col_list = cloudsync_memory_mprintf("\"%w\"", pk_names[0]);
     164          413 :     if (!col_list) goto fail;
     165          795 :     for (int i = 1; i < npks; i++) {
     166          382 :         char *prev = col_list;
     167          382 :         col_list = cloudsync_memory_mprintf("%s,\"%w\"", prev, pk_names[i]);
     168          382 :         cloudsync_memory_free(prev);
     169          382 :         if (!col_list) goto fail;
     170          382 :     }
     171         1279 :     for (int i = 0; i < ncolnames; i++) {
     172          866 :         char *prev = col_list;
     173          866 :         col_list = cloudsync_memory_mprintf("%s,\"%w\"", prev, colnames[i]);
     174          866 :         cloudsync_memory_free(prev);
     175          866 :         if (!col_list) goto fail;
     176          866 :     }
     177              : 
     178              :     // Build bind list: ?,?,?,?
     179          413 :     int total = npks + ncolnames;
     180          413 :     char *binds = (char *)cloudsync_memory_alloc(total * 2);
     181          413 :     if (!binds) { cloudsync_memory_free(col_list); goto fail; }
     182          413 :     int pos = 0;
     183         2074 :     for (int i = 0; i < total; i++) {
     184         1661 :         if (i > 0) binds[pos++] = ',';
     185         1661 :         binds[pos++] = '?';
     186         1661 :     }
     187          413 :     binds[pos] = '\0';
     188              : 
     189              :     // Build excluded set: "col_a"=EXCLUDED."col_a","col_b"=EXCLUDED."col_b"
     190          413 :     char *excl = cloudsync_memory_mprintf("\"%w\"=EXCLUDED.\"%w\"", colnames[0], colnames[0]);
     191          413 :     if (!excl) { cloudsync_memory_free(col_list); cloudsync_memory_free(binds); goto fail; }
     192          866 :     for (int i = 1; i < ncolnames; i++) {
     193          453 :         char *prev = excl;
     194          453 :         excl = cloudsync_memory_mprintf("%s,\"%w\"=EXCLUDED.\"%w\"", prev, colnames[i], colnames[i]);
     195          453 :         cloudsync_memory_free(prev);
     196          453 :         if (!excl) { cloudsync_memory_free(col_list); cloudsync_memory_free(binds); goto fail; }
     197          453 :     }
     198              : 
     199              :     // Assemble final SQL
     200          413 :     char *sql = cloudsync_memory_mprintf(
     201              :         "INSERT INTO \"%w\" (%s) VALUES (%s) ON CONFLICT DO UPDATE SET %s;",
     202          413 :         table_name, col_list, binds, excl
     203              :     );
     204              : 
     205          413 :     cloudsync_memory_free(col_list);
     206          413 :     cloudsync_memory_free(binds);
     207          413 :     cloudsync_memory_free(excl);
     208         1208 :     for (int i = 0; i < npks; i++) cloudsync_memory_free(pk_names[i]);
     209          413 :     cloudsync_memory_free(pk_names);
     210          413 :     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          413 : }
     219              : 
     220          406 : char *sql_build_update_pk_and_multi_cols (cloudsync_context *data, const char *table_name, const char **colnames, int ncolnames, const char *schema) {
     221          406 :     UNUSED_PARAMETER(schema);
     222          406 :     if (ncolnames <= 0 || !colnames) return NULL;
     223              : 
     224              :     // Get PK column names
     225          406 :     char **pk_names = NULL;
     226          406 :     int npks = 0;
     227          406 :     int rc = database_pk_names(data, table_name, &pk_names, &npks);
     228          406 :     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          406 :     char *set_clause = cloudsync_memory_mprintf("\"%w\"=?%d", colnames[0], npks + 1);
     234          406 :     if (!set_clause) goto fail;
     235          726 :     for (int i = 1; i < ncolnames; i++) {
     236          320 :         char *prev = set_clause;
     237          320 :         set_clause = cloudsync_memory_mprintf("%s,\"%w\"=?%d", prev, colnames[i], npks + 1 + i);
     238          320 :         cloudsync_memory_free(prev);
     239          320 :         if (!set_clause) goto fail;
     240          320 :     }
     241              : 
     242              :     // Build WHERE clause: "pk1"=?1 AND "pk2"=?2
     243          406 :     char *where_clause = cloudsync_memory_mprintf("\"%w\"=?%d", pk_names[0], 1);
     244          406 :     if (!where_clause) { cloudsync_memory_free(set_clause); goto fail; }
     245          802 :     for (int i = 1; i < npks; i++) {
     246          396 :         char *prev = where_clause;
     247          396 :         where_clause = cloudsync_memory_mprintf("%s AND \"%w\"=?%d", prev, pk_names[i], 1 + i);
     248          396 :         cloudsync_memory_free(prev);
     249          396 :         if (!where_clause) { cloudsync_memory_free(set_clause); goto fail; }
     250          396 :     }
     251              : 
     252              :     // Assemble: UPDATE "table" SET ... WHERE ...
     253          406 :     char *sql = cloudsync_memory_mprintf(
     254              :         "UPDATE \"%w\" SET %s WHERE %s;",
     255          406 :         table_name, set_clause, where_clause
     256              :     );
     257              : 
     258          406 :     cloudsync_memory_free(set_clause);
     259          406 :     cloudsync_memory_free(where_clause);
     260         1208 :     for (int i = 0; i < npks; i++) cloudsync_memory_free(pk_names[i]);
     261          406 :     cloudsync_memory_free(pk_names);
     262          406 :     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          406 : }
     271              : 
     272         1018 : char *sql_build_select_cols_by_pk (cloudsync_context *data, const char *table_name, const char *colname, const char *schema) {
     273         1018 :     UNUSED_PARAMETER(schema);
     274         1018 :     char *colnamequote = "\"";
     275              :     char buffer[1024];
     276              :     char buffer2[1024];
     277         1018 :     char *singlequote_escaped_table_name = sql_escape_identifier(table_name, buffer, sizeof(buffer));
     278         1018 :     char *singlequote_escaped_col_name = sql_escape_identifier(colname, buffer2, sizeof(buffer2));
     279         1018 :     char *sql = cloudsync_memory_mprintf(
     280         1018 :         SQL_BUILD_SELECT_COLS_BY_PK_FMT,
     281         1018 :         table_name,
     282         1018 :         colnamequote,
     283         1018 :         singlequote_escaped_col_name,
     284         1018 :         colnamequote,
     285         1018 :         singlequote_escaped_table_name
     286              :     );
     287         1018 :     if (!sql) return NULL;
     288              : 
     289         1018 :     char *query = NULL;
     290         1018 :     int rc = database_select_text(data, sql, &query);
     291         1018 :     cloudsync_memory_free(sql);
     292              : 
     293         1018 :     return (rc == DBRES_OK) ? query : NULL;
     294         1018 : }
     295              : 
     296          240 : char *sql_build_rekey_pk_and_reset_version_except_col (cloudsync_context *data, const char *table_name, const char *except_col) {
     297          240 :     UNUSED_PARAMETER(data);
     298              : 
     299          240 :     char *meta_ref = database_build_meta_ref(NULL, table_name);
     300          240 :     if (!meta_ref) return NULL;
     301              : 
     302          240 :     char *result = cloudsync_memory_mprintf(SQL_CLOUDSYNC_REKEY_PK_AND_RESET_VERSION_EXCEPT_COL, meta_ref, except_col);
     303          240 :     cloudsync_memory_free(meta_ref);
     304          240 :     return result;
     305          240 : }
     306              : 
     307          240 : char *database_table_schema (const char *table_name) {
     308          240 :     return NULL;
     309              : }
     310              : 
     311          480 : char *database_build_meta_ref (const char *schema, const char *table_name) {
     312              :     // schema unused in SQLite
     313          480 :     return cloudsync_memory_mprintf("%s_cloudsync", table_name);
     314              : }
     315              : 
     316          240 : char *database_build_base_ref (const char *schema, const char *table_name) {
     317              :     // schema unused in SQLite
     318          240 :     return cloudsync_string_dup(table_name);
     319              : }
     320              : 
     321           67 : char *database_build_blocks_ref (const char *schema, const char *table_name) {
     322              :     // schema unused in SQLite
     323           67 :     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          243 : char *sql_build_pk_collist_query (const char *schema, const char *table_name) {
     339          243 :     UNUSED_PARAMETER(schema);
     340          243 :     return cloudsync_memory_mprintf(
     341              :         "SELECT group_concat('\"' || format('%%w', name) || '\"', ',') "
     342              :         "FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk;",
     343          243 :         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          243 : 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          243 :     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          243 :     if (filter) {
     380            0 :         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            0 :             table_name, pkvalues_identifiers, base_ref, filter, meta_ref, pkvalues_identifiers
     387              :         );
     388              :     }
     389          243 :     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          243 :         table_name, pkvalues_identifiers, base_ref, meta_ref, pkvalues_identifiers
     396              :     );
     397          243 : }
     398              : 
     399              : // MARK: - PRIVATE -
     400              : 
     401         7583 : static int database_select1_value (cloudsync_context *data, const char *sql, char **ptr_value, int64_t *int_value, DBTYPE expected_type) {
     402         7583 :     sqlite3 *db = (sqlite3 *)cloudsync_db(data);
     403              :     
     404              :     // init values and sanity check expected_type
     405         7583 :     if (ptr_value) *ptr_value = NULL;
     406         7583 :     *int_value = 0;
     407         7583 :     if (expected_type != DBTYPE_INTEGER && expected_type != DBTYPE_TEXT && expected_type != DBTYPE_BLOB) return SQLITE_MISUSE;
     408              :     
     409         7583 :     sqlite3_stmt *vm = NULL;
     410         7583 :     int rc = sqlite3_prepare_v2((sqlite3 *)db, sql, -1, &vm, NULL);
     411         7583 :     if (rc != SQLITE_OK) goto cleanup_select;
     412              :     
     413              :     // ensure at least one column
     414         7583 :     if (sqlite3_column_count(vm) < 1) {rc = SQLITE_MISMATCH; goto cleanup_select;}
     415              :     
     416         7583 :     rc = sqlite3_step(vm);
     417         7583 :     if (rc == SQLITE_DONE) {rc = SQLITE_OK; goto cleanup_select;} // no rows OK
     418         7395 :     if (rc != SQLITE_ROW) goto cleanup_select;
     419              :     
     420              :     // sanity check column type
     421         7395 :     DBTYPE type = (DBTYPE)sqlite3_column_type(vm, 0);
     422         7395 :     if (type == SQLITE_NULL) {rc = SQLITE_OK; goto cleanup_select;}
     423         7356 :     if (type != expected_type) {rc = SQLITE_MISMATCH; goto cleanup_select;}
     424              :     
     425         7356 :     if (expected_type == DBTYPE_INTEGER) {
     426         2003 :         *int_value = (int64_t)sqlite3_column_int64(vm, 0);
     427         2003 :     } else {
     428         5353 :         const void *value = (expected_type == DBTYPE_TEXT) ? (const void *)sqlite3_column_text(vm, 0) : (const void *)sqlite3_column_blob(vm, 0);
     429         5353 :         int len = sqlite3_column_bytes(vm, 0);
     430         5353 :         if (len) {
     431         5353 :             char *ptr = cloudsync_memory_alloc(len + 1);
     432         5353 :             if (!ptr) {rc = SQLITE_NOMEM; goto cleanup_select;}
     433              :             
     434         5353 :             if (len > 0 && value) memcpy(ptr, value, len);
     435         5353 :             if (expected_type == DBTYPE_TEXT) ptr[len] = 0; // NULL terminate in case of TEXT
     436              :             
     437         5353 :             *ptr_value = ptr;
     438         5353 :             *int_value = len;
     439         5353 :         }
     440              :     }
     441         7356 :     rc = SQLITE_OK;
     442              :     
     443              : cleanup_select:
     444         7583 :     if (vm) sqlite3_finalize(vm);
     445         7583 :     return rc;
     446         7583 : }
     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         2037 : bool database_system_exists (cloudsync_context *data, const char *name, const char *type) {
     494         2037 :     sqlite3 *db = (sqlite3 *)cloudsync_db(data);
     495         2037 :     sqlite3_stmt *vm = NULL;
     496         2037 :     bool result = false;
     497              :     
     498              :     char sql[1024];
     499         2037 :     snprintf(sql, sizeof(sql), "SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE type='%s' AND name=?1 COLLATE NOCASE);", type);
     500         2037 :     int rc = sqlite3_prepare_v2(db, sql, -1, &vm, NULL);
     501         2037 :     if (rc != SQLITE_OK) goto finalize;
     502              :     
     503         2037 :     rc = sqlite3_bind_text(vm, 1, name, -1, SQLITE_STATIC);
     504         2037 :     if (rc != SQLITE_OK) goto finalize;
     505              :     
     506         2037 :     rc = sqlite3_step(vm);
     507         4074 :     if (rc == SQLITE_ROW) {
     508         2037 :         result = (bool)sqlite3_column_int(vm, 0);
     509         2037 :         rc = SQLITE_OK;
     510         2037 :     }
     511              :     
     512              : finalize:
     513         2037 :     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         2037 :     if (vm) sqlite3_finalize(vm);
     515         2037 :     return result;
     516              : }
     517              : 
     518              : // MARK: - GENERAL -
     519              : 
     520        28236 : int database_exec (cloudsync_context *data, const char *sql) {
     521        28236 :     return sqlite3_exec((sqlite3 *)cloudsync_db(data), sql, NULL, NULL, NULL);
     522              : }
     523              : 
     524          575 : int database_exec_callback (cloudsync_context *data, const char *sql, int (*callback)(void *xdata, int argc, char **values, char **names), void *xdata) {
     525          575 :     return sqlite3_exec((sqlite3 *)cloudsync_db(data), sql, callback, xdata, NULL);
     526              : }
     527              : 
     528         1407 : int database_write (cloudsync_context *data, const char *sql, const char **bind_values, DBTYPE bind_types[], int bind_lens[], int bind_count) {
     529         1407 :     sqlite3 *db = (sqlite3 *)cloudsync_db(data);
     530         1407 :     sqlite3_stmt *vm = NULL;
     531         1407 :     int rc = sqlite3_prepare_v2((sqlite3 *)db, sql, -1, &vm, NULL);
     532         1407 :     if (rc != SQLITE_OK) goto cleanup_write;
     533              :     
     534         4857 :     for (int i=0; i<bind_count; ++i) {
     535         3452 :         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         3018 :                 rc = sqlite3_bind_text(vm, i+1, bind_values[i], bind_lens[i], SQLITE_STATIC);
     541         3018 :                 break;
     542              :             case SQLITE_BLOB:
     543          186 :                 rc = sqlite3_bind_blob(vm, i+1, bind_values[i], bind_lens[i], SQLITE_STATIC);
     544          186 :                 break;
     545              :             case SQLITE_INTEGER: {
     546          246 :                 sqlite3_int64 value = strtoll(bind_values[i], NULL, 0);
     547          246 :                 rc = sqlite3_bind_int64(vm, i+1, value);
     548          246 :             }   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         3452 :         if (rc != SQLITE_OK) goto cleanup_write;
     555         3452 :     }
     556              :         
     557              :     // execute statement
     558         1405 :     rc = sqlite3_step(vm);
     559         1405 :     if (rc == SQLITE_DONE) rc = SQLITE_OK;
     560              :     
     561              : cleanup_write:
     562         1407 :     if (vm) sqlite3_finalize(vm);
     563         1407 :     return rc;
     564              : }
     565              : 
     566         2191 : int database_select_int (cloudsync_context *data, const char *sql, int64_t *value) {
     567         2191 :     return database_select1_value(data, sql, NULL, value, DBTYPE_INTEGER);
     568              : }
     569              : 
     570         5201 : int database_select_text (cloudsync_context *data, const char *sql, char **value) {
     571         5201 :     int64_t len = 0;
     572         5201 :     return database_select1_value(data, sql, value, &len, DBTYPE_TEXT);
     573              : }
     574              : 
     575          191 : int database_select_blob (cloudsync_context *data, const char *sql, char **value, int64_t *len) {
     576          191 :     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           17 : const char *database_errmsg (cloudsync_context *data) {
     584           17 :     return sqlite3_errmsg((sqlite3 *)cloudsync_db(data));
     585              : }
     586              : 
     587           18 : int database_errcode (cloudsync_context *data) {
     588           18 :     return sqlite3_errcode((sqlite3 *)cloudsync_db(data));
     589              : }
     590              : 
     591         4898 : bool database_in_transaction (cloudsync_context *data) {
     592         4898 :     sqlite3 *db = (sqlite3 *)cloudsync_db(data);
     593         4898 :     bool in_transaction = (sqlite3_get_autocommit(db) != true);
     594         4898 :     return in_transaction;
     595              : }
     596              : 
     597         1298 : bool database_table_exists (cloudsync_context *data, const char *name, const char *schema) {
     598         1298 :     UNUSED_PARAMETER(schema);
     599         1298 :     return database_system_exists(data, name, "table");
     600              : }
     601              : 
     602         1052 : bool database_internal_table_exists (cloudsync_context *data, const char *name) {
     603         1052 :     return database_table_exists(data, name, NULL);
     604              : }
     605              : 
     606          738 : bool database_trigger_exists (cloudsync_context *data, const char *name) {
     607          738 :     return database_system_exists(data, name, "trigger");
     608              : }
     609              : 
     610          484 : int database_count_pk (cloudsync_context *data, const char *table_name, bool not_null, const char *schema) {
     611          484 :     UNUSED_PARAMETER(schema);
     612              :     char buffer[1024];
     613          484 :     char *sql = NULL;
     614              :     
     615          484 :     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          484 :         sql = sqlite3_snprintf(sizeof(buffer), buffer, "SELECT count(*) FROM pragma_table_info('%q') WHERE pk>0;", table_name);
     619              :     }
     620              :     
     621          484 :     int64_t count = 0;
     622          484 :     int rc = database_select_int(data, sql, &count);
     623          484 :     if (rc != DBRES_OK) return -1;
     624          484 :     return (int)count;
     625          484 : }
     626              : 
     627          240 : int database_count_nonpk (cloudsync_context *data, const char *table_name, const char *schema) {
     628          240 :     UNUSED_PARAMETER(schema);
     629              :     char buffer[1024];
     630          240 :     char *sql = NULL;
     631              :     
     632          240 :     sql = sqlite3_snprintf(sizeof(buffer), buffer, "SELECT count(*) FROM pragma_table_info('%q') WHERE pk=0;", table_name);
     633          240 :     int64_t count = 0;
     634          240 :     int rc = database_select_int(data, sql, &count);
     635          240 :     if (rc != DBRES_OK) return -1;
     636          240 :     return (int)count;
     637          240 : }
     638              : 
     639          100 : int database_count_int_pk (cloudsync_context *data, const char *table_name, const char *schema) {
     640          100 :     UNUSED_PARAMETER(schema);
     641              :     char buffer[1024];
     642          100 :     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          100 :     int64_t count = 0;
     645          100 :     int rc = database_select_int(data, sql, &count);
     646          100 :     if (rc != DBRES_OK) return -1;
     647          100 :     return (int)count;
     648          100 : }
     649              : 
     650          242 : int database_count_notnull_without_default (cloudsync_context *data, const char *table_name, const char *schema) {
     651          242 :     UNUSED_PARAMETER(schema);
     652              :     char buffer[1024];
     653          242 :     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          242 :     int64_t count = 0;
     656          242 :     int rc = database_select_int(data, sql, &count);
     657          242 :     if (rc != DBRES_OK) return -1;
     658          242 :     return (int)count;
     659          242 : }
     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          243 : 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          243 :     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          243 :     int rc = database_exec(data, sql);
     697              :     DEBUG_SQL("\n%s", sql);
     698          243 :     return rc;
     699              : }
     700              : 
     701          246 : 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          246 :     char *trigger_name = sqlite3_snprintf(sizeof(buffer), buffer, "cloudsync_after_insert_%s", table_name);
     705          246 :     if (database_trigger_exists(data, trigger_name)) return SQLITE_OK;
     706              :     
     707              :     char buffer2[2048];
     708          241 :     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          241 :     char *pkclause = NULL;
     711          241 :     int rc = database_select_text(data, sql2, &pkclause);
     712          241 :     if (rc != SQLITE_OK) return rc;
     713          241 :     char *pkvalues = (pkclause) ? pkclause : "NEW.rowid";
     714              :     
     715          241 :     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          241 :     if (pkclause) cloudsync_memory_free(pkclause);
     717          241 :     if (!sql) return SQLITE_NOMEM;
     718              :     
     719          241 :     rc = database_exec(data, sql);
     720              :     DEBUG_SQL("\n%s", sql);
     721          241 :     cloudsync_memory_free(sql);
     722          241 :     return rc;
     723          246 : }
     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          239 : 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          239 :     char *trigger_name = sqlite3_snprintf(sizeof(buffer), buffer, "cloudsync_after_update_%s", table_name);
     748          239 :     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          234 :     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          234 :     char *pk_values_list = NULL;
     756          234 :     int rc = database_select_text(data, sql2, &pk_values_list);
     757          234 :     if (rc != SQLITE_OK) return rc;
     758              :     
     759              :     // then get all regular columns in order
     760          234 :     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          234 :     char *col_values_list = NULL;
     763          234 :     rc = database_select_text(data, sql2, &col_values_list);
     764          234 :     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          234 :     char *values_query = NULL;
     771          234 :     if (col_values_list && strlen(col_values_list) > 0) {
     772              :         // Table has both primary keys and regular columns
     773          195 :         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          195 :                                                 pk_values_list, col_values_list);
     777          195 :     } 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          234 :     if (pk_values_list) cloudsync_memory_free(pk_values_list);
     786          234 :     if (col_values_list) cloudsync_memory_free(col_values_list);
     787          234 :     if (!values_query) return SQLITE_NOMEM;
     788              :     
     789              :     // create the trigger with aggregate function
     790          234 :     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          234 :                                          trigger_name, table_name, trigger_when, values_query);
     795              :     
     796          234 :     cloudsync_memory_free(values_query);
     797          234 :     if (!sql) return SQLITE_NOMEM;
     798              :     
     799          234 :     rc = database_exec(data, sql);
     800              :     DEBUG_SQL("\n%s", sql);
     801          234 :     cloudsync_memory_free(sql);
     802          234 :     return rc;
     803          239 : }
     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          239 : 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          239 :     char *trigger_name = sqlite3_snprintf(sizeof(buffer), buffer, "cloudsync_after_delete_%s", table_name);
     826          239 :     if (database_trigger_exists(data, trigger_name)) return SQLITE_OK;
     827              :     
     828              :     char buffer2[1024];
     829          234 :     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          234 :     char *pkclause = NULL;
     832          234 :     int rc = database_select_text(data, sql2, &pkclause);
     833          234 :     if (rc != SQLITE_OK) return rc;
     834          234 :     char *pkvalues = (pkclause) ? pkclause : "OLD.rowid";
     835              :     
     836          234 :     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          234 :     if (pkclause) cloudsync_memory_free(pkclause);
     838          234 :     if (!sql) return SQLITE_NOMEM;
     839              :         
     840          234 :     rc = database_exec(data, sql);
     841              :     DEBUG_SQL("\n%s", sql);
     842          234 :     cloudsync_memory_free(sql);
     843          234 :     return rc;
     844          239 : }
     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          246 : 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          246 :     char *new_filter_str = NULL;
     854          246 :     char *old_filter_str = NULL;
     855              : 
     856          246 :     if (filter) {
     857              :         char sql_cols[1024];
     858            4 :         sqlite3_snprintf(sizeof(sql_cols), sql_cols,
     859            2 :             "SELECT name FROM pragma_table_info('%q') ORDER BY cid;", table_name);
     860              : 
     861              :         char *col_names[256];
     862            2 :         int ncols = 0;
     863              : 
     864            2 :         sqlite3_stmt *col_vm = NULL;
     865            2 :         int col_rc = sqlite3_prepare_v2((sqlite3 *)cloudsync_db(data), sql_cols, -1, &col_vm, NULL);
     866            2 :         if (col_rc == SQLITE_OK) {
     867            8 :             while (sqlite3_step(col_vm) == SQLITE_ROW && ncols < 256) {
     868            6 :                 const char *name = (const char *)sqlite3_column_text(col_vm, 0);
     869            6 :                 if (name) {
     870            6 :                     char *dup = cloudsync_memory_mprintf("%s", name);
     871            6 :                     if (!dup) break;
     872            6 :                     col_names[ncols++] = dup;
     873            6 :                 }
     874              :             }
     875            2 :             sqlite3_finalize(col_vm);
     876            2 :         }
     877              : 
     878            2 :         if (ncols > 0) {
     879            2 :             new_filter_str = cloudsync_filter_add_row_prefix(filter, "NEW", col_names, ncols);
     880            2 :             old_filter_str = cloudsync_filter_add_row_prefix(filter, "OLD", col_names, ncols);
     881            8 :             for (int i = 0; i < ncols; ++i) cloudsync_memory_free(col_names[i]);
     882            2 :         }
     883            2 :     }
     884              : 
     885          246 :     if (new_filter_str) {
     886            2 :         *when_new_out = cloudsync_memory_mprintf(
     887            2 :             "FOR EACH ROW WHEN cloudsync_is_sync('%q') = 0 AND (%s)", table_name, new_filter_str);
     888            2 :     } else {
     889          244 :         *when_new_out = cloudsync_memory_mprintf(
     890          244 :             "FOR EACH ROW WHEN cloudsync_is_sync('%q') = 0", table_name);
     891              :     }
     892              : 
     893          246 :     if (old_filter_str) {
     894            2 :         *when_old_out = cloudsync_memory_mprintf(
     895            2 :             "FOR EACH ROW WHEN cloudsync_is_sync('%q') = 0 AND (%s)", table_name, old_filter_str);
     896            2 :     } else {
     897          244 :         *when_old_out = cloudsync_memory_mprintf(
     898          244 :             "FOR EACH ROW WHEN cloudsync_is_sync('%q') = 0", table_name);
     899              :     }
     900              : 
     901          246 :     if (new_filter_str) cloudsync_memory_free(new_filter_str);
     902          246 :     if (old_filter_str) cloudsync_memory_free(old_filter_str);
     903          246 : }
     904              : 
     905          246 : 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          246 :     if (dbutils_settings_check_version(data, "0.8.25") <= 0) {
     909            0 :         database_delete_triggers(data, table_name);
     910            0 :     }
     911              : 
     912          246 :     char *trigger_when_new = NULL;
     913          246 :     char *trigger_when_old = NULL;
     914          246 :     database_build_trigger_when(data, table_name, filter,
     915              :         &trigger_when_new, &trigger_when_old);
     916              : 
     917          246 :     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          246 :     int rc = database_create_insert_trigger(data, table_name, trigger_when_new);
     925          246 :     if (rc != SQLITE_OK) goto done;
     926              : 
     927              :     // UPDATE TRIGGER (uses NEW prefix)
     928          246 :     if (algo == table_algo_crdt_gos) rc = database_create_update_trigger_gos(data, table_name);
     929          239 :     else rc = database_create_update_trigger(data, table_name, trigger_when_new);
     930          246 :     if (rc != SQLITE_OK) goto done;
     931              : 
     932              :     // DELETE TRIGGER (uses OLD prefix)
     933          485 :     if (algo == table_algo_crdt_gos) rc = database_create_delete_trigger_gos(data, table_name);
     934          239 :     else rc = database_create_delete_trigger(data, table_name, trigger_when_old);
     935              : 
     936              : done:
     937          246 :     if (rc != SQLITE_OK) DEBUG_ALWAYS("database_create_triggers error %s (%d)", sqlite3_errmsg(cloudsync_db(data)), rc);
     938          246 :     cloudsync_memory_free(trigger_when_new);
     939          246 :     cloudsync_memory_free(trigger_when_old);
     940          246 :     return rc;
     941          246 : }
     942              : 
     943           28 : 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           28 :     size_t blen = sizeof(buffer);
     949           28 :     int rc = SQLITE_ERROR;
     950              :     
     951           28 :     char *sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_before_update_%w\";", table);
     952           28 :     rc = database_exec(data, sql);
     953           28 :     if (rc != SQLITE_OK) goto finalize;
     954              :     
     955           28 :     sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_before_delete_%w\";", table);
     956           28 :     rc = database_exec(data, sql);
     957           28 :     if (rc != SQLITE_OK) goto finalize;
     958              :     
     959           28 :     sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_after_insert_%w\";", table);
     960           28 :     rc = database_exec(data, sql);
     961           28 :     if (rc != SQLITE_OK) goto finalize;
     962              :     
     963           28 :     sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_after_update_%w\";", table);
     964           28 :     rc = database_exec(data, sql);
     965           28 :     if (rc != SQLITE_OK) goto finalize;
     966              :     
     967           28 :     sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_after_delete_%w\";", table);
     968           28 :     rc = database_exec(data, sql);
     969           28 :     if (rc != SQLITE_OK) goto finalize;
     970              :     
     971              : finalize:
     972           28 :     if (rc != SQLITE_OK) DEBUG_ALWAYS("dbutils_delete_triggers error %s (%s)", database_errmsg(data), sql);
     973           28 :     return rc;
     974              : }
     975              : 
     976              : // MARK: - SCHEMA -
     977              : 
     978          186 : int64_t database_schema_version (cloudsync_context *data) {
     979          186 :     int64_t value = 0;
     980          186 :     int rc = database_select_int(data, SQL_SCHEMA_VERSION, &value);
     981          186 :     return (rc == DBRES_OK) ? value : 0;
     982              : }
     983              : 
     984          187 : uint64_t database_schema_hash (cloudsync_context *data) {
     985          187 :     int64_t value = 0;
     986          187 :     int rc = database_select_int(data, "SELECT hash FROM cloudsync_schema_versions ORDER BY seq DESC limit 1;", &value);
     987          187 :     return (rc == DBRES_OK) ? (uint64_t)value : 0;
     988              : }
     989              : 
     990            3 : 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            3 :     snprintf(sql, sizeof(sql), "SELECT 1 FROM cloudsync_schema_versions WHERE hash = (%" PRId64 ")", (int64_t)hash);
     999              :     
    1000            3 :     int64_t value = 0;
    1001            3 :     database_select_int(data, sql, &value);
    1002            3 :     return (value == 1);
    1003              : }
    1004              : 
    1005          245 : 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          245 :     sqlite3 *db = (sqlite3 *)cloudsync_db(data);
    1024              : 
    1025          245 :     char **tables = NULL;
    1026              :     int ntables, tcols;
    1027          245 :     int rc = sqlite3_get_table(db, "SELECT DISTINCT tbl_name FROM cloudsync_table_settings ORDER BY tbl_name;",
    1028              :                                &tables, &ntables, &tcols, NULL);
    1029          245 :     if (rc != SQLITE_OK || ntables == 0) {
    1030            0 :         if (tables) sqlite3_free_table(tables);
    1031            0 :         return SQLITE_ERROR;
    1032              :     }
    1033              : 
    1034          245 :     char *schema = NULL;
    1035          245 :     size_t schema_len = 0;
    1036          245 :     size_t schema_cap = 0;
    1037              : 
    1038          556 :     for (int t = 1; t <= ntables; t++) {
    1039          311 :         const char *tbl_name = tables[t];
    1040              : 
    1041              :         // Query pragma_table_info for this table with normalized type
    1042          311 :         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          311 :             "FROM pragma_table_info('%q') ORDER BY cid;", tbl_name);
    1054              : 
    1055          311 :         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          311 :         char **cols = NULL;
    1062              :         int nrows, ncols;
    1063          311 :         rc = sqlite3_get_table(db, col_sql, &cols, &nrows, &ncols, NULL);
    1064          311 :         cloudsync_memory_free(col_sql);
    1065              : 
    1066          311 :         if (rc != SQLITE_OK || ncols != 3) {
    1067            0 :             if (cols) sqlite3_free_table(cols);
    1068            0 :             if (schema) cloudsync_memory_free(schema);
    1069            0 :             sqlite3_free_table(tables);
    1070            0 :             return SQLITE_ERROR;
    1071              :         }
    1072              : 
    1073              :         // Append each column: tablename:colname:affinity:pk
    1074         1963 :         for (int r = 1; r <= nrows; r++) {
    1075         1652 :             const char *col_name = cols[r * 3];
    1076         1652 :             const char *col_type = cols[r * 3 + 1];
    1077         1652 :             const char *col_pk = cols[r * 3 + 2];
    1078              : 
    1079              :             // Calculate required size: tbl_name:col_name:col_type:col_pk,
    1080         1652 :             size_t entry_len = strlen(tbl_name) + 1 + strlen(col_name) + 1 + strlen(col_type) + 1 + strlen(col_pk) + 1;
    1081              : 
    1082         1652 :             if (schema_len + entry_len + 1 > schema_cap) {
    1083          250 :                 schema_cap = (schema_cap == 0) ? 1024 : schema_cap * 2;
    1084          250 :                 if (schema_cap < schema_len + entry_len + 1) schema_cap = schema_len + entry_len + 1;
    1085          250 :                 char *new_schema = cloudsync_memory_realloc(schema, schema_cap);
    1086          250 :                 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          250 :                 schema = new_schema;
    1093          250 :             }
    1094              : 
    1095         1652 :             int written = snprintf(schema + schema_len, schema_cap - schema_len, "%s:%s:%s:%s,",
    1096              :                                    tbl_name, col_name, col_type, col_pk);
    1097         1652 :             schema_len += written;
    1098         1652 :         }
    1099              : 
    1100          311 :         sqlite3_free_table(cols);
    1101          311 :     }
    1102              : 
    1103          245 :     sqlite3_free_table(tables);
    1104              : 
    1105          245 :     if (!schema || schema_len == 0) return SQLITE_ERROR;
    1106              : 
    1107              :     // Remove trailing comma
    1108          245 :     if (schema_len > 0 && schema[schema_len - 1] == ',') {
    1109          245 :         schema[schema_len - 1] = '\0';
    1110          245 :         schema_len--;
    1111          245 :     }
    1112              : 
    1113              :     DEBUG_MERGE("database_update_schema_hash len %zu schema %s", schema_len, schema);
    1114          245 :     sqlite3_uint64 h = fnv1a_hash(schema, schema_len);
    1115          245 :     cloudsync_memory_free(schema);
    1116          245 :     if (hash && *hash == h) return SQLITE_CONSTRAINT;
    1117              : 
    1118              :     char sql[1024];
    1119          241 :     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          241 :     rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
    1124          241 :     if (rc == SQLITE_OK && hash) *hash = h;
    1125          241 :     return rc;
    1126          245 : }
    1127              : 
    1128              : // MARK: - VM -
    1129              : 
    1130        11712 : int databasevm_prepare (cloudsync_context *data, const char *sql, dbvm_t **vm, int flags) {
    1131        11712 :     return sqlite3_prepare_v3((sqlite3 *)cloudsync_db(data), sql, -1, flags, (sqlite3_stmt **)vm, NULL);
    1132              : }
    1133              : 
    1134       423281 : int databasevm_step (dbvm_t *vm) {
    1135       423281 :     return sqlite3_step((sqlite3_stmt *)vm);
    1136              : }
    1137              : 
    1138        11712 : void databasevm_finalize (dbvm_t *vm) {
    1139        11712 :     sqlite3_finalize((sqlite3_stmt *)vm);
    1140        11712 : }
    1141              : 
    1142       404244 : void databasevm_reset (dbvm_t *vm) {
    1143       404244 :     sqlite3_reset((sqlite3_stmt *)vm);
    1144       404244 : }
    1145              : 
    1146       231934 : void databasevm_clear_bindings (dbvm_t *vm) {
    1147       231934 :     sqlite3_clear_bindings((sqlite3_stmt *)vm);
    1148       231934 : }
    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          865 : int database_pk_names (cloudsync_context *data, const char *table_name, char ***names, int *count) {
    1180              :     char buffer[2048];
    1181          865 :     char *sql = sqlite3_snprintf(sizeof(buffer), buffer, "SELECT name FROM pragma_table_info(%Q) WHERE pk > 0 ORDER BY pk;", table_name);
    1182          865 :     if (!sql) return SQLITE_NOMEM;
    1183              :     
    1184          865 :     sqlite3 *db = (sqlite3 *)cloudsync_db(data);
    1185          865 :     sqlite3_stmt *vm = NULL;
    1186              :     
    1187          865 :     int rc = sqlite3_prepare_v2(db, sql, -1, &vm, NULL);
    1188          865 :     if (rc != SQLITE_OK) goto cleanup;
    1189              :     
    1190              :     // count PK columns
    1191          865 :     int rows = 0;
    1192         2548 :     while ((rc = sqlite3_step(vm)) == SQLITE_ROW) rows++;
    1193          865 :     if (rc != SQLITE_DONE) goto cleanup;
    1194              :     
    1195          865 :     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          865 :     rc = sqlite3_reset(vm);
    1203          865 :     if (rc != SQLITE_OK) goto cleanup;
    1204              :     
    1205              :     // allocate array
    1206          865 :     char **r = (char**)cloudsync_memory_zeroalloc(sizeof(char*) * rows);
    1207          865 :     if (!r) {rc = SQLITE_NOMEM; goto cleanup;}
    1208              :     
    1209          865 :     int i = 0;
    1210         2548 :     while ((rc = sqlite3_step(vm)) == SQLITE_ROW) {
    1211         1683 :         const char *txt = (const char*)sqlite3_column_text(vm, 0);
    1212         1683 :         if (!txt) {rc = SQLITE_ERROR; goto cleanup_r;}
    1213         1683 :         r[i] = cloudsync_string_dup(txt);
    1214         1683 :         if (!r[i]) { rc = SQLITE_NOMEM; goto cleanup_r;}
    1215         1683 :         i++;
    1216              :     }
    1217          865 :     if (rc == SQLITE_DONE) rc = SQLITE_OK;
    1218              :     
    1219          865 :     *names = r;
    1220          865 :     *count = rows;
    1221          865 :     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          865 :     if (vm) sqlite3_finalize(vm);
    1231          865 :     return rc;
    1232          865 : }
    1233              : 
    1234              : // MARK: - BINDING -
    1235              : 
    1236       303774 : int databasevm_bind_blob (dbvm_t *vm, int index, const void *value, uint64_t size) {
    1237       303774 :     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       417743 : int databasevm_bind_int (dbvm_t *vm, int index, int64_t value) {
    1245       417743 :     return sqlite3_bind_int64((sqlite3_stmt *)vm, index, value);
    1246              : }
    1247              : 
    1248          864 : int databasevm_bind_null (dbvm_t *vm, int index) {
    1249          864 :     return sqlite3_bind_null((sqlite3_stmt *)vm, index);
    1250              : }
    1251              : 
    1252       475639 : int databasevm_bind_text (dbvm_t *vm, int index, const char *value, int size) {
    1253       475639 :     return sqlite3_bind_text((sqlite3_stmt *)vm, index, value, size, SQLITE_STATIC);
    1254              : }
    1255              : 
    1256        27991 : int databasevm_bind_value (dbvm_t *vm, int index, dbvalue_t *value) {
    1257        27991 :     return sqlite3_bind_value((sqlite3_stmt *)vm, index, (const sqlite3_value *)value);
    1258              : }
    1259              : 
    1260              : // MARK: - VALUE -
    1261              : 
    1262       679628 : const void *database_value_blob (dbvalue_t *value) {
    1263       679628 :     return sqlite3_value_blob((sqlite3_value *)value);
    1264              : }
    1265              : 
    1266       161115 : double database_value_double (dbvalue_t *value) {
    1267       161115 :     return sqlite3_value_double((sqlite3_value *)value);
    1268              : }
    1269              : 
    1270      1010048 : int64_t database_value_int (dbvalue_t *value) {
    1271      1010048 :     return (int64_t)sqlite3_value_int64((sqlite3_value *)value);
    1272              : }
    1273              : 
    1274       300817 : const char *database_value_text (dbvalue_t *value) {
    1275       300817 :     return (const char *)sqlite3_value_text((sqlite3_value *)value);
    1276              : }
    1277              : 
    1278      1463352 : int database_value_bytes (dbvalue_t *value) {
    1279      1463352 :     return sqlite3_value_bytes((sqlite3_value *)value);
    1280              : }
    1281              : 
    1282      3906095 : int database_value_type (dbvalue_t *value) {
    1283      3906095 :     return sqlite3_value_type((sqlite3_value *)value);
    1284              : }
    1285              : 
    1286        26044 : void database_value_free (dbvalue_t *value) {
    1287        26044 :     sqlite3_value_free((sqlite3_value *)value);
    1288        26044 : }
    1289              : 
    1290        26044 : void *database_value_dup (dbvalue_t *value) {
    1291        26044 :     return sqlite3_value_dup((const sqlite3_value *)value);
    1292              : }
    1293              : 
    1294              : 
    1295              : // MARK: - COLUMN -
    1296              : 
    1297           38 : const void *database_column_blob (dbvm_t *vm, int index, size_t *len) {
    1298           38 :     if (len) *len = sqlite3_column_bytes((sqlite3_stmt *)vm, index);
    1299           38 :     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       157070 : int64_t database_column_int (dbvm_t *vm, int index) {
    1307       157070 :     return (int64_t)sqlite3_column_int64((sqlite3_stmt *)vm, index);
    1308              : }
    1309              : 
    1310        25269 : const char *database_column_text (dbvm_t *vm, int index) {
    1311        25269 :     return (const char *)sqlite3_column_text((sqlite3_stmt *)vm, index);
    1312              : }
    1313              : 
    1314       145410 : dbvalue_t *database_column_value (dbvm_t *vm, int index) {
    1315       145410 :     return (dbvalue_t *)sqlite3_column_value((sqlite3_stmt *)vm, index);
    1316              : }
    1317              : 
    1318          298 : int database_column_bytes (dbvm_t *vm, int index) {
    1319          298 :     return sqlite3_column_bytes((sqlite3_stmt *)vm, index);
    1320              : }
    1321              : 
    1322         1350 : int database_column_type (dbvm_t *vm, int index) {
    1323         1350 :     return sqlite3_column_type((sqlite3_stmt *)vm, index);
    1324              : }
    1325              : 
    1326              : // MARK: - SAVEPOINT -
    1327              : 
    1328        12832 : int database_begin_savepoint (cloudsync_context *data, const char *savepoint_name) {
    1329              :     char sql[1024];
    1330        12832 :     snprintf(sql, sizeof(sql), "SAVEPOINT %s;", savepoint_name);
    1331        12832 :     return database_exec(data, sql);
    1332              : }
    1333              : 
    1334        12826 : int database_commit_savepoint (cloudsync_context *data, const char *savepoint_name) {
    1335              :     char sql[1024];
    1336        12826 :     snprintf(sql, sizeof(sql), "RELEASE %s;", savepoint_name);
    1337        12826 :     return database_exec(data, sql);
    1338              : }
    1339              : 
    1340            7 : int database_rollback_savepoint (cloudsync_context *data, const char *savepoint_name) {
    1341              :     char sql[1024];
    1342            7 :     snprintf(sql, sizeof(sql), "ROLLBACK TO %s; RELEASE %s;", savepoint_name, savepoint_name);
    1343            7 :     return database_exec(data, sql);
    1344              : }
    1345              : 
    1346              : // MARK: - MEMORY -
    1347              : 
    1348        63065 : void *dbmem_alloc (uint64_t size) {
    1349        63065 :     return sqlite3_malloc64((sqlite3_uint64)size);
    1350              : }
    1351              : 
    1352         2390 : void *dbmem_zeroalloc (uint64_t size) {
    1353         2390 :     void *ptr = (void *)dbmem_alloc(size);
    1354         2390 :     if (!ptr) return NULL;
    1355              :     
    1356         2390 :     memset(ptr, 0, (size_t)size);
    1357         2390 :     return ptr;
    1358         2390 : }
    1359              : 
    1360         4569 : void *dbmem_realloc (void *ptr, uint64_t new_size) {
    1361         4569 :     return sqlite3_realloc64(ptr, (sqlite3_uint64)new_size);
    1362              : }
    1363              : 
    1364        14360 : char *dbmem_vmprintf (const char *format, va_list list) {
    1365        14360 :     return sqlite3_vmprintf(format, list);
    1366              : }
    1367              : 
    1368        14360 : char *dbmem_mprintf(const char *format, ...) {
    1369              :     va_list ap;
    1370              :     char *z;
    1371              :     
    1372        14360 :     va_start(ap, format);
    1373        14360 :     z = dbmem_vmprintf(format, ap);
    1374        14360 :     va_end(ap);
    1375              :     
    1376        14360 :     return z;
    1377              : }
    1378              : 
    1379        80911 : void dbmem_free (void *ptr) {
    1380        80911 :     sqlite3_free(ptr);
    1381        80911 : }
    1382              : 
    1383            0 : uint64_t dbmem_size (void *ptr) {
    1384            0 :     return (uint64_t)sqlite3_msize(ptr);
    1385              : }
    1386              : 
    1387              : 
        

Generated by: LCOV version 2.4-0