@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/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
- };