@saltcorn/postgres 1.6.3-beta.1 → 1.7.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/postgres.d.ts +356 -0
- package/dist/postgres.d.ts.map +1 -0
- package/dist/postgres.js +824 -0
- package/dist/postgres.js.map +1 -0
- package/package.json +29 -5
- package/index.js +0 -14
- package/postgres.js +0 -786
package/dist/postgres.js
ADDED
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL data access layer
|
|
3
|
+
* @category postgres
|
|
4
|
+
* @module postgres
|
|
5
|
+
*/
|
|
6
|
+
// TODO move postgresql specific to this module
|
|
7
|
+
import { Pool, types } from "pg";
|
|
8
|
+
import * as copyStreams from "pg-copy-streams";
|
|
9
|
+
import { pipeline } from "stream/promises";
|
|
10
|
+
import replace from "replacestream";
|
|
11
|
+
import { createRequire } from "module";
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
import { sqlsanitize, mkWhere, mkSelectOptions, } from "@saltcorn/db-common/internal";
|
|
14
|
+
import PlainDate from "@saltcorn/plain-date";
|
|
15
|
+
import tenantsModule from "@saltcorn/db-common/tenants";
|
|
16
|
+
types.setTypeParser(types.builtins.DATE, (d) => d === null ? null : new PlainDate(d));
|
|
17
|
+
let getTenantSchema;
|
|
18
|
+
let getRequestContext;
|
|
19
|
+
let getConnectObject = null;
|
|
20
|
+
export let pool = null;
|
|
21
|
+
let log_sql_enabled = false;
|
|
22
|
+
const quote = (s) => `"${s}"`;
|
|
23
|
+
const ppPK = (pk) => (pk ? quote(pk) : "id");
|
|
24
|
+
/**
|
|
25
|
+
* Control Logging sql statements to console
|
|
26
|
+
* @param {boolean} [val = true] - if true then log sql statements to console
|
|
27
|
+
*/
|
|
28
|
+
export function set_sql_logging(val = true) {
|
|
29
|
+
log_sql_enabled = val;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get sql logging state
|
|
33
|
+
* @returns {boolean} if true then sql logging eabled
|
|
34
|
+
*/
|
|
35
|
+
export function get_sql_logging() {
|
|
36
|
+
return log_sql_enabled;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Log SQL statement to console
|
|
40
|
+
* @param {string} sql - SQL statement
|
|
41
|
+
* @param {object} [vs] - any additional parameter
|
|
42
|
+
*/
|
|
43
|
+
export function sql_log(sql, vs) {
|
|
44
|
+
if (log_sql_enabled)
|
|
45
|
+
if (typeof vs === "undefined")
|
|
46
|
+
console.log(sql);
|
|
47
|
+
else
|
|
48
|
+
console.log(sql, vs);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Close database connection
|
|
52
|
+
* @returns {Promise<void>}
|
|
53
|
+
*/
|
|
54
|
+
export const close = async () => {
|
|
55
|
+
if (pool)
|
|
56
|
+
await pool.end();
|
|
57
|
+
pool = null;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Change connection (close connection and open new connection from connObj)
|
|
61
|
+
* @param {object} [connObj = {}] - connection object
|
|
62
|
+
* @returns {Promise<void>}
|
|
63
|
+
*/
|
|
64
|
+
export const changeConnection = async (connObj = Object.create(null)) => {
|
|
65
|
+
await close();
|
|
66
|
+
pool = new Pool(getConnectObject(connObj));
|
|
67
|
+
};
|
|
68
|
+
export const begin = async () => {
|
|
69
|
+
//client = await getClient();
|
|
70
|
+
await query("BEGIN");
|
|
71
|
+
};
|
|
72
|
+
export const commit = async () => {
|
|
73
|
+
await query("COMMIT");
|
|
74
|
+
};
|
|
75
|
+
export const rollback = async () => {
|
|
76
|
+
await query("ROLLBACK");
|
|
77
|
+
};
|
|
78
|
+
const getMyClient = (selopts) => {
|
|
79
|
+
const ctx = getRequestContext();
|
|
80
|
+
return selopts?.client || ctx?.client || pool;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Execute Select statement
|
|
84
|
+
* @param {string} tbl - table name
|
|
85
|
+
* @param {object} whereObj - where object
|
|
86
|
+
* @param {object} [selectopts = {}] - select options
|
|
87
|
+
* @returns {Promise<*>} return rows
|
|
88
|
+
*/
|
|
89
|
+
export const select = async (tbl, whereObj, selectopts = Object.create(null)) => {
|
|
90
|
+
const { where, values } = mkWhere(whereObj);
|
|
91
|
+
const schema = selectopts.schema || getTenantSchema();
|
|
92
|
+
let sql;
|
|
93
|
+
if (selectopts.tree_field && !whereObj[selectopts.tree_field])
|
|
94
|
+
sql = `WITH RECURSIVE _tree AS (
|
|
95
|
+
SELECT ${selectopts.fields ? selectopts.fields.join(", ") : `*`}, 0 as _level
|
|
96
|
+
${selectopts.orderBy ? `, ARRAY[row_number() over (ORDER BY "${sqlsanitize(selectopts.orderBy)}"${selectopts.orderDesc ? " DESC" : ""})] as _sort_path` : ""}
|
|
97
|
+
FROM "${schema}"."${sqlsanitize(tbl)}"
|
|
98
|
+
WHERE "${selectopts.tree_field}" IS NULL ${where ? `AND ${where.replace("where ", "")}` : ""}
|
|
99
|
+
|
|
100
|
+
UNION ALL
|
|
101
|
+
|
|
102
|
+
SELECT ${selectopts.fields
|
|
103
|
+
? selectopts.fields.map((f) => `p."${f}"`).join(", ")
|
|
104
|
+
: `p.*`}, pt._level+1
|
|
105
|
+
${selectopts.orderBy ? `, pt._sort_path || row_number() OVER (PARTITION BY p."${selectopts.tree_field}" ORDER BY p."${selectopts.orderBy}"${selectopts.orderDesc ? " DESC" : ""})` : ""}
|
|
106
|
+
FROM "${schema}"."${sqlsanitize(tbl)}" p
|
|
107
|
+
JOIN _tree pt ON p."${selectopts.tree_field}" = pt."${selectopts.pk_name || "id"}"
|
|
108
|
+
)
|
|
109
|
+
SELECT ${selectopts.fields ? selectopts.fields.join(", ") : `*`}, _level FROM _tree ${where} ${mkSelectOptions(selectopts.orderBy
|
|
110
|
+
? { ...selectopts, orderBy: "_sort_path", orderDesc: false }
|
|
111
|
+
: selectopts, values, false)}`;
|
|
112
|
+
else
|
|
113
|
+
sql = `SELECT ${selectopts.fields ? selectopts.fields.join(", ") : `*`} FROM "${schema}"."${sqlsanitize(tbl)}" ${where} ${mkSelectOptions(selectopts, values, false)}`;
|
|
114
|
+
sql_log(sql, values);
|
|
115
|
+
const tq = await getMyClient(selectopts).query(sql, values);
|
|
116
|
+
return tq.rows;
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Reset DB Schema using drop schema and recreate it
|
|
120
|
+
* Atterntion! You will lost data after call this function!
|
|
121
|
+
* @param {string} schema - db schema name
|
|
122
|
+
* @returns {Promise<void>} no result
|
|
123
|
+
*/
|
|
124
|
+
export const drop_reset_schema = async (schema) => {
|
|
125
|
+
const sql = `DROP SCHEMA IF EXISTS "${schema}" CASCADE;
|
|
126
|
+
CREATE SCHEMA "${schema}";
|
|
127
|
+
GRANT ALL ON SCHEMA "${schema}" TO postgres;
|
|
128
|
+
GRANT ALL ON SCHEMA "${schema}" TO "public" ;
|
|
129
|
+
COMMENT ON SCHEMA "${schema}" IS 'standard public schema';`;
|
|
130
|
+
sql_log(sql);
|
|
131
|
+
await getMyClient().query(sql);
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Create the Postgres schema backing a tenant namespace.
|
|
135
|
+
* @param {string} name - tenant/schema name
|
|
136
|
+
* @param {boolean} [ifNotExists] - use IF NOT EXISTS (idempotent, used by tests)
|
|
137
|
+
* @returns {Promise<void>} no result
|
|
138
|
+
*/
|
|
139
|
+
export const create_tenant_schema = async (name, ifNotExists) => {
|
|
140
|
+
const sql = `CREATE SCHEMA ${ifNotExists ? "IF NOT EXISTS " : ""}"${sqlsanitize(name)}";`;
|
|
141
|
+
sql_log(sql);
|
|
142
|
+
await getMyClient().query(sql);
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* Drop the Postgres schema backing a tenant namespace.
|
|
146
|
+
* @param {string} name - tenant/schema name
|
|
147
|
+
* @returns {Promise<void>} no result
|
|
148
|
+
*/
|
|
149
|
+
export const drop_tenant_schema = async (name) => {
|
|
150
|
+
const sql = `drop schema if exists "${sqlsanitize(name)}" CASCADE`;
|
|
151
|
+
sql_log(sql);
|
|
152
|
+
await getMyClient().query(sql);
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Upsert a row into _sc_config.
|
|
156
|
+
* @param {string} key
|
|
157
|
+
* @param {any} value
|
|
158
|
+
* @returns {Promise<void>} no result
|
|
159
|
+
*/
|
|
160
|
+
export const upsert_config = async (key, value) => {
|
|
161
|
+
const sql = `insert into "${getTenantSchema()}"."_sc_config"("key", value) values($1, $2)
|
|
162
|
+
on conflict ("key") do update set value = $2`;
|
|
163
|
+
sql_log(sql, [key, { v: value }]);
|
|
164
|
+
await getMyClient().query(sql, [key, { v: value }]);
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* Build the express-session Store backed by this Postgres pool.
|
|
168
|
+
* @param {any} session - the express-session module instance the app uses
|
|
169
|
+
* @param {object} [opts]
|
|
170
|
+
* @returns {any} a session.Store instance
|
|
171
|
+
*/
|
|
172
|
+
export const getExpressSessionStore = (session, opts = {}) => {
|
|
173
|
+
const pgSession = require("connect-pg-simple")(session);
|
|
174
|
+
return new pgSession({
|
|
175
|
+
schemaName: getTenantSchema(),
|
|
176
|
+
pool,
|
|
177
|
+
tableName: "_sc_session",
|
|
178
|
+
createTableIfMissing: true,
|
|
179
|
+
pruneSessionInterval: (opts.pruneInterval ?? 0) > 0 ? opts.pruneInterval : false,
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* Get count of rows in table
|
|
184
|
+
* @param {string} - tbl - table name
|
|
185
|
+
* @param {object} - whereObj - where object
|
|
186
|
+
* @returns {Promise<number>} count of tables
|
|
187
|
+
*/
|
|
188
|
+
export const count = async (tbl, whereObj, opts) => {
|
|
189
|
+
const { where, values } = mkWhere(whereObj);
|
|
190
|
+
if (!where) {
|
|
191
|
+
try {
|
|
192
|
+
// fast count for large table but may be stale
|
|
193
|
+
// https://stackoverflow.com/questions/7943233/fast-way-to-discover-the-row-count-of-a-table-in-postgresql
|
|
194
|
+
//https://www.citusdata.com/blog/2016/10/12/count-performance/
|
|
195
|
+
const sql = `SELECT (CASE WHEN c.reltuples < 0 THEN NULL
|
|
196
|
+
WHEN c.relpages = 0 THEN float8 '0' -- empty table
|
|
197
|
+
ELSE c.reltuples / c.relpages END
|
|
198
|
+
* (pg_catalog.pg_relation_size(c.oid)
|
|
199
|
+
/ pg_catalog.current_setting('block_size')::int)
|
|
200
|
+
)::bigint
|
|
201
|
+
FROM pg_catalog.pg_class c
|
|
202
|
+
WHERE c.oid = '"${opts?.schema || getTenantSchema()}"."${sqlsanitize(tbl)}"'::regclass`;
|
|
203
|
+
sql_log(sql);
|
|
204
|
+
const tq = await getMyClient(opts).query(sql, []);
|
|
205
|
+
const n = +tq.rows[0].int8;
|
|
206
|
+
if (n && n > 10000)
|
|
207
|
+
return n;
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
//skip fast estimate
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const core_sql = `FROM "${opts?.schema || getTenantSchema()}"."${sqlsanitize(tbl)}" ${where}`;
|
|
214
|
+
//https://pganalyze.com/blog/5mins-postgres-limited-count
|
|
215
|
+
const sql = opts?.limit
|
|
216
|
+
? `SELECT count(*) AS count FROM (
|
|
217
|
+
SELECT 1 ${core_sql} limit ${+opts?.limit}) limited_count`
|
|
218
|
+
: `SELECT COUNT(*) ${core_sql}`;
|
|
219
|
+
sql_log(sql, values);
|
|
220
|
+
const tq = await getMyClient(opts).query(sql, values);
|
|
221
|
+
return parseInt(tq.rows[0].count);
|
|
222
|
+
};
|
|
223
|
+
/**
|
|
224
|
+
* Get version of PostgreSQL
|
|
225
|
+
* @param {boolean} short - if true return short version info else full version info
|
|
226
|
+
* @returns {Promise<string>} returns version
|
|
227
|
+
*/
|
|
228
|
+
export const getVersion = async (short) => {
|
|
229
|
+
const sql = `SELECT version();`;
|
|
230
|
+
sql_log(sql);
|
|
231
|
+
const tq = await getMyClient().query(sql);
|
|
232
|
+
const v = tq.rows[0].version;
|
|
233
|
+
if (short) {
|
|
234
|
+
const ws = v.split(" ");
|
|
235
|
+
return ws[1];
|
|
236
|
+
}
|
|
237
|
+
return v;
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* Delete rows in table
|
|
241
|
+
* @param {string} tbl - table name
|
|
242
|
+
* @param {object} whereObj - where object
|
|
243
|
+
* @param {object} [opts = {}]
|
|
244
|
+
* @returns {Promise<object[]>} result of delete execution
|
|
245
|
+
*/
|
|
246
|
+
export const deleteWhere = async (tbl, whereObj, opts = Object.create(null)) => {
|
|
247
|
+
const { where, values } = mkWhere(whereObj);
|
|
248
|
+
const sql = `delete FROM "${opts.schema || getTenantSchema()}"."${sqlsanitize(tbl)}" ${where}`;
|
|
249
|
+
sql_log(sql, values);
|
|
250
|
+
const tq = await getMyClient(opts).query(sql, values);
|
|
251
|
+
return tq.rows;
|
|
252
|
+
};
|
|
253
|
+
export const truncate = async (tbl) => {
|
|
254
|
+
const sql = `truncate "${getTenantSchema()}"."${sqlsanitize(tbl)}"`;
|
|
255
|
+
sql_log(sql, []);
|
|
256
|
+
const tq = await getMyClient().query(sql, []);
|
|
257
|
+
return tq.rows;
|
|
258
|
+
};
|
|
259
|
+
/**
|
|
260
|
+
* Insert rows into table
|
|
261
|
+
* @param {string} tbl - table name
|
|
262
|
+
* @param {object} obj - columns names and data
|
|
263
|
+
* @param {object} [opts = {}] - columns attributes
|
|
264
|
+
* @returns {Promise<string>} returns primary key column or Id column value. If primary key column is not defined then return value of Id column.
|
|
265
|
+
*/
|
|
266
|
+
export const insert = async (tbl, obj, opts = Object.create(null)) => {
|
|
267
|
+
const kvs = Object.entries(obj);
|
|
268
|
+
const fnameList = kvs.map(([k, v]) => `"${sqlsanitize(k)}"`).join();
|
|
269
|
+
var valPosList = [];
|
|
270
|
+
var valList = [];
|
|
271
|
+
const schema = opts.schema || getTenantSchema();
|
|
272
|
+
const conflict = opts.onConflictDoNothing ? "on conflict do nothing " : "";
|
|
273
|
+
kvs.forEach(([k, v]) => {
|
|
274
|
+
if (v && v.next_version_by_id) {
|
|
275
|
+
valList.push(v.next_version_by_id);
|
|
276
|
+
valPosList.push(`coalesce((select max(_version) from "${schema}"."${sqlsanitize(tbl)}" where "${v.pk_name || "id"}"=$${valList.length}), 0)+1`);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
valList.push(v);
|
|
280
|
+
valPosList.push(`$${valList.length}`);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
const sql = valPosList.length > 0
|
|
284
|
+
? `insert into "${schema}"."${sqlsanitize(tbl)}"(${fnameList}) values(${valPosList.join()}) ${conflict}returning ${opts.noid ? "*" : ppPK(opts.pk_name)}`
|
|
285
|
+
: `insert into "${schema}"."${sqlsanitize(tbl)}" DEFAULT VALUES returning ${opts.noid ? "*" : ppPK(opts.pk_name)}`;
|
|
286
|
+
sql_log(sql, valList);
|
|
287
|
+
const { rows } = await getMyClient(opts).query(sql, valList);
|
|
288
|
+
if (opts.noid)
|
|
289
|
+
return;
|
|
290
|
+
else if (conflict && rows.length === 0)
|
|
291
|
+
return;
|
|
292
|
+
else
|
|
293
|
+
return rows[0][opts.pk_name || "id"];
|
|
294
|
+
};
|
|
295
|
+
/**
|
|
296
|
+
* Update table records
|
|
297
|
+
* @param {string} tbl - table name
|
|
298
|
+
* @param {object} obj - columns names and data
|
|
299
|
+
* @param {number|undefined} id - id of record (primary key column value)
|
|
300
|
+
* @param {object} [opts = {}] - columns attributes
|
|
301
|
+
* @returns {Promise<void>} no result
|
|
302
|
+
*/
|
|
303
|
+
export const update = async (tbl, obj, id, opts = Object.create(null)) => {
|
|
304
|
+
const kvs = Object.entries(obj);
|
|
305
|
+
if (kvs.length === 0)
|
|
306
|
+
return;
|
|
307
|
+
const assigns = kvs
|
|
308
|
+
.map(([k, v], ix) => `"${sqlsanitize(k)}"=$${ix + 1}`)
|
|
309
|
+
.join();
|
|
310
|
+
let valList = kvs.map(([k, v]) => v);
|
|
311
|
+
// TBD check that is correct - because in insert function opts.noid ? "*" : opts.pk_name || "id"
|
|
312
|
+
//valList.push(id === "undefined"? obj[opts.pk_name]: id);
|
|
313
|
+
let whereS;
|
|
314
|
+
if (id && typeof id == "object") {
|
|
315
|
+
let n = kvs.length + 1;
|
|
316
|
+
const whereStrs = [];
|
|
317
|
+
Object.keys(id).forEach((k) => {
|
|
318
|
+
valList.push(id[k]);
|
|
319
|
+
whereStrs.push(`"${k}"=$${n}`);
|
|
320
|
+
n += 1;
|
|
321
|
+
});
|
|
322
|
+
whereS = whereStrs.join(" and ");
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
valList.push(id === "undefined" ? obj[opts.pk_name || "id"] : id);
|
|
326
|
+
whereS = `${ppPK(opts.pk_name)}=$${kvs.length + 1}`;
|
|
327
|
+
}
|
|
328
|
+
const q = `update "${opts.schema || getTenantSchema()}"."${sqlsanitize(tbl)}" set ${assigns} where ${whereS}`;
|
|
329
|
+
sql_log(q, valList);
|
|
330
|
+
await getMyClient(opts).query(q, valList);
|
|
331
|
+
};
|
|
332
|
+
/**
|
|
333
|
+
* Update table records
|
|
334
|
+
* @param {string} tbl - table name
|
|
335
|
+
* @param {object} obj - columns names and data
|
|
336
|
+
* @param {object} whereObj - where object
|
|
337
|
+
* @param {object} opts - can contain a db client for transactions
|
|
338
|
+
* @returns {Promise<void>} no result
|
|
339
|
+
*/
|
|
340
|
+
export const updateWhere = async (tbl, obj, whereObj, opts = Object.create(null)) => {
|
|
341
|
+
const kvs = Object.entries(obj);
|
|
342
|
+
if (kvs.length === 0)
|
|
343
|
+
return;
|
|
344
|
+
const { where, values } = mkWhere(whereObj, false, kvs.length);
|
|
345
|
+
const assigns = kvs
|
|
346
|
+
.map(([k, v], ix) => `"${sqlsanitize(k)}"=$${ix + 1}`)
|
|
347
|
+
.join();
|
|
348
|
+
let valList = [...kvs.map(([k, v]) => v), ...values];
|
|
349
|
+
const q = `update "${getTenantSchema()}"."${sqlsanitize(tbl)}" set ${assigns} ${where}`;
|
|
350
|
+
sql_log(q, valList);
|
|
351
|
+
await getMyClient().query(q, valList);
|
|
352
|
+
};
|
|
353
|
+
/**
|
|
354
|
+
* Select one record
|
|
355
|
+
* @param {srting} tbl - table name
|
|
356
|
+
* @param {object} where - where object
|
|
357
|
+
* @param {object} [selectopts = {}] - select options
|
|
358
|
+
* @returns {Promise<object>} return first record from sql result
|
|
359
|
+
* @throws {Error}
|
|
360
|
+
*/
|
|
361
|
+
export const selectOne = async (tbl, where, selectopts = Object.create(null)) => {
|
|
362
|
+
const rows = await select(tbl, where, { ...selectopts, limit: 1 });
|
|
363
|
+
if (rows.length === 0) {
|
|
364
|
+
const w = mkWhere(where);
|
|
365
|
+
throw new Error(`no ${tbl} ${w.where} are ${w.values}`);
|
|
366
|
+
}
|
|
367
|
+
else
|
|
368
|
+
return rows[0];
|
|
369
|
+
};
|
|
370
|
+
/**
|
|
371
|
+
* Select one record or null if no records
|
|
372
|
+
* @param {string} tbl - table name
|
|
373
|
+
* @param {object} where - where object
|
|
374
|
+
* @param {object} [selectopts = {}] - select options
|
|
375
|
+
* @returns {Promise<null|object>} - null if no record or first record data
|
|
376
|
+
*/
|
|
377
|
+
export const selectMaybeOne = async (tbl, where, selectopts = Object.create(null)) => {
|
|
378
|
+
const rows = await select(tbl, where, { ...selectopts, limit: 1 });
|
|
379
|
+
if (rows.length === 0)
|
|
380
|
+
return null;
|
|
381
|
+
else
|
|
382
|
+
return rows[0];
|
|
383
|
+
};
|
|
384
|
+
/**
|
|
385
|
+
* Open db connection
|
|
386
|
+
* Only for PG.
|
|
387
|
+
* @returns {Promise<*>} db connection object
|
|
388
|
+
*/
|
|
389
|
+
// TBD Currently this function supported only for PG
|
|
390
|
+
export const getClient = async () => await pool.connect();
|
|
391
|
+
/**
|
|
392
|
+
* Reset sequence
|
|
393
|
+
* Only for PG
|
|
394
|
+
* @param {string} tblname - table name
|
|
395
|
+
* @returns {Promise<void>} no result
|
|
396
|
+
*/
|
|
397
|
+
export const reset_sequence = async (tblname, pkname = "id") => {
|
|
398
|
+
const sql = `SELECT setval(pg_get_serial_sequence('"${getTenantSchema()}"."${sqlsanitize(tblname)}"', '${pkname}'), coalesce(max("${pkname}"),0) + 1, false) FROM "${getTenantSchema()}"."${sqlsanitize(tblname)}";`;
|
|
399
|
+
await getMyClient().query(sql);
|
|
400
|
+
};
|
|
401
|
+
/**
|
|
402
|
+
* Add unique constraint
|
|
403
|
+
* @param {string} table_name - table name
|
|
404
|
+
* @param {string[]} field_names - list of columns (members of constraint)
|
|
405
|
+
* @returns {Promise<void>} no result
|
|
406
|
+
*/
|
|
407
|
+
export const add_unique_constraint = async (table_name, field_names) => {
|
|
408
|
+
// TBD check that there are no problems with lenght of constraint name
|
|
409
|
+
const sql = `alter table "${getTenantSchema()}"."${sqlsanitize(table_name)}" add CONSTRAINT "${sqlsanitize(table_name)}_${field_names
|
|
410
|
+
.map((f) => sqlsanitize(f))
|
|
411
|
+
.join("_")}_unique" UNIQUE (${field_names
|
|
412
|
+
.map((f) => `"${sqlsanitize(f)}"`)
|
|
413
|
+
.join(",")});`;
|
|
414
|
+
sql_log(sql);
|
|
415
|
+
await getMyClient().query(sql);
|
|
416
|
+
};
|
|
417
|
+
/**
|
|
418
|
+
* Drop unique constraint
|
|
419
|
+
* @param {string} table_name - table name
|
|
420
|
+
* @param {string[]} field_names - list of columns (members of constraint)
|
|
421
|
+
* @returns {Promise<void>} no results
|
|
422
|
+
*/
|
|
423
|
+
export const drop_unique_constraint = async (table_name, field_names) => {
|
|
424
|
+
// TBD check that there are no problems with lenght of constraint name
|
|
425
|
+
const sql = `alter table "${getTenantSchema()}"."${sqlsanitize(table_name)}" drop CONSTRAINT IF EXISTS "${sqlsanitize(table_name)}_${field_names
|
|
426
|
+
.map((f) => sqlsanitize(f))
|
|
427
|
+
.join("_")}_unique";`;
|
|
428
|
+
sql_log(sql);
|
|
429
|
+
await getMyClient().query(sql);
|
|
430
|
+
};
|
|
431
|
+
/**
|
|
432
|
+
* Add index
|
|
433
|
+
* @param {string} table_name - table name
|
|
434
|
+
* @param {string} field_name - list of columns (members of constraint)
|
|
435
|
+
* @returns {Promise<void>} no result
|
|
436
|
+
*/
|
|
437
|
+
export const add_index = async (table_name, field_name) => {
|
|
438
|
+
// TBD check that there are no problems with lenght of constraint name
|
|
439
|
+
const sql = `create index "${sqlsanitize(table_name)}_${sqlsanitize(field_name)}_index" on "${getTenantSchema()}"."${sqlsanitize(table_name)}" ("${sqlsanitize(field_name)}");`;
|
|
440
|
+
sql_log(sql);
|
|
441
|
+
await getMyClient().query(sql);
|
|
442
|
+
};
|
|
443
|
+
/**
|
|
444
|
+
* Add Full-text search index
|
|
445
|
+
* @param {string} table_name - table name
|
|
446
|
+
* @param {string} field_name - list of columns (members of constraint)
|
|
447
|
+
* @returns {Promise<void>} no result
|
|
448
|
+
*/
|
|
449
|
+
export const add_fts_index = async (table_name, field_expression, language, disable_fts) => {
|
|
450
|
+
// TBD check that there are no problems with lenght of constraint name
|
|
451
|
+
//CREATE INDEX ON public.test_table USING gist
|
|
452
|
+
// ((name || (cars ->> 'values') || surname) gist_trgm_ops);
|
|
453
|
+
let sql;
|
|
454
|
+
if (disable_fts) {
|
|
455
|
+
await getMyClient().query("CREATE EXTENSION IF NOT EXISTS pg_trgm;");
|
|
456
|
+
sql = `create index "${sqlsanitize(table_name)}_fts_index" on "${getTenantSchema()}"."${sqlsanitize(table_name)}" USING gist ((${field_expression}) gist_trgm_ops);`;
|
|
457
|
+
}
|
|
458
|
+
else
|
|
459
|
+
sql = `create index "${sqlsanitize(table_name)}_fts_index" on "${getTenantSchema()}"."${sqlsanitize(table_name)}" USING GIN (to_tsvector('${language || "english"}', ${field_expression}));`;
|
|
460
|
+
sql_log(sql);
|
|
461
|
+
await getMyClient().query(sql);
|
|
462
|
+
};
|
|
463
|
+
export const drop_fts_index = async (table_name) => {
|
|
464
|
+
// TBD check that there are no problems with lenght of constraint name
|
|
465
|
+
const sql = `drop index "${getTenantSchema()}"."${sqlsanitize(table_name)}_fts_index";`;
|
|
466
|
+
sql_log(sql);
|
|
467
|
+
await getMyClient().query(sql);
|
|
468
|
+
};
|
|
469
|
+
/**
|
|
470
|
+
* Add index
|
|
471
|
+
* @param {string} table_name - table name
|
|
472
|
+
* @param {string} field_name - list of columns (members of constraint)
|
|
473
|
+
* @returns {Promise<void>} no result
|
|
474
|
+
*/
|
|
475
|
+
export const drop_index = async (table_name, field_name) => {
|
|
476
|
+
// TBD check that there are no problems with lenght of constraint name
|
|
477
|
+
const sql = `drop index "${getTenantSchema()}"."${sqlsanitize(table_name)}_${sqlsanitize(field_name)}_index";`;
|
|
478
|
+
sql_log(sql);
|
|
479
|
+
await getMyClient().query(sql);
|
|
480
|
+
};
|
|
481
|
+
/**
|
|
482
|
+
* Copy data from CSV to table?
|
|
483
|
+
* Only for PG
|
|
484
|
+
* @param {object} fileStream - file stream
|
|
485
|
+
* @param {string} tableName - table name
|
|
486
|
+
* @param {string[]} fieldNames - list of columns
|
|
487
|
+
* @param {object} client - db connection
|
|
488
|
+
* @returns {Promise<void>} no results
|
|
489
|
+
*/
|
|
490
|
+
export const copyFrom = async (fileStream, tableName, fieldNames, client) => {
|
|
491
|
+
const sql = `COPY "${getTenantSchema()}"."${sqlsanitize(tableName)}" (${fieldNames.map(quote).join(",")}) FROM STDIN CSV HEADER`;
|
|
492
|
+
sql_log(sql);
|
|
493
|
+
const stream = client.query(copyStreams.from(sql));
|
|
494
|
+
return await pipeline(fileStream, stream);
|
|
495
|
+
};
|
|
496
|
+
export const copyToJson = async (fileStream, tableName, client) => {
|
|
497
|
+
const sql = `COPY (SELECT (row_to_json("${sqlsanitize(tableName)}".*) || ',')
|
|
498
|
+
FROM "${getTenantSchema()}"."${sqlsanitize(tableName)}") TO STDOUT`;
|
|
499
|
+
sql_log(sql);
|
|
500
|
+
const stream = (client || getMyClient()).query(copyStreams.to(sql));
|
|
501
|
+
return await pipeline(stream, replace("\\\\", "\\"), fileStream);
|
|
502
|
+
};
|
|
503
|
+
export const slugify = (s) => s
|
|
504
|
+
.toLowerCase()
|
|
505
|
+
.replace(/\s+/g, "-")
|
|
506
|
+
.replace(/[^\w-]/g, "");
|
|
507
|
+
export const time = async () => {
|
|
508
|
+
const result = await getMyClient().query("select now()");
|
|
509
|
+
const row = result.rows[0];
|
|
510
|
+
return new Date(row.now);
|
|
511
|
+
};
|
|
512
|
+
/**
|
|
513
|
+
*
|
|
514
|
+
* @returns
|
|
515
|
+
*/
|
|
516
|
+
export const listTables = async () => {
|
|
517
|
+
const tq = await getMyClient().query(`SELECT table_name FROM information_schema.tables WHERE table_schema = '${getTenantSchema()}'`);
|
|
518
|
+
return tq.rows.map((row) => {
|
|
519
|
+
return { name: row.table_name };
|
|
520
|
+
});
|
|
521
|
+
};
|
|
522
|
+
/**
|
|
523
|
+
*
|
|
524
|
+
* @returns
|
|
525
|
+
*/
|
|
526
|
+
export const listUserDefinedTables = async () => {
|
|
527
|
+
const tq = await getMyClient().query(`SELECT table_name FROM information_schema.tables WHERE table_schema = '${getTenantSchema()}' AND table_name NOT LIKE '_sc_%'`);
|
|
528
|
+
return tq.rows.map((row) => {
|
|
529
|
+
return { name: row.table_name };
|
|
530
|
+
});
|
|
531
|
+
};
|
|
532
|
+
/**
|
|
533
|
+
*
|
|
534
|
+
* @returns
|
|
535
|
+
*/
|
|
536
|
+
export const listScTables = async () => {
|
|
537
|
+
const tq = await getMyClient().query(`SELECT table_name FROM information_schema.tables WHERE table_schema = '${getTenantSchema()}' AND table_name LIKE '_sc_%'`);
|
|
538
|
+
return tq.rows.map((row) => {
|
|
539
|
+
return { name: row.table_name };
|
|
540
|
+
});
|
|
541
|
+
};
|
|
542
|
+
export const setRequestUserContext = async (client, isLocal = false) => {
|
|
543
|
+
const reqCon = getRequestContext();
|
|
544
|
+
// No request context means an internal/background operation — leave GUC unset
|
|
545
|
+
// so COALESCE(…, 1) in sc_rls_elevated grants it admin-level access intentionally.
|
|
546
|
+
if (!reqCon)
|
|
547
|
+
return;
|
|
548
|
+
const user = reqCon?.req?.user;
|
|
549
|
+
await client.query(`SELECT set_config('app.current_user_id', $1, ${isLocal}), ` +
|
|
550
|
+
`set_config('app.current_user_role', $2, ${isLocal})`, user?.id
|
|
551
|
+
? [String(user.id), String(user.role_id ?? 100)]
|
|
552
|
+
: ["0", "100"]);
|
|
553
|
+
};
|
|
554
|
+
/* rules of using this:
|
|
555
|
+
|
|
556
|
+
- no try catch inside unless you rethrow: wouldnt roll back
|
|
557
|
+
- no state.refresh_*() inside: other works wouldnt see updates as they are in transactioon
|
|
558
|
+
- you can use state.refresh_*(true) for update on own worker only
|
|
559
|
+
|
|
560
|
+
*/
|
|
561
|
+
export const withTransaction = async (f, onError) => {
|
|
562
|
+
const client = await getClient();
|
|
563
|
+
const reqCon = getRequestContext();
|
|
564
|
+
if (reqCon) {
|
|
565
|
+
reqCon.client = client;
|
|
566
|
+
reqCon.inTransaction = true;
|
|
567
|
+
}
|
|
568
|
+
sql_log("BEGIN;");
|
|
569
|
+
await client.query("BEGIN;");
|
|
570
|
+
let aborted = false;
|
|
571
|
+
const rollback = async () => {
|
|
572
|
+
aborted = true;
|
|
573
|
+
sql_log("ROLLBACK;");
|
|
574
|
+
await client.query("ROLLBACK;");
|
|
575
|
+
};
|
|
576
|
+
try {
|
|
577
|
+
await setRequestUserContext(client, true);
|
|
578
|
+
const result = await f(rollback);
|
|
579
|
+
if (!aborted) {
|
|
580
|
+
sql_log("COMMIT;");
|
|
581
|
+
await client.query("COMMIT;");
|
|
582
|
+
}
|
|
583
|
+
return result;
|
|
584
|
+
}
|
|
585
|
+
catch (error) {
|
|
586
|
+
if (!aborted) {
|
|
587
|
+
sql_log("ROLLBACK;");
|
|
588
|
+
await client.query("ROLLBACK;");
|
|
589
|
+
}
|
|
590
|
+
if (onError)
|
|
591
|
+
return onError(error);
|
|
592
|
+
else
|
|
593
|
+
throw error;
|
|
594
|
+
}
|
|
595
|
+
finally {
|
|
596
|
+
if (reqCon) {
|
|
597
|
+
reqCon.client = null;
|
|
598
|
+
reqCon.inTransaction = false;
|
|
599
|
+
}
|
|
600
|
+
client.release();
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
export const commitAndBeginNewTransaction = async () => {
|
|
604
|
+
const client = await getMyClient();
|
|
605
|
+
sql_log("COMMIT;");
|
|
606
|
+
await client.query("COMMIT;");
|
|
607
|
+
sql_log("BEGIN;");
|
|
608
|
+
await client.query("BEGIN;");
|
|
609
|
+
};
|
|
610
|
+
export const tryCatchInTransaction = async (f, onError) => {
|
|
611
|
+
const rndid = Math.floor(Math.random() * 16777215).toString(16);
|
|
612
|
+
const reqCon = getRequestContext();
|
|
613
|
+
if (reqCon?.inTransaction)
|
|
614
|
+
await query(`SAVEPOINT sp${rndid}`);
|
|
615
|
+
try {
|
|
616
|
+
return await f();
|
|
617
|
+
}
|
|
618
|
+
catch (error) {
|
|
619
|
+
if (reqCon?.inTransaction)
|
|
620
|
+
await query(`ROLLBACK TO SAVEPOINT sp${rndid}`);
|
|
621
|
+
if (onError)
|
|
622
|
+
return await onError(error);
|
|
623
|
+
}
|
|
624
|
+
finally {
|
|
625
|
+
if (reqCon?.inTransaction)
|
|
626
|
+
await query(`RELEASE SAVEPOINT sp${rndid}`);
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
/**
|
|
630
|
+
* Should be used for code that is sometimes called from within a withTransaction block
|
|
631
|
+
* and sometimes not.
|
|
632
|
+
* @param {Function} f logic to execute
|
|
633
|
+
* @param {Function} onError error handler
|
|
634
|
+
* @returns
|
|
635
|
+
*/
|
|
636
|
+
export const openOrUseTransaction = async (f, onError) => {
|
|
637
|
+
const reqCon = getRequestContext();
|
|
638
|
+
if (reqCon?.inTransaction)
|
|
639
|
+
return await f();
|
|
640
|
+
else
|
|
641
|
+
return await withTransaction(f, onError);
|
|
642
|
+
};
|
|
643
|
+
/**
|
|
644
|
+
* Wait some time until current transaction COMMITs,
|
|
645
|
+
* then open another transaction.
|
|
646
|
+
* @param {Function} f logic to execute
|
|
647
|
+
* @param {Function} onError error handler
|
|
648
|
+
* @returns
|
|
649
|
+
*/
|
|
650
|
+
export const whenTransactionisFree = (f, onError) => {
|
|
651
|
+
return new Promise((resolve, reject) => {
|
|
652
|
+
// wait until transaction is free
|
|
653
|
+
let counter = 0;
|
|
654
|
+
const interval = setInterval(async () => {
|
|
655
|
+
const reqCon = getRequestContext();
|
|
656
|
+
if (!reqCon?.inTransaction) {
|
|
657
|
+
clearInterval(interval);
|
|
658
|
+
try {
|
|
659
|
+
resolve(await withTransaction(f, onError));
|
|
660
|
+
}
|
|
661
|
+
catch (e) {
|
|
662
|
+
reject(e);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
if (++counter > 100) {
|
|
666
|
+
clearInterval(interval);
|
|
667
|
+
reject(new Error("Timeout waiting for transaction to be free"));
|
|
668
|
+
}
|
|
669
|
+
}, 200);
|
|
670
|
+
});
|
|
671
|
+
};
|
|
672
|
+
export const query = (text, params) => {
|
|
673
|
+
sql_log(text, params);
|
|
674
|
+
return getMyClient().query(text, params);
|
|
675
|
+
};
|
|
676
|
+
export { mkWhere };
|
|
677
|
+
export const driverName = "postgres";
|
|
678
|
+
export const sql_backend_display_name = "PostgreSQL";
|
|
679
|
+
export const array_agg_sql_fn = "array_agg";
|
|
680
|
+
export const serial_pk_sql_type = "serial";
|
|
681
|
+
export const json_sql_type = "jsonb";
|
|
682
|
+
export const indexable_text_sql_type = "text";
|
|
683
|
+
export const timestamp_sql_type = "timestamptz";
|
|
684
|
+
export const millis_timestamp_sql_type = "timestamp";
|
|
685
|
+
export const supports_search_path = true;
|
|
686
|
+
// Backend capability flags (see DbExportsType in db-common/types)
|
|
687
|
+
export const supports_multiple_schemas = true;
|
|
688
|
+
export const pools_connections = true;
|
|
689
|
+
export const supports_for_update = true;
|
|
690
|
+
export const supports_row_level_security = true;
|
|
691
|
+
export const supports_alter_table = true;
|
|
692
|
+
export const supports_non_integer_pk = true;
|
|
693
|
+
export const json_read_returns_string = false;
|
|
694
|
+
export const json_write_needs_stringify = true;
|
|
695
|
+
export const stores_dates_as_text = false;
|
|
696
|
+
export const supports_large_bind_lists = true;
|
|
697
|
+
export const supports_database_views = true;
|
|
698
|
+
export const supports_table_discovery = true;
|
|
699
|
+
export const supports_session_pruning = true;
|
|
700
|
+
export const supports_agg_order_by = true;
|
|
701
|
+
export const coerce_numeric_aggregates_to_string = false;
|
|
702
|
+
export const uses_positional_placeholders = false;
|
|
703
|
+
export const coerce_read_dates = false;
|
|
704
|
+
export const epochToTimestampSql = (param) => `to_timestamp(${param})`;
|
|
705
|
+
export const truncateMillisSql = (expr) => `date_trunc('milliseconds', ${expr})`;
|
|
706
|
+
export const castExprToTextSql = (expr) => `${expr}::text`;
|
|
707
|
+
export const castBindParamSql = (param, sqlType) => `${param}::${sqlType}`;
|
|
708
|
+
export const timeDiffWithinSql = (startExpr, endExpr, seconds) => `${endExpr} - ${startExpr} <= INTERVAL '${seconds} seconds'`;
|
|
709
|
+
export const isDistinctFromSql = (a, b) => `${a} IS DISTINCT FROM ${b}`;
|
|
710
|
+
export const wrapDeleteSubselect = (subquery) => subquery;
|
|
711
|
+
export const text_requires_length_for_index = false;
|
|
712
|
+
export const setColumnNullabilitySql = (qTable, qCol, _sqlType, notNull) => `alter table ${qTable} alter column ${qCol} ${notNull ? "set" : "drop"} not null;`;
|
|
713
|
+
export const alterColumnTypeSql = (qTable, qCol, newType, using, def) => `alter table ${qTable} alter column ${qCol} TYPE ${newType} ${using} ${def};`;
|
|
714
|
+
export const changePkColumnTypeStatements = (qTable, qCol, newType, def, _wasAlreadyPk) => [
|
|
715
|
+
`ALTER TABLE ${qTable} drop column ${qCol};`,
|
|
716
|
+
`ALTER TABLE ${qTable} add column ${qCol} ${newType} primary key ${def};`,
|
|
717
|
+
];
|
|
718
|
+
// foreign-key DDL (see DbExportsType in db-common/types)
|
|
719
|
+
export const inline_fk_in_column_type = true;
|
|
720
|
+
export const fk_must_be_dropped_before_column = false;
|
|
721
|
+
export const fk_deferrable_clause = " DEFERRABLE";
|
|
722
|
+
export const dropForeignKeyIfExists = async (qTable, conName) => {
|
|
723
|
+
await query(`ALTER TABLE ${qTable} drop constraint if exists "${conName}"`);
|
|
724
|
+
};
|
|
725
|
+
export const replaceForeignKey = async (params) => {
|
|
726
|
+
const { qTable, oldConName, newConName, colName, qRefTable, refCol, onDelete } = params;
|
|
727
|
+
await query(`ALTER TABLE ${qTable} drop constraint "${oldConName}", add constraint "${newConName}" foreign key ("${colName}") references ${qRefTable}("${refCol}")${onDelete}${fk_deferrable_clause}`);
|
|
728
|
+
};
|
|
729
|
+
export const addColumnWithDefault = async (params) => {
|
|
730
|
+
const { qTable, colName, sqlType, bareType, required, defaultValue } = params;
|
|
731
|
+
const cn = sqlsanitize(colName);
|
|
732
|
+
await query(`DROP FUNCTION IF EXISTS add_field_${cn};
|
|
733
|
+
CREATE FUNCTION add_field_${cn}(thedef ${bareType}) RETURNS void AS $$
|
|
734
|
+
BEGIN
|
|
735
|
+
EXECUTE format('alter table ${qTable} add column "${cn}" ${sqlType} ${required ? "not null" : ""} default %L', thedef);
|
|
736
|
+
END;
|
|
737
|
+
$$ LANGUAGE plpgsql;`);
|
|
738
|
+
await query(`SELECT add_field_${cn}($1)`, [defaultValue]);
|
|
739
|
+
};
|
|
740
|
+
export const isDuplicateForeignKeyError = (_e) => false;
|
|
741
|
+
// misc dialect-specific SQL / behaviour (see DbExportsType)
|
|
742
|
+
export const jsonMergeExpr = (colExpr, param) => `coalesce(${colExpr}, '{}'::jsonb) || ${param}::jsonb`;
|
|
743
|
+
export const supports_array_param_in = true;
|
|
744
|
+
export const deleteSessionsForUser = async (userId, tenant) => {
|
|
745
|
+
await query(`delete from _sc_session
|
|
746
|
+
where sess->'passport'->'user'->>'id' = $1
|
|
747
|
+
and sess->'passport'->'user'->>'tenant' = $2`, [userId, tenant]);
|
|
748
|
+
};
|
|
749
|
+
export const deleteSessionsForTenant = async (tenant) => {
|
|
750
|
+
await query(`delete from _sc_session
|
|
751
|
+
where sess->'passport'->'user'->>'tenant' = $1`, [tenant]);
|
|
752
|
+
};
|
|
753
|
+
export const dropCheckConstraintIfExists = async (qTable, conName) => {
|
|
754
|
+
await query(`alter table ${qTable} drop constraint IF EXISTS "${conName}";`);
|
|
755
|
+
};
|
|
756
|
+
export const migration_sql_dialect = "sql_pg";
|
|
757
|
+
export const migration_translates_from_pg = false;
|
|
758
|
+
export const discovery_keys_uppercase = false;
|
|
759
|
+
export const discovery_columns_order_by = "";
|
|
760
|
+
export const discovery_pk_sql = `SELECT c.column_name, c.column_default
|
|
761
|
+
FROM information_schema.table_constraints tc
|
|
762
|
+
JOIN information_schema.constraint_column_usage AS ccu USING (constraint_schema, constraint_name)
|
|
763
|
+
JOIN information_schema.columns AS c ON c.table_schema = tc.constraint_schema
|
|
764
|
+
AND tc.table_name = c.table_name AND ccu.column_name = c.column_name
|
|
765
|
+
WHERE constraint_type = 'PRIMARY KEY' and tc.table_schema=$1 and tc.table_name = $2;`;
|
|
766
|
+
export const discovery_fk_sql = `SELECT
|
|
767
|
+
tc.table_schema,
|
|
768
|
+
tc.constraint_name,
|
|
769
|
+
tc.table_name,
|
|
770
|
+
kcu.column_name,
|
|
771
|
+
ccu.table_schema AS foreign_table_schema,
|
|
772
|
+
ccu.table_name AS foreign_table_name,
|
|
773
|
+
ccu.column_name AS foreign_column_name
|
|
774
|
+
FROM
|
|
775
|
+
information_schema.table_constraints AS tc
|
|
776
|
+
JOIN information_schema.key_column_usage AS kcu
|
|
777
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
778
|
+
AND tc.table_schema = kcu.table_schema
|
|
779
|
+
JOIN information_schema.constraint_column_usage AS ccu
|
|
780
|
+
ON ccu.constraint_name = tc.constraint_name
|
|
781
|
+
AND ccu.table_schema = tc.table_schema
|
|
782
|
+
WHERE tc.constraint_type = 'FOREIGN KEY' and tc.table_schema=$1 AND tc.table_name=$2;`;
|
|
783
|
+
export const dropPrimaryKey = async (schemaPrefix, tableName, tenantSchema) => {
|
|
784
|
+
const { rows } = await query(`select constraint_name
|
|
785
|
+
from information_schema.table_constraints
|
|
786
|
+
where table_schema = '${tenantSchema || "public"}'
|
|
787
|
+
and table_name = '${tableName}'
|
|
788
|
+
and constraint_type = 'PRIMARY KEY';`);
|
|
789
|
+
const cname = rows[0]?.constraint_name;
|
|
790
|
+
await query(`alter table ${schemaPrefix}"${tableName}" drop constraint "${cname}"`);
|
|
791
|
+
};
|
|
792
|
+
// Defer FK checks for the remainder of the current transaction.
|
|
793
|
+
export const deferForeignKeys = async (client) => {
|
|
794
|
+
await client.query("SET CONSTRAINTS ALL DEFERRED");
|
|
795
|
+
};
|
|
796
|
+
// Pull the offending field name out of a unique-violation error message.
|
|
797
|
+
// e.g. `duplicate key value violates unique constraint "books_author_unique"`
|
|
798
|
+
export const parseUniqueConstraintError = (msg, _tableName) => {
|
|
799
|
+
const m = msg.match(/duplicate key value violates unique constraint "(.*?)_(.*?)_unique"/);
|
|
800
|
+
return m ? m[2] : "";
|
|
801
|
+
};
|
|
802
|
+
// Canonical key for a multi-field unique constraint, to compare against the
|
|
803
|
+
// field name parsed above (postgres names them `<field1>_<field2>_...`).
|
|
804
|
+
export const uniqueConstraintFieldsKey = (fields, _tableName) => fields.join("_");
|
|
805
|
+
/**
|
|
806
|
+
* Initializes internals of the the postgres module.
|
|
807
|
+
* It must be called after importing the module.
|
|
808
|
+
* @param getConnectObjectPara function returning the connection object
|
|
809
|
+
*/
|
|
810
|
+
export const init = (getConnectObjectPara) => {
|
|
811
|
+
if (!pool) {
|
|
812
|
+
getConnectObject = getConnectObjectPara;
|
|
813
|
+
const connectObj = getConnectObject();
|
|
814
|
+
if (connectObj) {
|
|
815
|
+
pool = new Pool(connectObj);
|
|
816
|
+
getTenantSchema = tenantsModule(connectObj).getTenantSchema;
|
|
817
|
+
getRequestContext = tenantsModule(connectObj).getRequestContext;
|
|
818
|
+
}
|
|
819
|
+
else {
|
|
820
|
+
throw new Error("Unable to retrieve a database connection object.");
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
//# sourceMappingURL=postgres.js.map
|