openvibers/openvibe.sdk Commits

sqlite-schema-to-pg: quote every column name PostgreSQL reserves and SQLite does not (Billing's settings.freeze), in definitions, index lists and seed INSERTs.

OP OpenVibers committed 28 Sept 2026 · 955e1d1365 · 2 files, +11 −4


Changes

ddiff --git a/tools/asyncify/gen-migration.js b/tools/asyncify/gen-migration.js
iindex 6403b07..eafdd4a 100644
---- a/tools/asyncify/gen-migration.js
++++ b/tools/asyncify/gen-migration.js
@@@ -23,7 +23,7 @@
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
--const { toPg, SQLITE_DATE_FUNCTIONS } = require('./sqlite-schema-to-pg');
++const { toPg, SQLITE_DATE_FUNCTIONS, PG_ONLY_RESERVED } = require('./sqlite-schema-to-pg');
const args = process.argv.slice(2);
const Name = args[0];
@@@ -104,7 +104,7 @@ function fromOpenedDb(expr) {
if (!rows.length) continue;
const cols = Object.keys(rows[0]).filter((c) => !tsDefault.has(`${t.name}.${c}`));
const k = pk.get(t.name);
-- const q = (c) => (/^(window|user)$/.test(c) ? `"${c}"` : c);
++ const q = (c) => (PG_ONLY_RESERVED.includes(c.toLowerCase()) ? `"${c}"` : c);
seeds += `INSERT INTO ${t.name} (${cols.map(q).join(', ')})${k && k.always && cols.includes(k.col) ? ' OVERRIDING SYSTEM VALUE' : ''} VALUES\n`
+ `${rows.map((r) => ` (${cols.map((c) => lit(r[c])).join(', ')})`).join(',\n')};\n`;
if (k && cols.includes(k.col)) seeds += `SELECT setval(pg_get_serial_sequence('${t.name}', '${k.col}'), (SELECT MAX(${k.col}) FROM ${t.name}));\n`;
ddiff --git a/tools/asyncify/sqlite-schema-to-pg.js b/tools/asyncify/sqlite-schema-to-pg.js
iindex ad5ebf2..45ef567 100644
---- a/tools/asyncify/sqlite-schema-to-pg.js
++++ b/tools/asyncify/sqlite-schema-to-pg.js
@@@ -13,6 +13,11 @@
*
* const { toPg } = require('openvibe-sdk/tools/asyncify/sqlite-schema-to-pg'); (from a checkout)
*/
++/** Words PostgreSQL reserves that SQLite takes as plain column names. */
++const PG_ONLY_RESERVED = ['window', 'user', 'freeze', 'analyse', 'both', 'leading', 'trailing', 'symmetric', 'asymmetric', 'variadic',
++ 'lateral', 'ilike', 'similar', 'verbose', 'tablesample', 'concurrently', 'overlaps', 'authorization', 'binary', 'collation',
++ 'only', 'placing', 'array', 'fetch', 'current_role', 'session_user', 'current_user', 'current_catalog', 'current_schema'];
++
function toPg(sql) {
// A SQLite row trigger → a PL/pgSQL function and a trigger. Its WHEN condition (which may hold a subquery, not
// allowed in a PostgreSQL trigger's WHEN) goes inside the function as IF cond THEN body END IF; RAISE(ABORT, msg)
@@@ -36,7 +41,9 @@ function toPg(sql) {
if (/^\s*\w+\s+TEXT\b[^\n]*COLLATE NOCASE[^\n]*UNIQUE|UNIQUE[^\n]*COLLATE NOCASE/m.test(sql)) throw new Error('sqlite-schema-to-pg: a case-insensitive UNIQUE column needs a unique index on lower(col), by hand');
sql = sql.replace(/^(\s*\w+\s+TEXT\b[^\n]*?) COLLATE NOCASE/gm, '$1');
// Column names PostgreSQL reserves (SQLite does not) are quoted: a column definition, and inside index/unique lists.
-- sql = sql.replace(/^(\s*)(window|user)(\s+[A-Z])/gm, '$1"$2"$3').replace(/([(,]\s*)(window|user)(\s*[,)])/g, '$1"$2"$3');
++ // The service's queries must quote them too (Billing's settings.freeze).
++ const R = PG_ONLY_RESERVED.join('|');
++ sql = sql.replace(new RegExp(`^(\\s*)(${R})(\\s+[A-Za-z])`, 'gim'), '$1"$2"$3').replace(new RegExp(`([(,]\\s*)(${R})(?=\\s*[,)])`, 'gi'), '$1"$2"');
return sql
.replace(/\bINTEGER PRIMARY KEY AUTOINCREMENT\b/g, 'bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY')
// An INTEGER PRIMARY KEY that references another table is that row's key, not a counter.
@@@ -88,4 +95,4 @@ END $$;
CREATE FUNCTION julianday(t text) RETURNS double precision LANGUAGE sql STABLE AS $$ SELECT extract(epoch FROM ov_ts(t))::double precision / 86400.0 + 2440587.5 $$;
`;
--module.exports = { toPg, SQLITE_DATE_FUNCTIONS };
++module.exports = { toPg, SQLITE_DATE_FUNCTIONS, PG_ONLY_RESERVED };