@saltcorn/postgres 1.6.0 → 1.7.0-alpha.0
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 +286 -0
- package/dist/postgres.d.ts.map +1 -0
- package/dist/postgres.js +702 -0
- package/dist/postgres.js.map +1 -0
- package/package.json +29 -5
- package/index.js +0 -14
- package/postgres.js +0 -786
package/postgres.js
DELETED
|
@@ -1,786 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PostgreSQL data access layer
|
|
3
|
-
* @category postgres
|
|
4
|
-
* @module postgres
|
|
5
|
-
*/
|
|
6
|
-
// TODO move postgresql specific to this module
|
|
7
|
-
const { Pool } = require("pg");
|
|
8
|
-
const copyStreams = require("pg-copy-streams");
|
|
9
|
-
const { promisify } = require("util");
|
|
10
|
-
const { pipeline } = require("stream/promises");
|
|
11
|
-
const { Transform } = require("stream");
|
|
12
|
-
const replace = require("replacestream");
|
|
13
|
-
const {
|
|
14
|
-
sqlsanitize,
|
|
15
|
-
mkWhere,
|
|
16
|
-
mkSelectOptions,
|
|
17
|
-
} = require("@saltcorn/db-common/internal");
|
|
18
|
-
const PlainDate = require("@saltcorn/plain-date");
|
|
19
|
-
|
|
20
|
-
var types = require("pg").types;
|
|
21
|
-
types.setTypeParser(types.builtins.DATE, (d) =>
|
|
22
|
-
d === null ? null : new PlainDate(d)
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
let getTenantSchema;
|
|
26
|
-
let getRequestContext;
|
|
27
|
-
let getConnectObject = null;
|
|
28
|
-
let pool = null;
|
|
29
|
-
|
|
30
|
-
let log_sql_enabled = false;
|
|
31
|
-
|
|
32
|
-
const quote = (s) => `"${s}"`;
|
|
33
|
-
|
|
34
|
-
const ppPK = (pk) => (pk ? quote(pk) : "id");
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Control Logging sql statements to console
|
|
38
|
-
* @param {boolean} [val = true] - if true then log sql statements to console
|
|
39
|
-
*/
|
|
40
|
-
function set_sql_logging(val = true) {
|
|
41
|
-
log_sql_enabled = val;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Get sql logging state
|
|
46
|
-
* @returns {boolean} if true then sql logging eabled
|
|
47
|
-
*/
|
|
48
|
-
function get_sql_logging() {
|
|
49
|
-
return log_sql_enabled;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Log SQL statement to console
|
|
54
|
-
* @param {string} sql - SQL statement
|
|
55
|
-
* @param {object} [vs] - any additional parameter
|
|
56
|
-
*/
|
|
57
|
-
function sql_log(sql, vs) {
|
|
58
|
-
if (log_sql_enabled)
|
|
59
|
-
if (typeof vs === "undefined") console.log(sql);
|
|
60
|
-
else console.log(sql, vs);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Close database connection
|
|
65
|
-
* @returns {Promise<void>}
|
|
66
|
-
*/
|
|
67
|
-
const close = async () => {
|
|
68
|
-
if (pool) await pool.end();
|
|
69
|
-
pool = null;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Change connection (close connection and open new connection from connObj)
|
|
74
|
-
* @param {object} [connObj = {}] - connection object
|
|
75
|
-
* @returns {Promise<void>}
|
|
76
|
-
*/
|
|
77
|
-
const changeConnection = async (connObj = Object.create(null)) => {
|
|
78
|
-
await close();
|
|
79
|
-
pool = new Pool(getConnectObject(connObj));
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const begin = async () => {
|
|
83
|
-
//client = await getClient();
|
|
84
|
-
await query("BEGIN");
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const commit = async () => {
|
|
88
|
-
await query("COMMIT");
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const rollback = async () => {
|
|
92
|
-
await query("ROLLBACK");
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const getMyClient = (selopts) => {
|
|
96
|
-
return selopts?.client || getRequestContext()?.client || pool;
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Execute Select statement
|
|
101
|
-
* @param {string} tbl - table name
|
|
102
|
-
* @param {object} whereObj - where object
|
|
103
|
-
* @param {object} [selectopts = {}] - select options
|
|
104
|
-
* @returns {Promise<*>} return rows
|
|
105
|
-
*/
|
|
106
|
-
const select = async (tbl, whereObj, selectopts = Object.create(null)) => {
|
|
107
|
-
const { where, values } = mkWhere(whereObj);
|
|
108
|
-
const schema = selectopts.schema || getTenantSchema();
|
|
109
|
-
let sql;
|
|
110
|
-
if (selectopts.tree_field && !whereObj[selectopts.tree_field])
|
|
111
|
-
sql = `WITH RECURSIVE _tree AS (
|
|
112
|
-
SELECT ${
|
|
113
|
-
selectopts.fields ? selectopts.fields.join(", ") : `*`
|
|
114
|
-
}, 0 as _level
|
|
115
|
-
${selectopts.orderBy ? `, ARRAY[row_number() over (ORDER BY "${sqlsanitize(selectopts.orderBy)}"${selectopts.orderDesc ? " DESC" : ""})] as _sort_path` : ""}
|
|
116
|
-
FROM "${schema}"."${sqlsanitize(tbl)}"
|
|
117
|
-
WHERE "${selectopts.tree_field}" IS NULL ${where ? `AND ${where.replace("where ", "")}` : ""}
|
|
118
|
-
|
|
119
|
-
UNION ALL
|
|
120
|
-
|
|
121
|
-
SELECT ${
|
|
122
|
-
selectopts.fields
|
|
123
|
-
? selectopts.fields.map((f) => `p."${f}"`).join(", ")
|
|
124
|
-
: `p.*`
|
|
125
|
-
}, pt._level+1
|
|
126
|
-
${selectopts.orderBy ? `, pt._sort_path || row_number() OVER (PARTITION BY p."${selectopts.tree_field}" ORDER BY p."${selectopts.orderBy}"${selectopts.orderDesc ? " DESC" : ""})` : ""}
|
|
127
|
-
FROM "${schema}"."${sqlsanitize(tbl)}" p
|
|
128
|
-
JOIN _tree pt ON p."${selectopts.tree_field}" = pt."${selectopts.pk_name || "id"}"
|
|
129
|
-
)
|
|
130
|
-
SELECT ${
|
|
131
|
-
selectopts.fields ? selectopts.fields.join(", ") : `*`
|
|
132
|
-
}, _level FROM _tree ${where} ${mkSelectOptions(
|
|
133
|
-
selectopts.orderBy
|
|
134
|
-
? { ...selectopts, orderBy: "_sort_path", orderDesc: false }
|
|
135
|
-
: selectopts,
|
|
136
|
-
values,
|
|
137
|
-
false
|
|
138
|
-
)}`;
|
|
139
|
-
else
|
|
140
|
-
sql = `SELECT ${
|
|
141
|
-
selectopts.fields ? selectopts.fields.join(", ") : `*`
|
|
142
|
-
} FROM "${schema}"."${sqlsanitize(tbl)}" ${where} ${mkSelectOptions(
|
|
143
|
-
selectopts,
|
|
144
|
-
values,
|
|
145
|
-
false
|
|
146
|
-
)}`;
|
|
147
|
-
sql_log(sql, values);
|
|
148
|
-
const tq = await getMyClient(selectopts).query(sql, values);
|
|
149
|
-
|
|
150
|
-
return tq.rows;
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Reset DB Schema using drop schema and recreate it
|
|
155
|
-
* Atterntion! You will lost data after call this function!
|
|
156
|
-
* @param {string} schema - db schema name
|
|
157
|
-
* @returns {Promise<void>} no result
|
|
158
|
-
*/
|
|
159
|
-
const drop_reset_schema = async (schema) => {
|
|
160
|
-
const sql = `DROP SCHEMA IF EXISTS "${schema}" CASCADE;
|
|
161
|
-
CREATE SCHEMA "${schema}";
|
|
162
|
-
GRANT ALL ON SCHEMA "${schema}" TO postgres;
|
|
163
|
-
GRANT ALL ON SCHEMA "${schema}" TO "public" ;
|
|
164
|
-
COMMENT ON SCHEMA "${schema}" IS 'standard public schema';`;
|
|
165
|
-
sql_log(sql);
|
|
166
|
-
|
|
167
|
-
await getMyClient().query(sql);
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Get count of rows in table
|
|
172
|
-
* @param {string} - tbl - table name
|
|
173
|
-
* @param {object} - whereObj - where object
|
|
174
|
-
* @returns {Promise<number>} count of tables
|
|
175
|
-
*/
|
|
176
|
-
const count = async (tbl, whereObj, opts) => {
|
|
177
|
-
const { where, values } = mkWhere(whereObj);
|
|
178
|
-
if (!where) {
|
|
179
|
-
try {
|
|
180
|
-
// fast count for large table but may be stale
|
|
181
|
-
// https://stackoverflow.com/questions/7943233/fast-way-to-discover-the-row-count-of-a-table-in-postgresql
|
|
182
|
-
//https://www.citusdata.com/blog/2016/10/12/count-performance/
|
|
183
|
-
const sql = `SELECT (CASE WHEN c.reltuples < 0 THEN NULL
|
|
184
|
-
WHEN c.relpages = 0 THEN float8 '0' -- empty table
|
|
185
|
-
ELSE c.reltuples / c.relpages END
|
|
186
|
-
* (pg_catalog.pg_relation_size(c.oid)
|
|
187
|
-
/ pg_catalog.current_setting('block_size')::int)
|
|
188
|
-
)::bigint
|
|
189
|
-
FROM pg_catalog.pg_class c
|
|
190
|
-
WHERE c.oid = '"${opts?.schema || getTenantSchema()}"."${sqlsanitize(tbl)}"'::regclass`;
|
|
191
|
-
sql_log(sql);
|
|
192
|
-
const tq = await getMyClient(opts).query(sql, []);
|
|
193
|
-
const n = +tq.rows[0].int8;
|
|
194
|
-
if (n && n > 10000) return n;
|
|
195
|
-
} catch {
|
|
196
|
-
//skip fast estimate
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const core_sql = `FROM "${opts?.schema || getTenantSchema()}"."${sqlsanitize(
|
|
201
|
-
tbl
|
|
202
|
-
)}" ${where}`;
|
|
203
|
-
//https://pganalyze.com/blog/5mins-postgres-limited-count
|
|
204
|
-
const sql = opts?.limit
|
|
205
|
-
? `SELECT count(*) AS count FROM (
|
|
206
|
-
SELECT 1 ${core_sql} limit ${+opts?.limit}) limited_count`
|
|
207
|
-
: `SELECT COUNT(*) ${core_sql}`;
|
|
208
|
-
sql_log(sql, values);
|
|
209
|
-
const tq = await getMyClient(opts).query(sql, values);
|
|
210
|
-
|
|
211
|
-
return parseInt(tq.rows[0].count);
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Get version of PostgreSQL
|
|
216
|
-
* @param {boolean} short - if true return short version info else full version info
|
|
217
|
-
* @returns {Promise<string>} returns version
|
|
218
|
-
*/
|
|
219
|
-
const getVersion = async (short) => {
|
|
220
|
-
const sql = `SELECT version();`;
|
|
221
|
-
sql_log(sql);
|
|
222
|
-
const tq = await getMyClient().query(sql);
|
|
223
|
-
const v = tq.rows[0].version;
|
|
224
|
-
if (short) {
|
|
225
|
-
const ws = v.split(" ");
|
|
226
|
-
return ws[1];
|
|
227
|
-
}
|
|
228
|
-
return v;
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Delete rows in table
|
|
233
|
-
* @param {string} tbl - table name
|
|
234
|
-
* @param {object} whereObj - where object
|
|
235
|
-
* @param {object} [opts = {}]
|
|
236
|
-
* @returns {Promise<object[]>} result of delete execution
|
|
237
|
-
*/
|
|
238
|
-
const deleteWhere = async (tbl, whereObj, opts = Object.create(null)) => {
|
|
239
|
-
const { where, values } = mkWhere(whereObj);
|
|
240
|
-
const sql = `delete FROM "${opts.schema || getTenantSchema()}"."${sqlsanitize(
|
|
241
|
-
tbl
|
|
242
|
-
)}" ${where}`;
|
|
243
|
-
sql_log(sql, values);
|
|
244
|
-
|
|
245
|
-
const tq = await getMyClient(opts).query(sql, values);
|
|
246
|
-
|
|
247
|
-
return tq.rows;
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
const truncate = async (tbl) => {
|
|
251
|
-
const sql = `truncate "${getTenantSchema()}"."${sqlsanitize(tbl)}"`;
|
|
252
|
-
sql_log(sql, []);
|
|
253
|
-
|
|
254
|
-
const tq = await getMyClient().query(sql, []);
|
|
255
|
-
|
|
256
|
-
return tq.rows;
|
|
257
|
-
};
|
|
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
|
-
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(
|
|
277
|
-
`coalesce((select max(_version) from "${schema}"."${sqlsanitize(
|
|
278
|
-
tbl
|
|
279
|
-
)}" where "${v.pk_name || "id"}"=$${valList.length}), 0)+1`
|
|
280
|
-
);
|
|
281
|
-
} else {
|
|
282
|
-
valList.push(v);
|
|
283
|
-
valPosList.push(`$${valList.length}`);
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
const sql =
|
|
287
|
-
valPosList.length > 0
|
|
288
|
-
? `insert into "${schema}"."${sqlsanitize(
|
|
289
|
-
tbl
|
|
290
|
-
)}"(${fnameList}) values(${valPosList.join()}) ${conflict}returning ${
|
|
291
|
-
opts.noid ? "*" : ppPK(opts.pk_name)
|
|
292
|
-
}`
|
|
293
|
-
: `insert into "${schema}"."${sqlsanitize(
|
|
294
|
-
tbl
|
|
295
|
-
)}" DEFAULT VALUES returning ${opts.noid ? "*" : ppPK(opts.pk_name)}`;
|
|
296
|
-
sql_log(sql, valList);
|
|
297
|
-
const { rows } = await getMyClient(opts).query(sql, valList);
|
|
298
|
-
if (opts.noid) return;
|
|
299
|
-
else if (conflict && rows.length === 0) return;
|
|
300
|
-
else return rows[0][opts.pk_name || "id"];
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Update table records
|
|
305
|
-
* @param {string} tbl - table name
|
|
306
|
-
* @param {object} obj - columns names and data
|
|
307
|
-
* @param {number|undefined} id - id of record (primary key column value)
|
|
308
|
-
* @param {object} [opts = {}] - columns attributes
|
|
309
|
-
* @returns {Promise<void>} no result
|
|
310
|
-
*/
|
|
311
|
-
const update = async (tbl, obj, id, opts = Object.create(null)) => {
|
|
312
|
-
const kvs = Object.entries(obj);
|
|
313
|
-
if (kvs.length === 0) return;
|
|
314
|
-
const assigns = kvs
|
|
315
|
-
.map(([k, v], ix) => `"${sqlsanitize(k)}"=$${ix + 1}`)
|
|
316
|
-
.join();
|
|
317
|
-
let valList = kvs.map(([k, v]) => v);
|
|
318
|
-
// TBD check that is correct - because in insert function opts.noid ? "*" : opts.pk_name || "id"
|
|
319
|
-
//valList.push(id === "undefined"? obj[opts.pk_name]: id);
|
|
320
|
-
let whereS;
|
|
321
|
-
if (id && typeof id == "object") {
|
|
322
|
-
let n = kvs.length + 1;
|
|
323
|
-
const whereStrs = [];
|
|
324
|
-
Object.keys(id).forEach((k) => {
|
|
325
|
-
valList.push(id[k]);
|
|
326
|
-
whereStrs.push(`"${k}"=$${n}`);
|
|
327
|
-
n += 1;
|
|
328
|
-
});
|
|
329
|
-
whereS = whereStrs.join(" and ");
|
|
330
|
-
} else {
|
|
331
|
-
valList.push(id === "undefined" ? obj[opts.pk_name || "id"] : id);
|
|
332
|
-
whereS = `${ppPK(opts.pk_name)}=$${kvs.length + 1}`;
|
|
333
|
-
}
|
|
334
|
-
const q = `update "${opts.schema || getTenantSchema()}"."${sqlsanitize(
|
|
335
|
-
tbl
|
|
336
|
-
)}" set ${assigns} where ${whereS}`;
|
|
337
|
-
sql_log(q, valList);
|
|
338
|
-
await getMyClient(opts).query(q, valList);
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Update table records
|
|
343
|
-
* @param {string} tbl - table name
|
|
344
|
-
* @param {object} obj - columns names and data
|
|
345
|
-
* @param {object} whereObj - where object
|
|
346
|
-
* @param {object} opts - can contain a db client for transactions
|
|
347
|
-
* @returns {Promise<void>} no result
|
|
348
|
-
*/
|
|
349
|
-
const updateWhere = async (tbl, obj, whereObj, opts = Object.create(null)) => {
|
|
350
|
-
const kvs = Object.entries(obj);
|
|
351
|
-
if (kvs.length === 0) return;
|
|
352
|
-
const { where, values } = mkWhere(whereObj, false, kvs.length);
|
|
353
|
-
const assigns = kvs
|
|
354
|
-
.map(([k, v], ix) => `"${sqlsanitize(k)}"=$${ix + 1}`)
|
|
355
|
-
.join();
|
|
356
|
-
let valList = [...kvs.map(([k, v]) => v), ...values];
|
|
357
|
-
|
|
358
|
-
const q = `update "${getTenantSchema()}"."${sqlsanitize(
|
|
359
|
-
tbl
|
|
360
|
-
)}" set ${assigns} ${where}`;
|
|
361
|
-
sql_log(q, valList);
|
|
362
|
-
await getMyClient().query(q, valList);
|
|
363
|
-
};
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Select one record
|
|
367
|
-
* @param {srting} tbl - table name
|
|
368
|
-
* @param {object} where - where object
|
|
369
|
-
* @param {object} [selectopts = {}] - select options
|
|
370
|
-
* @returns {Promise<object>} return first record from sql result
|
|
371
|
-
* @throws {Error}
|
|
372
|
-
*/
|
|
373
|
-
const selectOne = async (tbl, where, selectopts = Object.create(null)) => {
|
|
374
|
-
const rows = await select(tbl, where, { ...selectopts, limit: 1 });
|
|
375
|
-
if (rows.length === 0) {
|
|
376
|
-
const w = mkWhere(where);
|
|
377
|
-
throw new Error(`no ${tbl} ${w.where} are ${w.values}`);
|
|
378
|
-
} else return rows[0];
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Select one record or null if no records
|
|
383
|
-
* @param {string} tbl - table name
|
|
384
|
-
* @param {object} where - where object
|
|
385
|
-
* @param {object} [selectopts = {}] - select options
|
|
386
|
-
* @returns {Promise<null|object>} - null if no record or first record data
|
|
387
|
-
*/
|
|
388
|
-
const selectMaybeOne = async (tbl, where, selectopts = Object.create(null)) => {
|
|
389
|
-
const rows = await select(tbl, where, { ...selectopts, limit: 1 });
|
|
390
|
-
if (rows.length === 0) return null;
|
|
391
|
-
else return rows[0];
|
|
392
|
-
};
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Open db connection
|
|
396
|
-
* Only for PG.
|
|
397
|
-
* @returns {Promise<*>} db connection object
|
|
398
|
-
*/
|
|
399
|
-
// TBD Currently this function supported only for PG
|
|
400
|
-
const getClient = async () => await pool.connect();
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Reset sequence
|
|
404
|
-
* Only for PG
|
|
405
|
-
* @param {string} tblname - table name
|
|
406
|
-
* @returns {Promise<void>} no result
|
|
407
|
-
*/
|
|
408
|
-
const reset_sequence = async (tblname, pkname = "id") => {
|
|
409
|
-
const sql = `SELECT setval(pg_get_serial_sequence('"${getTenantSchema()}"."${sqlsanitize(
|
|
410
|
-
tblname
|
|
411
|
-
)}"', '${pkname}'), coalesce(max("${pkname}"),0) + 1, false) FROM "${getTenantSchema()}"."${sqlsanitize(
|
|
412
|
-
tblname
|
|
413
|
-
)}";`;
|
|
414
|
-
await getMyClient().query(sql);
|
|
415
|
-
};
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Add 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 result
|
|
422
|
-
*/
|
|
423
|
-
const add_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(
|
|
426
|
-
table_name
|
|
427
|
-
)}" add CONSTRAINT "${sqlsanitize(table_name)}_${field_names
|
|
428
|
-
.map((f) => sqlsanitize(f))
|
|
429
|
-
.join("_")}_unique" UNIQUE (${field_names
|
|
430
|
-
.map((f) => `"${sqlsanitize(f)}"`)
|
|
431
|
-
.join(",")});`;
|
|
432
|
-
sql_log(sql);
|
|
433
|
-
await getMyClient().query(sql);
|
|
434
|
-
};
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Drop unique constraint
|
|
438
|
-
* @param {string} table_name - table name
|
|
439
|
-
* @param {string[]} field_names - list of columns (members of constraint)
|
|
440
|
-
* @returns {Promise<void>} no results
|
|
441
|
-
*/
|
|
442
|
-
const drop_unique_constraint = async (table_name, field_names) => {
|
|
443
|
-
// TBD check that there are no problems with lenght of constraint name
|
|
444
|
-
const sql = `alter table "${getTenantSchema()}"."${sqlsanitize(
|
|
445
|
-
table_name
|
|
446
|
-
)}" drop CONSTRAINT IF EXISTS "${sqlsanitize(table_name)}_${field_names
|
|
447
|
-
.map((f) => sqlsanitize(f))
|
|
448
|
-
.join("_")}_unique";`;
|
|
449
|
-
sql_log(sql);
|
|
450
|
-
await getMyClient().query(sql);
|
|
451
|
-
};
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* Add index
|
|
455
|
-
* @param {string} table_name - table name
|
|
456
|
-
* @param {string} field_name - list of columns (members of constraint)
|
|
457
|
-
* @returns {Promise<void>} no result
|
|
458
|
-
*/
|
|
459
|
-
const add_index = async (table_name, field_name) => {
|
|
460
|
-
// TBD check that there are no problems with lenght of constraint name
|
|
461
|
-
const sql = `create index "${sqlsanitize(table_name)}_${sqlsanitize(
|
|
462
|
-
field_name
|
|
463
|
-
)}_index" on "${getTenantSchema()}"."${sqlsanitize(
|
|
464
|
-
table_name
|
|
465
|
-
)}" ("${sqlsanitize(field_name)}");`;
|
|
466
|
-
sql_log(sql);
|
|
467
|
-
await getMyClient().query(sql);
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* Add Full-text search index
|
|
472
|
-
* @param {string} table_name - table name
|
|
473
|
-
* @param {string} field_name - list of columns (members of constraint)
|
|
474
|
-
* @returns {Promise<void>} no result
|
|
475
|
-
*/
|
|
476
|
-
const add_fts_index = async (
|
|
477
|
-
table_name,
|
|
478
|
-
field_expression,
|
|
479
|
-
language,
|
|
480
|
-
disable_fts
|
|
481
|
-
) => {
|
|
482
|
-
// TBD check that there are no problems with lenght of constraint name
|
|
483
|
-
//CREATE INDEX ON public.test_table USING gist
|
|
484
|
-
// ((name || (cars ->> 'values') || surname) gist_trgm_ops);
|
|
485
|
-
let sql;
|
|
486
|
-
if (disable_fts) {
|
|
487
|
-
await getMyClient().query("CREATE EXTENSION IF NOT EXISTS pg_trgm;");
|
|
488
|
-
sql = `create index "${sqlsanitize(
|
|
489
|
-
table_name
|
|
490
|
-
)}_fts_index" on "${getTenantSchema()}"."${sqlsanitize(
|
|
491
|
-
table_name
|
|
492
|
-
)}" USING gist ((${field_expression}) gist_trgm_ops);`;
|
|
493
|
-
} else
|
|
494
|
-
sql = `create index "${sqlsanitize(
|
|
495
|
-
table_name
|
|
496
|
-
)}_fts_index" on "${getTenantSchema()}"."${sqlsanitize(
|
|
497
|
-
table_name
|
|
498
|
-
)}" USING GIN (to_tsvector('${
|
|
499
|
-
language || "english"
|
|
500
|
-
}', ${field_expression}));`;
|
|
501
|
-
sql_log(sql);
|
|
502
|
-
await getMyClient().query(sql);
|
|
503
|
-
};
|
|
504
|
-
const drop_fts_index = async (table_name) => {
|
|
505
|
-
// TBD check that there are no problems with lenght of constraint name
|
|
506
|
-
const sql = `drop index "${getTenantSchema()}"."${sqlsanitize(
|
|
507
|
-
table_name
|
|
508
|
-
)}_fts_index";`;
|
|
509
|
-
sql_log(sql);
|
|
510
|
-
await getMyClient().query(sql);
|
|
511
|
-
};
|
|
512
|
-
|
|
513
|
-
/**
|
|
514
|
-
* Add index
|
|
515
|
-
* @param {string} table_name - table name
|
|
516
|
-
* @param {string} field_name - list of columns (members of constraint)
|
|
517
|
-
* @returns {Promise<void>} no result
|
|
518
|
-
*/
|
|
519
|
-
const drop_index = async (table_name, field_name) => {
|
|
520
|
-
// TBD check that there are no problems with lenght of constraint name
|
|
521
|
-
const sql = `drop index "${getTenantSchema()}"."${sqlsanitize(
|
|
522
|
-
table_name
|
|
523
|
-
)}_${sqlsanitize(field_name)}_index";`;
|
|
524
|
-
sql_log(sql);
|
|
525
|
-
await getMyClient().query(sql);
|
|
526
|
-
};
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Copy data from CSV to table?
|
|
530
|
-
* Only for PG
|
|
531
|
-
* @param {object} fileStream - file stream
|
|
532
|
-
* @param {string} tableName - table name
|
|
533
|
-
* @param {string[]} fieldNames - list of columns
|
|
534
|
-
* @param {object} client - db connection
|
|
535
|
-
* @returns {Promise<void>} no results
|
|
536
|
-
*/
|
|
537
|
-
const copyFrom = async (fileStream, tableName, fieldNames, client) => {
|
|
538
|
-
const sql = `COPY "${getTenantSchema()}"."${sqlsanitize(
|
|
539
|
-
tableName
|
|
540
|
-
)}" (${fieldNames.map(quote).join(",")}) FROM STDIN CSV HEADER`;
|
|
541
|
-
sql_log(sql);
|
|
542
|
-
|
|
543
|
-
const stream = client.query(copyStreams.from(sql));
|
|
544
|
-
return await pipeline(fileStream, stream);
|
|
545
|
-
};
|
|
546
|
-
|
|
547
|
-
const copyToJson = async (fileStream, tableName, client) => {
|
|
548
|
-
const sql = `COPY (SELECT (row_to_json("${sqlsanitize(tableName)}".*) || ',')
|
|
549
|
-
FROM "${getTenantSchema()}"."${sqlsanitize(tableName)}") TO STDOUT`;
|
|
550
|
-
sql_log(sql);
|
|
551
|
-
const stream = (client || getMyClient()).query(copyStreams.to(sql));
|
|
552
|
-
|
|
553
|
-
return await pipeline(stream, replace("\\\\", "\\"), fileStream);
|
|
554
|
-
};
|
|
555
|
-
|
|
556
|
-
const slugify = (s) =>
|
|
557
|
-
s
|
|
558
|
-
.toLowerCase()
|
|
559
|
-
.replace(/\s+/g, "-")
|
|
560
|
-
.replace(/[^\w-]/g, "");
|
|
561
|
-
|
|
562
|
-
const time = async () => {
|
|
563
|
-
const result = await getMyClient().query("select now()");
|
|
564
|
-
const row = result.rows[0];
|
|
565
|
-
return new Date(row.now);
|
|
566
|
-
};
|
|
567
|
-
|
|
568
|
-
/**
|
|
569
|
-
*
|
|
570
|
-
* @returns
|
|
571
|
-
*/
|
|
572
|
-
const listTables = async () => {
|
|
573
|
-
const tq = await getMyClient().query(
|
|
574
|
-
`SELECT table_name FROM information_schema.tables WHERE table_schema = '${getTenantSchema()}'`
|
|
575
|
-
);
|
|
576
|
-
return tq.rows.map((row) => {
|
|
577
|
-
return { name: row.table_name };
|
|
578
|
-
});
|
|
579
|
-
};
|
|
580
|
-
|
|
581
|
-
/**
|
|
582
|
-
*
|
|
583
|
-
* @returns
|
|
584
|
-
*/
|
|
585
|
-
const listUserDefinedTables = async () => {
|
|
586
|
-
const tq = await getMyClient().query(
|
|
587
|
-
`SELECT table_name FROM information_schema.tables WHERE table_schema = '${getTenantSchema()}' AND table_name NOT LIKE '_sc_%'`
|
|
588
|
-
);
|
|
589
|
-
return tq.rows.map((row) => {
|
|
590
|
-
return { name: row.table_name };
|
|
591
|
-
});
|
|
592
|
-
};
|
|
593
|
-
|
|
594
|
-
/**
|
|
595
|
-
*
|
|
596
|
-
* @returns
|
|
597
|
-
*/
|
|
598
|
-
const listScTables = async () => {
|
|
599
|
-
const tq = await getMyClient().query(
|
|
600
|
-
`SELECT table_name FROM information_schema.tables WHERE table_schema = '${getTenantSchema()}' AND table_name LIKE '_sc_%'`
|
|
601
|
-
);
|
|
602
|
-
return tq.rows.map((row) => {
|
|
603
|
-
return { name: row.table_name };
|
|
604
|
-
});
|
|
605
|
-
};
|
|
606
|
-
|
|
607
|
-
/* rules of using this:
|
|
608
|
-
|
|
609
|
-
- no try catch inside unless you rethrow: wouldnt roll back
|
|
610
|
-
- no state.refresh_*() inside: other works wouldnt see updates as they are in transactioon
|
|
611
|
-
- you can use state.refresh_*(true) for update on own worker only
|
|
612
|
-
|
|
613
|
-
*/
|
|
614
|
-
const withTransaction = async (f, onError) => {
|
|
615
|
-
const client = await getClient();
|
|
616
|
-
const reqCon = getRequestContext();
|
|
617
|
-
if (reqCon)
|
|
618
|
-
//if not, probably in a test
|
|
619
|
-
reqCon.client = client;
|
|
620
|
-
sql_log("BEGIN;");
|
|
621
|
-
await client.query("BEGIN;");
|
|
622
|
-
let aborted = false;
|
|
623
|
-
const rollback = async () => {
|
|
624
|
-
aborted = true;
|
|
625
|
-
sql_log("ROLLBACK;");
|
|
626
|
-
await client.query("ROLLBACK;");
|
|
627
|
-
};
|
|
628
|
-
try {
|
|
629
|
-
const result = await f(rollback);
|
|
630
|
-
|
|
631
|
-
if (!aborted) {
|
|
632
|
-
sql_log("COMMIT;");
|
|
633
|
-
await client.query("COMMIT;");
|
|
634
|
-
}
|
|
635
|
-
return result;
|
|
636
|
-
} catch (error) {
|
|
637
|
-
if (!aborted) {
|
|
638
|
-
sql_log("ROLLBACK;");
|
|
639
|
-
await client.query("ROLLBACK;");
|
|
640
|
-
}
|
|
641
|
-
if (onError) return onError(error);
|
|
642
|
-
else throw error;
|
|
643
|
-
} finally {
|
|
644
|
-
if (reqCon) reqCon.client = null;
|
|
645
|
-
client.release();
|
|
646
|
-
}
|
|
647
|
-
};
|
|
648
|
-
|
|
649
|
-
const commitAndBeginNewTransaction = async () => {
|
|
650
|
-
const client = await getMyClient();
|
|
651
|
-
sql_log("COMMIT;");
|
|
652
|
-
await client.query("COMMIT;");
|
|
653
|
-
sql_log("BEGIN;");
|
|
654
|
-
await client.query("BEGIN;");
|
|
655
|
-
};
|
|
656
|
-
|
|
657
|
-
const tryCatchInTransaction = async (f, onError) => {
|
|
658
|
-
const rndid = Math.floor(Math.random() * 16777215).toString(16);
|
|
659
|
-
const reqCon = getRequestContext();
|
|
660
|
-
if (reqCon?.client) await query(`SAVEPOINT sp${rndid}`);
|
|
661
|
-
try {
|
|
662
|
-
return await f();
|
|
663
|
-
} catch (error) {
|
|
664
|
-
if (reqCon?.client) await query(`ROLLBACK TO SAVEPOINT sp${rndid}`);
|
|
665
|
-
if (onError) return await onError(error);
|
|
666
|
-
} finally {
|
|
667
|
-
if (reqCon?.client) await query(`RELEASE SAVEPOINT sp${rndid}`);
|
|
668
|
-
}
|
|
669
|
-
};
|
|
670
|
-
|
|
671
|
-
/**
|
|
672
|
-
* Should be used for code that is sometimes called from within a withTransaction block
|
|
673
|
-
* and sometimes not.
|
|
674
|
-
* @param {Function} f logic to execute
|
|
675
|
-
* @param {Function} onError error handler
|
|
676
|
-
* @returns
|
|
677
|
-
*/
|
|
678
|
-
const openOrUseTransaction = async (f, onError) => {
|
|
679
|
-
const reqCon = getRequestContext();
|
|
680
|
-
if (reqCon?.client) return await f();
|
|
681
|
-
else return await withTransaction(f, onError);
|
|
682
|
-
};
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* Wait some time until current transaction COMMITs,
|
|
686
|
-
* then open another transaction.
|
|
687
|
-
* @param {Function} f logic to execute
|
|
688
|
-
* @param {Function} onError error handler
|
|
689
|
-
* @returns
|
|
690
|
-
*/
|
|
691
|
-
const whenTransactionisFree = (f, onError) => {
|
|
692
|
-
return new Promise((resolve, reject) => {
|
|
693
|
-
// wait until transaction is free
|
|
694
|
-
let counter = 0;
|
|
695
|
-
const interval = setInterval(async () => {
|
|
696
|
-
const reqCon = getRequestContext();
|
|
697
|
-
if (!reqCon?.client) {
|
|
698
|
-
clearInterval(interval);
|
|
699
|
-
try {
|
|
700
|
-
resolve(await withTransaction(f, onError));
|
|
701
|
-
} catch (e) {
|
|
702
|
-
reject(e);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
if (++counter > 100) {
|
|
706
|
-
clearInterval(interval);
|
|
707
|
-
reject(new Error("Timeout waiting for transaction to be free"));
|
|
708
|
-
}
|
|
709
|
-
}, 200);
|
|
710
|
-
});
|
|
711
|
-
};
|
|
712
|
-
|
|
713
|
-
const query = (text, params) => {
|
|
714
|
-
sql_log(text, params);
|
|
715
|
-
return getMyClient().query(text, params);
|
|
716
|
-
};
|
|
717
|
-
|
|
718
|
-
const postgresExports = {
|
|
719
|
-
pool,
|
|
720
|
-
/**
|
|
721
|
-
* @param {string} text
|
|
722
|
-
* @param {object} params
|
|
723
|
-
* @returns {object}
|
|
724
|
-
*/
|
|
725
|
-
query,
|
|
726
|
-
begin,
|
|
727
|
-
commit,
|
|
728
|
-
rollback,
|
|
729
|
-
select,
|
|
730
|
-
selectOne,
|
|
731
|
-
selectMaybeOne,
|
|
732
|
-
count,
|
|
733
|
-
insert,
|
|
734
|
-
update,
|
|
735
|
-
updateWhere,
|
|
736
|
-
deleteWhere,
|
|
737
|
-
close,
|
|
738
|
-
sql_log,
|
|
739
|
-
changeConnection,
|
|
740
|
-
set_sql_logging,
|
|
741
|
-
get_sql_logging,
|
|
742
|
-
drop_fts_index,
|
|
743
|
-
getClient,
|
|
744
|
-
mkWhere,
|
|
745
|
-
drop_reset_schema,
|
|
746
|
-
add_unique_constraint,
|
|
747
|
-
drop_unique_constraint,
|
|
748
|
-
add_index,
|
|
749
|
-
add_fts_index,
|
|
750
|
-
drop_index,
|
|
751
|
-
reset_sequence,
|
|
752
|
-
getVersion,
|
|
753
|
-
copyFrom,
|
|
754
|
-
copyToJson,
|
|
755
|
-
slugify,
|
|
756
|
-
time,
|
|
757
|
-
listTables,
|
|
758
|
-
listScTables,
|
|
759
|
-
listUserDefinedTables,
|
|
760
|
-
truncate,
|
|
761
|
-
withTransaction,
|
|
762
|
-
tryCatchInTransaction,
|
|
763
|
-
openOrUseTransaction,
|
|
764
|
-
whenTransactionisFree,
|
|
765
|
-
commitAndBeginNewTransaction,
|
|
766
|
-
};
|
|
767
|
-
|
|
768
|
-
module.exports = (getConnectObjectPara) => {
|
|
769
|
-
if (!pool) {
|
|
770
|
-
getConnectObject = getConnectObjectPara;
|
|
771
|
-
const connectObj = getConnectObject();
|
|
772
|
-
if (connectObj) {
|
|
773
|
-
pool = new Pool(connectObj);
|
|
774
|
-
getTenantSchema = require("@saltcorn/db-common/tenants")(
|
|
775
|
-
connectObj
|
|
776
|
-
).getTenantSchema;
|
|
777
|
-
getRequestContext = require("@saltcorn/db-common/tenants")(
|
|
778
|
-
connectObj
|
|
779
|
-
).getRequestContext;
|
|
780
|
-
postgresExports.pool = pool;
|
|
781
|
-
} else {
|
|
782
|
-
throw new Error("Unable to retrieve a database connection object.");
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
return postgresExports;
|
|
786
|
-
};
|