Skip to content

Commit c261dbb

Browse files
committed
Updated SQL_DATA_VERSION and added a new sql_build_select_nonpk_by_pk function to database.h
1 parent 448aa79 commit c261dbb

File tree

5 files changed

+102
-77
lines changed

5 files changed

+102
-77
lines changed

src/cloudsync.c

Lines changed: 1 addition & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -544,60 +544,6 @@ void table_pknames_free (char **names, int nrows) {
544544
cloudsync_memory_free(names);
545545
}
546546

547-
char *table_build_values_sql (db_t *db, cloudsync_table_context *table) {
548-
char *sql = NULL;
549-
550-
/*
551-
This SQL statement dynamically generates a SELECT query for a specified table.
552-
It uses Common Table Expressions (CTEs) to construct the column names and
553-
primary key conditions based on the table schema, which is obtained through
554-
the `pragma_table_info` function.
555-
556-
1. `col_names` CTE:
557-
- Retrieves a comma-separated list of non-primary key column names from
558-
the specified table's schema.
559-
560-
2. `pk_where` CTE:
561-
- Retrieves a condition string representing the primary key columns in the
562-
format: "column1=? AND column2=? AND ...", used to create the WHERE clause
563-
for selecting rows based on primary key values.
564-
565-
3. Final SELECT:
566-
- Constructs the complete SELECT statement as a string, combining:
567-
- Column names from `col_names`.
568-
- The target table name.
569-
- The WHERE clause conditions from `pk_where`.
570-
571-
The resulting query can be used to select rows from the table based on primary
572-
key values, and can be executed within the application to retrieve data dynamically.
573-
*/
574-
575-
// Unfortunately in SQLite column names (or table names) cannot be bound parameters in a SELECT statement
576-
// otherwise we should have used something like SELECT 'SELECT ? FROM %w WHERE rowid=?';
577-
char buffer[1024];
578-
char *singlequote_escaped_table_name = sql_escape_name(table->name, buffer, sizeof(buffer));
579-
580-
#if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES
581-
if (table->rowid_only) {
582-
sql = memory_mprintf(SQL_BUILD_SELECT_NONPK_COLS_BY_ROWID, table->name, table->name);
583-
goto process_process;
584-
}
585-
#endif
586-
587-
sql = cloudsync_memory_mprintf(SQL_BUILD_SELECT_NONPK_COLS_BY_PK, table->name, table->name, singlequote_escaped_table_name);
588-
589-
#if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES
590-
process_process:
591-
#endif
592-
if (!sql) return NULL;
593-
594-
char *query = NULL;
595-
int rc = database_select_text(db, sql, &query);
596-
cloudsync_memory_free(sql);
597-
598-
return (rc == DBRES_OK) ? query : NULL;
599-
}
600-
601547
char *table_build_mergedelete_sql (db_t *db, cloudsync_table_context *table) {
602548
#if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES
603549
if (table->rowid_only) {
@@ -870,7 +816,7 @@ int table_add_stmts (db_t *db, cloudsync_table_context *table, int ncols) {
870816

871817
// precompile the get column value statement
872818
if (ncols > 0) {
873-
sql = table_build_values_sql(db, table);
819+
sql = sql_build_select_nonpk_by_pk(db, table->name);
874820
if (!sql) {rc = DBRES_NOMEM; goto cleanup;}
875821
DEBUG_SQL("real_col_values_stmt: %s", sql);
876822

src/database.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ int database_count_nonpk (db_t *db, const char *table_name);
7878
int database_count_int_pk (db_t *db, const char *table_name);
7979
int database_count_notnull_without_default (db_t *db, const char *table_name);
8080

81-
int64_t database_schema_version (db_t *db);
81+
int64_t database_schema_version (db_t *db);
8282
uint64_t database_schema_hash (db_t *db);
8383
bool database_check_schema_hash (db_t *db, uint64_t hash);
8484
int database_update_schema_hash (db_t *db, uint64_t *hash);
@@ -145,6 +145,7 @@ uint64_t dbmem_size (void *ptr);
145145
// SQL
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);
148+
char *sql_build_select_nonpk_by_pk (db_t *db, const char *table_name);
148149

149150
// USED ONLY by SQLite Cloud to implement RLS
150151
typedef struct cloudsync_pk_decode_bind_context cloudsync_pk_decode_bind_context;

src/postgresql/database_postgresql.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,17 @@ char *sql_escape_name (const char *name, char *buffer, size_t bsize) {
120120
return buffer;
121121
}
122122

123+
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);
125+
if (!sql) return NULL;
126+
127+
char *query = NULL;
128+
int rc = database_select_text(db, sql, &query);
129+
cloudsync_memory_free(sql);
130+
131+
return (rc == DBRES_OK) ? query : NULL;
132+
}
133+
123134
// MARK: - HELPER FUNCTIONS -
124135

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

src/postgresql/sql_postgresql.c

Lines changed: 34 additions & 21 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 1"; // TODO: PostgreSQL equivalent of sqlite "PRAGMA data_version", "SELECT txid_current();" is not equivalent
150+
"SELECT 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,27 +165,40 @@ 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 =
169-
"WITH nonpk AS ("
170-
" SELECT string_agg(quote_ident(column_name), ',' ORDER BY ordinal_position) AS cols "
171-
" FROM information_schema.columns "
172-
" WHERE table_schema = current_schema() AND table_name = '%s' AND ordinal_position NOT IN ("
173-
" SELECT ordinal_position FROM information_schema.columns c "
174-
" WHERE table_schema = current_schema() AND table_name = '%s' AND column_name IN ("
175-
" SELECT column_name FROM information_schema.key_column_usage "
176-
" WHERE table_schema = current_schema() AND table_name = '%s' AND constraint_name LIKE '%%_pkey'"
177-
" )"
178-
" )"
179-
"), pk_cols AS ("
180-
" SELECT column_name, row_number() OVER (ORDER BY position_in_unique_constraint) AS rn "
181-
" FROM information_schema.key_column_usage "
182-
" WHERE table_schema = current_schema() AND table_name = '%s' AND constraint_name LIKE '%%_pkey'"
183-
"), pk AS ("
184-
" SELECT string_agg(quote_ident(column_name) || ' = $' || rn, ' AND ' ORDER BY rn) AS clause "
185-
" FROM pk_cols"
168+
const char * const SQL_BUILD_SELECT_NONPK_COLS_BY_PK_PG =
169+
"WITH tbl AS ("
170+
" SELECT to_regclass(%L) AS oid"
171+
"), "
172+
"pk AS ("
173+
" SELECT a.attname, k.ord "
174+
" FROM pg_index x "
175+
" JOIN tbl t ON t.oid = x.indrelid "
176+
" JOIN LATERAL unnest(x.indkey) WITH ORDINALITY AS k(attnum, ord) ON true "
177+
" JOIN pg_attribute a ON a.attrelid = x.indrelid AND a.attnum = k.attnum "
178+
" WHERE x.indisprimary "
179+
" ORDER BY k.ord"
180+
"), "
181+
"nonpk AS ("
182+
" SELECT a.attname "
183+
" FROM pg_attribute a "
184+
" JOIN tbl t ON t.oid = a.attrelid "
185+
" WHERE a.attnum > 0 AND NOT a.attisdropped "
186+
" AND a.attnum NOT IN ("
187+
" SELECT k.attnum "
188+
" FROM pg_index x "
189+
" JOIN tbl t2 ON t2.oid = x.indrelid "
190+
" JOIN LATERAL unnest(x.indkey) AS k(attnum) ON true "
191+
" WHERE x.indisprimary"
192+
" ) "
193+
" ORDER BY a.attnum"
186194
") "
187-
"SELECT 'SELECT ' || COALESCE((SELECT cols FROM nonpk), '*') || ' FROM ' || quote_ident('%s') || ' WHERE ' || clause || ';' "
188-
"FROM pk;"; // Generates full SELECT with ordered non-PK columns and PK WHERE clause for cloudsync_memory_mprintf
195+
"SELECT "
196+
" 'SELECT '"
197+
" || (SELECT string_agg(format('%%I', attname), ',') FROM nonpk)"
198+
" || ' FROM ' || (SELECT (oid::regclass)::text FROM tbl)"
199+
" || ' WHERE '"
200+
" || (SELECT string_agg(format('%%I=?', attname), ' AND ') FROM pk)"
201+
" || ';';";
189202

190203
const char * const SQL_DELETE_ROW_BY_ROWID =
191204
"DELETE FROM %s WHERE ctid = $1;"; // TODO: consider using PK-based deletion; ctid is unstable

src/sqlite/database_sqlite.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,60 @@ char *sql_escape_name (const char *name, char *buffer, size_t bsize) {
4545
return sqlite3_snprintf((int)bsize, buffer, "%q", name);
4646
}
4747

48+
char *sql_build_select_nonpk_by_pk (db_t *db, const char *table_name) {
49+
char *sql = NULL;
50+
51+
/*
52+
This SQL statement dynamically generates a SELECT query for a specified table.
53+
It uses Common Table Expressions (CTEs) to construct the column names and
54+
primary key conditions based on the table schema, which is obtained through
55+
the `pragma_table_info` function.
56+
57+
1. `col_names` CTE:
58+
- Retrieves a comma-separated list of non-primary key column names from
59+
the specified table's schema.
60+
61+
2. `pk_where` CTE:
62+
- Retrieves a condition string representing the primary key columns in the
63+
format: "column1=? AND column2=? AND ...", used to create the WHERE clause
64+
for selecting rows based on primary key values.
65+
66+
3. Final SELECT:
67+
- Constructs the complete SELECT statement as a string, combining:
68+
- Column names from `col_names`.
69+
- The target table name.
70+
- The WHERE clause conditions from `pk_where`.
71+
72+
The resulting query can be used to select rows from the table based on primary
73+
key values, and can be executed within the application to retrieve data dynamically.
74+
*/
75+
76+
// Unfortunately in SQLite column names (or table names) cannot be bound parameters in a SELECT statement
77+
// otherwise we should have used something like SELECT 'SELECT ? FROM %w WHERE rowid=?';
78+
char buffer[1024];
79+
char *singlequote_escaped_table_name = sql_escape_name(table_name, buffer, sizeof(buffer));
80+
81+
#if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES
82+
if (table->rowid_only) {
83+
sql = memory_mprintf(SQL_BUILD_SELECT_NONPK_COLS_BY_ROWID, table->name, table->name);
84+
goto process_process;
85+
}
86+
#endif
87+
88+
sql = cloudsync_memory_mprintf(SQL_BUILD_SELECT_NONPK_COLS_BY_PK, table_name, table_name, singlequote_escaped_table_name);
89+
90+
#if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES
91+
process_process:
92+
#endif
93+
if (!sql) return NULL;
94+
95+
char *query = NULL;
96+
int rc = database_select_text(db, sql, &query);
97+
cloudsync_memory_free(sql);
98+
99+
return (rc == DBRES_OK) ? query : NULL;
100+
}
101+
48102
// MARK: - PRIVATE -
49103

50104
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)