@@ -167,7 +167,7 @@ struct cloudsync_table_context {
167167 #endif
168168
169169 char * * pk_name ; // array of primary key names
170-
170+
171171 // precompiled statements
172172 dbvm_t * meta_pkexists_stmt ; // check if a primary key already exist in the augmented table
173173 dbvm_t * meta_sentinel_update_stmt ; // update a local sentinel row
@@ -573,6 +573,13 @@ const char *cloudsync_schema (cloudsync_context *data) {
573573 return data -> current_schema ;
574574}
575575
576+ const char * cloudsync_table_schema (cloudsync_context * data , const char * table_name ) {
577+ cloudsync_table_context * table = table_lookup (data , table_name );
578+ if (!table ) return NULL ;
579+
580+ return table -> schema ;
581+ }
582+
576583// MARK: - Table Utils -
577584
578585void table_pknames_free (char * * names , int nrows ) {
@@ -589,7 +596,7 @@ char *table_build_mergedelete_sql (cloudsync_table_context *table) {
589596 }
590597 #endif
591598
592- return sql_build_delete_by_pk (table -> context , table -> name );
599+ return sql_build_delete_by_pk (table -> context , table -> name , table -> schema );
593600}
594601
595602char * table_build_mergeinsert_sql (cloudsync_table_context * table , const char * colname ) {
@@ -610,9 +617,9 @@ char *table_build_mergeinsert_sql (cloudsync_table_context *table, const char *c
610617
611618 if (colname == NULL ) {
612619 // is sentinel insert
613- sql = sql_build_insert_pk_ignore (table -> context , table -> name );
620+ sql = sql_build_insert_pk_ignore (table -> context , table -> name , table -> schema );
614621 } else {
615- sql = sql_build_upsert_pk_and_col (table -> context , table -> name , colname );
622+ sql = sql_build_upsert_pk_and_col (table -> context , table -> name , colname , table -> schema );
616623 }
617624 return sql ;
618625}
@@ -627,7 +634,7 @@ char *table_build_value_sql (cloudsync_table_context *table, const char *colname
627634 #endif
628635
629636 // SELECT age FROM customers WHERE first_name=? AND last_name=?;
630- return sql_build_select_cols_by_pk (table -> context , table -> name , colname );
637+ return sql_build_select_cols_by_pk (table -> context , table -> name , colname , table -> schema );
631638}
632639
633640cloudsync_table_context * table_create (cloudsync_context * data , const char * name , table_algo algo ) {
@@ -639,7 +646,18 @@ cloudsync_table_context *table_create (cloudsync_context *data, const char *name
639646 table -> context = data ;
640647 table -> algo = algo ;
641648 table -> name = cloudsync_string_dup_lowercase (name );
642- table -> schema = (data -> current_schema ) ? cloudsync_string_dup (data -> current_schema ) : NULL ;
649+
650+ // Detect schema from metadata table location. If metadata table doesn't
651+ // exist yet (during initialization), fall back to cloudsync_schema() which
652+ // returns the explicitly set schema or current_schema().
653+ table -> schema = database_table_schema (name );
654+ if (!table -> schema ) {
655+ const char * fallback_schema = cloudsync_schema (data );
656+ if (fallback_schema ) {
657+ table -> schema = cloudsync_string_dup (fallback_schema );
658+ }
659+ }
660+
643661 if (!table -> name ) {
644662 cloudsync_memory_free (table );
645663 return NULL ;
@@ -827,7 +845,7 @@ int table_add_stmts (cloudsync_table_context *table, int ncols) {
827845
828846 // precompile the get column value statement
829847 if (ncols > 0 ) {
830- sql = sql_build_select_nonpk_by_pk (data , table -> name );
848+ sql = sql_build_select_nonpk_by_pk (data , table -> name , table -> schema );
831849 if (!sql ) {rc = DBRES_NOMEM ; goto cleanup ;}
832850 DEBUG_SQL ("real_col_values_stmt: %s" , sql );
833851
@@ -954,8 +972,14 @@ bool table_ensure_capacity (cloudsync_context *data) {
954972
955973bool table_add_to_context (cloudsync_context * data , table_algo algo , const char * table_name ) {
956974 DEBUG_DBFUNCTION ("cloudsync_context_add_table %s" , table_name );
957-
958- // check if table is already in the global context and in that case just return
975+
976+ // Check if table already initialized in this connection's context.
977+ // Note: This prevents same-connection duplicate initialization.
978+ // SQLite clients cannot distinguish schemas, so having 'public.users'
979+ // and 'auth.users' would cause sync ambiguity. Users should avoid
980+ // initializing tables with the same name in different schemas.
981+ // If two concurrent connections initialize tables with the same name
982+ // in different schemas, the behavior is undefined.
959983 cloudsync_table_context * table = table_lookup (data , table_name );
960984 if (table ) return true;
961985
@@ -967,7 +991,7 @@ bool table_add_to_context (cloudsync_context *data, table_algo algo, const char
967991 if (!table ) return false;
968992
969993 // fill remaining metadata in the table
970- int count = database_count_pk (data , table_name , false);
994+ int count = database_count_pk (data , table_name , false, table -> schema );
971995 if (count < 0 ) {cloudsync_set_dberror (data ); goto abort_add_table ;}
972996 table -> npks = count ;
973997 if (table -> npks == 0 ) {
@@ -979,7 +1003,7 @@ bool table_add_to_context (cloudsync_context *data, table_algo algo, const char
9791003 #endif
9801004 }
9811005
982- int ncols = database_count_nonpk (data , table_name );
1006+ int ncols = database_count_nonpk (data , table_name , table -> schema );
9831007 if (ncols < 0 ) {cloudsync_set_dberror (data ); goto abort_add_table ;}
9841008 int rc = table_add_stmts (table , ncols );
9851009 if (rc != DBRES_OK ) goto abort_add_table ;
@@ -997,8 +1021,11 @@ bool table_add_to_context (cloudsync_context *data, table_algo algo, const char
9971021
9981022 table -> col_value_stmt = (dbvm_t * * )cloudsync_memory_alloc ((uint64_t )(sizeof (void * ) * ncols ));
9991023 if (!table -> col_value_stmt ) goto abort_add_table ;
1000-
1001- char * sql = cloudsync_memory_mprintf (SQL_PRAGMA_TABLEINFO_LIST_NONPK_NAME_CID , table_name , table_name );
1024+
1025+ // Pass empty string when schema is NULL; SQL will fall back to current_schema()
1026+ const char * schema = table -> schema ? table -> schema : "" ;
1027+ char * sql = cloudsync_memory_mprintf (SQL_PRAGMA_TABLEINFO_LIST_NONPK_NAME_CID ,
1028+ table_name , schema , table_name , schema );
10021029 if (!sql ) goto abort_add_table ;
10031030 rc = database_exec_callback (data , sql , table_add_to_context_cb , (void * )table );
10041031 cloudsync_memory_free (sql );
@@ -1678,7 +1705,8 @@ int cloudsync_finalize_alter (cloudsync_context *data, cloudsync_table_context *
16781705 } else {
16791706 // compact meta-table
16801707 // delete entries for removed columns
1681- char * sql = cloudsync_memory_mprintf (SQL_CLOUDSYNC_DELETE_COLS_NOT_IN_SCHEMA_OR_PKCOL , table -> meta_ref , table -> name , CLOUDSYNC_TOMBSTONE_VALUE );
1708+ const char * schema = table -> schema ? table -> schema : "" ;
1709+ char * sql = sql_build_delete_cols_not_in_schema_query (schema , table -> name , table -> meta_ref , CLOUDSYNC_TOMBSTONE_VALUE );
16821710 rc = database_exec (data , sql );
16831711 cloudsync_memory_free (sql );
16841712 if (rc != DBRES_OK ) {
@@ -1688,7 +1716,7 @@ int cloudsync_finalize_alter (cloudsync_context *data, cloudsync_table_context *
16881716
16891717 char buffer [1024 ];
16901718 char * singlequote_escaped_table_name = sql_escape_name (table -> name , buffer , sizeof (buffer ));
1691- sql = cloudsync_memory_mprintf ( SQL_PRAGMA_TABLEINFO_PK_QUALIFIED_COLLIST_FMT , singlequote_escaped_table_name , singlequote_escaped_table_name );
1719+ sql = sql_build_pk_qualified_collist_query ( schema , singlequote_escaped_table_name );
16921720 if (!sql ) {rc = DBRES_NOMEM ; goto finalize ;}
16931721
16941722 char * pkclause = NULL ;
@@ -1775,15 +1803,16 @@ int cloudsync_refill_metatable (cloudsync_context *data, const char *table_name)
17751803 dbvm_t * vm = NULL ;
17761804 int64_t db_version = cloudsync_dbversion_next (data , CLOUDSYNC_VALUE_NOTSET );
17771805 char * pkdecode = NULL ;
1778-
1779- char * sql = cloudsync_memory_mprintf (SQL_PRAGMA_TABLEINFO_PK_COLLIST , table_name );
1806+
1807+ const char * schema = table -> schema ? table -> schema : "" ;
1808+ char * sql = sql_build_pk_collist_query (schema , table_name );
17801809 char * pkclause_identifiers = NULL ;
17811810 int rc = database_select_text (data , sql , & pkclause_identifiers );
17821811 cloudsync_memory_free (sql );
17831812 if (rc != DBRES_OK ) goto finalize ;
17841813 char * pkvalues_identifiers = (pkclause_identifiers ) ? pkclause_identifiers : "rowid" ;
1785-
1786- sql = cloudsync_memory_mprintf ( SQL_PRAGMA_TABLEINFO_PK_DECODE_SELECTLIST , table_name );
1814+
1815+ sql = sql_build_pk_decode_selectlist_query ( schema , table_name );
17871816 rc = database_select_text (data , sql , & pkdecode );
17881817 cloudsync_memory_free (sql );
17891818 if (rc != DBRES_OK ) goto finalize ;
@@ -2504,14 +2533,18 @@ int cloudsync_table_sanity_check (cloudsync_context *data, const char *name, boo
25042533 return cloudsync_set_error (data , buffer , DBRES_ERROR );
25052534 }
25062535
2536+ // check if already initialized
2537+ cloudsync_table_context * table = table_lookup (data , name );
2538+ if (table ) return DBRES_OK ;
2539+
25072540 // check if table exists
2508- if (database_table_exists (data , name ) == false) {
2541+ if (database_table_exists (data , name , cloudsync_schema ( data ) ) == false) {
25092542 snprintf (buffer , sizeof (buffer ), "Table %s does not exist" , name );
25102543 return cloudsync_set_error (data , buffer , DBRES_ERROR );
25112544 }
25122545
25132546 // no more than 128 columns can be used as a composite primary key (SQLite hard limit)
2514- int npri_keys = database_count_pk (data , name , false);
2547+ int npri_keys = database_count_pk (data , name , false, cloudsync_schema ( data ) );
25152548 if (npri_keys < 0 ) return cloudsync_set_dberror (data );
25162549 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 );
25172550
@@ -2528,7 +2561,7 @@ int cloudsync_table_sanity_check (cloudsync_context *data, const char *name, boo
25282561 // the affinity of a column is determined by the declared type of the column,
25292562 // according to the following rules in the order shown:
25302563 // 1. If the declared type contains the string "INT" then it is assigned INTEGER affinity.
2531- int npri_keys_int = database_count_int_pk (data , name );
2564+ int npri_keys_int = database_count_int_pk (data , name , cloudsync_schema ( data ) );
25322565 if (npri_keys_int < 0 ) return cloudsync_set_dberror (data );
25332566 if (npri_keys == npri_keys_int ) {
25342567 snprintf (buffer , sizeof (buffer ), "Table %s uses a 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 );
@@ -2540,7 +2573,7 @@ int cloudsync_table_sanity_check (cloudsync_context *data, const char *name, boo
25402573
25412574 // if user declared explicit primary key(s) then make sure they are all declared as NOT NULL
25422575 if (npri_keys > 0 ) {
2543- int npri_keys_notnull = database_count_pk (data , name , true);
2576+ int npri_keys_notnull = database_count_pk (data , name , true, cloudsync_schema ( data ) );
25442577 if (npri_keys_notnull < 0 ) return cloudsync_set_dberror (data );
25452578 if (npri_keys != npri_keys_notnull ) {
25462579 snprintf (buffer , sizeof (buffer ), "All primary keys must be explicitly declared as NOT NULL (table %s)" , name );
@@ -2550,7 +2583,7 @@ int cloudsync_table_sanity_check (cloudsync_context *data, const char *name, boo
25502583
25512584 // check for columns declared as NOT NULL without a DEFAULT value.
25522585 // Otherwise, col_merge_stmt would fail if changes to other columns are inserted first.
2553- int n_notnull_nodefault = database_count_notnull_without_default (data , name );
2586+ int n_notnull_nodefault = database_count_notnull_without_default (data , name , cloudsync_schema ( data ) );
25542587 if (n_notnull_nodefault < 0 ) return cloudsync_set_dberror (data );
25552588 if (n_notnull_nodefault > 0 ) {
25562589 snprintf (buffer , sizeof (buffer ), "All non-primary key columns declared as NOT NULL must have a DEFAULT value. (table %s)" , name );
0 commit comments