Skip to content

Commit b308432

Browse files
committed
New architecture WP 9
1 parent bf2858d commit b308432

File tree

11 files changed

+624
-682
lines changed

11 files changed

+624
-682
lines changed

src/cloudsync.c

Lines changed: 117 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,31 @@ bool force_uncompressed_blob = false;
215215
int local_mark_insert_or_update_meta (cloudsync_table_context *table, const char *pk, size_t pklen, const char *col_name, db_int64 db_version, int seq);
216216
int cloudsync_set_dberror (cloudsync_context *data);
217217

218+
// MARK: - CRDT algos -
219+
220+
table_algo cloudsync_algo_from_name (const char *algo_name) {
221+
if (algo_name == NULL) return table_algo_none;
222+
223+
if ((strcasecmp(algo_name, "CausalLengthSet") == 0) || (strcasecmp(algo_name, "cls") == 0)) return table_algo_crdt_cls;
224+
if ((strcasecmp(algo_name, "GrowOnlySet") == 0) || (strcasecmp(algo_name, "gos") == 0)) return table_algo_crdt_gos;
225+
if ((strcasecmp(algo_name, "DeleteWinsSet") == 0) || (strcasecmp(algo_name, "dws") == 0)) return table_algo_crdt_dws;
226+
if ((strcasecmp(algo_name, "AddWinsSet") == 0) || (strcasecmp(algo_name, "aws") == 0)) return table_algo_crdt_aws;
227+
228+
// if nothing is found
229+
return table_algo_none;
230+
}
231+
232+
const char *cloudsync_algo_name (table_algo algo) {
233+
switch (algo) {
234+
case table_algo_crdt_cls: return "cls";
235+
case table_algo_crdt_gos: return "gos";
236+
case table_algo_crdt_dws: return "dws";
237+
case table_algo_crdt_aws: return "aws";
238+
case table_algo_none: return NULL;
239+
}
240+
return NULL;
241+
}
242+
218243
// MARK: - DBVM Utils -
219244

220245
DBVM_VALUE dbvm_execute (dbvm_t *stmt, cloudsync_context *data) {
@@ -273,14 +298,12 @@ int dbvm_count (dbvm_t *stmt, const char *value, size_t len, int type) {
273298
return result;
274299
}
275300

276-
dbvm_t *dbvm_reset (dbvm_t *stmt) {
301+
void dbvm_reset (dbvm_t *stmt) {
302+
if (!stmt) return;
277303
databasevm_clear_bindings(stmt);
278304
databasevm_reset(stmt);
279-
return NULL;
280305
}
281306

282-
// MARK: - Settings -
283-
284307
// MARK: - Database Version -
285308

286309
char *cloudsync_dbversion_build_query (db_t *db) {
@@ -1377,7 +1400,8 @@ int merge_did_cid_win (cloudsync_context *data, cloudsync_table_context *table,
13771400
// compare values
13781401
int ret = dbutils_value_compare(insert_value, local_value);
13791402
// reset after compare, otherwise local value would be deallocated
1380-
vm = dbvm_reset(vm);
1403+
dbvm_reset(vm);
1404+
vm = NULL;
13811405

13821406
bool compare_site_id = (ret == 0 && data->merge_equal_values == true);
13831407
if (!compare_site_id) {
@@ -1408,7 +1432,7 @@ int merge_did_cid_win (cloudsync_context *data, cloudsync_table_context *table,
14081432

14091433
cleanup:
14101434
if (rc != DBRES_OK) cloudsync_set_dberror(data);
1411-
if (vm) dbvm_reset(vm);
1435+
dbvm_reset(vm);
14121436
return rc;
14131437
}
14141438

@@ -1521,7 +1545,7 @@ int merge_insert (cloudsync_context *data, cloudsync_table_context *table, const
15211545
// MARK: - Private -
15221546

15231547
bool cloudsync_config_exists (db_t *db) {
1524-
return dbutils_table_exists(db, CLOUDSYNC_SITEID_NAME) == true;
1548+
return database_table_exists(db, CLOUDSYNC_SITEID_NAME) == true;
15251549
}
15261550

15271551
cloudsync_context *cloudsync_context_create (void *db) {
@@ -1563,8 +1587,8 @@ const char *cloudsync_context_init (cloudsync_context *data, void *db) {
15631587
// The data->site_id value could exists while settings tables don't exists if the
15641588
// cloudsync_context_init was previously called in init transaction that was rolled back
15651589
// because of an error during the init process.
1566-
if (data->site_id[0] == 0 || !dbutils_table_exists(db, CLOUDSYNC_SITEID_NAME)) {
1567-
if (dbutils_settings_init(db, data, NULL) != DBRES_OK) return NULL;
1590+
if (data->site_id[0] == 0 || !database_table_exists(db, CLOUDSYNC_SITEID_NAME)) {
1591+
if (dbutils_settings_init(db, data) != DBRES_OK) return NULL;
15681592
if (cloudsync_add_dbvms(db, data) != DBRES_OK) return NULL;
15691593
if (cloudsync_load_siteid(db, data) != DBRES_OK) return NULL;
15701594

@@ -1658,7 +1682,7 @@ int cloudsync_begin_alter (cloudsync_context *data, const char *table_name) {
16581682
}
16591683

16601684
// drop original triggers
1661-
dbutils_delete_triggers(db, table_name);
1685+
database_delete_triggers(db, table_name);
16621686
if (rc != DBRES_OK) {
16631687
char buffer[1024];
16641688
snprintf(buffer, sizeof(buffer), "Unable to delete triggers for table %s in cloudsync_begin_alter.", table_name);
@@ -1705,7 +1729,6 @@ int cloudsync_finalize_alter (cloudsync_context *data, cloudsync_table_context *
17051729
}
17061730
}
17071731

1708-
// TODO: FIX SQL
17091732
if (pk_diff) {
17101733
// drop meta-table, it will be recreated
17111734
char *sql = cloudsync_memory_mprintf("DROP TABLE IF EXISTS \"%w_cloudsync\";", table->name);
@@ -1794,7 +1817,7 @@ int cloudsync_commit_alter (cloudsync_context *data, const char *table_name) {
17941817
// init again cloudsync for the table
17951818
table_algo algo_current = dbutils_table_settings_get_algo(db, table_name);
17961819
if (algo_current == table_algo_none) algo_current = dbutils_table_settings_get_algo(db, "*");
1797-
rc = cloudsync_init_table(data, table_name, crdt_algo_name(algo_current), true);
1820+
rc = cloudsync_init_table(data, table_name, cloudsync_algo_name(algo_current), true);
17981821
if (rc != DBRES_OK) goto rollback_finalize_alter;
17991822

18001823
// release savepoint
@@ -2462,6 +2485,81 @@ int cloudsync_payload_save (cloudsync_context *data, const char *payload_path, i
24622485

24632486
// MARK: - Core -
24642487

2488+
int cloudsync_table_sanity_check (cloudsync_context *data, const char *name, bool skip_int_pk_check) {
2489+
DEBUG_DBFUNCTION("cloudsync_table_sanity_check %s", name);
2490+
2491+
db_t *db = data->db;
2492+
char buffer[2048];
2493+
2494+
// sanity check table name
2495+
if (name == NULL) {
2496+
return cloudsync_set_error(data, "cloudsync_init requires a non-null table parameter", DBRES_ERROR);
2497+
}
2498+
2499+
// avoid allocating heap memory for SQL statements by setting a maximum length of 1900 characters
2500+
// for table names. This limit is reasonable and helps prevent memory management issues.
2501+
const size_t maxlen = CLOUDSYNC_MAX_TABLENAME_LEN;
2502+
if (strlen(name) > maxlen) {
2503+
snprintf(buffer, sizeof(buffer), "Table name cannot be longer than %d characters", (int)maxlen);
2504+
return cloudsync_set_error(data, buffer, DBRES_ERROR);
2505+
}
2506+
2507+
// check if table exists
2508+
if (database_table_exists(db, name) == false) {
2509+
snprintf(buffer, sizeof(buffer), "Table %s does not exist", name);
2510+
return cloudsync_set_error(data, buffer, DBRES_ERROR);
2511+
}
2512+
2513+
// no more than 128 columns can be used as a composite primary key (SQLite hard limit)
2514+
int npri_keys = database_count_pk(db, name, false);
2515+
if (npri_keys < 0) return cloudsync_set_dberror(data);
2516+
if (npri_keys > 128) return cloudsync_set_error(data, "No more than 128 columns can be used to form a composite primary key", DBRES_ERROR);
2517+
2518+
#if CLOUDSYNC_DISABLE_ROWIDONLY_TABLES
2519+
// if count == 0 means that rowid will be used as primary key (BTW: very bad choice for the user)
2520+
if (npri_keys == 0) {
2521+
snprintf(buffer, sizeof(buffer), "Rowid only tables are not supported, all primary keys must be explicitly set and declared as NOT NULL (table %s)", name);
2522+
return cloudsync_set_error(data, buffer, DBRES_ERROR);
2523+
}
2524+
#endif
2525+
2526+
if (!skip_int_pk_check) {
2527+
if (npri_keys == 1) {
2528+
// the affinity of a column is determined by the declared type of the column,
2529+
// according to the following rules in the order shown:
2530+
// 1. If the declared type contains the string "INT" then it is assigned INTEGER affinity.
2531+
int npri_keys_int = database_count_int_pk(db, name);
2532+
if (npri_keys_int < 0) return cloudsync_set_dberror(data);
2533+
if (npri_keys == npri_keys_int) {
2534+
snprintf(buffer, sizeof(buffer), "Table %s uses an single-column INTEGER primary key. For CRDT replication, primary keys must be globally unique. Consider using a TEXT primary key with UUIDs or ULID to avoid conflicts across nodes. If you understand the risk and still want to use this INTEGER primary key, set the third argument of the cloudsync_init function to 1 to skip this check.", name);
2535+
return cloudsync_set_error(data, buffer, DBRES_ERROR);
2536+
}
2537+
2538+
}
2539+
}
2540+
2541+
// if user declared explicit primary key(s) then make sure they are all declared as NOT NULL
2542+
if (npri_keys > 0) {
2543+
int npri_keys_notnull = database_count_pk(db, name, true);
2544+
if (npri_keys_notnull < 0) return cloudsync_set_dberror(data);
2545+
if (npri_keys != npri_keys_notnull) {
2546+
snprintf(buffer, sizeof(buffer), "All primary keys must be explicitly declared as NOT NULL (table %s)", name);
2547+
return cloudsync_set_error(data, buffer, DBRES_ERROR);
2548+
}
2549+
}
2550+
2551+
// check for columns declared as NOT NULL without a DEFAULT value.
2552+
// Otherwise, col_merge_stmt would fail if changes to other columns are inserted first.
2553+
int n_notnull_nodefault = database_count_notnull_without_default(db, name);
2554+
if (n_notnull_nodefault < 0) return cloudsync_set_dberror(data);
2555+
if (n_notnull_nodefault > 0) {
2556+
snprintf(buffer, sizeof(buffer), "All non-primary key columns declared as NOT NULL must have a DEFAULT value. (table %s)", name);
2557+
return cloudsync_set_error(data, buffer, DBRES_ERROR);
2558+
}
2559+
2560+
return DBRES_OK;
2561+
}
2562+
24652563
int cloudsync_cleanup_internal (cloudsync_context *data, cloudsync_table_context *table) {
24662564
db_t *db = data->db;
24672565
if (cloudsync_context_init(data, db) == NULL) return DBRES_MISUSE;
@@ -2478,7 +2576,7 @@ int cloudsync_cleanup_internal (cloudsync_context *data, cloudsync_table_context
24782576
}
24792577

24802578
// drop original triggers
2481-
dbutils_delete_triggers(db, table_name);
2579+
database_delete_triggers(db, table_name);
24822580
if (rc != DBRES_OK) {
24832581
char buffer[1024];
24842582
snprintf(buffer, sizeof(buffer), "Unable to delete triggers for table %s", table_name);
@@ -2508,7 +2606,7 @@ int cloudsync_cleanup (cloudsync_context *data, const char *table_name) {
25082606
cloudsync_reset_siteid(data);
25092607
dbutils_settings_cleanup(data->db);
25102608
} else {
2511-
if (dbutils_table_exists(data->db, CLOUDSYNC_TABLE_SETTINGS_NAME) == true) {
2609+
if (database_table_exists(data->db, CLOUDSYNC_TABLE_SETTINGS_NAME) == true) {
25122610
cloudsync_update_schema_hash(data);
25132611
}
25142612
}
@@ -2560,10 +2658,8 @@ int cloudsync_init_table (cloudsync_context *data, const char *table_name, const
25602658
db_t *db = data->db;
25612659

25622660
// sanity check table and its primary key(s)
2563-
if (dbutils_table_sanity_check(db, NULL, table_name, skip_int_pk_check) == false) {
2564-
// TODO: check error message here
2565-
return DBRES_MISUSE;
2566-
}
2661+
int rc = cloudsync_table_sanity_check(data, table_name, skip_int_pk_check);
2662+
if (rc != DBRES_OK) return rc;
25672663

25682664
// init cloudsync_settings
25692665
if (cloudsync_context_init(data, db) == NULL) {
@@ -2575,7 +2671,7 @@ int cloudsync_init_table (cloudsync_context *data, const char *table_name, const
25752671
table_algo algo_new = table_algo_none;
25762672
if (!algo_name) algo_name = CLOUDSYNC_DEFAULT_ALGO;
25772673

2578-
algo_new = crdt_algo_from_name(algo_name);
2674+
algo_new = cloudsync_algo_from_name(algo_name);
25792675
if (algo_new == table_algo_none) {
25802676
char buffer[1024];
25812677
snprintf(buffer, sizeof(buffer), "Unknown CRDT algorithm name %s", algo_name);
@@ -2611,11 +2707,11 @@ int cloudsync_init_table (cloudsync_context *data, const char *table_name, const
26112707
// cloudsync_sync_table_key(data, table_name, "*", CLOUDSYNC_KEY_ALGO, crdt_algo_name(algo_new));
26122708

26132709
// check triggers
2614-
int rc = dbutils_check_triggers(db, table_name, algo_new);
2710+
rc = database_create_triggers(db, table_name, algo_new);
26152711
if (rc != DBRES_OK) return cloudsync_set_error(data, "An error occurred while creating triggers", DBRES_MISUSE);
26162712

26172713
// check meta-table
2618-
rc = dbutils_check_metatable(db, table_name, algo_new);
2714+
rc = database_create_metatable(db, table_name);
26192715
if (rc != DBRES_OK) return cloudsync_set_error(data, "An error occurred while creating metatable", DBRES_MISUSE);
26202716

26212717
// add prepared statements

src/cloudsync.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ extern "C" {
1818
#endif
1919

2020
#define CLOUDSYNC_VERSION "0.9.0"
21+
#define CLOUDSYNC_MAX_TABLENAME_LEN 512
22+
23+
// algos
24+
table_algo cloudsync_algo_from_name (const char *algo_name);
25+
const char *cloudsync_algo_name (table_algo algo);
2126

2227
// Opaque structures
2328
typedef struct cloudsync_context cloudsync_context;
@@ -28,7 +33,7 @@ cloudsync_context *cloudsync_context_create (void *db);
2833
const char *cloudsync_context_init (cloudsync_context *data, void *db);
2934
void cloudsync_context_free (void *ctx);
3035

31-
// OK
36+
3237
int cloudsync_cleanup (cloudsync_context *data, const char *table_name);
3338
int cloudsync_cleanup_all (cloudsync_context *data);
3439

@@ -70,15 +75,12 @@ int cloudsync_payload_encode_final (cloudsync_payload_context *payload, cloud
7075
char *cloudsync_payload_blob (cloudsync_payload_context *payload, db_int64 *blob_size, db_int64 *nrows);
7176
size_t cloudsync_payload_context_size (size_t *header_size);
7277

73-
74-
75-
// END OK
76-
7778
// CLOUDSYNCTABLE CONTEXT
7879
cloudsync_table_context *table_lookup (cloudsync_context *data, const char *table_name);
7980
void *table_column_lookup (cloudsync_table_context *table, const char *col_name, bool is_merge, int *index);
8081
bool table_enabled (cloudsync_table_context *table);
8182
void table_set_enabled (cloudsync_table_context *table, bool value);
83+
bool table_add_to_context (db_t *db, cloudsync_context *data, table_algo algo, const char *table_name);
8284

8385
bool table_pk_exists (cloudsync_table_context *table, const char *value, size_t len);
8486
int table_count_cols (cloudsync_table_context *table);

src/cloudsync_sqlite.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ void dbsync_set (sqlite3_context *context, int argc, sqlite3_value **argv) {
137137
if (key == NULL) return;
138138

139139
sqlite3 *db = sqlite3_context_db_handle(context);
140-
dbutils_settings_set_key_value(db, context, key, value);
140+
cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
141+
dbutils_settings_set_key_value(db, data, key, value);
141142
}
142143

143144
void dbsync_set_column (sqlite3_context *context, int argc, sqlite3_value **argv) {
@@ -147,7 +148,10 @@ void dbsync_set_column (sqlite3_context *context, int argc, sqlite3_value **argv
147148
const char *col = (const char *)database_value_text(argv[1]);
148149
const char *key = (const char *)database_value_text(argv[2]);
149150
const char *value = (const char *)database_value_text(argv[3]);
150-
dbutils_table_settings_set_key_value(NULL, context, tbl, col, key, value);
151+
152+
sqlite3 *db = sqlite3_context_db_handle(context);
153+
cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
154+
dbutils_table_settings_set_key_value(db, data, tbl, col, key, value);
151155
}
152156

153157
void dbsync_set_table (sqlite3_context *context, int argc, sqlite3_value **argv) {
@@ -156,7 +160,10 @@ void dbsync_set_table (sqlite3_context *context, int argc, sqlite3_value **argv)
156160
const char *tbl = (const char *)database_value_text(argv[0]);
157161
const char *key = (const char *)database_value_text(argv[1]);
158162
const char *value = (const char *)database_value_text(argv[2]);
159-
dbutils_table_settings_set_key_value(NULL, context, tbl, "*", key, value);
163+
164+
sqlite3 *db = sqlite3_context_db_handle(context);
165+
cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
166+
dbutils_table_settings_set_key_value(db, data, tbl, "*", key, value);
160167
}
161168

162169
void dbsync_is_sync (sqlite3_context *context, int argc, sqlite3_value **argv) {
@@ -874,7 +881,7 @@ int dbsync_register (sqlite3 *db, const char *name, void (*xfunc)(sqlite3_contex
874881
int rc = sqlite3_create_function_v2(db, name, nargs, DEFAULT_FLAGS, ctx, xfunc, xstep, xfinal, ctx_free);
875882

876883
if (rc != SQLITE_OK) {
877-
if (pzErrMsg) *pzErrMsg = cloudsync_memory_mprintf("Error creating function %s: %s", name, database_errmsg(db));
884+
if (pzErrMsg) *pzErrMsg = sqlite3_mprintf("Error creating function %s: %s", name, database_errmsg(db));
878885
return rc;
879886
}
880887
return SQLITE_OK;

0 commit comments

Comments
 (0)