@@ -147,7 +147,7 @@ const char * const SQL_SITEID_SELECT_ROWID0 =
147147 "SELECT site_id FROM cloudsync_site_id WHERE id = 0;" ;
148148
149149const 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
152152const 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
206206const 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
209225const 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
216232const 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
219251const 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
223275const 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
226278const 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
229301const 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 =
317389const 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
323395const char * const SQL_PRAGMA_TABLEINFO_PK_DECODE_SELECTLIST =
0 commit comments