@@ -215,6 +215,31 @@ bool force_uncompressed_blob = false;
215215int 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 );
216216int 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
220245DBVM_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
286309char * 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
14091433cleanup :
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
15231547bool 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
15271551cloudsync_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+
24652563int 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
0 commit comments