Skip to content

Commit bcfff0c

Browse files
committed
fix: improved SQL queries (WIP)
1 parent 52b205b commit bcfff0c

File tree

5 files changed

+207
-53
lines changed

5 files changed

+207
-53
lines changed

src/cloudsync.c

Lines changed: 6 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -551,17 +551,8 @@ char *table_build_mergedelete_sql (db_t *db, cloudsync_table_context *table) {
551551
return sql;
552552
}
553553
#endif
554-
555-
char buffer[1024];
556-
char *singlequote_escaped_table_name = sql_escape_name(table->name, buffer, sizeof(buffer));
557-
char *sql = cloudsync_memory_mprintf(SQL_BUILD_DELETE_ROW_BY_PK, table->name, singlequote_escaped_table_name);
558-
if (!sql) return NULL;
559-
560-
char *query = NULL;
561-
int rc = database_select_text(db, sql, &query);
562-
cloudsync_memory_free(sql);
563-
564-
return (rc == DBRES_OK) ? query : NULL;
554+
555+
return sql_build_delete_by_pk(db, table->name);
565556
}
566557

567558
char *table_build_mergeinsert_sql (db_t *db, cloudsync_table_context *table, const char *colname) {
@@ -580,24 +571,13 @@ char *table_build_mergeinsert_sql (db_t *db, cloudsync_table_context *table, con
580571
}
581572
#endif
582573

583-
char buffer[1024];
584-
char *singlequote_escaped_table_name = sql_escape_name(table->name, buffer, sizeof(buffer));
585-
586574
if (colname == NULL) {
587575
// is sentinel insert
588-
sql = cloudsync_memory_mprintf(SQL_BUILD_INSERT_PK_IGNORE, table->name, table->name, singlequote_escaped_table_name);
576+
sql = sql_build_insert_pk_ignore(db, table->name);
589577
} else {
590-
char buffer2[1024];
591-
char *singlequote_escaped_col_name = sql_escape_name(colname, buffer2, sizeof(buffer2));
592-
sql = cloudsync_memory_mprintf(SQL_BUILD_UPSERT_PK_AND_COL, table->name, table->name, singlequote_escaped_table_name, singlequote_escaped_col_name, singlequote_escaped_col_name);
578+
sql = sql_build_upsert_pk_and_col(db, table->name, colname);
593579
}
594-
if (!sql) return NULL;
595-
596-
char *query = NULL;
597-
int rc = database_select_text(db, sql, &query);
598-
cloudsync_memory_free(sql);
599-
600-
return (rc == DBRES_OK) ? query : NULL;
580+
return sql;
601581
}
602582

603583
char *table_build_value_sql (db_t *db, cloudsync_table_context *table, const char *colname) {
@@ -611,18 +591,7 @@ char *table_build_value_sql (db_t *db, cloudsync_table_context *table, const cha
611591
#endif
612592

613593
// SELECT age FROM customers WHERE first_name=? AND last_name=?;
614-
char buffer[1024];
615-
char buffer2[1024];
616-
char *singlequote_escaped_table_name = sql_escape_name(table->name, buffer, sizeof(buffer));
617-
char *singlequote_escaped_col_name = sql_escape_name(colname, buffer2, sizeof(buffer2));
618-
char *sql = cloudsync_memory_mprintf(SQL_BUILD_SELECT_COLS_BY_PK_FMT, table->name, colnamequote, singlequote_escaped_col_name, colnamequote, singlequote_escaped_table_name);
619-
if (!sql) return NULL;
620-
621-
char *query = NULL;
622-
int rc = database_select_text(db, sql, &query);
623-
cloudsync_memory_free(sql);
624-
625-
return (rc == DBRES_OK) ? query : NULL;
594+
return sql_build_select_cols_by_pk(db, table->name, colname);
626595
}
627596

628597
cloudsync_table_context *table_create (cloudsync_context *data, const char *name, table_algo algo) {
@@ -812,7 +781,6 @@ int table_add_stmts (db_t *db, cloudsync_table_context *table, int ncols) {
812781
if (rc != DBRES_OK) goto cleanup;
813782

814783
// REAL TABLE statements
815-
DEBUG_SQL("REAL TABLE statements: %d", ncols);
816784

817785
// precompile the get column value statement
818786
if (ncols > 0) {
@@ -825,7 +793,6 @@ int table_add_stmts (db_t *db, cloudsync_table_context *table, int ncols) {
825793
if (rc != DBRES_OK) goto cleanup;
826794
}
827795

828-
DEBUG_SQL("real_merge_delete ...", sql);
829796
sql = table_build_mergedelete_sql(db, table);
830797
if (!sql) {rc = DBRES_NOMEM; goto cleanup;}
831798
DEBUG_SQL("real_merge_delete: %s", sql);

src/database.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ uint64_t dbmem_size (void *ptr);
146146
char *sql_build_drop_table (const char *table_name, char *buffer, int bsize, bool is_meta);
147147
char *sql_escape_name (const char *name, char *buffer, size_t bsize);
148148
char *sql_build_select_nonpk_by_pk (db_t *db, const char *table_name);
149+
char *sql_build_delete_by_pk (db_t *db, const char *table_name);
150+
char *sql_build_insert_pk_ignore (db_t *db, const char *table_name);
151+
char *sql_build_upsert_pk_and_col (db_t *db, const char *table_name, const char *colname);
152+
char *sql_build_select_cols_by_pk (db_t *db, const char *table_name, const char *colname);
149153

150154
// USED ONLY by SQLite Cloud to implement RLS
151155
typedef struct cloudsync_pk_decode_bind_context cloudsync_pk_decode_bind_context;

src/postgresql/database_postgresql.c

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@
55
// Created by Marco Bambini on 03/12/25.
66
//
77

8-
// Define POSIX feature test macros before any includes
9-
#define _POSIX_C_SOURCE 200809L
10-
#define _GNU_SOURCE
11-
128
// PostgreSQL requires postgres.h to be included FIRST
139
// It sets up the entire environment including platform compatibility
1410
#include "postgres.h"
@@ -121,7 +117,7 @@ char *sql_escape_name (const char *name, char *buffer, size_t bsize) {
121117
}
122118

123119
char *sql_build_select_nonpk_by_pk (db_t *db, const char *table_name) {
124-
char *sql = cloudsync_memory_mprintf(SQL_BUILD_SELECT_NONPK_COLS_BY_PK_PG, table_name);
120+
char *sql = cloudsync_memory_mprintf(SQL_BUILD_SELECT_NONPK_COLS_BY_PK, table_name);
125121
if (!sql) return NULL;
126122

127123
char *query = NULL;
@@ -131,6 +127,50 @@ char *sql_build_select_nonpk_by_pk (db_t *db, const char *table_name) {
131127
return (rc == DBRES_OK) ? query : NULL;
132128
}
133129

130+
char *sql_build_delete_by_pk (db_t *db, const char *table_name) {
131+
char *sql = cloudsync_memory_mprintf(SQL_BUILD_DELETE_ROW_BY_PK, table_name);
132+
if (!sql) return NULL;
133+
134+
char *query = NULL;
135+
int rc = database_select_text(db, sql, &query);
136+
cloudsync_memory_free(sql);
137+
138+
return (rc == DBRES_OK) ? query : NULL;
139+
}
140+
141+
char *sql_build_insert_pk_ignore (db_t *db, const char *table_name) {
142+
char *sql = cloudsync_memory_mprintf(SQL_BUILD_INSERT_PK_IGNORE, table_name);
143+
if (!sql) return NULL;
144+
145+
char *query = NULL;
146+
int rc = database_select_text(db, sql, &query);
147+
cloudsync_memory_free(sql);
148+
149+
return (rc == DBRES_OK) ? query : NULL;
150+
}
151+
152+
char *sql_build_upsert_pk_and_col (db_t *db, const char *table_name, const char *colname) {
153+
char *sql = cloudsync_memory_mprintf(SQL_BUILD_UPSERT_PK_AND_COL, table_name, colname);
154+
if (!sql) return NULL;
155+
156+
char *query = NULL;
157+
int rc = database_select_text(db, sql, &query);
158+
cloudsync_memory_free(sql);
159+
160+
return (rc == DBRES_OK) ? query : NULL;
161+
}
162+
163+
char *sql_build_select_cols_by_pk (db_t *db, const char *table_name, const char *colname) {
164+
char *sql = cloudsync_memory_mprintf(SQL_BUILD_SELECT_COLS_BY_PK_FMT, table_name, colname);
165+
if (!sql) return NULL;
166+
167+
char *query = NULL;
168+
int rc = database_select_text(db, sql, &query);
169+
cloudsync_memory_free(sql);
170+
171+
return (rc == DBRES_OK) ? query : NULL;
172+
}
173+
134174
// MARK: - HELPER FUNCTIONS -
135175

136176
// Convert SQLite-style ? placeholders to PostgreSQL-style $1, $2, etc.

src/postgresql/sql_postgresql.c

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ const char * const SQL_SITEID_SELECT_ROWID0 =
147147
"SELECT site_id FROM cloudsync_site_id WHERE id = 0;";
148148

149149
const char * const SQL_DATA_VERSION =
150-
"SELECT SELECT txid_snapshot_xmin(txid_current_snapshot());"; // was "PRAGMA data_version"
150+
"SELECT txid_snapshot_xmin(txid_current_snapshot());"; // was "PRAGMA data_version"
151151

152152
const char * const SQL_SCHEMA_VERSION =
153153
"SELECT 1;"; // TODO: PostgreSQL equivalent of sqlite "PRAGMA schema_version", "SELECT current_schema();" is not equivalent
@@ -165,9 +165,9 @@ const char * const SQL_BUILD_SELECT_NONPK_COLS_BY_ROWID =
165165
"WHERE table_name = $1 AND constraint_name LIKE '%_pkey'"
166166
");"; // TODO: build full SELECT ... WHERE ctid=? analog with ordered columns like SQLite
167167

168-
const char * const SQL_BUILD_SELECT_NONPK_COLS_BY_PK_PG =
168+
const char * const SQL_BUILD_SELECT_NONPK_COLS_BY_PK =
169169
"WITH tbl AS ("
170-
" SELECT to_regclass(%L) AS oid"
170+
" SELECT to_regclass('%s') AS oid"
171171
"), "
172172
"pk AS ("
173173
" SELECT a.attname, k.ord "
@@ -204,7 +204,23 @@ const char * const SQL_DELETE_ROW_BY_ROWID =
204204
"DELETE FROM %s WHERE ctid = $1;"; // TODO: consider using PK-based deletion; ctid is unstable
205205

206206
const char * const SQL_BUILD_DELETE_ROW_BY_PK =
207-
"DELETE FROM %s WHERE %s;"; // TODO: build full PK WHERE clause (ordered) like SQLite format
207+
"WITH tbl AS ("
208+
" SELECT to_regclass('%s') AS oid"
209+
"), "
210+
"pk AS ("
211+
" SELECT a.attname, k.ord "
212+
" FROM pg_index x "
213+
" JOIN tbl t ON t.oid = x.indrelid "
214+
" JOIN LATERAL unnest(x.indkey) WITH ORDINALITY AS k(attnum, ord) ON true "
215+
" JOIN pg_attribute a ON a.attrelid = x.indrelid AND a.attnum = k.attnum "
216+
" WHERE x.indisprimary "
217+
" ORDER BY k.ord"
218+
") "
219+
"SELECT "
220+
" 'DELETE FROM ' || (SELECT (oid::regclass)::text FROM tbl)"
221+
" || ' WHERE '"
222+
" || (SELECT string_agg(format('%%I=?', attname), ' AND ') FROM pk)"
223+
" || ';';";
208224

209225
const char * const SQL_INSERT_ROWID_IGNORE =
210226
"INSERT INTO %s DEFAULT VALUES ON CONFLICT DO NOTHING;"; // TODO: adapt to explicit PK inserts (no rowid in PG)
@@ -214,17 +230,73 @@ const char * const SQL_UPSERT_ROWID_AND_COL_BY_ROWID =
214230
"ON CONFLICT DO UPDATE SET %s = $2;"; // TODO: align with SQLite upsert by rowid; avoid ctid
215231

216232
const char * const SQL_BUILD_INSERT_PK_IGNORE =
217-
"INSERT INTO %s (%s) VALUES (%s) ON CONFLICT DO NOTHING;"; // TODO: construct PK columns/binds dynamically
233+
"WITH tbl AS ("
234+
" SELECT to_regclass('%s') AS oid"
235+
"), "
236+
"pk AS ("
237+
" SELECT a.attname, k.ord "
238+
" FROM pg_index x "
239+
" JOIN tbl t ON t.oid = x.indrelid "
240+
" JOIN LATERAL unnest(x.indkey) WITH ORDINALITY AS k(attnum, ord) ON true "
241+
" JOIN pg_attribute a ON a.attrelid = x.indrelid AND a.attnum = k.attnum "
242+
" WHERE x.indisprimary "
243+
" ORDER BY k.ord"
244+
") "
245+
"SELECT "
246+
" 'INSERT INTO ' || (SELECT (oid::regclass)::text FROM tbl)"
247+
" || ' (' || (SELECT string_agg(format('%%I', attname), ',') FROM pk) || ')'"
248+
" || ' VALUES (' || (SELECT string_agg('?', ',') FROM pk) || ')'"
249+
" || ' ON CONFLICT DO NOTHING;';";
218250

219251
const char * const SQL_BUILD_UPSERT_PK_AND_COL =
220-
"INSERT INTO %s (%s, %s) VALUES (%s, $1) "
221-
"ON CONFLICT DO UPDATE SET %s = $1;"; // TODO: match SQLite's ON CONFLICT DO UPDATE with full PK bindings
252+
"WITH tbl AS ("
253+
" SELECT to_regclass('%s') AS oid"
254+
"), "
255+
"pk AS ("
256+
" SELECT a.attname, k.ord "
257+
" FROM pg_index x "
258+
" JOIN tbl t ON t.oid = x.indrelid "
259+
" JOIN LATERAL unnest(x.indkey) WITH ORDINALITY AS k(attnum, ord) ON true "
260+
" JOIN pg_attribute a ON a.attrelid = x.indrelid AND a.attnum = k.attnum "
261+
" WHERE x.indisprimary "
262+
" ORDER BY k.ord"
263+
"), "
264+
"col AS ("
265+
" SELECT '%s'::text AS colname"
266+
") "
267+
"SELECT "
268+
" 'INSERT INTO ' || (SELECT (oid::regclass)::text FROM tbl)"
269+
" || ' (' || (SELECT string_agg(format('%%I', attname), ',') FROM pk)"
270+
" || ',' || (SELECT format('%%I', colname) FROM col) || ')'"
271+
" || ' VALUES (' || (SELECT string_agg('?', ',') FROM pk) || ',?)'"
272+
" || ' ON CONFLICT (' || (SELECT string_agg(format('%%I', attname), ',') FROM pk) || ')'"
273+
" || ' DO UPDATE SET ' || (SELECT format('%%I', colname) FROM col) || '=?;';";
222274

223275
const char * const SQL_SELECT_COLS_BY_ROWID_FMT =
224276
"SELECT %s%s%s FROM %s WHERE ctid = $1;"; // TODO: align with PK/rowid selection builder
225277

226278
const char * const SQL_BUILD_SELECT_COLS_BY_PK_FMT =
227-
"SELECT %s%s%s FROM %s WHERE %s;"; // TODO: generate full WHERE clause with ordered PK columns
279+
"WITH tbl AS ("
280+
" SELECT to_regclass('%s') AS oid"
281+
"), "
282+
"pk AS ("
283+
" SELECT a.attname, k.ord "
284+
" FROM pg_index x "
285+
" JOIN tbl t ON t.oid = x.indrelid "
286+
" JOIN LATERAL unnest(x.indkey) WITH ORDINALITY AS k(attnum, ord) ON true "
287+
" JOIN pg_attribute a ON a.attrelid = x.indrelid AND a.attnum = k.attnum "
288+
" WHERE x.indisprimary "
289+
" ORDER BY k.ord"
290+
"), "
291+
"col AS ("
292+
" SELECT '%s'::text AS colname"
293+
") "
294+
"SELECT "
295+
" 'SELECT ' || (SELECT format('%%I', colname) FROM col)"
296+
" || ' FROM ' || (SELECT (oid::regclass)::text FROM tbl)"
297+
" || ' WHERE '"
298+
" || (SELECT string_agg(format('%%I=?', attname), ' AND ') FROM pk)"
299+
" || ';';";
228300

229301
const char * const SQL_CLOUDSYNC_ROW_EXISTS_BY_PK =
230302
"SELECT EXISTS(SELECT 1 FROM %s_cloudsync WHERE pk = $1 LIMIT 1);";
@@ -317,7 +389,7 @@ const char * const SQL_CLOUDSYNC_GC_DELETE_ORPHANED_PK =
317389
const char * const SQL_PRAGMA_TABLEINFO_PK_COLLIST =
318390
"SELECT string_agg(quote_ident(column_name), ',') "
319391
"FROM information_schema.key_column_usage "
320-
"WHERE table_name = $1 AND constraint_name LIKE '%%_pkey' "
392+
"WHERE table_name = %s AND constraint_name LIKE '%%_pkey' "
321393
"ORDER BY ordinal_position;";
322394

323395
const char * const SQL_PRAGMA_TABLEINFO_PK_DECODE_SELECTLIST =

src/sqlite/database_sqlite.c

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,77 @@ char *sql_build_select_nonpk_by_pk (db_t *db, const char *table_name) {
9999
return (rc == DBRES_OK) ? query : NULL;
100100
}
101101

102+
char *sql_build_delete_by_pk (db_t *db, const char *table_name) {
103+
char buffer[1024];
104+
char *singlequote_escaped_table_name = sql_escape_name(table_name, buffer, sizeof(buffer));
105+
char *sql = cloudsync_memory_mprintf(SQL_BUILD_DELETE_ROW_BY_PK, table_name, singlequote_escaped_table_name);
106+
if (!sql) return NULL;
107+
108+
char *query = NULL;
109+
int rc = database_select_text(db, sql, &query);
110+
cloudsync_memory_free(sql);
111+
112+
return (rc == DBRES_OK) ? query : NULL;
113+
}
114+
115+
char *sql_build_insert_pk_ignore (db_t *db, const char *table_name) {
116+
char buffer[1024];
117+
char *singlequote_escaped_table_name = sql_escape_name(table_name, buffer, sizeof(buffer));
118+
char *sql = cloudsync_memory_mprintf(SQL_BUILD_INSERT_PK_IGNORE, table_name, table_name, singlequote_escaped_table_name);
119+
if (!sql) return NULL;
120+
121+
char *query = NULL;
122+
int rc = database_select_text(db, sql, &query);
123+
cloudsync_memory_free(sql);
124+
125+
return (rc == DBRES_OK) ? query : NULL;
126+
}
127+
128+
char *sql_build_upsert_pk_and_col (db_t *db, const char *table_name, const char *colname) {
129+
char buffer[1024];
130+
char buffer2[1024];
131+
char *singlequote_escaped_table_name = sql_escape_name(table_name, buffer, sizeof(buffer));
132+
char *singlequote_escaped_col_name = sql_escape_name(colname, buffer2, sizeof(buffer2));
133+
char *sql = cloudsync_memory_mprintf(
134+
SQL_BUILD_UPSERT_PK_AND_COL,
135+
table_name,
136+
table_name,
137+
singlequote_escaped_table_name,
138+
singlequote_escaped_col_name,
139+
singlequote_escaped_col_name
140+
);
141+
if (!sql) return NULL;
142+
143+
char *query = NULL;
144+
int rc = database_select_text(db, sql, &query);
145+
cloudsync_memory_free(sql);
146+
147+
return (rc == DBRES_OK) ? query : NULL;
148+
}
149+
150+
char *sql_build_select_cols_by_pk (db_t *db, const char *table_name, const char *colname) {
151+
char *colnamequote = "\"";
152+
char buffer[1024];
153+
char buffer2[1024];
154+
char *singlequote_escaped_table_name = sql_escape_name(table_name, buffer, sizeof(buffer));
155+
char *singlequote_escaped_col_name = sql_escape_name(colname, buffer2, sizeof(buffer2));
156+
char *sql = cloudsync_memory_mprintf(
157+
SQL_BUILD_SELECT_COLS_BY_PK_FMT,
158+
table_name,
159+
colnamequote,
160+
singlequote_escaped_col_name,
161+
colnamequote,
162+
singlequote_escaped_table_name
163+
);
164+
if (!sql) return NULL;
165+
166+
char *query = NULL;
167+
int rc = database_select_text(db, sql, &query);
168+
cloudsync_memory_free(sql);
169+
170+
return (rc == DBRES_OK) ? query : NULL;
171+
}
172+
102173
// MARK: - PRIVATE -
103174

104175
int database_select1_value (db_t *db, const char *sql, char **ptr_value, int64_t *int_value, DBTYPE expected_type) {

0 commit comments

Comments
 (0)