Skip to content

Commit 8e28966

Browse files
committed
fix(postgres): support mixed-type composite primary keys with VARIADIC "any"
Changed PostgreSQL functions from VARIADIC anyarray to VARIADIC "any" to support composite primary keys with mixed data types (e.g., TEXT + INTEGER). PostgreSQL arrays require all elements to be the same type, causing errors like "function cloudsync_insert(unknown, text, integer) does not exist" for heterogeneous composite keys. VARIADIC "any" accepts arguments of any type. This ensures pk_encode_prikey() receives correct type information for encoding, maintaining compatibility with SQLite.
1 parent a6624fd commit 8e28966

File tree

6 files changed

+70
-29
lines changed

6 files changed

+70
-29
lines changed

src/cloudsync.c

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1800,7 +1800,6 @@ int cloudsync_refill_metatable (cloudsync_context *data, const char *table_name)
18001800

18011801
dbvm_t *vm = NULL;
18021802
int64_t db_version = cloudsync_dbversion_next(data, CLOUDSYNC_VALUE_NOTSET);
1803-
char *pkdecode = NULL;
18041803

18051804
const char *schema = table->schema ? table->schema : "";
18061805
char *sql = sql_build_pk_collist_query(schema, table_name);
@@ -1810,13 +1809,9 @@ int cloudsync_refill_metatable (cloudsync_context *data, const char *table_name)
18101809
if (rc != DBRES_OK) goto finalize;
18111810
char *pkvalues_identifiers = (pkclause_identifiers) ? pkclause_identifiers : "rowid";
18121811

1813-
sql = sql_build_pk_decode_selectlist_query(schema, table_name);
1814-
rc = database_select_text(data, sql, &pkdecode);
1815-
cloudsync_memory_free(sql);
1816-
if (rc != DBRES_OK) goto finalize;
1817-
char *pkdecodeval = (pkdecode) ? pkdecode : "cloudsync_pk_decode(pk, 1) AS rowid";
1818-
1819-
sql = cloudsync_memory_mprintf(SQL_CLOUDSYNC_INSERT_MISSING_PKS_FROM_BASE_EXCEPT_SYNC, table_name, pkvalues_identifiers, pkvalues_identifiers, table->base_ref, pkdecodeval, table->meta_ref);
1812+
// Use database-specific query builder to handle type differences in composite PKs
1813+
sql = sql_build_insert_missing_pks_query(schema, table_name, pkvalues_identifiers, table->base_ref, table->meta_ref);
1814+
if (!sql) {rc = DBRES_NOMEM; goto finalize;}
18201815
rc = database_exec(data, sql);
18211816
cloudsync_memory_free(sql);
18221817
if (rc != DBRES_OK) goto finalize;
@@ -1858,7 +1853,6 @@ int cloudsync_refill_metatable (cloudsync_context *data, const char *table_name)
18581853
finalize:
18591854
if (rc != DBRES_OK) {DEBUG_ALWAYS("cloudsync_refill_metatable error: %s", database_errmsg(data));}
18601855
if (pkclause_identifiers) cloudsync_memory_free(pkclause_identifiers);
1861-
if (pkdecode) cloudsync_memory_free(pkdecode);
18621856
if (vm) databasevm_finalize(vm);
18631857
return rc;
18641858
}

src/database.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ char *sql_build_delete_cols_not_in_schema_query(const char *schema, const char *
148148
char *sql_build_pk_collist_query(const char *schema, const char *table_name);
149149
char *sql_build_pk_decode_selectlist_query(const char *schema, const char *table_name);
150150
char *sql_build_pk_qualified_collist_query(const char *schema, const char *table_name);
151+
char *sql_build_insert_missing_pks_query(const char *schema, const char *table_name, const char *pkvalues_identifiers, const char *base_ref, const char *meta_ref);
151152

152153
char *database_table_schema(const char *table_name);
153154
char *database_build_meta_ref(const char *schema, const char *table_name);

src/postgresql/cloudsync--1.0.sql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,13 @@ AS 'MODULE_PATHNAME', 'cloudsync_is_sync'
160160
LANGUAGE C STABLE;
161161

162162
-- Internal insert handler (variadic for multiple PK columns)
163-
CREATE OR REPLACE FUNCTION cloudsync_insert(table_name text, VARIADIC pk_values anyarray)
163+
CREATE OR REPLACE FUNCTION cloudsync_insert(table_name text, VARIADIC pk_values "any")
164164
RETURNS boolean
165165
AS 'MODULE_PATHNAME', 'cloudsync_insert'
166166
LANGUAGE C VOLATILE;
167167

168168
-- Internal delete handler (variadic for multiple PK columns)
169-
CREATE OR REPLACE FUNCTION cloudsync_delete(table_name text, VARIADIC pk_values anyarray)
169+
CREATE OR REPLACE FUNCTION cloudsync_delete(table_name text, VARIADIC pk_values "any")
170170
RETURNS boolean
171171
AS 'MODULE_PATHNAME', 'cloudsync_delete'
172172
LANGUAGE C VOLATILE;
@@ -195,7 +195,7 @@ AS 'MODULE_PATHNAME', 'cloudsync_seq'
195195
LANGUAGE C VOLATILE;
196196

197197
-- Encode primary key (variadic for multiple columns)
198-
CREATE OR REPLACE FUNCTION cloudsync_pk_encode(VARIADIC pk_values anyarray)
198+
CREATE OR REPLACE FUNCTION cloudsync_pk_encode(VARIADIC pk_values "any")
199199
RETURNS bytea
200200
AS 'MODULE_PATHNAME', 'cloudsync_pk_encode'
201201
LANGUAGE C IMMUTABLE STRICT;

src/postgresql/cloudsync_postgresql.c

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -993,10 +993,11 @@ Datum cloudsync_pk_encode (PG_FUNCTION_ARGS) {
993993
int argc = 0;
994994
pgvalue_t **argv = NULL;
995995

996-
// Signature is VARIADIC anyarray, so arg 0 is an array of PK values.
997-
if (!PG_ARGISNULL(0)) {
998-
ArrayType *array = PG_GETARG_ARRAYTYPE_P(0);
999-
argv = pgvalues_from_array(array, &argc);
996+
// Signature is VARIADIC "any", so extract all arguments starting from index 0
997+
argv = pgvalues_from_args(fcinfo, 0, &argc);
998+
if (!argv || argc == 0) {
999+
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1000+
errmsg("cloudsync_pk_encode requires at least one primary key value")));
10001001
}
10011002

10021003
size_t pklen = 0;
@@ -1134,11 +1135,8 @@ Datum cloudsync_insert (PG_FUNCTION_ARGS) {
11341135
}
11351136
}
11361137

1137-
// Extract PK values from VARIADIC anyarray (arg 1)
1138-
if (!PG_ARGISNULL(1)) {
1139-
ArrayType *pk_array = PG_GETARG_ARRAYTYPE_P(1);
1140-
cleanup.argv = pgvalues_from_array(pk_array, &cleanup.argc);
1141-
}
1138+
// Extract PK values from VARIADIC "any" (args starting from index 1)
1139+
cleanup.argv = pgvalues_from_args(fcinfo, 1, &cleanup.argc);
11421140

11431141
// Verify we have the correct number of PK columns
11441142
int expected_pks = table_count_pks(table);
@@ -1228,10 +1226,8 @@ Datum cloudsync_delete (PG_FUNCTION_ARGS) {
12281226
}
12291227
}
12301228

1231-
if (!PG_ARGISNULL(1)) {
1232-
ArrayType *pk_array = PG_GETARG_ARRAYTYPE_P(1);
1233-
cleanup.argv = pgvalues_from_array(pk_array, &cleanup.argc);
1234-
}
1229+
// Extract PK values from VARIADIC "any" (args starting from index 1)
1230+
cleanup.argv = pgvalues_from_args(fcinfo, 1, &cleanup.argc);
12351231

12361232
int expected_pks = table_count_pks(table);
12371233
if (cleanup.argc != expected_pks) {

src/postgresql/database_postgresql.c

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,33 @@ char *sql_build_pk_qualified_collist_query (const char *schema, const char *tabl
381381
);
382382
}
383383

384+
char *sql_build_insert_missing_pks_query(const char *schema, const char *table_name,
385+
const char *pkvalues_identifiers,
386+
const char *base_ref, const char *meta_ref) {
387+
UNUSED_PARAMETER(schema);
388+
389+
char esc_table[1024];
390+
sql_escape_literal(table_name, esc_table, sizeof(esc_table));
391+
392+
// PostgreSQL: Use NOT EXISTS with cloudsync_pk_encode to avoid EXCEPT type mismatch.
393+
//
394+
// CRITICAL: Pass PK columns directly to VARIADIC functions (NOT wrapped in ARRAY[]).
395+
// This preserves each column's actual type (TEXT, INTEGER, etc.) for correct pk_encode.
396+
// Using ARRAY[] would require all elements to be the same type, causing errors with
397+
// mixed-type composite PKs (e.g., TEXT + INTEGER).
398+
//
399+
// Example: cloudsync_insert('table', col1, col2) where col1=TEXT, col2=INTEGER
400+
// PostgreSQL's VARIADIC handling preserves each type and matches SQLite's encoding.
401+
return cloudsync_memory_mprintf(
402+
"SELECT cloudsync_insert('%s', %s) "
403+
"FROM %s b "
404+
"WHERE NOT EXISTS ("
405+
" SELECT 1 FROM %s m WHERE m.pk = cloudsync_pk_encode(%s)"
406+
");",
407+
esc_table, pkvalues_identifiers, base_ref, meta_ref, pkvalues_identifiers
408+
);
409+
}
410+
384411
// MARK: - HELPER FUNCTIONS -
385412

386413
// Map SPI result codes to DBRES
@@ -1181,7 +1208,7 @@ static int database_create_insert_trigger_internal (cloudsync_context *data, con
11811208
"CREATE OR REPLACE FUNCTION \"%s\"() RETURNS trigger AS $$ "
11821209
"BEGIN "
11831210
" IF cloudsync_is_sync('%s') THEN RETURN NEW; END IF; "
1184-
" PERFORM cloudsync_insert('%s', VARIADIC ARRAY[%s]); "
1211+
" PERFORM cloudsync_insert('%s', %s); "
11851212
" RETURN NEW; "
11861213
"END; "
11871214
"$$ LANGUAGE plpgsql;",
@@ -1449,7 +1476,7 @@ static int database_create_delete_trigger_internal (cloudsync_context *data, con
14491476
"CREATE OR REPLACE FUNCTION \"%s\"() RETURNS trigger AS $$ "
14501477
"BEGIN "
14511478
" IF cloudsync_is_sync('%s') THEN RETURN OLD; END IF; "
1452-
" PERFORM cloudsync_delete('%s', VARIADIC ARRAY[%s]); "
1479+
" PERFORM cloudsync_delete('%s', %s); "
14531480
" RETURN OLD; "
14541481
"END; "
14551482
"$$ LANGUAGE plpgsql;",

src/sqlite/database_sqlite.c

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,17 +234,40 @@ char *sql_build_pk_decode_selectlist_query (const char *schema, const char *tabl
234234

235235
char *sql_build_pk_qualified_collist_query (const char *schema, const char *table_name) {
236236
UNUSED_PARAMETER(schema);
237-
237+
238238
char buffer[1024];
239239
char *singlequote_escaped_table_name = sql_escape_identifier(table_name, buffer, sizeof(buffer));
240240
if (!singlequote_escaped_table_name) return NULL;
241-
241+
242242
return cloudsync_memory_mprintf(
243243
"SELECT group_concat('\"%w\".\"' || format('%%w', name) || '\"', ',') "
244244
"FROM pragma_table_info('%s') WHERE pk>0 ORDER BY pk;", singlequote_escaped_table_name, singlequote_escaped_table_name
245245
);
246246
}
247247

248+
char *sql_build_insert_missing_pks_query(const char *schema, const char *table_name,
249+
const char *pkvalues_identifiers,
250+
const char *base_ref, const char *meta_ref) {
251+
UNUSED_PARAMETER(schema);
252+
253+
// Build pk_decode select list
254+
char *pkdecode = sql_build_pk_decode_selectlist_query(NULL, table_name);
255+
if (!pkdecode) {
256+
pkdecode = cloudsync_memory_strdup("cloudsync_pk_decode(pk, 1) AS rowid");
257+
if (!pkdecode) return NULL;
258+
}
259+
260+
// SQLite: Use EXCEPT (type-flexible)
261+
char *result = cloudsync_memory_mprintf(
262+
"SELECT cloudsync_insert('%q', %s) "
263+
"FROM (SELECT %s FROM \"%w\" EXCEPT SELECT %s FROM \"%w\");",
264+
table_name, pkvalues_identifiers, pkvalues_identifiers, base_ref, pkdecode, meta_ref
265+
);
266+
267+
cloudsync_memory_free(pkdecode);
268+
return result;
269+
}
270+
248271
// MARK: - PRIVATE -
249272

250273
static int database_select1_value (cloudsync_context *data, const char *sql, char **ptr_value, int64_t *int_value, DBTYPE expected_type) {

0 commit comments

Comments
 (0)