@@ -2401,6 +2401,84 @@ bool do_test_stale_table_settings(bool cleanup_databases) {
24012401 return result;
24022402}
24032403
2404+ // Same as do_test_stale_table_settings, but also drops the <table>_cloudsync
2405+ // meta-table before reopening. With a stale cloudsync_table_settings row and
2406+ // no matching *_cloudsync meta-table in sqlite_master, the dbversion query
2407+ // builder produces an empty (NULL) SQL string, causing sqlite3_cloudsync_init
2408+ // to fail on reopen — previously crashing in some environments.
2409+ bool do_test_stale_table_settings_dropped_meta(bool cleanup_databases) {
2410+ bool result = false;
2411+ char dbpath[256];
2412+ time_t timestamp = time(NULL);
2413+
2414+ #ifdef __ANDROID__
2415+ snprintf(dbpath, sizeof(dbpath), "%s/cloudsync-test-stale-meta-%ld.sqlite", ".", timestamp);
2416+ #else
2417+ snprintf(dbpath, sizeof(dbpath), "%s/cloudsync-test-stale-meta-%ld.sqlite", getenv("HOME"), timestamp);
2418+ #endif
2419+
2420+ // Phase 1: create database, table, and init cloudsync
2421+ sqlite3 *db = NULL;
2422+ int rc = sqlite3_open(dbpath, &db);
2423+ if (rc != SQLITE_OK) return false;
2424+ sqlite3_exec(db, "PRAGMA journal_mode=WAL;", NULL, NULL, NULL);
2425+ sqlite3_cloudsync_init(db, NULL, NULL);
2426+
2427+ rc = sqlite3_exec(db, "CREATE TABLE cloud (id TEXT PRIMARY KEY NOT NULL, value TEXT, extra INTEGER);", NULL, NULL, NULL);
2428+ if (rc != SQLITE_OK) goto cleanup;
2429+
2430+ rc = sqlite3_exec(db, "SELECT cloudsync_init('cloud');", NULL, NULL, NULL);
2431+ if (rc != SQLITE_OK) goto cleanup;
2432+
2433+ // Phase 2: drop both the base table AND the meta-table without calling
2434+ // cloudsync_cleanup. This leaves stale entries in cloudsync_table_settings
2435+ // with no matching *_cloudsync table in sqlite_master.
2436+ sqlite3_exec(db, "SELECT cloudsync_terminate();", NULL, NULL, NULL);
2437+ sqlite3_close(db);
2438+ db = NULL;
2439+
2440+ rc = sqlite3_open(dbpath, &db);
2441+ if (rc != SQLITE_OK) goto cleanup;
2442+ rc = sqlite3_exec(db, "DROP TABLE IF EXISTS cloud;", NULL, NULL, NULL);
2443+ if (rc != SQLITE_OK) goto cleanup;
2444+ rc = sqlite3_exec(db, "DROP TABLE IF EXISTS cloud_cloudsync;", NULL, NULL, NULL);
2445+ if (rc != SQLITE_OK) goto cleanup;
2446+ sqlite3_close(db);
2447+ db = NULL;
2448+
2449+ // Phase 3: reopen the database and load the extension — must succeed.
2450+ rc = sqlite3_open(dbpath, &db);
2451+ if (rc != SQLITE_OK) goto cleanup;
2452+ rc = sqlite3_cloudsync_init(db, NULL, NULL);
2453+ if (rc != SQLITE_OK) goto cleanup;
2454+
2455+ // Sanity check: we can still call cloudsync_version and create a new table.
2456+ rc = sqlite3_exec(db, "SELECT cloudsync_version();", NULL, NULL, NULL);
2457+ if (rc != SQLITE_OK) goto cleanup;
2458+
2459+ rc = sqlite3_exec(db, "CREATE TABLE cloud2 (id TEXT PRIMARY KEY NOT NULL, v TEXT);", NULL, NULL, NULL);
2460+ if (rc != SQLITE_OK) goto cleanup;
2461+ rc = sqlite3_exec(db, "SELECT cloudsync_init('cloud2');", NULL, NULL, NULL);
2462+ if (rc != SQLITE_OK) goto cleanup;
2463+
2464+ result = true;
2465+
2466+ cleanup:
2467+ if (db) {
2468+ sqlite3_exec(db, "SELECT cloudsync_terminate();", NULL, NULL, NULL);
2469+ sqlite3_close(db);
2470+ }
2471+ if (cleanup_databases) {
2472+ file_delete_internal(dbpath);
2473+ char walpath[280];
2474+ snprintf(walpath, sizeof(walpath), "%s-wal", dbpath);
2475+ file_delete_internal(walpath);
2476+ snprintf(walpath, sizeof(walpath), "%s-shm", dbpath);
2477+ file_delete_internal(walpath);
2478+ }
2479+ return result;
2480+ }
2481+
24042482// Authorizer state for do_test_context_cb_error_cleanup.
24052483// Denies INSERT on a specific table after allowing a set number of INSERTs.
24062484static const char *g_deny_insert_table = NULL;
@@ -12268,6 +12346,7 @@ int main (int argc, const char * argv[]) {
1226812346 result += test_report("Large Composite PK Test:", do_test_large_composite_pk(2, print_result, cleanup_databases));
1226912347 result += test_report("Schema Hash Mismatch:", do_test_schema_hash_mismatch(2, print_result, cleanup_databases));
1227012348 result += test_report("Stale Table Settings:", do_test_stale_table_settings(cleanup_databases));
12349+ result += test_report("Stale Table Settings Dropped Meta:", do_test_stale_table_settings_dropped_meta(cleanup_databases));
1227112350 result += test_report("Block LWW Existing Data:", do_test_block_lww_existing_data(cleanup_databases));
1227212351 result += test_report("Block Column Reload:", do_test_block_column_reload(cleanup_databases));
1227312352 result += test_report("CB Error Cleanup:", do_test_context_cb_error_cleanup());
0 commit comments