Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [1.1.2] - 2026-07-13

### Fixed

- Querying the SQLite `cloudsync_changes` virtual table (directly or via `cloudsync_payload_blob_checked()`) on a database whose `cloudsync_table_settings` rows survived while all `*_cloudsync` meta tables were lost (e.g. a dump/restore that skipped them) now raises an actionable error ("cloudsync settings reference tables whose sync metadata is missing ...") instead of a misclassified `SQLITE_NOMEM`, which servers reported as "Not enough memory to execute query".

## [1.1.1] - 2026-07-10

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion src/cloudsync.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
extern "C" {
#endif

#define CLOUDSYNC_VERSION "1.1.1"
#define CLOUDSYNC_VERSION "1.1.2"
#define CLOUDSYNC_MAX_TABLENAME_LEN 512

#define CLOUDSYNC_VALUE_NOTSET -1
Expand Down
15 changes: 15 additions & 0 deletions src/sqlite/cloudsync_changes_sqlite.c
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,21 @@ int cloudsync_changesvtab_filter (sqlite3_vtab_cursor *cursor, int idxn, const c
"SELECT cloudsync_init('<table_name>') to enable sync on a "
"table before querying cloudsync_changes.");
}
// settings rows exist but no *_cloudsync tables do (e.g. a dump/restore
// that skipped the meta tables): still not an OOM
sqlite3_stmt *vm = NULL;
bool meta_missing = false;
if (sqlite3_prepare_v2(db, "SELECT 1 FROM sqlite_master WHERE type='table' AND tbl_name LIKE '%_cloudsync' LIMIT 1;", -1, &vm, NULL) == SQLITE_OK) {
meta_missing = (sqlite3_step(vm) != SQLITE_ROW);
}
if (vm) sqlite3_finalize(vm);
if (meta_missing) {
return vtab_set_error((sqlite3_vtab *)c->vtab,
"cloudsync settings reference tables whose sync metadata is "
"missing (no *_cloudsync tables found). Re-run "
"SELECT cloudsync_init('<table_name>') for each configured "
"table to rebuild it.");
}
return SQLITE_NOMEM;
}

Expand Down
36 changes: 36 additions & 0 deletions test/unit.c
Original file line number Diff line number Diff line change
Expand Up @@ -12976,6 +12976,41 @@ bool do_test_payload_chunks_uninitialized (bool print_result, bool cleanup_datab
return result;
}

// A database whose cloudsync_table_settings rows survived while the *_cloudsync
// meta tables were lost (e.g. a dump/restore that skipped them) must raise an
// actionable error from cloudsync_changes, not a misclassified SQLITE_NOMEM
// that servers report as "Not enough memory to execute query".
bool do_test_changes_vtab_missing_meta_tables (bool print_result) {
sqlite3 *db = NULL;
sqlite3_stmt *stmt = NULL;
bool result = false;

db = do_create_database();
if (!db) goto finalize;

if (sqlite3_exec(db, "CREATE TABLE barang (id TEXT PRIMARY KEY, name TEXT);", NULL, NULL, NULL) != SQLITE_OK) goto finalize;
if (sqlite3_exec(db, "SELECT cloudsync_init('barang');", NULL, NULL, NULL) != SQLITE_OK) goto finalize;
if (sqlite3_exec(db, "INSERT INTO barang VALUES ('1','test');", NULL, NULL, NULL) != SQLITE_OK) goto finalize;

// settings rows survive, meta table does not
if (sqlite3_exec(db, "DROP TABLE barang_cloudsync;", NULL, NULL, NULL) != SQLITE_OK) goto finalize;

if (sqlite3_prepare_v2(db, "SELECT COUNT(*) FROM cloudsync_changes;", -1, &stmt, NULL) != SQLITE_OK) goto finalize;
if (sqlite3_step(stmt) != SQLITE_ERROR) goto finalize;
if (!strstr(sqlite3_errmsg(db), "sync metadata")) goto finalize;
sqlite3_finalize(stmt); stmt = NULL;

result = true;

finalize:
if (!result && print_result) {
printf("do_test_changes_vtab_missing_meta_tables error: %s\n", db ? sqlite3_errmsg(db) : "no db");
}
if (stmt) sqlite3_finalize(stmt);
if (db) close_db(db);
return result;
}

bool do_test_payload_idempotency (int nclients, bool print_result, bool cleanup_databases) {
sqlite3 *db[2] = {NULL, NULL};
bool result = false;
Expand Down Expand Up @@ -13429,6 +13464,7 @@ int main (int argc, const char * argv[]) {
result += test_report("Payload Chunks Split db_version:", do_test_payload_chunks_split_dbversion(print_result, cleanup_databases));
result += test_report("Payload Chunks Positional Resume:", do_test_payload_chunks_positional_resume(print_result, cleanup_databases));
result += test_report("Payload Chunks Uninitialized:", do_test_payload_chunks_uninitialized(print_result, cleanup_databases));
result += test_report("Changes Vtab Missing Meta Tables:", do_test_changes_vtab_missing_meta_tables(print_result));

// close local database
close_db(db);
Expand Down
Loading