From 5ee068e2983185cfc5090e2c5af971391eee375f Mon Sep 17 00:00:00 2001 From: Andrea Donetti Date: Tue, 8 Jul 2025 09:46:54 -0600 Subject: [PATCH 1/7] fix quoted identifiers in build_changes_sql --- src/vtab.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vtab.c b/src/vtab.c index ca29429..e3bc3e9 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -146,9 +146,9 @@ char *build_changes_sql (sqlite3 *db, const char *idxs) { " site_tbl.site_id AS site_id, " " t1.seq AS seq, " " COALESCE(t2.col_version, 1) AS cl " - " FROM ''' || \"table_meta\" || ''' AS t1 " + " FROM \"' || \"table_meta\" || '\" AS t1 " " LEFT JOIN cloudsync_site_id AS site_tbl ON t1.site_id = site_tbl.rowid " - " LEFT JOIN ''' || \"table_meta\" || ''' AS t2 ON t1.pk = t2.pk AND t2.col_name = ''" CLOUDSYNC_TOMBSTONE_VALUE "'' " + " LEFT JOIN \"' || \"table_meta\" || '\" AS t2 ON t1.pk = t2.pk AND t2.col_name = ''" CLOUDSYNC_TOMBSTONE_VALUE "'' " " WHERE col_value IS NOT ''" CLOUDSYNC_RLS_RESTRICTED_VALUE "''' " " AS query_string FROM table_names " "), " From 6184457b38e5a7c51127cea4a8eef93d4facf0ad Mon Sep 17 00:00:00 2001 From: Andrea Donetti Date: Tue, 8 Jul 2025 09:49:21 -0600 Subject: [PATCH 2/7] fix a compile warning for different enumeration types --- src/network.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network.c b/src/network.c index eb87c1d..fa21207 100644 --- a/src/network.c +++ b/src/network.c @@ -507,7 +507,7 @@ bool network_compute_endpoints (sqlite3_context *context, network_data *data, co // store proper result code/message #ifndef SQLITE_WASM_EXTRA_INIT if (rc != CURLUE_OK) sqlite3_result_error(context, curl_url_strerror(rc), -1); - sqlite3_result_error_code(context, (rc != CURLE_OK) ? SQLITE_ERROR : SQLITE_NOMEM); + sqlite3_result_error_code(context, (rc != CURLUE_OK) ? SQLITE_ERROR : SQLITE_NOMEM); #else sqlite3_result_error(context, "URL parse error", -1); sqlite3_result_error_code(context, SQLITE_ERROR); From 6773b5e4a2f684cac1b2d11f12c1c1a50818fb9f Mon Sep 17 00:00:00 2001 From: Andrea Donetti Date: Tue, 8 Jul 2025 09:50:15 -0600 Subject: [PATCH 3/7] fix: escape identifiers with double quotes --- src/cloudsync.c | 50 +++++++++++++++++++++++++------------------------ src/dbutils.c | 30 ++++++++++++++--------------- 2 files changed, 41 insertions(+), 39 deletions(-) diff --git a/src/cloudsync.c b/src/cloudsync.c index ef16093..711c259 100644 --- a/src/cloudsync.c +++ b/src/cloudsync.c @@ -360,7 +360,7 @@ char *db_version_build_query (sqlite3 *db) { // the good news is that the query can be computed in SQLite without the need to do any extra computation from the host language const char *sql = "WITH table_names AS (" - "SELECT format('%w', name) as tbl_name " + "SELECT format('%q', name) as tbl_name " "FROM sqlite_master " "WHERE type='table' " "AND name LIKE '%_cloudsync'" @@ -507,12 +507,12 @@ char *table_build_values_sql (sqlite3 *db, cloudsync_table_context *table) { #if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES if (table->rowid_only) { - sql = memory_mprintf("WITH col_names AS (SELECT group_concat(name, ',') AS cols FROM pragma_table_info('%w') WHERE pk=0 ORDER BY cid) SELECT 'SELECT ' || (SELECT cols FROM col_names) || ' FROM %w WHERE rowid=?;'", table->name, table->name); + sql = memory_mprintf("WITH col_names AS (SELECT group_concat('\"' || name || '\"', ',') AS cols FROM pragma_table_info('%q') WHERE pk=0 ORDER BY cid) SELECT 'SELECT ' || (SELECT cols FROM col_names) || ' FROM \"%w\" WHERE rowid=?;'", table->name, table->name); goto process_process; } #endif - sql = cloudsync_memory_mprintf("WITH col_names AS (SELECT group_concat(name, ',') AS cols FROM pragma_table_info('%w') WHERE pk=0 ORDER BY cid), pk_where AS (SELECT group_concat(name, '=? AND ') || '=?' AS pk_clause FROM pragma_table_info('%w') WHERE pk>0 ORDER BY pk) SELECT 'SELECT ' || (SELECT cols FROM col_names) || ' FROM %w WHERE ' || (SELECT pk_clause FROM pk_where) || ';'", table->name, table->name, table->name); + sql = cloudsync_memory_mprintf("WITH col_names AS (SELECT group_concat('\"' || name || '\"', ',') AS cols FROM pragma_table_info('%q') WHERE pk=0 ORDER BY cid), pk_where AS (SELECT group_concat('\"' || name || '\"', '=? AND ') || '=?' AS pk_clause FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk) SELECT 'SELECT ' || (SELECT cols FROM col_names) || ' FROM \"%w\" WHERE ' || (SELECT pk_clause FROM pk_where) || ';'", table->name, table->name, table->name); #if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES process_process: @@ -527,12 +527,12 @@ char *table_build_values_sql (sqlite3 *db, cloudsync_table_context *table) { char *table_build_mergedelete_sql (sqlite3 *db, cloudsync_table_context *table) { #if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES if (table->rowid_only) { - char *sql = memory_mprintf("DELETE FROM %w WHERE rowid=?;", table->name); + char *sql = memory_mprintf("DELETE FROM \"%w\" WHERE rowid=?;", table->name); return sql; } #endif - char *sql = cloudsync_memory_mprintf("WITH pk_where AS (SELECT group_concat(name, '=? AND ') || '=?' AS pk_clause FROM pragma_table_info('%w') WHERE pk>0 ORDER BY pk) SELECT 'DELETE FROM %w WHERE ' || (SELECT pk_clause FROM pk_where) || ';'", table->name, table->name); + char *sql = cloudsync_memory_mprintf("WITH pk_where AS (SELECT group_concat('\"' || name || '\"', '=? AND ') || '=?' AS pk_clause FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk) SELECT 'DELETE FROM \"%w\" WHERE ' || (SELECT pk_clause FROM pk_where) || ';'", table->name, table->name); if (!sql) return NULL; char *query = dbutils_text_select(db, sql); @@ -548,10 +548,10 @@ char *table_build_mergeinsert_sql (sqlite3 *db, cloudsync_table_context *table, if (table->rowid_only) { if (colname == NULL) { // INSERT OR IGNORE INTO customers (first_name,last_name) VALUES (?,?); - sql = memory_mprintf("INSERT OR IGNORE INTO %w (rowid) VALUES (?);", table->name); + sql = memory_mprintf("INSERT OR IGNORE INTO \"%w\" (rowid) VALUES (?);", table->name); } else { // INSERT INTO customers (first_name,last_name,age) VALUES (?,?,?) ON CONFLICT DO UPDATE SET age=?; - sql = memory_mprintf("INSERT INTO %w (rowid, %w) VALUES (?, ?) ON CONFLICT DO UPDATE SET %w=?;", table->name, colname, colname); + sql = memory_mprintf("INSERT INTO \"%w\" (rowid, \"%w\") VALUES (?, ?) ON CONFLICT DO UPDATE SET \"%w\"=?;", table->name, colname, colname); } return sql; } @@ -559,9 +559,9 @@ char *table_build_mergeinsert_sql (sqlite3 *db, cloudsync_table_context *table, if (colname == NULL) { // is sentinel insert - sql = cloudsync_memory_mprintf("WITH pk_where AS (SELECT group_concat(name) AS pk_clause FROM pragma_table_info('%w') WHERE pk>0 ORDER BY pk), pk_bind AS (SELECT group_concat('?') AS pk_binding FROM pragma_table_info('%w') WHERE pk>0 ORDER BY pk) SELECT 'INSERT OR IGNORE INTO %w (' || (SELECT pk_clause FROM pk_where) || ') VALUES (' || (SELECT pk_binding FROM pk_bind) || ');'", table->name, table->name, table->name); + sql = cloudsync_memory_mprintf("WITH pk_where AS (SELECT group_concat('\"' || name || '\"') AS pk_clause FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk), pk_bind AS (SELECT group_concat('?') AS pk_binding FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk) SELECT 'INSERT OR IGNORE INTO \"%w\" (' || (SELECT pk_clause FROM pk_where) || ') VALUES (' || (SELECT pk_binding FROM pk_bind) || ');'", table->name, table->name, table->name); } else { - sql = cloudsync_memory_mprintf("WITH pk_where AS (SELECT group_concat(name) AS pk_clause FROM pragma_table_info('%w') WHERE pk>0 ORDER BY pk), pk_bind AS (SELECT group_concat('?') AS pk_binding FROM pragma_table_info('%w') WHERE pk>0 ORDER BY pk) SELECT 'INSERT INTO %w (' || (SELECT pk_clause FROM pk_where) || ',%w) VALUES (' || (SELECT pk_binding FROM pk_bind) || ',?) ON CONFLICT DO UPDATE SET %w=?;'", table->name, table->name, table->name, colname, colname); + sql = cloudsync_memory_mprintf("WITH pk_where AS (SELECT group_concat('\"' || name || '\"') AS pk_clause FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk), pk_bind AS (SELECT group_concat('?') AS pk_binding FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk) SELECT 'INSERT INTO \"%w\" (' || (SELECT pk_clause FROM pk_where) || ',\"%w\") VALUES (' || (SELECT pk_binding FROM pk_bind) || ',?) ON CONFLICT DO UPDATE SET \"%w\"=?;'", table->name, table->name, table->name, colname, colname); } if (!sql) return NULL; @@ -572,15 +572,17 @@ char *table_build_mergeinsert_sql (sqlite3 *db, cloudsync_table_context *table, } char *table_build_value_sql (sqlite3 *db, cloudsync_table_context *table, const char *colname) { + char *colnamequote = dbutils_is_star_table(colname) ? "" : "\""; + #if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES if (table->rowid_only) { - char *sql = memory_mprintf("SELECT %w FROM %w WHERE rowid=?;", colname, table->name); + char *sql = memory_mprintf("SELECT %s%w%s FROM \"%w\" WHERE rowid=?;", colnamequote, colname, colnamequote, table->name); return sql; } #endif - + // SELECT age FROM customers WHERE first_name=? AND last_name=?; - char *sql = cloudsync_memory_mprintf("WITH pk_where AS (SELECT group_concat(name, '=? AND ') || '=?' AS pk_clause FROM pragma_table_info('%w') WHERE pk>0 ORDER BY pk) SELECT 'SELECT %w FROM %w WHERE ' || (SELECT pk_clause FROM pk_where) || ';'", table->name, colname, table->name); + char *sql = cloudsync_memory_mprintf("WITH pk_where AS (SELECT group_concat('\"' || name || '\"', '=? AND ') || '=?' AS pk_clause FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk) SELECT 'SELECT %s%w%s FROM \"%w\" WHERE ' || (SELECT pk_clause FROM pk_where) || ';'", table->name, colnamequote, colname, colnamequote, table->name); if (!sql) return NULL; char *query = dbutils_text_select(db, sql); @@ -1627,7 +1629,7 @@ int cloudsync_finalize_alter (sqlite3_context *context, cloudsync_context *data, // compact meta-table // delete entries for removed columns char *sql = cloudsync_memory_mprintf("DELETE FROM \"%w_cloudsync\" WHERE \"col_name\" NOT IN (" - "SELECT name FROM pragma_table_info('%w') UNION SELECT '%s'" + "SELECT name FROM pragma_table_info('%q') UNION SELECT '%s'" ")", table->name, table->name, CLOUDSYNC_TOMBSTONE_VALUE); rc = sqlite3_exec(db, sql, NULL, NULL, NULL); cloudsync_memory_free(sql); @@ -1636,7 +1638,7 @@ int cloudsync_finalize_alter (sqlite3_context *context, cloudsync_context *data, goto finalize; } - sql = cloudsync_memory_mprintf("SELECT group_concat('%w.' || name, ',') FROM pragma_table_info('%w') WHERE pk>0 ORDER BY pk;", table->name, table->name); + sql = cloudsync_memory_mprintf("SELECT group_concat('\"%w\".\"' || name || '\"', ',') FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk;", table->name, table->name); if (!sql) { rc = SQLITE_NOMEM; goto finalize; @@ -1646,7 +1648,7 @@ int cloudsync_finalize_alter (sqlite3_context *context, cloudsync_context *data, cloudsync_memory_free(sql); // delete entries related to rows that no longer exist in the original table, but preserve tombstone - sql = cloudsync_memory_mprintf("DELETE FROM \"%w_cloudsync\" WHERE (\"col_name\" != '%s' OR (\"col_name\" = '%s' AND col_version %% 2 != 0)) AND NOT EXISTS (SELECT 1 FROM \"%w\" WHERE \"%w_cloudsync\".pk = cloudsync_pk_encode(%w) LIMIT 1);", table->name, CLOUDSYNC_TOMBSTONE_VALUE, CLOUDSYNC_TOMBSTONE_VALUE, table->name, table->name, pkvalues); + sql = cloudsync_memory_mprintf("DELETE FROM \"%w_cloudsync\" WHERE (\"col_name\" != '%s' OR (\"col_name\" = '%s' AND col_version %% 2 != 0)) AND NOT EXISTS (SELECT 1 FROM \"%w\" WHERE \"%w_cloudsync\".pk = cloudsync_pk_encode(%s) LIMIT 1);", table->name, CLOUDSYNC_TOMBSTONE_VALUE, CLOUDSYNC_TOMBSTONE_VALUE, table->name, table->name, pkvalues); rc = sqlite3_exec(db, sql, NULL, NULL, NULL); if (pkclause) cloudsync_memory_free(pkclause); cloudsync_memory_free(sql); @@ -1674,23 +1676,23 @@ int cloudsync_refill_metatable (sqlite3 *db, cloudsync_context *data, const char sqlite3_stmt *vm = NULL; sqlite3_int64 db_version = db_version_next(db, data, CLOUDSYNC_VALUE_NOTSET); - - char *sql = cloudsync_memory_mprintf("SELECT group_concat(name, ',') FROM pragma_table_info('%w') WHERE pk>0 ORDER BY pk;", table_name); - char *pkclause = dbutils_text_select(db, sql); - char *pkvalues = (pkclause) ? pkclause : "rowid"; + + char *sql = cloudsync_memory_mprintf("SELECT group_concat('\"' || name || '\"', ',') FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk;", table_name); + char *pkclause_identifiers = dbutils_text_select(db, sql); + char *pkvalues_identifiers = (pkclause_identifiers) ? pkclause_identifiers : "rowid"; cloudsync_memory_free(sql); - sql = cloudsync_memory_mprintf("SELECT group_concat('cloudsync_pk_decode(pk, ' || pk || ') AS ' || name, ',') FROM pragma_table_info('%w') WHERE pk>0 ORDER BY pk;", table_name); + sql = cloudsync_memory_mprintf("SELECT group_concat('cloudsync_pk_decode(pk, ' || pk || ') AS ' || '\"' || name || '\"', ',') FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk;", table_name); char *pkdecode = dbutils_text_select(db, sql); char *pkdecodeval = (pkdecode) ? pkdecode : "cloudsync_pk_decode(pk, 1) AS rowid"; cloudsync_memory_free(sql); - sql = cloudsync_memory_mprintf("SELECT group_concat(name || ' = cloudsync_pk_decode(pk, ' || pk || ')', ' AND ') FROM pragma_table_info('%w') WHERE pk>0 ORDER BY pk;", table_name); + sql = cloudsync_memory_mprintf("SELECT group_concat('\"' || name || '\"' || ' = cloudsync_pk_decode(pk, ' || pk || ')', ' AND ') FROM pragma_table_info('%q') WHERE pk>0 ORDER BY pk;", table_name); char *pkonclause = dbutils_text_select(db, sql); char *pkonclauseval = (pkonclause) ? pkonclause : "rowid = cloudsync_pk_decode(pk, 1) AS rowid"; cloudsync_memory_free(sql); - sql = cloudsync_memory_mprintf("SELECT cloudsync_insert('%w', %w) FROM (SELECT %w FROM \"%w\" EXCEPT SELECT %s FROM \"%w_cloudsync\");", table_name, pkvalues, pkvalues, table_name, pkdecodeval, table_name); + sql = cloudsync_memory_mprintf("SELECT cloudsync_insert('%q', %s) FROM (SELECT %s FROM \"%w\" EXCEPT SELECT %s FROM \"%w_cloudsync\");", table_name, pkvalues_identifiers, pkvalues_identifiers, table_name, pkdecodeval, table_name); int rc = sqlite3_exec(db, sql, NULL, NULL, NULL); cloudsync_memory_free(sql); if (rc != SQLITE_OK) goto finalize; @@ -1698,7 +1700,7 @@ int cloudsync_refill_metatable (sqlite3 *db, cloudsync_context *data, const char // fill missing colums // for each non-pk column: - sql = cloudsync_memory_mprintf("SELECT cloudsync_pk_encode(%w) FROM \"%w\" LEFT JOIN \"%w_cloudsync\" ON %s AND \"%w_cloudsync\".col_name = ? WHERE \"%w_cloudsync\".db_version IS NULL", pkvalues, table_name, table_name, pkonclauseval, table_name, table_name); + sql = cloudsync_memory_mprintf("SELECT cloudsync_pk_encode(%s) FROM \"%w\" LEFT JOIN \"%w_cloudsync\" ON %s AND \"%w_cloudsync\".col_name = ? WHERE \"%w_cloudsync\".db_version IS NULL", pkvalues_identifiers, table_name, table_name, pkonclauseval, table_name, table_name); rc = sqlite3_prepare(db, sql, -1, &vm, NULL); cloudsync_memory_free(sql); if (rc != SQLITE_OK) goto finalize; @@ -1729,7 +1731,7 @@ int cloudsync_refill_metatable (sqlite3 *db, cloudsync_context *data, const char finalize: if (rc != SQLITE_OK) DEBUG_ALWAYS("cloudsync_refill_metatable error: %s", sqlite3_errmsg(db)); - if (pkclause) cloudsync_memory_free(pkclause); + if (pkclause_identifiers) cloudsync_memory_free(pkclause_identifiers); if (pkdecode) cloudsync_memory_free(pkdecode); if (pkonclause) cloudsync_memory_free(pkonclause); if (vm) sqlite3_finalize(vm); diff --git a/src/dbutils.c b/src/dbutils.c index 54993fd..2f5ef0d 100644 --- a/src/dbutils.c +++ b/src/dbutils.c @@ -470,23 +470,23 @@ int dbutils_delete_triggers (sqlite3 *db, const char *table) { size_t blen = sizeof(buffer); int rc = SQLITE_ERROR; - char *sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS cloudsync_before_update_%w;", table); + char *sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_before_update_%w\";", table); rc = sqlite3_exec(db, sql, NULL, NULL, NULL); if (rc != SQLITE_OK) goto finalize; - sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS cloudsync_before_delete_%w;", table); + sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_before_delete_%w\";", table); rc = sqlite3_exec(db, sql, NULL, NULL, NULL); if (rc != SQLITE_OK) goto finalize; - sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS cloudsync_after_insert_%w;", table); + sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_after_insert_%w\";", table); rc = sqlite3_exec(db, sql, NULL, NULL, NULL); if (rc != SQLITE_OK) goto finalize; - sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS cloudsync_after_update_%w;", table); + sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_after_update_%w\";", table); rc = sqlite3_exec(db, sql, NULL, NULL, NULL); if (rc != SQLITE_OK) goto finalize; - sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS cloudsync_after_delete_%w;", table); + sql = sqlite3_snprintf((int)blen, buffer, "DROP TRIGGER IF EXISTS \"cloudsync_after_delete_%w\";", table); rc = sqlite3_exec(db, sql, NULL, NULL, NULL); if (rc != SQLITE_OK) goto finalize; @@ -512,14 +512,14 @@ int dbutils_check_triggers (sqlite3 *db, const char *table, table_algo algo) { if (!dbutils_trigger_exists(db, trigger_name)) { rc = SQLITE_NOMEM; - char *sql = cloudsync_memory_mprintf("SELECT group_concat('NEW.' || name, ',') FROM pragma_table_info('%w') WHERE pk>0 ORDER BY pk;", table); + char *sql = cloudsync_memory_mprintf("SELECT group_concat('NEW.\"' || name || '\"', ',') FROM pragma_table_info('%w') WHERE pk>0 ORDER BY pk;", table); if (!sql) goto finalize; char *pkclause = dbutils_text_select(db, sql); char *pkvalues = (pkclause) ? pkclause : "NEW.rowid"; cloudsync_memory_free(sql); - sql = cloudsync_memory_mprintf("CREATE TRIGGER %s AFTER INSERT ON %w %s BEGIN SELECT cloudsync_insert('%w', %s); END", trigger_name, table, trigger_when, table, pkvalues); + sql = cloudsync_memory_mprintf("CREATE TRIGGER \"%s\" AFTER INSERT ON \"%w\" %s BEGIN SELECT cloudsync_insert('%w', %s); END", trigger_name, table, trigger_when, table, pkvalues); if (pkclause) cloudsync_memory_free(pkclause); if (!sql) goto finalize; @@ -541,22 +541,22 @@ int dbutils_check_triggers (sqlite3 *db, const char *table, table_algo algo) { if (!trigger_name) goto finalize; if (!dbutils_trigger_exists(db, trigger_name)) { - char *sql = cloudsync_memory_mprintf("SELECT group_concat('NEW.' || name, ',') || ',' || group_concat('OLD.' || name, ',') FROM pragma_table_info('%w') WHERE pk>0 ORDER BY pk;", table); + char *sql = cloudsync_memory_mprintf("SELECT group_concat('NEW.\"' || name || '\"', ',') || ',' || group_concat('OLD.\"' || name || '\"', ',') FROM pragma_table_info('%w') WHERE pk>0 ORDER BY pk;", table); if (!sql) goto finalize; char *pkclause = dbutils_text_select(db, sql); char *pkvalues = (pkclause) ? pkclause : "NEW.rowid,OLD.rowid"; cloudsync_memory_free(sql); - sql = cloudsync_memory_mprintf("SELECT group_concat('NEW.' || name || ', OLD.' || name, ',') FROM pragma_table_info('%w') WHERE pk=0 ORDER BY cid;", table); + sql = cloudsync_memory_mprintf("SELECT group_concat('NEW.\"' || name || '\"' || ', OLD.\"' || name || '\"', ',') FROM pragma_table_info('%w') WHERE pk=0 ORDER BY cid;", table); if (!sql) goto finalize; char *colvalues = dbutils_text_select(db, sql); cloudsync_memory_free(sql); if (colvalues == NULL) { - sql = cloudsync_memory_mprintf("CREATE TRIGGER %s AFTER UPDATE ON %w %s BEGIN SELECT cloudsync_update('%w',%s); END", trigger_name, table, trigger_when, table, pkvalues); + sql = cloudsync_memory_mprintf("CREATE TRIGGER \"%s\" AFTER UPDATE ON \"%w\" %s BEGIN SELECT cloudsync_update('%w',%s); END", trigger_name, table, trigger_when, table, pkvalues); } else { - sql = cloudsync_memory_mprintf("CREATE TRIGGER %s AFTER UPDATE ON %w %s BEGIN SELECT cloudsync_update('%w',%s,%s); END", trigger_name, table, trigger_when, table, pkvalues, colvalues); + sql = cloudsync_memory_mprintf("CREATE TRIGGER \"%s\" AFTER UPDATE ON \"%w\" %s BEGIN SELECT cloudsync_update('%w',%s,%s); END", trigger_name, table, trigger_when, table, pkvalues, colvalues); cloudsync_memory_free(colvalues); } if (pkclause) cloudsync_memory_free(pkclause); @@ -579,7 +579,7 @@ int dbutils_check_triggers (sqlite3 *db, const char *table, table_algo algo) { if (!trigger_name) goto finalize; if (!dbutils_trigger_exists(db, trigger_name)) { - char *sql = cloudsync_memory_mprintf("CREATE TRIGGER %s BEFORE UPDATE ON %w FOR EACH ROW WHEN cloudsync_is_enabled('%w') = 1 BEGIN SELECT RAISE(ABORT, 'Error: UPDATE operation is not allowed on table %w.'); END", trigger_name, table, table, table); + char *sql = cloudsync_memory_mprintf("CREATE TRIGGER \"%s\" BEFORE UPDATE ON \"%w\" FOR EACH ROW WHEN cloudsync_is_enabled('%w') = 1 BEGIN SELECT RAISE(ABORT, 'Error: UPDATE operation is not allowed on table %w.'); END", trigger_name, table, table, table); if (!sql) goto finalize; rc = sqlite3_exec(db, sql, NULL, NULL, NULL); @@ -598,14 +598,14 @@ int dbutils_check_triggers (sqlite3 *db, const char *table, table_algo algo) { if (!trigger_name) goto finalize; if (!dbutils_trigger_exists(db, trigger_name)) { - char *sql = cloudsync_memory_mprintf("SELECT group_concat('OLD.' || name, ',') FROM pragma_table_info('%w') WHERE pk>0 ORDER BY pk;", table); + char *sql = cloudsync_memory_mprintf("SELECT group_concat('OLD.\"' || name || '\"', ',') FROM pragma_table_info('%w') WHERE pk>0 ORDER BY pk;", table); if (!sql) goto finalize; char *pkclause = dbutils_text_select(db, sql); char *pkvalues = (pkclause) ? pkclause : "OLD.rowid"; cloudsync_memory_free(sql); - sql = cloudsync_memory_mprintf("CREATE TRIGGER %s AFTER DELETE ON %w %s BEGIN SELECT cloudsync_delete('%w',%s); END", trigger_name, table, trigger_when, table, pkvalues); + sql = cloudsync_memory_mprintf("CREATE TRIGGER \"%s\" AFTER DELETE ON \"%w\" %s BEGIN SELECT cloudsync_delete('%w',%s); END", trigger_name, table, trigger_when, table, pkvalues); if (pkclause) cloudsync_memory_free(pkclause); if (!sql) goto finalize; @@ -624,7 +624,7 @@ int dbutils_check_triggers (sqlite3 *db, const char *table, table_algo algo) { if (!trigger_name) goto finalize; if (!dbutils_trigger_exists(db, trigger_name)) { - char *sql = cloudsync_memory_mprintf("CREATE TRIGGER %s BEFORE DELETE ON %w FOR EACH ROW WHEN cloudsync_is_enabled('%w') = 1 BEGIN SELECT RAISE(ABORT, 'Error: DELETE operation is not allowed on table %w.'); END", trigger_name, table, table, table); + char *sql = cloudsync_memory_mprintf("CREATE TRIGGER \"%s\" BEFORE DELETE ON \"%w\" FOR EACH ROW WHEN cloudsync_is_enabled('%w') = 1 BEGIN SELECT RAISE(ABORT, 'Error: DELETE operation is not allowed on table %w.'); END", trigger_name, table, table, table); if (!sql) goto finalize; rc = sqlite3_exec(db, sql, NULL, NULL, NULL); From 32027069946dae50ec23a258b2cbd7c352fcdc2d Mon Sep 17 00:00:00 2001 From: Andrea Donetti Date: Tue, 8 Jul 2025 09:50:52 -0600 Subject: [PATCH 4/7] test: add tests with quoted identifiers --- test/unit.c | 314 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 183 insertions(+), 131 deletions(-) diff --git a/test/unit.c b/test/unit.c index 69d43d9..05ba216 100644 --- a/test/unit.c +++ b/test/unit.c @@ -62,6 +62,9 @@ static int test_counter = 1; #define MAX_SIMULATED_CLIENTS 128 +#define CUSTOMERS_TABLE "customers 1 test table" +#define CUSTOMERS_NOCOLS_TABLE "customers nocols" + typedef struct { int type; union { @@ -487,11 +490,11 @@ void do_query (sqlite3 *db, const char *sql, const char *type) { void do_insert (sqlite3 *db, int table_mask, int ninsert, bool print_result) { if (table_mask & TEST_PRIKEYS) { - const char *table_name = "customers"; + const char *table_name = CUSTOMERS_TABLE; if (print_result) printf("TESTING INSERT on %s\n", table_name); for (int i=0; i customers\n"); - do_query(db[0], "SELECT * FROM customers ORDER BY first_name, last_name;", query_table); + do_query(db[0], "SELECT * FROM \"" CUSTOMERS_TABLE "\" ORDER BY first_name, last_name;", query_table); } result = true; @@ -2342,7 +2394,7 @@ bool do_test_merge_2 (int nclients, int table_mask, bool print_result, bool clea if (nrows == NINSERT) { do_update_random(db[i], table_mask, print_result); } else { - rc = sqlite3_exec(db[i], "UPDATE customers SET age = ABS(RANDOM()), note='hello' || HEX(RANDOMBLOB(2)), stamp='stamp' || ABS(RANDOM() % 99) WHERE rowid=1;", NULL, NULL, NULL); + rc = sqlite3_exec(db[i], "UPDATE \"" CUSTOMERS_TABLE "\" SET age = ABS(RANDOM()), note='hello' || HEX(RANDOMBLOB(2)), stamp='stamp' || ABS(RANDOM() % 99) WHERE rowid=1;", NULL, NULL, NULL); if (rc != SQLITE_OK) goto finalize; } } @@ -2362,7 +2414,7 @@ bool do_test_merge_2 (int nclients, int table_mask, bool print_result, bool clea if (nrows == NINSERT) { do_update_random(db[i], table_mask, print_result); } else { - rc = sqlite3_exec(db[i], "UPDATE customers SET age = ABS(RANDOM()), note='hello' || HEX(RANDOMBLOB(2)), stamp='stamp' || ABS(RANDOM() % 99) WHERE rowid=1;", NULL, NULL, NULL); + rc = sqlite3_exec(db[i], "UPDATE \"" CUSTOMERS_TABLE "\" SET age = ABS(RANDOM()), note='hello' || HEX(RANDOMBLOB(2)), stamp='stamp' || ABS(RANDOM() % 99) WHERE rowid=1;", NULL, NULL, NULL); if (rc != SQLITE_OK) goto finalize; } } @@ -2378,23 +2430,23 @@ bool do_test_merge_2 (int nclients, int table_mask, bool print_result, bool clea // compare results for (int i=1; i customers\n"); - do_query(db[0], "SELECT * FROM customers ORDER BY first_name, last_name;", query_table); + printf("\n-> " CUSTOMERS_TABLE "\n"); + do_query(db[0], "SELECT * FROM \"" CUSTOMERS_TABLE "\" ORDER BY first_name, last_name;", query_table); } if (table_mask & TEST_NOCOLS) { - printf("\n-> customers_nocols\n"); - do_query(db[0], "SELECT * FROM customers_nocols ORDER BY first_name, last_name;", query_table); + printf("\n-> \"" CUSTOMERS_NOCOLS_TABLE "s\"\n"); + do_query(db[0], "SELECT * FROM \"" CUSTOMERS_NOCOLS_TABLE "\" ORDER BY first_name, last_name;", query_table); } } @@ -2463,7 +2515,7 @@ bool do_test_merge_4 (int nclients, bool print_result, bool cleanup_databases) { // insert, update and delete some data in all clients for (int i=0; i customers\n"); - do_query(db[0], "SELECT * FROM customers ORDER BY first_name, last_name;", query_table); + printf("\n-> " CUSTOMERS_TABLE "\n"); + do_query(db[0], "SELECT * FROM \"" CUSTOMERS_TABLE "\" ORDER BY first_name, last_name;", query_table); } result = true; @@ -2554,7 +2606,7 @@ bool do_test_merge_5 (int nclients, bool print_result, bool cleanup_databases, b } int n = 99; - snprintf(buf, sizeof(buf), "UPDATE customers SET age=%d;", n); + snprintf(buf, sizeof(buf), "UPDATE \"" CUSTOMERS_TABLE "\" SET age=%d;", n); rc = sqlite3_exec(db[0], buf, NULL, NULL, NULL); if (rc != SQLITE_OK) goto finalize; @@ -2562,7 +2614,7 @@ bool do_test_merge_5 (int nclients, bool print_result, bool cleanup_databases, b return false; } - snprintf(buf, sizeof(buf), "UPDATE customers SET first_name='name%d';", n); + snprintf(buf, sizeof(buf), "UPDATE \"" CUSTOMERS_TABLE "\" SET first_name='name%d';", n); rc = sqlite3_exec(db[1], buf, NULL, NULL, NULL); if (rc != SQLITE_OK) goto finalize; @@ -2573,13 +2625,13 @@ bool do_test_merge_5 (int nclients, bool print_result, bool cleanup_databases, b // compare results for (int i=1; i customers\n"); - do_query(db[0], "SELECT * FROM customers ORDER BY first_name, last_name;", query_table); + do_query(db[0], "SELECT * FROM \"" CUSTOMERS_TABLE "\" ORDER BY first_name, last_name;", query_table); } result = true; @@ -2661,13 +2713,13 @@ bool do_test_merge_alter_schema_1 (int nclients, bool print_result, bool cleanup // compare results for (int i=1; i customers\n"); - do_query(db[0], "SELECT * FROM customers ORDER BY first_name, last_name;", query_table); + do_query(db[0], "SELECT * FROM \"" CUSTOMERS_TABLE "\" ORDER BY first_name, last_name;", query_table); } result = true; @@ -2750,13 +2802,13 @@ bool do_test_merge_alter_schema_2 (int nclients, bool print_result, bool cleanup // compare results for (int i=1; i customers\n"); - do_query(db[0], "SELECT * FROM customers ORDER BY first_name, last_name;", query_table); + do_query(db[0], "SELECT * FROM \"" CUSTOMERS_TABLE "\" ORDER BY first_name, last_name;", query_table); } result = true; @@ -3059,13 +3111,13 @@ bool do_test_network_encode_decode (int nclients, bool print_result, bool cleanu // compare results for (int i=1; i customers\n"); - do_query(db[0], "SELECT * FROM customers ORDER BY first_name, last_name;", query_table); + do_query(db[0], "SELECT * FROM \"" CUSTOMERS_TABLE "\" ORDER BY first_name, last_name;", query_table); } result = true; @@ -3127,11 +3179,11 @@ bool do_test_fill_initial_data(int nclients, bool print_result, bool cleanup_dat // compare results for (int i=1; i customers\n"); - do_query(db[0], "SELECT * FROM customers ORDER BY first_name, last_name;", query_table); + printf("\n-> " CUSTOMERS_TABLE "\n"); + do_query(db[0], "SELECT * FROM \"" CUSTOMERS_TABLE "\" ORDER BY first_name, last_name;", query_table); } if (table_mask & TEST_NOCOLS) { - printf("\n-> customers_nocols\n"); - do_query(db[0], "SELECT * FROM customers_nocols ORDER BY first_name, last_name;", query_table); + printf("\n-> \"" CUSTOMERS_NOCOLS_TABLE "\"\n"); + do_query(db[0], "SELECT * FROM \"" CUSTOMERS_NOCOLS_TABLE "\" ORDER BY first_name, last_name;", query_table); } if (table_mask & TEST_NOPRIKEYS) { printf("\n-> customers_noprikey\n"); @@ -3232,10 +3284,10 @@ bool do_test_alter(int nclients, int alter_version, bool print_result, bool clea const char *sql; switch (alter_version) { case 3: - sql = "SELECT * FROM customers ORDER BY name;"; + sql = "SELECT * FROM \"" CUSTOMERS_TABLE "\" ORDER BY name;"; break; default: - sql = "SELECT * FROM customers ORDER BY first_name, last_name;"; + sql = "SELECT * FROM \"" CUSTOMERS_TABLE "\" ORDER BY first_name, last_name;"; break; } if (do_compare_queries(db[0], sql, db[i], sql, -1, -1, print_result) == false) goto finalize; @@ -3244,10 +3296,10 @@ bool do_test_alter(int nclients, int alter_version, bool print_result, bool clea const char *sql; switch (alter_version) { case 3: - sql = "SELECT * FROM customers_nocols ORDER BY name;"; + sql = "SELECT * FROM \"" CUSTOMERS_NOCOLS_TABLE "\" ORDER BY name;"; break; default: - sql = "SELECT * FROM customers_nocols ORDER BY first_name, last_name;"; + sql = "SELECT * FROM \"" CUSTOMERS_NOCOLS_TABLE "\" ORDER BY first_name, last_name;"; break; } if (do_compare_queries(db[0], sql, db[i], sql, -1, -1, print_result) == false) goto finalize; @@ -3268,12 +3320,12 @@ bool do_test_alter(int nclients, int alter_version, bool print_result, bool clea if (print_result) { if (table_mask & TEST_PRIKEYS) { - printf("\n-> customers\n"); - do_query(db[0], "SELECT * FROM customers ORDER BY first_name, last_name;", query_table); + printf("\n-> " CUSTOMERS_TABLE "\n"); + do_query(db[0], "SELECT * FROM " CUSTOMERS_TABLE " ORDER BY first_name, last_name;", query_table); } if (table_mask & TEST_NOCOLS) { - printf("\n-> customers_nocols\n"); - do_query(db[0], "SELECT * FROM customers_nocols ORDER BY first_name, last_name;", query_table); + printf("\n-> \"" CUSTOMERS_NOCOLS_TABLE "\"\n"); + do_query(db[0], "SELECT * FROM \"" CUSTOMERS_NOCOLS_TABLE "\" ORDER BY first_name, last_name;", query_table); } if (table_mask & TEST_NOPRIKEYS) { printf("\n-> customers_noprikey\n"); @@ -3329,23 +3381,23 @@ int main(int argc, const char * argv[]) { result += test_report("DBUtils Test:", do_test_dbutils()); result += test_report("Minor Test:", do_test_others(db)); result += test_report("Test Error Cases:", do_test_error_cases(db)); - + int test_mask = TEST_INSERT | TEST_UPDATE | TEST_DELETE; int table_mask = TEST_PRIKEYS | TEST_NOCOLS; #if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES table_mask |= TEST_NOPRIKEYS; #endif - + // test local changes result += test_report("Local Test:", do_test_local(test_mask, table_mask, db, print_result)); result += test_report("VTab Test: ", do_test_vtab(db)); result += test_report("Functions Test:", do_test_functions(db, print_result)); result += test_report("Functions Test (Int):", do_test_internal_functions()); result += test_report("String Func Test:", do_test_string_replace_prefix()); - + // close local database db = close_db(db); - + // simulate remote merge result += test_report("Merge Test:", do_test_merge(3, print_result, cleanup_databases)); result += test_report("Merge Test 2:", do_test_merge_2(3, TEST_PRIKEYS, print_result, cleanup_databases)); From fd8d07e693b5caf3032edf32959046ece1e45198 Mon Sep 17 00:00:00 2001 From: Andrea Donetti Date: Tue, 8 Jul 2025 10:18:20 -0600 Subject: [PATCH 5/7] test: add -DSQLITE_DQS=0 when building for unittest --- Makefile | 2 +- test/unit.c | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 599f998..408f131 100644 --- a/Makefile +++ b/Makefile @@ -181,7 +181,7 @@ $(TEST_TARGET): $(TEST_OBJ) $(BUILD_RELEASE)/%.o: %.c $(CC) $(CFLAGS) -O3 -fPIC -c $< -o $@ $(BUILD_TEST)/sqlite3.o: $(SQLITE_DIR)/sqlite3.c - $(CC) $(CFLAGS) -DSQLITE_CORE -c $< -o $@ + $(CC) $(CFLAGS) -DSQLITE_DQS=0 -DSQLITE_CORE -c $< -o $@ $(BUILD_TEST)/%.o: %.c $(CC) $(T_CFLAGS) -c $< -o $@ diff --git a/test/unit.c b/test/unit.c index 05ba216..2f66b5d 100644 --- a/test/unit.c +++ b/test/unit.c @@ -1956,8 +1956,13 @@ bool do_test_internal_functions (void) { int rc = sqlite3_open(":memory:", &db); if (rc != SQLITE_OK) goto abort_test; - rc = sqlite3_exec(db, "SELECT \"double-quoted string literal misfeature\"", NULL, NULL, NULL); - if (rc != SQLITE_ERROR) goto abort_test; + sql = "SELECT \"double-quoted string literal misfeature\""; + rc = sqlite3_exec(db, sql, NULL, NULL, NULL); + if (rc != SQLITE_ERROR) { + printf("invalid result code for the following query, expected 1 (ERROR), got %d: '%s'\n", rc, sql); + printf("the unittest must be built with -DSQLITE_DQS=0\n"); + goto abort_test; + } sql = "CREATE TABLE foo (name TEXT PRIMARY KEY NOT NULL, age INTEGER UNIQUE);"; rc = sqlite3_exec(db, sql, NULL, NULL, NULL); From 4ef983084c21bd2e948bb6b24ff6b5df72487656 Mon Sep 17 00:00:00 2001 From: Andrea Donetti Date: Thu, 10 Jul 2025 09:34:36 +0200 Subject: [PATCH 6/7] fix: use literal for function's arguments --- src/network.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network.c b/src/network.c index fa21207..f18626f 100644 --- a/src/network.c +++ b/src/network.c @@ -890,7 +890,7 @@ void cloudsync_network_logout (sqlite3_context *context, int argc, sqlite3_value goto finalize; } - sql = cloudsync_memory_mprintf("SELECT cloudsync_init(\"%w\", \"%w\", 1);", tbl_name, value); + sql = cloudsync_memory_mprintf("SELECT cloudsync_init('%q', '%q', 1);", tbl_name, value); rc = sqlite3_exec(db, sql, NULL, NULL, NULL); cloudsync_memory_free(sql); if (rc != SQLITE_OK) { From 2e4adce82e04492f6d8edd42b506d8a063deae07 Mon Sep 17 00:00:00 2001 From: Andrea Donetti Date: Thu, 10 Jul 2025 16:08:00 +0200 Subject: [PATCH 7/7] Raised version --- src/cloudsync.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cloudsync.h b/src/cloudsync.h index ddc47fd..61af5bf 100644 --- a/src/cloudsync.h +++ b/src/cloudsync.h @@ -16,7 +16,7 @@ #include "sqlite3.h" #endif -#define CLOUDSYNC_VERSION "0.8.8" +#define CLOUDSYNC_VERSION "0.8.9" int sqlite3_cloudsync_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi);