sedentary-pg 0.0.19 → 0.0.24

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/README.md CHANGED
@@ -33,6 +33,34 @@
33
33
 
34
34
  The **PostgreSQL** specilized package of [Sedentary](https://www.npmjs.com/package/sedentary).
35
35
 
36
+ # Usage
37
+
38
+ ```javascript
39
+ import { SedentaryPG } from "sedentary-pg";
40
+
41
+ const db = new SedentaryPG(/* PG connection */);
42
+
43
+ class Items extends db.model("Item", {
44
+ num: db.INT,
45
+ str: db.VARCHAR(30)
46
+ });
47
+
48
+ (async function () {
49
+ await db.connect();
50
+
51
+ const item = Items.create();
52
+
53
+ item.num = 0;
54
+ item.str = "0";
55
+
56
+ await item.save();
57
+
58
+ const records = await Items.load({});
59
+
60
+ console.log(records); // [{ id: 1, num: 0, str: "0" }]
61
+ })();
62
+ ```
63
+
36
64
  # Installation
37
65
 
38
66
  With [npm](https://www.npmjs.com/package/sedentary-pg):
File without changes
File without changes
package/index.d.ts CHANGED
@@ -1,9 +1,12 @@
1
- import { EntryBase, ForeignKeyOptions, Natural, SedentaryOptions, Sedentary, Type } from "sedentary";
1
+ import { EntryBase, ForeignKeyOptions, Natural, Sedentary, SedentaryOptions, Type } from "sedentary";
2
+ import { Attribute } from "sedentary/db";
2
3
  import { PoolConfig } from "pg";
3
- export { AttributeDefinition, AttributeOptions, AttributesDefinition, EntryBase, ForeignKeyActions, ForeignKeyOptions } from "sedentary";
4
- export { IndexAttributes, IndexDefinition, IndexOptions, IndexesDefinition, ModelOptions, Natural, SedentaryOptions, Type, TypeDefinition } from "sedentary";
4
+ import { TransactionPG } from "./pgdb";
5
+ export { EntryBase, SedentaryOptions, Type } from "sedentary";
6
+ export { TransactionPG } from "./pgdb";
5
7
  export declare class SedentaryPG extends Sedentary {
6
8
  constructor(connection: PoolConfig, options?: SedentaryOptions);
7
- FKEY<N extends Natural, E extends EntryBase>(attribute: Type<N, E>, options?: ForeignKeyOptions): Type<N, E>;
9
+ FKEY<N extends Natural, E extends EntryBase>(attribute: Attribute<N, E>, options?: ForeignKeyOptions): Type<N, E>;
10
+ begin(): Promise<TransactionPG>;
8
11
  }
9
12
  export declare const Package: typeof SedentaryPG;
package/index.js CHANGED
@@ -1,15 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Package = exports.SedentaryPG = exports.Type = exports.EntryBase = void 0;
3
+ exports.Package = exports.SedentaryPG = exports.TransactionPG = exports.Type = exports.EntryBase = void 0;
4
4
  const sedentary_1 = require("sedentary");
5
- const pgdb_1 = require("./lib/pgdb");
5
+ const pgdb_1 = require("./pgdb");
6
6
  var sedentary_2 = require("sedentary");
7
7
  Object.defineProperty(exports, "EntryBase", { enumerable: true, get: function () { return sedentary_2.EntryBase; } });
8
- var sedentary_3 = require("sedentary");
9
- Object.defineProperty(exports, "Type", { enumerable: true, get: function () { return sedentary_3.Type; } });
8
+ Object.defineProperty(exports, "Type", { enumerable: true, get: function () { return sedentary_2.Type; } });
9
+ var pgdb_2 = require("./pgdb");
10
+ Object.defineProperty(exports, "TransactionPG", { enumerable: true, get: function () { return pgdb_2.TransactionPG; } });
10
11
  class SedentaryPG extends sedentary_1.Sedentary {
11
12
  constructor(connection, options) {
12
- super("", options);
13
+ super(options);
13
14
  if (!(connection instanceof Object))
14
15
  throw new Error("SedentaryPG.constructor: 'connection' argument: Wrong type, expected 'Object'");
15
16
  this.db = new pgdb_1.PGDB(connection, this.log);
@@ -20,6 +21,9 @@ class SedentaryPG extends sedentary_1.Sedentary {
20
21
  throw new Error(`Sedentary.FKEY: '${modelName}' model: '${attributeName}' attribute: is not unique: can't be used as FKEY target`);
21
22
  return super.FKEY(attribute, options);
22
23
  }
24
+ async begin() {
25
+ return this.db.begin();
26
+ }
23
27
  }
24
28
  exports.SedentaryPG = SedentaryPG;
25
29
  exports.Package = SedentaryPG;
package/package.json CHANGED
@@ -1,26 +1,29 @@
1
1
  {
2
2
  "author": "Daniele Ricci <daniele.icc@gmail.com> (https://github.com/iccicci)",
3
3
  "bugs": "https://github.com/iccicci/sedentary-pg/issues",
4
+ "contributors": [
5
+ "Daniele Ricci <daniele.icc@gmail.com> (https://github.com/iccicci)",
6
+ "yossarian <sergiybiluk@gmail.com> (https://github.com/captain-yossarian)"
7
+ ],
4
8
  "dependencies": {
5
- "@types/pg": "8.6.2",
6
- "@types/pg-format": "1.0.2",
9
+ "@types/pg": "8.6.3",
7
10
  "pg": "8.7.1",
8
11
  "pg-format": "1.0.4",
9
- "sedentary": "0.0.19"
12
+ "sedentary": "0.0.24"
10
13
  },
11
14
  "description": "The ORM which never needs to migrate - PostgreSQL",
12
15
  "devDependencies": {
13
16
  "@types/mocha": "9.0.0",
14
- "@types/node": "17.0.0",
17
+ "@types/node": "17.0.8",
18
+ "@types/pg-format": "1.0.2",
15
19
  "@types/yamljs": "0.2.31",
16
- "@typescript-eslint/eslint-plugin": "5.7.0",
17
- "@typescript-eslint/parser": "5.7.0",
18
- "eslint": "8.5.0",
20
+ "@typescript-eslint/eslint-plugin": "5.9.0",
21
+ "@typescript-eslint/parser": "5.9.0",
22
+ "eslint": "8.6.0",
19
23
  "mocha": "9.1.3",
20
24
  "nyc": "15.1.0",
21
25
  "prettier": "2.5.1",
22
26
  "ts-node": "10.4.0",
23
- "tsd": "0.19.0",
24
27
  "typescript": "4.5.4",
25
28
  "yamljs": "0.3.0"
26
29
  },
@@ -64,9 +67,21 @@
64
67
  },
65
68
  "tsd": {
66
69
  "compilerOptions": {
67
- "strict": false
70
+ "alwaysStrict": true,
71
+ "declaration": true,
72
+ "esModuleInterop": true,
73
+ "module": "commonjs",
74
+ "noImplicitAny": true,
75
+ "noImplicitReturns": true,
76
+ "noImplicitThis": true,
77
+ "strict": true,
78
+ "strictBindCallApply": true,
79
+ "strictFunctionTypes": true,
80
+ "strictNullChecks": true,
81
+ "strictPropertyInitialization": true,
82
+ "target": "es2017"
68
83
  }
69
84
  },
70
85
  "types": "index.d.ts",
71
- "version": "0.0.19"
86
+ "version": "0.0.24"
72
87
  }
package/pgdb.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ import { PoolClient, PoolConfig } from "pg";
2
+ import { Attribute, DB, EntryBase, Natural, Table, Transaction } from "sedentary/db";
3
+ export declare class PGDB extends DB {
4
+ private client;
5
+ private indexes;
6
+ private oidLoad;
7
+ private pool;
8
+ private version;
9
+ constructor(connection: PoolConfig, log: (message: string) => void);
10
+ connect(): Promise<void>;
11
+ end(): Promise<void>;
12
+ defaultNeq(src: string, value: Natural): boolean;
13
+ begin(): Promise<TransactionPG>;
14
+ escape(value: Natural): string;
15
+ fill(attributes: Record<string, string>, row: Record<string, Natural>, entry: Record<string, Natural>): void;
16
+ load(tableName: string, attributes: Record<string, string>, pk: Attribute<Natural, unknown>, model: new (from: "load") => EntryBase, table: Table): (where: string, order?: string[], tx?: Transaction) => Promise<EntryBase[]>;
17
+ save(tableName: string, attributes: Record<string, string>, pk: Attribute<Natural, unknown>): (this: Record<string, Natural> & {
18
+ loaded?: Record<string, unknown>;
19
+ tx?: TransactionPG;
20
+ }) => Promise<boolean>;
21
+ dropConstraints(table: Table): Promise<number[]>;
22
+ dropField(tableName: string, fieldName: string): Promise<void>;
23
+ dropFields(table: Table): Promise<void>;
24
+ dropIndexes(table: Table, constraintIndexes: number[]): Promise<void>;
25
+ syncConstraints(table: Table): Promise<void>;
26
+ syncDataBase(): Promise<void>;
27
+ fieldType(attribute: Attribute<Natural, unknown>): string[];
28
+ syncFields(table: Table): Promise<void>;
29
+ syncIndexes(table: Table): Promise<void>;
30
+ syncSequence(table: Table): Promise<void>;
31
+ syncTable(table: Table): Promise<void>;
32
+ }
33
+ export declare class TransactionPG extends Transaction {
34
+ client: PoolClient;
35
+ released: boolean;
36
+ constructor(client: PoolClient);
37
+ private release;
38
+ commit(): Promise<void>;
39
+ rollback(): Promise<void>;
40
+ }
@@ -3,10 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.PGDB = void 0;
6
+ exports.TransactionPG = exports.PGDB = void 0;
7
7
  const pg_1 = require("pg");
8
8
  const pg_format_1 = __importDefault(require("pg-format"));
9
- const db_1 = require("sedentary/lib/db");
9
+ const db_1 = require("sedentary/db");
10
10
  const adsrc_1 = require("./adsrc");
11
11
  const needDrop = [
12
12
  ["DATETIME", "int2"],
@@ -20,18 +20,141 @@ const needUsing = [
20
20
  ["INT", "varchar"],
21
21
  ["INT8", "varchar"]
22
22
  ];
23
- const types = { int2: "SMALLINT", int4: "INTEGER", int8: "BIGINT", varchar: "VARCHAR" };
23
+ const types = { int2: "SMALLINT", int4: "INTEGER", int8: "BIGINT", timestamptz: "DATETIME", varchar: "VARCHAR" };
24
24
  const actions = { cascade: "c", "no action": "a", restrict: "r", "set default": "d", "set null": "n" };
25
25
  class PGDB extends db_1.DB {
26
26
  constructor(connection, log) {
27
27
  super(log);
28
+ this.oidLoad = {};
29
+ this.client = {};
30
+ this.indexes = [];
28
31
  this.pool = new pg_1.Pool(connection);
32
+ this.version = 0;
29
33
  }
30
34
  async connect() {
31
35
  this.client = await this.pool.connect();
32
36
  const res = await this.client.query("SELECT version()");
33
37
  this.version = parseInt(res.rows[0].version.split(" ")[1].split(".")[0], 10);
34
38
  }
39
+ async end() {
40
+ if (this.client.release)
41
+ this.client.release();
42
+ await this.pool.end();
43
+ }
44
+ defaultNeq(src, value) {
45
+ if (src === value)
46
+ return false;
47
+ return src.split("::")[0] !== value;
48
+ }
49
+ async begin() {
50
+ const ret = new TransactionPG(await this.pool.connect());
51
+ await ret.client.query("BEGIN");
52
+ return ret;
53
+ }
54
+ escape(value) {
55
+ if (value === null || value === undefined)
56
+ throw new Error("SedentaryPG: Can't escape null nor undefined values; use the 'IS NULL' operator instead");
57
+ const type = typeof value;
58
+ if (type === "number" || type === "boolean")
59
+ return value.toString();
60
+ if (type === "string")
61
+ return (0, pg_format_1.default)("%L", value);
62
+ if (value instanceof Date)
63
+ return (0, pg_format_1.default)("%L", value).replace(/\.\d\d\d\+/, "+");
64
+ return (0, pg_format_1.default)("%L", JSON.stringify(value));
65
+ }
66
+ fill(attributes, row, entry) {
67
+ const loaded = {};
68
+ for (const attribute in attributes)
69
+ entry[attribute] = loaded[attribute] = row[attributes[attribute]];
70
+ Object.defineProperty(entry, "loaded", { configurable: true, value: loaded });
71
+ }
72
+ load(tableName, attributes, pk, model, table) {
73
+ const pkFldName = pk.fieldName;
74
+ return async (where, order, tx) => {
75
+ const { oid } = table;
76
+ const ret = [];
77
+ const client = tx ? tx.client : await this.pool.connect();
78
+ const oidPK = {};
79
+ try {
80
+ const query = `SELECT *, tableoid FROM ${tableName}${where ? ` WHERE ${where}` : ""}${order && order.length ? ` ORDER BY ${order.map(_ => (_.startsWith("-") ? `${_.substring(1)} DESC` : _)).join(",")}` : ""}`;
81
+ this.log(query);
82
+ const res = await client.query(query);
83
+ for (const row of res.rows) {
84
+ if (row.tableoid === oid) {
85
+ const entry = new model("load");
86
+ this.fill(attributes, row, entry);
87
+ ret.push(entry);
88
+ entry.postLoad();
89
+ }
90
+ else {
91
+ if (!oidPK[row.tableoid])
92
+ oidPK[row.tableoid] = [];
93
+ oidPK[row.tableoid].push([ret.length, row[pkFldName]]);
94
+ ret.push(null);
95
+ }
96
+ }
97
+ for (const oid in oidPK) {
98
+ const res = await this.oidLoad[oid](oidPK[oid].map(_ => _[1]));
99
+ for (const entry of res)
100
+ for (const [id] of oidPK[oid])
101
+ ret[id] = entry;
102
+ }
103
+ }
104
+ finally {
105
+ if (!tx)
106
+ client.release();
107
+ }
108
+ return ret;
109
+ };
110
+ }
111
+ save(tableName, attributes, pk) {
112
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
113
+ const self = this;
114
+ const pkAttrnName = pk.attributeName;
115
+ const pkFldName = pk.fieldName;
116
+ return async function () {
117
+ let changed = false;
118
+ const client = this.tx ? this.tx.client : await self.pool.connect();
119
+ try {
120
+ const { loaded } = this;
121
+ if (loaded) {
122
+ const actions = [];
123
+ for (const attribute in attributes) {
124
+ const value = this[attribute];
125
+ if (value !== loaded[attribute])
126
+ actions.push(`${attributes[attribute]} = ${self.escape(value)}`);
127
+ }
128
+ if (actions.length) {
129
+ const query = `UPDATE ${tableName} SET ${actions.join(", ")} WHERE ${pkFldName} = ${self.escape(this[pkAttrnName])}`;
130
+ self.log(query);
131
+ self.fill(attributes, (await client.query(query + " RETURNING *")).rows[0], this);
132
+ changed = true;
133
+ }
134
+ }
135
+ else {
136
+ const fields = [];
137
+ const values = [];
138
+ for (const attribute in attributes) {
139
+ const value = this[attribute];
140
+ if (value !== null && value !== undefined) {
141
+ fields.push(attributes[attribute]);
142
+ values.push(self.escape(value));
143
+ }
144
+ }
145
+ const query = fields.length ? `INSERT INTO ${tableName} (${fields.join(", ")}) VALUES (${values.join(", ")})` : `INSERT INTO ${tableName} DEFAULT VALUES`;
146
+ self.log(query);
147
+ self.fill(attributes, (await client.query(query + " RETURNING *")).rows[0], this);
148
+ changed = true;
149
+ }
150
+ }
151
+ finally {
152
+ if (!this.tx)
153
+ client.release();
154
+ }
155
+ return changed;
156
+ };
157
+ }
35
158
  async dropConstraints(table) {
36
159
  const indexes = [];
37
160
  const res = await this.client.query("SELECT confdeltype, confupdtype, conindid, conname, contype FROM pg_constraint WHERE conrelid = $1 ORDER BY conname", [table.oid]);
@@ -96,37 +219,6 @@ class PGDB extends db_1.DB {
96
219
  await this.client.query(statement);
97
220
  }
98
221
  }
99
- async end() {
100
- await this.pool.end();
101
- }
102
- fieldType(attribute) {
103
- const { size, type } = attribute;
104
- let ret;
105
- switch (type) {
106
- case "DATETIME":
107
- return ["DATETIME", "TIMESTAMP (3) WITH TIME ZONE"];
108
- case "INT":
109
- ret = size === 2 ? "SMALLINT" : "INTEGER";
110
- return [ret, ret];
111
- case "INT8":
112
- return ["BIGINT", "BIGINT"];
113
- case "VARCHAR":
114
- return ["VARCHAR", "VARCHAR" + (size ? `(${size})` : "")];
115
- }
116
- throw new Error(`Unknown type: '${type}', '${size}'`);
117
- }
118
- async syncDataBase() {
119
- let err;
120
- try {
121
- await super.syncDataBase();
122
- }
123
- catch (e) {
124
- err = e;
125
- }
126
- this.client.release();
127
- if (err)
128
- throw err;
129
- }
130
222
  async syncConstraints(table) {
131
223
  for (const constraint of table.constraints) {
132
224
  const { attribute, constraintName, type } = constraint;
@@ -151,11 +243,32 @@ class PGDB extends db_1.DB {
151
243
  }
152
244
  }
153
245
  }
246
+ async syncDataBase() {
247
+ await super.syncDataBase();
248
+ for (const table of this.tables)
249
+ this.oidLoad[table.oid || 0] = (ids) => table.model.load({ [table.pk.attributeName]: ["IN", ids] });
250
+ }
251
+ fieldType(attribute) {
252
+ const { size, type } = attribute;
253
+ let ret;
254
+ switch (type) {
255
+ case "DATETIME":
256
+ return ["DATETIME", "TIMESTAMP (3) WITH TIME ZONE"];
257
+ case "INT":
258
+ ret = size === 2 ? "SMALLINT" : "INTEGER";
259
+ return [ret, ret];
260
+ case "INT8":
261
+ return ["BIGINT", "BIGINT"];
262
+ case "VARCHAR":
263
+ return ["VARCHAR", "VARCHAR" + (size ? `(${size})` : "")];
264
+ }
265
+ throw new Error(`Unknown type: '${type}', '${size}'`);
266
+ }
154
267
  async syncFields(table) {
155
- const { attributes, oid, tableName } = table;
268
+ const { attributes, autoIncrement, oid, tableName } = table;
156
269
  for (const attribute of attributes) {
157
270
  const { fieldName, notNull, size } = attribute;
158
- const defaultValue = attribute.defaultValue === undefined ? undefined : (0, pg_format_1.default)("%L", attribute.defaultValue);
271
+ const defaultValue = attribute.defaultValue === undefined ? (autoIncrement && fieldName === "id" ? `nextval('${tableName}_id_seq'::regclass)` : undefined) : this.escape(attribute.defaultValue);
159
272
  const [base, type] = this.fieldType(attribute);
160
273
  const where = "attrelid = $1 AND attnum > 0 AND atttypid = pg_type.oid AND attislocal = 't' AND attname = $2";
161
274
  const res = await this.client.query(`SELECT attnotnull, atttypmod, typname, ${(0, adsrc_1.adsrc)(this.version)} FROM pg_type, pg_attribute LEFT JOIN pg_attrdef ON adrelid = attrelid AND adnum = attnum WHERE ${where}`, [oid, fieldName]);
@@ -171,32 +284,32 @@ class PGDB extends db_1.DB {
171
284
  if (this.sync)
172
285
  await this.client.query(statement);
173
286
  };
174
- const setNotNull = async (isNull) => {
175
- if (isNull === notNull)
287
+ const setNotNull = async (isNotNull) => {
288
+ if (isNotNull === notNull)
176
289
  return;
177
290
  const statement = `ALTER TABLE ${tableName} ALTER COLUMN ${fieldName} ${notNull ? "SET" : "DROP"} NOT NULL`;
178
291
  this.syncLog(statement);
179
292
  if (this.sync)
180
293
  await this.client.query(statement);
181
294
  };
182
- const setDefault = async (isNull) => {
295
+ const setDefault = async (isNotNull, create) => {
183
296
  if (defaultValue !== undefined) {
184
297
  let statement = `ALTER TABLE ${tableName} ALTER COLUMN ${fieldName} SET DEFAULT ${defaultValue}`;
185
298
  this.syncLog(statement);
186
299
  if (this.sync)
187
300
  await this.client.query(statement);
188
- if (isNull) {
301
+ if (!isNotNull && !create) {
189
302
  statement = `UPDATE ${tableName} SET ${fieldName} = ${defaultValue} WHERE ${fieldName} IS NULL`;
190
303
  this.syncLog(statement);
191
304
  if (this.sync)
192
305
  this.client.query(statement);
193
306
  }
194
307
  }
195
- await setNotNull(isNull);
308
+ await setNotNull(isNotNull);
196
309
  };
197
310
  if (!res.rowCount) {
198
311
  await addField();
199
- await setDefault(false);
312
+ await setDefault(false, true);
200
313
  }
201
314
  else {
202
315
  const { adsrc, attnotnull, atttypmod, typname } = res.rows[0];
@@ -204,7 +317,7 @@ class PGDB extends db_1.DB {
204
317
  if (needDrop.filter(([type, name]) => attribute.type === type && typname === name).length) {
205
318
  await this.dropField(tableName, fieldName);
206
319
  await addField();
207
- await setDefault(false);
320
+ await setDefault(false, true);
208
321
  }
209
322
  else {
210
323
  if (adsrc)
@@ -214,7 +327,7 @@ class PGDB extends db_1.DB {
214
327
  this.syncLog(statement);
215
328
  if (this.sync)
216
329
  await this.client.query(statement);
217
- await setDefault(attnotnull);
330
+ await setDefault(attnotnull, false);
218
331
  }
219
332
  }
220
333
  else if (defaultValue === undefined) {
@@ -222,8 +335,8 @@ class PGDB extends db_1.DB {
222
335
  dropDefault();
223
336
  await setNotNull(attnotnull);
224
337
  }
225
- else if (!adsrc || adsrc.split("::")[0] !== defaultValue)
226
- await setDefault(attnotnull);
338
+ else if (!adsrc || this.defaultNeq(adsrc, defaultValue))
339
+ await setDefault(attnotnull, false);
227
340
  }
228
341
  }
229
342
  }
@@ -255,9 +368,9 @@ class PGDB extends db_1.DB {
255
368
  await this.client.query(`SELECT currval('${table.tableName}_id_seq')`);
256
369
  }
257
370
  catch (e) {
258
- if (e.code === "55000")
371
+ if (e instanceof pg_1.DatabaseError && e.code === "55000")
259
372
  return;
260
- if (e.code === "42P01") {
373
+ if (e instanceof pg_1.DatabaseError && e.code === "42P01") {
261
374
  const statement = `CREATE SEQUENCE ${table.tableName}_id_seq`;
262
375
  this.syncLog(statement);
263
376
  if (this.sync)
@@ -269,11 +382,11 @@ class PGDB extends db_1.DB {
269
382
  }
270
383
  })();
271
384
  }
272
- let create;
385
+ let create = false;
273
386
  const resTable = await this.client.query("SELECT oid FROM pg_class WHERE relname = $1", [table.tableName]);
274
387
  if (resTable.rowCount) {
275
388
  table.oid = resTable.rows[0].oid;
276
- let drop;
389
+ let drop = false;
277
390
  const resParent = await this.client.query("SELECT inhparent FROM pg_inherits WHERE inhrelid = $1", [table.oid]);
278
391
  if (resParent.rowCount) {
279
392
  if (!table.parent)
@@ -306,4 +419,34 @@ class PGDB extends db_1.DB {
306
419
  }
307
420
  }
308
421
  exports.PGDB = PGDB;
309
- // farray[0].defaultValue = "nextval('" + tname + "_id_seq'::regclass)";
422
+ class TransactionPG extends db_1.Transaction {
423
+ constructor(client) {
424
+ super();
425
+ this.released = false;
426
+ this.client = client;
427
+ }
428
+ release() {
429
+ this.released = true;
430
+ this.client.release();
431
+ }
432
+ async commit() {
433
+ if (!this.released) {
434
+ await this.client.query("COMMIT");
435
+ this.release();
436
+ super.commit();
437
+ }
438
+ }
439
+ async rollback() {
440
+ try {
441
+ if (!this.released) {
442
+ super.rollback();
443
+ await this.client.query("ROLLBACK");
444
+ }
445
+ }
446
+ finally {
447
+ if (!this.released)
448
+ this.release();
449
+ }
450
+ }
451
+ }
452
+ exports.TransactionPG = TransactionPG;
package/lib/adsrc.ts DELETED
@@ -1,3 +0,0 @@
1
- export function adsrc(version: number) {
2
- return version >= 12 ? "pg_get_expr(pg_attrdef.adbin, pg_attrdef.adrelid) AS adsrc" : "adsrc";
3
- }
package/lib/pgdb.d.ts DELETED
@@ -1,22 +0,0 @@
1
- import { PoolConfig } from "pg";
2
- import { Attribute, DB, Natural, Table } from "sedentary/lib/db";
3
- export declare class PGDB extends DB {
4
- private client;
5
- private indexes;
6
- private pool;
7
- private version;
8
- constructor(connection: PoolConfig, log: (message: string) => void);
9
- connect(): Promise<void>;
10
- dropConstraints(table: Table): Promise<number[]>;
11
- dropField(tableName: string, fieldName: string): Promise<void>;
12
- dropFields(table: Table): Promise<void>;
13
- dropIndexes(table: Table, constraintIndexes: number[]): Promise<void>;
14
- end(): Promise<void>;
15
- fieldType(attribute: Attribute<Natural, unknown>): string[];
16
- syncDataBase(): Promise<void>;
17
- syncConstraints(table: Table): Promise<void>;
18
- syncFields(table: Table): Promise<void>;
19
- syncIndexes(table: Table): Promise<void>;
20
- syncSequence(table: Table): Promise<void>;
21
- syncTable(table: Table): Promise<void>;
22
- }
package/lib/pgdb.ts DELETED
@@ -1,351 +0,0 @@
1
- import { Pool, PoolClient, PoolConfig } from "pg";
2
- import format from "pg-format";
3
- import { Attribute, DB, Index, Natural, ForeignKeyActions, Table } from "sedentary/lib/db";
4
- import { adsrc } from "./adsrc";
5
-
6
- const needDrop = [
7
- ["DATETIME", "int2"],
8
- ["DATETIME", "int4"],
9
- ["DATETIME", "int8"],
10
- ["INT", "timestamptz"],
11
- ["INT8", "timestamptz"]
12
- ];
13
- const needUsing = [
14
- ["DATETIME", "varchar"],
15
- ["INT", "varchar"],
16
- ["INT8", "varchar"]
17
- ];
18
- const types = { int2: "SMALLINT", int4: "INTEGER", int8: "BIGINT", varchar: "VARCHAR" };
19
-
20
- const actions: { [k in ForeignKeyActions]: string } = { cascade: "c", "no action": "a", restrict: "r", "set default": "d", "set null": "n" };
21
-
22
- export class PGDB extends DB {
23
- private client: PoolClient;
24
- private indexes: string[];
25
- private pool: Pool;
26
- private version: number;
27
-
28
- constructor(connection: PoolConfig, log: (message: string) => void) {
29
- super(log);
30
-
31
- this.pool = new Pool(connection);
32
- }
33
-
34
- async connect(): Promise<void> {
35
- this.client = await this.pool.connect();
36
-
37
- const res = await this.client.query("SELECT version()");
38
-
39
- this.version = parseInt(res.rows[0].version.split(" ")[1].split(".")[0], 10);
40
- }
41
-
42
- async dropConstraints(table: Table): Promise<number[]> {
43
- const indexes: number[] = [];
44
- const res = await this.client.query("SELECT confdeltype, confupdtype, conindid, conname, contype FROM pg_constraint WHERE conrelid = $1 ORDER BY conname", [table.oid]);
45
-
46
- for(const row of res.rows) {
47
- const arr = table.constraints.filter(_ => _.constraintName === row.conname && _.type === row.contype);
48
- let drop = false;
49
-
50
- if(arr.length === 0) drop = true;
51
- else if(row.contype === "u") indexes.push(row.conindid);
52
- else {
53
- const { options } = arr[0].attribute.foreignKey;
54
-
55
- if(actions[options.onDelete] !== row.confdeltype || actions[options.onUpdate] !== row.confupdtype) drop = true;
56
- }
57
-
58
- if(drop) {
59
- const statement = `ALTER TABLE ${table.tableName} DROP CONSTRAINT ${row.conname} CASCADE`;
60
-
61
- this.syncLog(statement);
62
- if(this.sync) await this.client.query(statement);
63
- }
64
- }
65
-
66
- return indexes;
67
- }
68
-
69
- async dropField(tableName: string, fieldName: string): Promise<void> {
70
- const statement = `ALTER TABLE ${tableName} DROP COLUMN ${fieldName}`;
71
-
72
- this.syncLog(statement);
73
- if(this.sync) await this.client.query(statement);
74
- }
75
-
76
- async dropFields(table: Table): Promise<void> {
77
- 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]);
78
-
79
- for(const i in res.rows) if(! table.findField(res.rows[i].attname)) await this.dropField(table.tableName, res.rows[i].attname);
80
- }
81
-
82
- async dropIndexes(table: Table, constraintIndexes: number[]): Promise<void> {
83
- const { indexes, oid } = table;
84
- const iobject: { [key: string]: Index } = {};
85
- const res = await this.client.query(
86
- "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",
87
- [oid]
88
- );
89
-
90
- for(const row of res.rows) {
91
- const { amname, attname, indexrelid, indisunique, relname } = row;
92
-
93
- if(! constraintIndexes.includes(indexrelid)) {
94
- if(iobject[relname]) iobject[relname].fields.push(attname);
95
- else iobject[relname] = { fields: [attname], indexName: relname, type: amname, unique: indisunique };
96
- }
97
- }
98
-
99
- this.indexes = [];
100
- for(const index of indexes) {
101
- const { indexName } = index;
102
-
103
- if(iobject[indexName] && this.indexesEq(index, iobject[indexName])) {
104
- this.indexes.push(indexName);
105
- delete iobject[indexName];
106
- }
107
- }
108
-
109
- for(const index of Object.keys(iobject).sort()) {
110
- const statement = `DROP INDEX ${index}`;
111
-
112
- this.syncLog(statement);
113
- if(this.sync) await this.client.query(statement);
114
- }
115
- }
116
-
117
- async end(): Promise<void> {
118
- await this.pool.end();
119
- }
120
-
121
- fieldType(attribute: Attribute<Natural, unknown>): string[] {
122
- const { size, type } = attribute;
123
- let ret;
124
-
125
- switch(type) {
126
- case "DATETIME":
127
- return ["DATETIME", "TIMESTAMP (3) WITH TIME ZONE"];
128
- case "INT":
129
- ret = size === 2 ? "SMALLINT" : "INTEGER";
130
-
131
- return [ret, ret];
132
- case "INT8":
133
- return ["BIGINT", "BIGINT"];
134
- case "VARCHAR":
135
- return ["VARCHAR", "VARCHAR" + (size ? `(${size})` : "")];
136
- }
137
-
138
- throw new Error(`Unknown type: '${type}', '${size}'`);
139
- }
140
-
141
- async syncDataBase(): Promise<void> {
142
- let err: Error;
143
-
144
- try {
145
- await super.syncDataBase();
146
- } catch(e) {
147
- err = e;
148
- }
149
-
150
- this.client.release();
151
-
152
- if(err) throw err;
153
- }
154
-
155
- async syncConstraints(table: Table): Promise<void> {
156
- for(const constraint of table.constraints) {
157
- const { attribute, constraintName, type } = constraint;
158
- 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", [
159
- table.oid,
160
- attribute.fieldName
161
- ]);
162
-
163
- if(! res.rowCount) {
164
- let query: string;
165
-
166
- if(type === "f") {
167
- const { fieldName, options, tableName } = attribute.foreignKey;
168
- const onDelete = options.onDelete !== "no action" ? ` ON DELETE ${options.onDelete.toUpperCase()}` : "";
169
- const onUpdate = options.onUpdate !== "no action" ? ` ON UPDATE ${options.onUpdate.toUpperCase()}` : "";
170
-
171
- query = `FOREIGN KEY (${attribute.fieldName}) REFERENCES ${tableName}(${fieldName})${onDelete}${onUpdate}`;
172
- } else query = `UNIQUE(${attribute.fieldName})`;
173
-
174
- const statement = `ALTER TABLE ${table.tableName} ADD CONSTRAINT ${constraintName} ${query}`;
175
-
176
- this.syncLog(statement);
177
- if(this.sync) await this.client.query(statement);
178
- }
179
- }
180
- }
181
-
182
- async syncFields(table: Table): Promise<void> {
183
- const { attributes, oid, tableName } = table;
184
-
185
- for(const attribute of attributes) {
186
- const { fieldName, notNull, size } = attribute;
187
- const defaultValue = attribute.defaultValue === undefined ? undefined : format("%L", attribute.defaultValue);
188
- const [base, type] = this.fieldType(attribute);
189
- const where = "attrelid = $1 AND attnum > 0 AND atttypid = pg_type.oid AND attislocal = 't' AND attname = $2";
190
-
191
- const res = await this.client.query(
192
- `SELECT attnotnull, atttypmod, typname, ${adsrc(this.version)} FROM pg_type, pg_attribute LEFT JOIN pg_attrdef ON adrelid = attrelid AND adnum = attnum WHERE ${where}`,
193
- [oid, fieldName]
194
- );
195
-
196
- const addField = async () => {
197
- const statement = `ALTER TABLE ${tableName} ADD COLUMN ${fieldName} ${type}`;
198
-
199
- this.syncLog(statement);
200
- if(this.sync) await this.client.query(statement);
201
- };
202
-
203
- const dropDefault = async () => {
204
- const statement = `ALTER TABLE ${tableName} ALTER COLUMN ${fieldName} DROP DEFAULT`;
205
-
206
- this.syncLog(statement);
207
- if(this.sync) await this.client.query(statement);
208
- };
209
-
210
- const setNotNull = async (isNull: boolean) => {
211
- if(isNull === notNull) return;
212
-
213
- const statement = `ALTER TABLE ${tableName} ALTER COLUMN ${fieldName} ${notNull ? "SET" : "DROP"} NOT NULL`;
214
-
215
- this.syncLog(statement);
216
- if(this.sync) await this.client.query(statement);
217
- };
218
-
219
- const setDefault = async (isNull: boolean) => {
220
- if(defaultValue !== undefined) {
221
- let statement = `ALTER TABLE ${tableName} ALTER COLUMN ${fieldName} SET DEFAULT ${defaultValue}`;
222
-
223
- this.syncLog(statement);
224
- if(this.sync) await this.client.query(statement);
225
-
226
- if(isNull) {
227
- statement = `UPDATE ${tableName} SET ${fieldName} = ${defaultValue} WHERE ${fieldName} IS NULL`;
228
-
229
- this.syncLog(statement);
230
- if(this.sync) this.client.query(statement);
231
- }
232
- }
233
-
234
- await setNotNull(isNull);
235
- };
236
-
237
- if(! res.rowCount) {
238
- await addField();
239
- await setDefault(false);
240
- } else {
241
- const { adsrc, attnotnull, atttypmod, typname } = res.rows[0];
242
-
243
- if(types[typname] !== base || (base === "VARCHAR" && (size ? size + 4 !== atttypmod : atttypmod !== -1))) {
244
- if(needDrop.filter(([type, name]) => attribute.type === type && typname === name).length) {
245
- await this.dropField(tableName, fieldName);
246
- await addField();
247
- await setDefault(false);
248
- } else {
249
- if(adsrc) dropDefault();
250
-
251
- const using = needUsing.filter(([type, name]) => attribute.type === type && typname === name).length ? " USING " + fieldName + "::" + type : "";
252
- const statement = `ALTER TABLE ${tableName} ALTER COLUMN ${fieldName} TYPE ${type}${using}`;
253
-
254
- this.syncLog(statement);
255
- if(this.sync) await this.client.query(statement);
256
- await setDefault(attnotnull);
257
- }
258
- } else if(defaultValue === undefined) {
259
- if(adsrc) dropDefault();
260
- await setNotNull(attnotnull);
261
- } else if(! adsrc || adsrc.split("::")[0] !== defaultValue) await setDefault(attnotnull);
262
- }
263
- }
264
- }
265
-
266
- async syncIndexes(table: Table): Promise<void> {
267
- const { indexes, tableName } = table;
268
-
269
- for(const index of indexes) {
270
- const { fields, indexName, type, unique } = index;
271
-
272
- if(! this.indexes.includes(indexName)) {
273
- const statement = `CREATE${unique ? " UNIQUE" : ""} INDEX ${indexName} ON ${tableName} USING ${type} (${fields.join(", ")})`;
274
-
275
- this.syncLog(statement);
276
- if(this.sync) await this.client.query(statement);
277
- }
278
- }
279
- }
280
-
281
- async syncSequence(table: Table): Promise<void> {
282
- if(! table.autoIncrementOwn) return;
283
-
284
- const statement = `ALTER SEQUENCE ${table.tableName}_id_seq OWNED BY ${table.tableName}.id`;
285
-
286
- this.syncLog(statement);
287
- if(this.sync) await this.client.query(statement);
288
- }
289
-
290
- async syncTable(table: Table): Promise<void> {
291
- if(table.autoIncrement) {
292
- await (async () => {
293
- try {
294
- await this.client.query(`SELECT currval('${table.tableName}_id_seq')`);
295
- } catch(e) {
296
- if(e.code === "55000") return;
297
- if(e.code === "42P01") {
298
- const statement = `CREATE SEQUENCE ${table.tableName}_id_seq`;
299
-
300
- this.syncLog(statement);
301
- if(this.sync) await this.client.query(statement);
302
- table.autoIncrementOwn = true;
303
-
304
- return;
305
- }
306
-
307
- throw e;
308
- }
309
- })();
310
- }
311
-
312
- let create: boolean;
313
- const resTable = await this.client.query("SELECT oid FROM pg_class WHERE relname = $1", [table.tableName]);
314
-
315
- if(resTable.rowCount) {
316
- table.oid = resTable.rows[0].oid;
317
-
318
- let drop: boolean;
319
- const resParent = await this.client.query("SELECT inhparent FROM pg_inherits WHERE inhrelid = $1", [table.oid]);
320
-
321
- if(resParent.rowCount) {
322
- if(! table.parent) drop = true;
323
- else if(this.findTable(table.parent.tableName).oid === resParent.rows[0].inhparent) return;
324
-
325
- drop = true;
326
- } else if(table.parent) drop = true;
327
-
328
- if(drop) {
329
- const statement = `DROP TABLE ${table.tableName} CASCADE`;
330
-
331
- create = true;
332
- this.syncLog(statement);
333
- if(this.sync) await this.client.query(statement);
334
- }
335
- } else create = true;
336
-
337
- if(create) {
338
- const parent = table.parent ? ` INHERITS (${table.parent.tableName})` : "";
339
- const statement = `CREATE TABLE ${table.tableName} ()${parent}`;
340
-
341
- this.syncLog(statement);
342
- if(this.sync) await this.client.query(statement);
343
-
344
- const resTable = await this.client.query("SELECT oid FROM pg_class WHERE relname = $1", [table.tableName]);
345
-
346
- table.oid = resTable.rows[0]?.oid;
347
- }
348
- }
349
- }
350
-
351
- // farray[0].defaultValue = "nextval('" + tname + "_id_seq'::regclass)";