sedentary-pg 0.1.0 → 0.1.2

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.
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SedentaryPG = exports.Type = exports.EntryBase = exports.TransactionPG = void 0;
4
+ const sedentary_1 = require("sedentary");
5
+ const pgdb_1 = require("./pgdb");
6
+ var pgdb_2 = require("./pgdb");
7
+ Object.defineProperty(exports, "TransactionPG", { enumerable: true, get: function () { return pgdb_2.TransactionPG; } });
8
+ var sedentary_2 = require("sedentary");
9
+ Object.defineProperty(exports, "EntryBase", { enumerable: true, get: function () { return sedentary_2.EntryBase; } });
10
+ Object.defineProperty(exports, "Type", { enumerable: true, get: function () { return sedentary_2.Type; } });
11
+ class SedentaryPG extends sedentary_1.Sedentary {
12
+ constructor(connection, options) {
13
+ super(options);
14
+ if (!(connection instanceof Object))
15
+ throw new Error("SedentaryPG.constructor: 'connection' argument: Wrong type, expected 'Object'");
16
+ this.db = new pgdb_1.PGDB(connection, this.log);
17
+ }
18
+ begin() {
19
+ return this.db.begin();
20
+ }
21
+ client() {
22
+ return this.db.client();
23
+ }
24
+ }
25
+ exports.SedentaryPG = SedentaryPG;
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -0,0 +1,553 @@
1
+ "use strict";
2
+ // cSpell:ignore adbin adnum adrelid adsrc amname attinhcount attisdropped attislocal attname attnotnull attnum attrdef attrelid atttypid atttypmod confdeltype confdeltype
3
+ // cSpell:ignore confupdtype conindid conkey conname conrelid contype currval indexrelid indisunique indrelid inhparent inhrelid relam relname timestamptz typname
4
+ var __importDefault = (this && this.__importDefault) || function (mod) {
5
+ return (mod && mod.__esModule) ? mod : { "default": mod };
6
+ };
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.TransactionPG = exports.PGDB = void 0;
9
+ const pg_1 = require("pg");
10
+ const pg_format_1 = __importDefault(require("pg-format"));
11
+ const sedentary_1 = require("sedentary");
12
+ const needDrop = [
13
+ ["DATETIME", "float4"],
14
+ ["DATETIME", "float8"],
15
+ ["DATETIME", "int2"],
16
+ ["DATETIME", "int4"],
17
+ ["DATETIME", "int8"],
18
+ ["DATETIME", "numeric"],
19
+ ["FLOAT", "json"],
20
+ ["FLOAT", "timestamptz"],
21
+ ["INT", "json"],
22
+ ["INT", "timestamptz"],
23
+ ["INT8", "json"],
24
+ ["INT8", "timestamptz"],
25
+ ["JSON", "int2"],
26
+ ["JSON", "int4"],
27
+ ["JSON", "int8"],
28
+ ["JSON", "numeric"],
29
+ ["NUMBER", "json"],
30
+ ["NUMBER", "timestamptz"]
31
+ ];
32
+ const needUsing = [
33
+ ["BOOLEAN", "varchar"],
34
+ ["DATETIME", "varchar"],
35
+ ["INT", "varchar"],
36
+ ["INT8", "varchar"],
37
+ ["JSON", "varchar"],
38
+ ["NUMBER", "varchar"]
39
+ ];
40
+ const types = { bool: "BOOL", float4: "FLOAT4", float8: "FLOAT8", int2: "SMALLINT", int4: "INTEGER", int8: "BIGINT", json: "JSON", numeric: "NUMERIC", timestamptz: "DATETIME", varchar: "VARCHAR" };
41
+ const actions = { cascade: "c", "no action": "a", restrict: "r", "set default": "d", "set null": "n" };
42
+ function parseInt8(value) {
43
+ return BigInt(value);
44
+ }
45
+ function parseNumber(value) {
46
+ return parseFloat(value);
47
+ }
48
+ pg_1.types.setTypeParser(20, parseInt8);
49
+ pg_1.types.setTypeParser(1700, parseNumber);
50
+ class PGDB extends sedentary_1.DB {
51
+ constructor(connection, log) {
52
+ super(log);
53
+ this._client = {};
54
+ this.indexes = [];
55
+ this.oidLoad = {};
56
+ this.released = false;
57
+ this.version = 0;
58
+ this.pool = new pg_1.Pool(connection);
59
+ }
60
+ async connect() {
61
+ this._client = await this.pool.connect();
62
+ const res = await this._client.query("SELECT version()");
63
+ this.version = parseInt(res.rows[0].version.split(" ")[1].split(".")[0], 10);
64
+ }
65
+ async end() {
66
+ if (!this.released)
67
+ this._client.release();
68
+ await this.pool.end();
69
+ }
70
+ defaultNeq(src, value) {
71
+ if (src === value)
72
+ return false;
73
+ return src.split("::")[0] !== value;
74
+ }
75
+ async begin() {
76
+ const ret = new TransactionPG(this.log, await this.pool.connect());
77
+ this.log("BEGIN");
78
+ await ret._client.query("BEGIN");
79
+ return ret;
80
+ }
81
+ cancel(tableName) {
82
+ return async (where, tx) => {
83
+ const client = tx ? tx._client : await this.pool.connect();
84
+ let rowCount = 0;
85
+ try {
86
+ const query = `DELETE FROM ${tableName}${where ? ` WHERE ${where}` : ""}`;
87
+ this.log(query);
88
+ ({ rowCount } = (await client.query(query)));
89
+ }
90
+ finally {
91
+ if (!tx)
92
+ client.release();
93
+ }
94
+ return rowCount;
95
+ };
96
+ }
97
+ async client() {
98
+ return await this.pool.connect();
99
+ }
100
+ escape(value) {
101
+ if (value === null || value === undefined)
102
+ throw new Error("SedentaryPG: Can't escape null nor undefined values; use the 'IS NULL' operator instead");
103
+ if (typeof value === "boolean" || typeof value === "number")
104
+ return value.toString();
105
+ if (value instanceof Date)
106
+ return (0, pg_format_1.default)("%L", value).replace(/\.\d\d\d\+/, "+");
107
+ return (0, pg_format_1.default)("%L", value);
108
+ }
109
+ fill(attr2field, row, entry) {
110
+ const value = {};
111
+ for (const attribute in attr2field)
112
+ value[attribute] = (0, sedentary_1.deepCopy)((entry[attribute] = row[attr2field[attribute]]));
113
+ Object.defineProperty(entry, sedentary_1.loaded, { configurable: true, value });
114
+ }
115
+ load(tableName, attributes, pk, model, table) {
116
+ const pkFldName = pk.fieldName;
117
+ return async (where, order, limit, tx, lock) => {
118
+ const { oid } = table;
119
+ const ret = [];
120
+ const client = tx ? tx._client : await this.pool.connect();
121
+ const oidPK = {};
122
+ try {
123
+ const forUpdate = lock ? " FOR UPDATE" : "";
124
+ const orderBy = order?.length
125
+ ? ` ORDER BY ${(typeof order === "string" ? [order] : order)
126
+ .map(_ => (_.startsWith("-") ? `${table.findAttribute(_.substring(1)).fieldName} DESC` : table.findAttribute(_).fieldName))
127
+ .join(",")}`
128
+ : "";
129
+ const limitTo = typeof limit === "number" ? ` LIMIT ${limit}` : "";
130
+ const query = `SELECT *, tableoid FROM ${tableName}${where ? ` WHERE ${where}` : ""}${orderBy}${limitTo}${forUpdate}`;
131
+ this.log(query);
132
+ const res = await client.query(query);
133
+ for (const row of res.rows) {
134
+ if (row.tableoid === oid) {
135
+ const entry = new model("load");
136
+ this.fill(attributes, row, entry);
137
+ if (tx)
138
+ tx.addEntry(entry);
139
+ ret.push(entry);
140
+ entry.postLoad();
141
+ }
142
+ else {
143
+ if (!oidPK[row.tableoid])
144
+ oidPK[row.tableoid] = [];
145
+ oidPK[row.tableoid].push([ret.length, row[pkFldName]]);
146
+ ret.push(null);
147
+ }
148
+ }
149
+ for (const oid in oidPK) {
150
+ const res = await this.oidLoad[oid](oidPK[oid].map(_ => _[1]));
151
+ for (const entry of res)
152
+ for (const [id, pk] of oidPK[oid])
153
+ if (pk === entry[pkFldName])
154
+ ret[id] = entry;
155
+ }
156
+ }
157
+ finally {
158
+ if (!tx)
159
+ client.release();
160
+ }
161
+ return ret;
162
+ };
163
+ }
164
+ remove(tableName, pk) {
165
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
166
+ const self = this;
167
+ const pkAttrName = pk.attributeName;
168
+ const pkFldName = pk.fieldName;
169
+ return async function () {
170
+ const client = this[sedentary_1.transaction] ? this[sedentary_1.transaction]._client : await self.pool.connect();
171
+ let removed;
172
+ try {
173
+ const query = `DELETE FROM ${tableName} WHERE ${pkFldName} = ${self.escape(this[pkAttrName])}`;
174
+ self.log(query);
175
+ removed = (await client.query(query)).rowCount;
176
+ }
177
+ finally {
178
+ if (!this[sedentary_1.transaction])
179
+ client.release();
180
+ }
181
+ return removed;
182
+ };
183
+ }
184
+ save(tableName, attr2field, pk) {
185
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
186
+ const self = this;
187
+ const pkAttrName = pk.attributeName;
188
+ const pkFldName = pk.fieldName;
189
+ return async function () {
190
+ const client = this[sedentary_1.transaction] ? this[sedentary_1.transaction]._client : await self.pool.connect();
191
+ let changed = false;
192
+ let result = null;
193
+ const save = async (query) => {
194
+ self.log(query);
195
+ changed = true;
196
+ result = await client.query(`${query} RETURNING *`);
197
+ self.fill(attr2field, result.rows[0], this);
198
+ };
199
+ try {
200
+ const loadedRecord = this[sedentary_1.loaded];
201
+ if (loadedRecord) {
202
+ const actions = [];
203
+ for (const attribute in attr2field) {
204
+ const value = this[attribute];
205
+ if ((0, sedentary_1.deepDiff)(value, loadedRecord[attribute]))
206
+ actions.push(`${attr2field[attribute]} = ${self.escape(value)}`);
207
+ }
208
+ if (actions.length)
209
+ await save(`UPDATE ${tableName} SET ${actions.join(", ")} WHERE ${pkFldName} = ${self.escape(this[pkAttrName])}`);
210
+ }
211
+ else {
212
+ const fields = [];
213
+ const values = [];
214
+ for (const attribute in attr2field) {
215
+ const value = this[attribute];
216
+ if (value !== null && value !== undefined) {
217
+ fields.push(attr2field[attribute]);
218
+ values.push(self.escape(value));
219
+ }
220
+ }
221
+ await save(fields.length ? `INSERT INTO ${tableName} (${fields.join(", ")}) VALUES (${values.join(", ")})` : `INSERT INTO ${tableName} DEFAULT VALUES`);
222
+ }
223
+ }
224
+ finally {
225
+ if (!this[sedentary_1.transaction])
226
+ client.release();
227
+ }
228
+ return changed && result.rowCount;
229
+ };
230
+ }
231
+ async dropConstraints(table) {
232
+ const indexes = [];
233
+ const res = await this._client.query("SELECT confdeltype, confupdtype, conindid, conname, contype FROM pg_constraint WHERE conrelid = $1 ORDER BY conname", [table.oid]);
234
+ for (const row of res.rows) {
235
+ const arr = table.constraints.filter(_ => _.constraintName === row.conname && _.type === row.contype);
236
+ let drop = false;
237
+ if (arr.length === 0)
238
+ drop = true;
239
+ else if (row.contype === "u")
240
+ indexes.push(row.conindid);
241
+ else {
242
+ const { options } = arr[0].attribute.foreignKey;
243
+ if (actions[options.onDelete] !== row.confdeltype || actions[options.onUpdate] !== row.confupdtype)
244
+ drop = true;
245
+ }
246
+ if (drop) {
247
+ const statement = `ALTER TABLE ${table.tableName} DROP CONSTRAINT ${row.conname} CASCADE`;
248
+ this.syncLog(statement);
249
+ if (this.sync)
250
+ await this._client.query(statement);
251
+ }
252
+ }
253
+ return indexes;
254
+ }
255
+ async dropField(tableName, fieldName) {
256
+ const statement = `ALTER TABLE ${tableName} DROP COLUMN ${fieldName}`;
257
+ this.syncLog(statement);
258
+ if (this.sync)
259
+ await this._client.query(statement);
260
+ }
261
+ async dropFields(table) {
262
+ const res = await this._client.query("SELECT attname FROM pg_attribute WHERE attrelid = $1 AND attnum > 0 AND attisdropped = false AND attinhcount = 0", [table.oid]);
263
+ for (const row of res.rows) {
264
+ const field = table.findField(row.attname);
265
+ if (!field?.[sedentary_1.base])
266
+ await this.dropField(table.tableName, row.attname);
267
+ }
268
+ }
269
+ async dropIndexes(table, constraintIndexes) {
270
+ const { indexes, oid } = table;
271
+ const iObject = {};
272
+ const res = await this._client.query("SELECT amname, attname, indexrelid, indisunique, relname FROM pg_class, pg_index, pg_attribute, pg_am WHERE indrelid = $1 AND indexrelid = pg_class.oid AND attrelid = pg_class.oid AND relam = pg_am.oid ORDER BY attnum", [oid]);
273
+ for (const row of res.rows) {
274
+ const { amname, attname, indexrelid, indisunique, relname } = row;
275
+ if (!constraintIndexes.includes(indexrelid)) {
276
+ if (iObject[relname])
277
+ iObject[relname].fields.push(attname);
278
+ else
279
+ iObject[relname] = { fields: [attname], indexName: relname, type: amname, unique: indisunique };
280
+ }
281
+ }
282
+ this.indexes = [];
283
+ for (const index of indexes) {
284
+ const { indexName } = index;
285
+ if (iObject[indexName] && this.indexesEq(index, iObject[indexName])) {
286
+ this.indexes.push(indexName);
287
+ delete iObject[indexName];
288
+ }
289
+ }
290
+ for (const index of Object.keys(iObject).sort()) {
291
+ const statement = `DROP INDEX ${index}`;
292
+ this.syncLog(statement);
293
+ if (this.sync)
294
+ await this._client.query(statement);
295
+ }
296
+ }
297
+ async syncConstraints(table) {
298
+ for (const constraint of table.constraints) {
299
+ const { attribute, constraintName, type } = constraint;
300
+ const res = await this._client.query("SELECT attname FROM pg_attribute, pg_constraint WHERE attrelid = $1 AND conrelid = $1 AND attnum = conkey[1] AND attname = $2", [
301
+ table.oid,
302
+ attribute.fieldName
303
+ ]);
304
+ if (!res.rowCount) {
305
+ let query;
306
+ if (type === "f") {
307
+ const { fieldName, options, tableName } = attribute.foreignKey;
308
+ const onDelete = options.onDelete !== "no action" ? ` ON DELETE ${options.onDelete.toUpperCase()}` : "";
309
+ const onUpdate = options.onUpdate !== "no action" ? ` ON UPDATE ${options.onUpdate.toUpperCase()}` : "";
310
+ query = `FOREIGN KEY (${attribute.fieldName}) REFERENCES ${tableName}(${fieldName})${onDelete}${onUpdate}`;
311
+ }
312
+ else
313
+ query = `UNIQUE(${attribute.fieldName})`;
314
+ const statement = `ALTER TABLE ${table.tableName} ADD CONSTRAINT ${constraintName} ${query}`;
315
+ this.syncLog(statement);
316
+ if (this.sync)
317
+ await this._client.query(statement);
318
+ }
319
+ }
320
+ }
321
+ async syncDataBase() {
322
+ try {
323
+ await super.syncDataBase();
324
+ for (const table of this.tables)
325
+ this.oidLoad[table.oid || 0] = (ids) => table.model.load({ [table.pk.attributeName]: ["IN", ids] });
326
+ }
327
+ finally {
328
+ this.released = true;
329
+ this._client.release();
330
+ }
331
+ }
332
+ fieldType(attribute) {
333
+ const { [sedentary_1.size]: _size, type } = attribute;
334
+ let ret;
335
+ switch (type) {
336
+ case "BOOLEAN":
337
+ return ["BOOL", "BOOL"];
338
+ case "DATETIME":
339
+ return ["DATETIME", "TIMESTAMP (3) WITH TIME ZONE"];
340
+ case "FLOAT":
341
+ ret = _size === 4 ? "FLOAT4" : "FLOAT8";
342
+ return [ret, ret];
343
+ case "INT":
344
+ ret = _size === 2 ? "SMALLINT" : "INTEGER";
345
+ return [ret, ret];
346
+ case "INT8":
347
+ return ["BIGINT", "BIGINT"];
348
+ case "JSON":
349
+ return ["JSON", "JSON"];
350
+ case "NONE":
351
+ return ["NONE", "NONE"];
352
+ case "NUMBER":
353
+ return ["NUMERIC", "NUMERIC"];
354
+ case "VARCHAR":
355
+ return ["VARCHAR", `VARCHAR${_size ? `(${_size})` : ""}`];
356
+ }
357
+ throw new Error(`Unknown type: '${type}', '${_size}'`);
358
+ }
359
+ async syncFields(table) {
360
+ const { attributes, autoIncrement, oid, tableName } = table;
361
+ for (const attribute of attributes) {
362
+ const { fieldName, notNull, [sedentary_1.size]: _size } = attribute;
363
+ const defaultValue = attribute.defaultValue === undefined ? (autoIncrement && fieldName === "id" ? `nextval('${tableName}_id_seq'::regclass)` : undefined) : this.escape(attribute.defaultValue);
364
+ const [base, type] = this.fieldType(attribute);
365
+ const where = "attrelid = $1 AND attnum > 0 AND atttypid = pg_type.oid AND attislocal = 't' AND attname = $2";
366
+ const res = await this._client.query(
367
+ // eslint-disable-next-line max-len
368
+ `SELECT attnotnull, atttypmod, typname, pg_get_expr(pg_attrdef.adbin, pg_attrdef.adrelid) AS adsrc FROM pg_type, pg_attribute LEFT JOIN pg_attrdef ON adrelid = attrelid AND adnum = attnum WHERE ${where}`, [oid, fieldName]);
369
+ const addField = async () => {
370
+ const statement = `ALTER TABLE ${tableName} ADD COLUMN ${fieldName} ${type}`;
371
+ this.syncLog(statement);
372
+ if (this.sync)
373
+ await this._client.query(statement);
374
+ };
375
+ const dropDefault = async () => {
376
+ const statement = `ALTER TABLE ${tableName} ALTER COLUMN ${fieldName} DROP DEFAULT`;
377
+ this.syncLog(statement);
378
+ if (this.sync)
379
+ await this._client.query(statement);
380
+ };
381
+ const setNotNull = async (isNotNull) => {
382
+ if (isNotNull === notNull)
383
+ return;
384
+ const statement = `ALTER TABLE ${tableName} ALTER COLUMN ${fieldName} ${notNull ? "SET" : "DROP"} NOT NULL`;
385
+ this.syncLog(statement);
386
+ if (this.sync)
387
+ await this._client.query(statement);
388
+ };
389
+ const setDefault = async (isNotNull) => {
390
+ if (defaultValue !== undefined) {
391
+ let statement = `ALTER TABLE ${tableName} ALTER COLUMN ${fieldName} SET DEFAULT ${defaultValue}`;
392
+ this.syncLog(statement);
393
+ if (this.sync)
394
+ await this._client.query(statement);
395
+ if (notNull && !isNotNull) {
396
+ statement = `UPDATE ${tableName} SET ${fieldName} = ${defaultValue} WHERE ${fieldName} IS NULL`;
397
+ this.syncLog(statement);
398
+ if (this.sync)
399
+ await this._client.query(statement);
400
+ }
401
+ }
402
+ await setNotNull(isNotNull);
403
+ };
404
+ if (!res.rowCount) {
405
+ if (type !== "NONE") {
406
+ await addField();
407
+ await setDefault(false);
408
+ }
409
+ }
410
+ else {
411
+ const { adsrc, attnotnull, atttypmod, typname } = res.rows[0];
412
+ if (types[typname] !== base || (base === "VARCHAR" && (_size ? _size + 4 !== atttypmod : atttypmod !== -1))) {
413
+ if (needDrop.some(([type, name]) => attribute.type === type && typname === name)) {
414
+ await this.dropField(tableName, fieldName);
415
+ await addField();
416
+ await setDefault(false);
417
+ }
418
+ else {
419
+ if (adsrc)
420
+ await dropDefault();
421
+ const using = needUsing.some(([type, name]) => attribute.type === type && typname === name) ? ` USING ${fieldName}::${type}` : "";
422
+ const statement = `ALTER TABLE ${tableName} ALTER COLUMN ${fieldName} TYPE ${type}${using}`;
423
+ this.syncLog(statement);
424
+ if (this.sync)
425
+ await this._client.query(statement);
426
+ await setDefault(attnotnull);
427
+ }
428
+ }
429
+ else if (defaultValue === undefined) {
430
+ if (adsrc)
431
+ await dropDefault();
432
+ await setNotNull(attnotnull);
433
+ }
434
+ else if (!adsrc || this.defaultNeq(adsrc, defaultValue))
435
+ await setDefault(attnotnull);
436
+ }
437
+ }
438
+ }
439
+ async syncIndexes(table) {
440
+ const { indexes, tableName } = table;
441
+ for (const index of indexes) {
442
+ const { fields, indexName, type, unique } = index;
443
+ if (!this.indexes.includes(indexName)) {
444
+ const statement = `CREATE${unique ? " UNIQUE" : ""} INDEX ${indexName} ON ${tableName} USING ${type} (${fields.join(", ")})`;
445
+ this.syncLog(statement);
446
+ if (this.sync)
447
+ await this._client.query(statement);
448
+ }
449
+ }
450
+ }
451
+ async syncSequence(table) {
452
+ if (!table.autoIncrementOwn)
453
+ return;
454
+ const statement = `ALTER SEQUENCE ${table.tableName}_id_seq OWNED BY ${table.tableName}.id`;
455
+ this.syncLog(statement);
456
+ if (this.sync)
457
+ await this._client.query(statement);
458
+ }
459
+ async syncTable(table) {
460
+ if (table.autoIncrement) {
461
+ await (async () => {
462
+ try {
463
+ await this._client.query(`SELECT currval('${table.tableName}_id_seq')`);
464
+ }
465
+ catch (error) {
466
+ if (error instanceof pg_1.DatabaseError && error.code === "55000")
467
+ return;
468
+ if (error instanceof pg_1.DatabaseError && error.code === "42P01") {
469
+ const statement = `CREATE SEQUENCE ${table.tableName}_id_seq`;
470
+ this.syncLog(statement);
471
+ if (this.sync)
472
+ await this._client.query(statement);
473
+ table.autoIncrementOwn = true;
474
+ return;
475
+ }
476
+ throw error;
477
+ }
478
+ })();
479
+ }
480
+ let create = false;
481
+ const resTable = await this._client.query("SELECT oid FROM pg_class WHERE relname = $1", [table.tableName]);
482
+ if (resTable.rowCount) {
483
+ table.oid = resTable.rows[0].oid;
484
+ let drop = false;
485
+ const resParent = await this._client.query("SELECT inhparent FROM pg_inherits WHERE inhrelid = $1", [table.oid]);
486
+ if (resParent.rowCount) {
487
+ if (!table.parent)
488
+ drop = true;
489
+ else if (this.findTable(table.parent.tableName).oid === resParent.rows[0].inhparent)
490
+ return;
491
+ drop = true;
492
+ }
493
+ else if (table.parent)
494
+ drop = true;
495
+ if (drop) {
496
+ const statement = `DROP TABLE ${table.tableName} CASCADE`;
497
+ create = true;
498
+ this.syncLog(statement);
499
+ if (this.sync)
500
+ await this._client.query(statement);
501
+ }
502
+ }
503
+ else
504
+ create = true;
505
+ if (create) {
506
+ const parent = table.parent ? ` INHERITS (${table.parent.tableName})` : "";
507
+ const statement = `CREATE TABLE ${table.tableName} ()${parent}`;
508
+ this.syncLog(statement);
509
+ if (this.sync)
510
+ await this._client.query(statement);
511
+ const resTable = await this._client.query("SELECT oid FROM pg_class WHERE relname = $1", [table.tableName]);
512
+ table.oid = resTable.rows[0]?.oid;
513
+ }
514
+ }
515
+ }
516
+ exports.PGDB = PGDB;
517
+ class TransactionPG extends sedentary_1.Transaction {
518
+ constructor(log, client) {
519
+ super(log);
520
+ this.released = false;
521
+ this._client = client;
522
+ }
523
+ client() {
524
+ return Promise.resolve(this._client);
525
+ }
526
+ release() {
527
+ this.released = true;
528
+ this._client.release();
529
+ }
530
+ async commit() {
531
+ if (!this.released) {
532
+ this.preCommit();
533
+ this.log("COMMIT");
534
+ await this._client.query("COMMIT");
535
+ this.release();
536
+ await super.commit();
537
+ }
538
+ }
539
+ async rollback() {
540
+ try {
541
+ if (!this.released) {
542
+ await super.rollback();
543
+ this.log("ROLLBACK");
544
+ await this._client.query("ROLLBACK");
545
+ }
546
+ }
547
+ finally {
548
+ if (!this.released)
549
+ this.release();
550
+ }
551
+ }
552
+ }
553
+ exports.TransactionPG = TransactionPG;
@@ -0,0 +1,18 @@
1
+ import { Sedentary } from "sedentary";
2
+ import { PGDB } from "./pgdb";
3
+ export { TransactionPG } from "./pgdb";
4
+ export { EntryBase, Type } from "sedentary";
5
+ export class SedentaryPG extends Sedentary {
6
+ constructor(connection, options) {
7
+ super(options);
8
+ if (!(connection instanceof Object))
9
+ throw new Error("SedentaryPG.constructor: 'connection' argument: Wrong type, expected 'Object'");
10
+ this.db = new PGDB(connection, this.log);
11
+ }
12
+ begin() {
13
+ return this.db.begin();
14
+ }
15
+ client() {
16
+ return this.db.client();
17
+ }
18
+ }
@@ -0,0 +1,547 @@
1
+ // cSpell:ignore adbin adnum adrelid adsrc amname attinhcount attisdropped attislocal attname attnotnull attnum attrdef attrelid atttypid atttypmod confdeltype confdeltype
2
+ // cSpell:ignore confupdtype conindid conkey conname conrelid contype currval indexrelid indisunique indrelid inhparent inhrelid relam relname timestamptz typname
3
+ import { DatabaseError, Pool, types as PGtypes } from "pg";
4
+ import format from "pg-format";
5
+ import { base, DB, deepCopy, deepDiff, loaded, size, Transaction, transaction } from "sedentary";
6
+ const needDrop = [
7
+ ["DATETIME", "float4"],
8
+ ["DATETIME", "float8"],
9
+ ["DATETIME", "int2"],
10
+ ["DATETIME", "int4"],
11
+ ["DATETIME", "int8"],
12
+ ["DATETIME", "numeric"],
13
+ ["FLOAT", "json"],
14
+ ["FLOAT", "timestamptz"],
15
+ ["INT", "json"],
16
+ ["INT", "timestamptz"],
17
+ ["INT8", "json"],
18
+ ["INT8", "timestamptz"],
19
+ ["JSON", "int2"],
20
+ ["JSON", "int4"],
21
+ ["JSON", "int8"],
22
+ ["JSON", "numeric"],
23
+ ["NUMBER", "json"],
24
+ ["NUMBER", "timestamptz"]
25
+ ];
26
+ const needUsing = [
27
+ ["BOOLEAN", "varchar"],
28
+ ["DATETIME", "varchar"],
29
+ ["INT", "varchar"],
30
+ ["INT8", "varchar"],
31
+ ["JSON", "varchar"],
32
+ ["NUMBER", "varchar"]
33
+ ];
34
+ const types = { bool: "BOOL", float4: "FLOAT4", float8: "FLOAT8", int2: "SMALLINT", int4: "INTEGER", int8: "BIGINT", json: "JSON", numeric: "NUMERIC", timestamptz: "DATETIME", varchar: "VARCHAR" };
35
+ const actions = { cascade: "c", "no action": "a", restrict: "r", "set default": "d", "set null": "n" };
36
+ function parseInt8(value) {
37
+ return BigInt(value);
38
+ }
39
+ function parseNumber(value) {
40
+ return parseFloat(value);
41
+ }
42
+ PGtypes.setTypeParser(20, parseInt8);
43
+ PGtypes.setTypeParser(1700, parseNumber);
44
+ export class PGDB extends DB {
45
+ _client = {};
46
+ indexes = [];
47
+ oidLoad = {};
48
+ pool;
49
+ released = false;
50
+ version = 0;
51
+ constructor(connection, log) {
52
+ super(log);
53
+ this.pool = new Pool(connection);
54
+ }
55
+ async connect() {
56
+ this._client = await this.pool.connect();
57
+ const res = await this._client.query("SELECT version()");
58
+ this.version = parseInt(res.rows[0].version.split(" ")[1].split(".")[0], 10);
59
+ }
60
+ async end() {
61
+ if (!this.released)
62
+ this._client.release();
63
+ await this.pool.end();
64
+ }
65
+ defaultNeq(src, value) {
66
+ if (src === value)
67
+ return false;
68
+ return src.split("::")[0] !== value;
69
+ }
70
+ async begin() {
71
+ const ret = new TransactionPG(this.log, await this.pool.connect());
72
+ this.log("BEGIN");
73
+ await ret._client.query("BEGIN");
74
+ return ret;
75
+ }
76
+ cancel(tableName) {
77
+ return async (where, tx) => {
78
+ const client = tx ? tx._client : await this.pool.connect();
79
+ let rowCount = 0;
80
+ try {
81
+ const query = `DELETE FROM ${tableName}${where ? ` WHERE ${where}` : ""}`;
82
+ this.log(query);
83
+ ({ rowCount } = (await client.query(query)));
84
+ }
85
+ finally {
86
+ if (!tx)
87
+ client.release();
88
+ }
89
+ return rowCount;
90
+ };
91
+ }
92
+ async client() {
93
+ return await this.pool.connect();
94
+ }
95
+ escape(value) {
96
+ if (value === null || value === undefined)
97
+ throw new Error("SedentaryPG: Can't escape null nor undefined values; use the 'IS NULL' operator instead");
98
+ if (typeof value === "boolean" || typeof value === "number")
99
+ return value.toString();
100
+ if (value instanceof Date)
101
+ return format("%L", value).replace(/\.\d\d\d\+/, "+");
102
+ return format("%L", value);
103
+ }
104
+ fill(attr2field, row, entry) {
105
+ const value = {};
106
+ for (const attribute in attr2field)
107
+ value[attribute] = deepCopy((entry[attribute] = row[attr2field[attribute]]));
108
+ Object.defineProperty(entry, loaded, { configurable: true, value });
109
+ }
110
+ load(tableName, attributes, pk, model, table) {
111
+ const pkFldName = pk.fieldName;
112
+ return async (where, order, limit, tx, lock) => {
113
+ const { oid } = table;
114
+ const ret = [];
115
+ const client = tx ? tx._client : await this.pool.connect();
116
+ const oidPK = {};
117
+ try {
118
+ const forUpdate = lock ? " FOR UPDATE" : "";
119
+ const orderBy = order?.length
120
+ ? ` ORDER BY ${(typeof order === "string" ? [order] : order)
121
+ .map(_ => (_.startsWith("-") ? `${table.findAttribute(_.substring(1)).fieldName} DESC` : table.findAttribute(_).fieldName))
122
+ .join(",")}`
123
+ : "";
124
+ const limitTo = typeof limit === "number" ? ` LIMIT ${limit}` : "";
125
+ const query = `SELECT *, tableoid FROM ${tableName}${where ? ` WHERE ${where}` : ""}${orderBy}${limitTo}${forUpdate}`;
126
+ this.log(query);
127
+ const res = await client.query(query);
128
+ for (const row of res.rows) {
129
+ if (row.tableoid === oid) {
130
+ const entry = new model("load");
131
+ this.fill(attributes, row, entry);
132
+ if (tx)
133
+ tx.addEntry(entry);
134
+ ret.push(entry);
135
+ entry.postLoad();
136
+ }
137
+ else {
138
+ if (!oidPK[row.tableoid])
139
+ oidPK[row.tableoid] = [];
140
+ oidPK[row.tableoid].push([ret.length, row[pkFldName]]);
141
+ ret.push(null);
142
+ }
143
+ }
144
+ for (const oid in oidPK) {
145
+ const res = await this.oidLoad[oid](oidPK[oid].map(_ => _[1]));
146
+ for (const entry of res)
147
+ for (const [id, pk] of oidPK[oid])
148
+ if (pk === entry[pkFldName])
149
+ ret[id] = entry;
150
+ }
151
+ }
152
+ finally {
153
+ if (!tx)
154
+ client.release();
155
+ }
156
+ return ret;
157
+ };
158
+ }
159
+ remove(tableName, pk) {
160
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
161
+ const self = this;
162
+ const pkAttrName = pk.attributeName;
163
+ const pkFldName = pk.fieldName;
164
+ return async function () {
165
+ const client = this[transaction] ? this[transaction]._client : await self.pool.connect();
166
+ let removed;
167
+ try {
168
+ const query = `DELETE FROM ${tableName} WHERE ${pkFldName} = ${self.escape(this[pkAttrName])}`;
169
+ self.log(query);
170
+ removed = (await client.query(query)).rowCount;
171
+ }
172
+ finally {
173
+ if (!this[transaction])
174
+ client.release();
175
+ }
176
+ return removed;
177
+ };
178
+ }
179
+ save(tableName, attr2field, pk) {
180
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
181
+ const self = this;
182
+ const pkAttrName = pk.attributeName;
183
+ const pkFldName = pk.fieldName;
184
+ return async function () {
185
+ const client = this[transaction] ? this[transaction]._client : await self.pool.connect();
186
+ let changed = false;
187
+ let result = null;
188
+ const save = async (query) => {
189
+ self.log(query);
190
+ changed = true;
191
+ result = await client.query(`${query} RETURNING *`);
192
+ self.fill(attr2field, result.rows[0], this);
193
+ };
194
+ try {
195
+ const loadedRecord = this[loaded];
196
+ if (loadedRecord) {
197
+ const actions = [];
198
+ for (const attribute in attr2field) {
199
+ const value = this[attribute];
200
+ if (deepDiff(value, loadedRecord[attribute]))
201
+ actions.push(`${attr2field[attribute]} = ${self.escape(value)}`);
202
+ }
203
+ if (actions.length)
204
+ await save(`UPDATE ${tableName} SET ${actions.join(", ")} WHERE ${pkFldName} = ${self.escape(this[pkAttrName])}`);
205
+ }
206
+ else {
207
+ const fields = [];
208
+ const values = [];
209
+ for (const attribute in attr2field) {
210
+ const value = this[attribute];
211
+ if (value !== null && value !== undefined) {
212
+ fields.push(attr2field[attribute]);
213
+ values.push(self.escape(value));
214
+ }
215
+ }
216
+ await save(fields.length ? `INSERT INTO ${tableName} (${fields.join(", ")}) VALUES (${values.join(", ")})` : `INSERT INTO ${tableName} DEFAULT VALUES`);
217
+ }
218
+ }
219
+ finally {
220
+ if (!this[transaction])
221
+ client.release();
222
+ }
223
+ return changed && result.rowCount;
224
+ };
225
+ }
226
+ async dropConstraints(table) {
227
+ const indexes = [];
228
+ const res = await this._client.query("SELECT confdeltype, confupdtype, conindid, conname, contype FROM pg_constraint WHERE conrelid = $1 ORDER BY conname", [table.oid]);
229
+ for (const row of res.rows) {
230
+ const arr = table.constraints.filter(_ => _.constraintName === row.conname && _.type === row.contype);
231
+ let drop = false;
232
+ if (arr.length === 0)
233
+ drop = true;
234
+ else if (row.contype === "u")
235
+ indexes.push(row.conindid);
236
+ else {
237
+ const { options } = arr[0].attribute.foreignKey;
238
+ if (actions[options.onDelete] !== row.confdeltype || actions[options.onUpdate] !== row.confupdtype)
239
+ drop = true;
240
+ }
241
+ if (drop) {
242
+ const statement = `ALTER TABLE ${table.tableName} DROP CONSTRAINT ${row.conname} CASCADE`;
243
+ this.syncLog(statement);
244
+ if (this.sync)
245
+ await this._client.query(statement);
246
+ }
247
+ }
248
+ return indexes;
249
+ }
250
+ async dropField(tableName, fieldName) {
251
+ const statement = `ALTER TABLE ${tableName} DROP COLUMN ${fieldName}`;
252
+ this.syncLog(statement);
253
+ if (this.sync)
254
+ await this._client.query(statement);
255
+ }
256
+ async dropFields(table) {
257
+ const res = await this._client.query("SELECT attname FROM pg_attribute WHERE attrelid = $1 AND attnum > 0 AND attisdropped = false AND attinhcount = 0", [table.oid]);
258
+ for (const row of res.rows) {
259
+ const field = table.findField(row.attname);
260
+ if (!field?.[base])
261
+ await this.dropField(table.tableName, row.attname);
262
+ }
263
+ }
264
+ async dropIndexes(table, constraintIndexes) {
265
+ const { indexes, oid } = table;
266
+ const iObject = {};
267
+ const res = await this._client.query("SELECT amname, attname, indexrelid, indisunique, relname FROM pg_class, pg_index, pg_attribute, pg_am WHERE indrelid = $1 AND indexrelid = pg_class.oid AND attrelid = pg_class.oid AND relam = pg_am.oid ORDER BY attnum", [oid]);
268
+ for (const row of res.rows) {
269
+ const { amname, attname, indexrelid, indisunique, relname } = row;
270
+ if (!constraintIndexes.includes(indexrelid)) {
271
+ if (iObject[relname])
272
+ iObject[relname].fields.push(attname);
273
+ else
274
+ iObject[relname] = { fields: [attname], indexName: relname, type: amname, unique: indisunique };
275
+ }
276
+ }
277
+ this.indexes = [];
278
+ for (const index of indexes) {
279
+ const { indexName } = index;
280
+ if (iObject[indexName] && this.indexesEq(index, iObject[indexName])) {
281
+ this.indexes.push(indexName);
282
+ delete iObject[indexName];
283
+ }
284
+ }
285
+ for (const index of Object.keys(iObject).sort()) {
286
+ const statement = `DROP INDEX ${index}`;
287
+ this.syncLog(statement);
288
+ if (this.sync)
289
+ await this._client.query(statement);
290
+ }
291
+ }
292
+ async syncConstraints(table) {
293
+ for (const constraint of table.constraints) {
294
+ const { attribute, constraintName, type } = constraint;
295
+ const res = await this._client.query("SELECT attname FROM pg_attribute, pg_constraint WHERE attrelid = $1 AND conrelid = $1 AND attnum = conkey[1] AND attname = $2", [
296
+ table.oid,
297
+ attribute.fieldName
298
+ ]);
299
+ if (!res.rowCount) {
300
+ let query;
301
+ if (type === "f") {
302
+ const { fieldName, options, tableName } = attribute.foreignKey;
303
+ const onDelete = options.onDelete !== "no action" ? ` ON DELETE ${options.onDelete.toUpperCase()}` : "";
304
+ const onUpdate = options.onUpdate !== "no action" ? ` ON UPDATE ${options.onUpdate.toUpperCase()}` : "";
305
+ query = `FOREIGN KEY (${attribute.fieldName}) REFERENCES ${tableName}(${fieldName})${onDelete}${onUpdate}`;
306
+ }
307
+ else
308
+ query = `UNIQUE(${attribute.fieldName})`;
309
+ const statement = `ALTER TABLE ${table.tableName} ADD CONSTRAINT ${constraintName} ${query}`;
310
+ this.syncLog(statement);
311
+ if (this.sync)
312
+ await this._client.query(statement);
313
+ }
314
+ }
315
+ }
316
+ async syncDataBase() {
317
+ try {
318
+ await super.syncDataBase();
319
+ for (const table of this.tables)
320
+ this.oidLoad[table.oid || 0] = (ids) => table.model.load({ [table.pk.attributeName]: ["IN", ids] });
321
+ }
322
+ finally {
323
+ this.released = true;
324
+ this._client.release();
325
+ }
326
+ }
327
+ fieldType(attribute) {
328
+ const { [size]: _size, type } = attribute;
329
+ let ret;
330
+ switch (type) {
331
+ case "BOOLEAN":
332
+ return ["BOOL", "BOOL"];
333
+ case "DATETIME":
334
+ return ["DATETIME", "TIMESTAMP (3) WITH TIME ZONE"];
335
+ case "FLOAT":
336
+ ret = _size === 4 ? "FLOAT4" : "FLOAT8";
337
+ return [ret, ret];
338
+ case "INT":
339
+ ret = _size === 2 ? "SMALLINT" : "INTEGER";
340
+ return [ret, ret];
341
+ case "INT8":
342
+ return ["BIGINT", "BIGINT"];
343
+ case "JSON":
344
+ return ["JSON", "JSON"];
345
+ case "NONE":
346
+ return ["NONE", "NONE"];
347
+ case "NUMBER":
348
+ return ["NUMERIC", "NUMERIC"];
349
+ case "VARCHAR":
350
+ return ["VARCHAR", `VARCHAR${_size ? `(${_size})` : ""}`];
351
+ }
352
+ throw new Error(`Unknown type: '${type}', '${_size}'`);
353
+ }
354
+ async syncFields(table) {
355
+ const { attributes, autoIncrement, oid, tableName } = table;
356
+ for (const attribute of attributes) {
357
+ const { fieldName, notNull, [size]: _size } = attribute;
358
+ const defaultValue = attribute.defaultValue === undefined ? (autoIncrement && fieldName === "id" ? `nextval('${tableName}_id_seq'::regclass)` : undefined) : this.escape(attribute.defaultValue);
359
+ const [base, type] = this.fieldType(attribute);
360
+ const where = "attrelid = $1 AND attnum > 0 AND atttypid = pg_type.oid AND attislocal = 't' AND attname = $2";
361
+ const res = await this._client.query(
362
+ // eslint-disable-next-line max-len
363
+ `SELECT attnotnull, atttypmod, typname, pg_get_expr(pg_attrdef.adbin, pg_attrdef.adrelid) AS adsrc FROM pg_type, pg_attribute LEFT JOIN pg_attrdef ON adrelid = attrelid AND adnum = attnum WHERE ${where}`, [oid, fieldName]);
364
+ const addField = async () => {
365
+ const statement = `ALTER TABLE ${tableName} ADD COLUMN ${fieldName} ${type}`;
366
+ this.syncLog(statement);
367
+ if (this.sync)
368
+ await this._client.query(statement);
369
+ };
370
+ const dropDefault = async () => {
371
+ const statement = `ALTER TABLE ${tableName} ALTER COLUMN ${fieldName} DROP DEFAULT`;
372
+ this.syncLog(statement);
373
+ if (this.sync)
374
+ await this._client.query(statement);
375
+ };
376
+ const setNotNull = async (isNotNull) => {
377
+ if (isNotNull === notNull)
378
+ return;
379
+ const statement = `ALTER TABLE ${tableName} ALTER COLUMN ${fieldName} ${notNull ? "SET" : "DROP"} NOT NULL`;
380
+ this.syncLog(statement);
381
+ if (this.sync)
382
+ await this._client.query(statement);
383
+ };
384
+ const setDefault = async (isNotNull) => {
385
+ if (defaultValue !== undefined) {
386
+ let statement = `ALTER TABLE ${tableName} ALTER COLUMN ${fieldName} SET DEFAULT ${defaultValue}`;
387
+ this.syncLog(statement);
388
+ if (this.sync)
389
+ await this._client.query(statement);
390
+ if (notNull && !isNotNull) {
391
+ statement = `UPDATE ${tableName} SET ${fieldName} = ${defaultValue} WHERE ${fieldName} IS NULL`;
392
+ this.syncLog(statement);
393
+ if (this.sync)
394
+ await this._client.query(statement);
395
+ }
396
+ }
397
+ await setNotNull(isNotNull);
398
+ };
399
+ if (!res.rowCount) {
400
+ if (type !== "NONE") {
401
+ await addField();
402
+ await setDefault(false);
403
+ }
404
+ }
405
+ else {
406
+ const { adsrc, attnotnull, atttypmod, typname } = res.rows[0];
407
+ if (types[typname] !== base || (base === "VARCHAR" && (_size ? _size + 4 !== atttypmod : atttypmod !== -1))) {
408
+ if (needDrop.some(([type, name]) => attribute.type === type && typname === name)) {
409
+ await this.dropField(tableName, fieldName);
410
+ await addField();
411
+ await setDefault(false);
412
+ }
413
+ else {
414
+ if (adsrc)
415
+ await dropDefault();
416
+ const using = needUsing.some(([type, name]) => attribute.type === type && typname === name) ? ` USING ${fieldName}::${type}` : "";
417
+ const statement = `ALTER TABLE ${tableName} ALTER COLUMN ${fieldName} TYPE ${type}${using}`;
418
+ this.syncLog(statement);
419
+ if (this.sync)
420
+ await this._client.query(statement);
421
+ await setDefault(attnotnull);
422
+ }
423
+ }
424
+ else if (defaultValue === undefined) {
425
+ if (adsrc)
426
+ await dropDefault();
427
+ await setNotNull(attnotnull);
428
+ }
429
+ else if (!adsrc || this.defaultNeq(adsrc, defaultValue))
430
+ await setDefault(attnotnull);
431
+ }
432
+ }
433
+ }
434
+ async syncIndexes(table) {
435
+ const { indexes, tableName } = table;
436
+ for (const index of indexes) {
437
+ const { fields, indexName, type, unique } = index;
438
+ if (!this.indexes.includes(indexName)) {
439
+ const statement = `CREATE${unique ? " UNIQUE" : ""} INDEX ${indexName} ON ${tableName} USING ${type} (${fields.join(", ")})`;
440
+ this.syncLog(statement);
441
+ if (this.sync)
442
+ await this._client.query(statement);
443
+ }
444
+ }
445
+ }
446
+ async syncSequence(table) {
447
+ if (!table.autoIncrementOwn)
448
+ return;
449
+ const statement = `ALTER SEQUENCE ${table.tableName}_id_seq OWNED BY ${table.tableName}.id`;
450
+ this.syncLog(statement);
451
+ if (this.sync)
452
+ await this._client.query(statement);
453
+ }
454
+ async syncTable(table) {
455
+ if (table.autoIncrement) {
456
+ await (async () => {
457
+ try {
458
+ await this._client.query(`SELECT currval('${table.tableName}_id_seq')`);
459
+ }
460
+ catch (error) {
461
+ if (error instanceof DatabaseError && error.code === "55000")
462
+ return;
463
+ if (error instanceof DatabaseError && error.code === "42P01") {
464
+ const statement = `CREATE SEQUENCE ${table.tableName}_id_seq`;
465
+ this.syncLog(statement);
466
+ if (this.sync)
467
+ await this._client.query(statement);
468
+ table.autoIncrementOwn = true;
469
+ return;
470
+ }
471
+ throw error;
472
+ }
473
+ })();
474
+ }
475
+ let create = false;
476
+ const resTable = await this._client.query("SELECT oid FROM pg_class WHERE relname = $1", [table.tableName]);
477
+ if (resTable.rowCount) {
478
+ table.oid = resTable.rows[0].oid;
479
+ let drop = false;
480
+ const resParent = await this._client.query("SELECT inhparent FROM pg_inherits WHERE inhrelid = $1", [table.oid]);
481
+ if (resParent.rowCount) {
482
+ if (!table.parent)
483
+ drop = true;
484
+ else if (this.findTable(table.parent.tableName).oid === resParent.rows[0].inhparent)
485
+ return;
486
+ drop = true;
487
+ }
488
+ else if (table.parent)
489
+ drop = true;
490
+ if (drop) {
491
+ const statement = `DROP TABLE ${table.tableName} CASCADE`;
492
+ create = true;
493
+ this.syncLog(statement);
494
+ if (this.sync)
495
+ await this._client.query(statement);
496
+ }
497
+ }
498
+ else
499
+ create = true;
500
+ if (create) {
501
+ const parent = table.parent ? ` INHERITS (${table.parent.tableName})` : "";
502
+ const statement = `CREATE TABLE ${table.tableName} ()${parent}`;
503
+ this.syncLog(statement);
504
+ if (this.sync)
505
+ await this._client.query(statement);
506
+ const resTable = await this._client.query("SELECT oid FROM pg_class WHERE relname = $1", [table.tableName]);
507
+ table.oid = resTable.rows[0]?.oid;
508
+ }
509
+ }
510
+ }
511
+ export class TransactionPG extends Transaction {
512
+ _client;
513
+ released = false;
514
+ constructor(log, client) {
515
+ super(log);
516
+ this._client = client;
517
+ }
518
+ client() {
519
+ return Promise.resolve(this._client);
520
+ }
521
+ release() {
522
+ this.released = true;
523
+ this._client.release();
524
+ }
525
+ async commit() {
526
+ if (!this.released) {
527
+ this.preCommit();
528
+ this.log("COMMIT");
529
+ await this._client.query("COMMIT");
530
+ this.release();
531
+ await super.commit();
532
+ }
533
+ }
534
+ async rollback() {
535
+ try {
536
+ if (!this.released) {
537
+ await super.rollback();
538
+ this.log("ROLLBACK");
539
+ await this._client.query("ROLLBACK");
540
+ }
541
+ }
542
+ finally {
543
+ if (!this.released)
544
+ this.release();
545
+ }
546
+ }
547
+ }
@@ -0,0 +1,10 @@
1
+ import { PoolConfig } from "pg";
2
+ import { Sedentary, SedentaryOptions } from "sedentary";
3
+ import { PGDB, TransactionPG } from "./pgdb";
4
+ export { TransactionPG } from "./pgdb";
5
+ export { Entry, EntryBase, SedentaryOptions, TxAction, Type } from "sedentary";
6
+ export declare class SedentaryPG extends Sedentary<PGDB, TransactionPG> {
7
+ constructor(connection: PoolConfig, options?: SedentaryOptions);
8
+ begin(): Promise<TransactionPG>;
9
+ client(): Promise<import("pg").PoolClient>;
10
+ }
@@ -0,0 +1,47 @@
1
+ import { PoolClient, PoolConfig } from "pg";
2
+ import { Attribute, DB, EntryBase, loaded, Table, Transaction, transaction } from "sedentary";
3
+ export declare class PGDB extends DB<TransactionPG> {
4
+ private _client;
5
+ private indexes;
6
+ private oidLoad;
7
+ private pool;
8
+ private released;
9
+ private version;
10
+ constructor(connection: PoolConfig, log: (message: string) => void);
11
+ connect(): Promise<void>;
12
+ end(): Promise<void>;
13
+ defaultNeq(src: string, value: unknown): boolean;
14
+ begin(): Promise<TransactionPG>;
15
+ cancel(tableName: string): (where: string, tx?: Transaction) => Promise<number>;
16
+ client(): Promise<PoolClient>;
17
+ escape(value: unknown): string;
18
+ fill(attr2field: Record<string, string>, row: Record<string, unknown>, entry: Record<string, unknown>): void;
19
+ load(tableName: string, attributes: Record<string, string>, pk: Attribute<unknown, boolean, unknown>, model: new (from: "load") => EntryBase, table: Table): (where: string, order?: string | string[], limit?: number, tx?: Transaction) => Promise<EntryBase[]>;
20
+ remove(tableName: string, pk: Attribute<unknown, boolean, unknown>): (this: Record<string, unknown> & {
21
+ [transaction]?: TransactionPG;
22
+ }) => Promise<number>;
23
+ save(tableName: string, attr2field: Record<string, string>, pk: Attribute<unknown, boolean, unknown>): (this: Record<string, unknown> & {
24
+ [loaded]?: Record<string, unknown>;
25
+ [transaction]?: TransactionPG;
26
+ }) => Promise<number | false>;
27
+ dropConstraints(table: Table): Promise<number[]>;
28
+ dropField(tableName: string, fieldName: string): Promise<void>;
29
+ dropFields(table: Table): Promise<void>;
30
+ dropIndexes(table: Table, constraintIndexes: number[]): Promise<void>;
31
+ syncConstraints(table: Table): Promise<void>;
32
+ syncDataBase(): Promise<void>;
33
+ fieldType(attribute: Attribute<unknown, boolean, unknown>): string[];
34
+ syncFields(table: Table): Promise<void>;
35
+ syncIndexes(table: Table): Promise<void>;
36
+ syncSequence(table: Table): Promise<void>;
37
+ syncTable(table: Table): Promise<void>;
38
+ }
39
+ export declare class TransactionPG extends Transaction {
40
+ private _client;
41
+ private released;
42
+ constructor(log: (message: string) => void, client: PoolClient);
43
+ client(): Promise<PoolClient>;
44
+ private release;
45
+ commit(): Promise<void>;
46
+ rollback(): Promise<void>;
47
+ }
package/package.json CHANGED
@@ -9,12 +9,15 @@
9
9
  "@types/pg": "^8.16.0",
10
10
  "pg": "^8.18.0",
11
11
  "pg-format": "^1.0.4",
12
- "sedentary": "0.1.0"
12
+ "sedentary": "0.1.2"
13
13
  },
14
14
  "description": "The ORM which never needs to migrate - PostgreSQL",
15
15
  "engines": {
16
16
  "node": ">=20.19"
17
17
  },
18
+ "files": [
19
+ "dist"
20
+ ],
18
21
  "funding": {
19
22
  "url": "https://blockchain.info/address/1Md9WFAHrXTb3yPBwQWmUfv2RmzrtbHioB"
20
23
  },
@@ -41,5 +44,5 @@
41
44
  "preinstall": "if [ -f Makefile ] ; then make ; fi"
42
45
  },
43
46
  "types": "./dist/types/index.d.ts",
44
- "version": "0.1.0"
47
+ "version": "0.1.2"
45
48
  }