pogi 2.11.0 → 3.0.0-beta3

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.
Files changed (82) hide show
  1. package/.env +5 -0
  2. package/.vscode/launch.json +47 -15
  3. package/jest.config.js +23 -0
  4. package/lib/bin/generateInterface.js +3 -3
  5. package/lib/bin/generateInterface.js.map +1 -1
  6. package/lib/connectionOptions.d.ts +7 -0
  7. package/lib/index.d.ts +1 -1
  8. package/lib/pgConverters.d.ts +9 -10
  9. package/lib/pgConverters.js +44 -39
  10. package/lib/pgConverters.js.map +1 -1
  11. package/lib/pgConverters.test.d.ts +1 -0
  12. package/lib/pgConverters.test.js +13 -0
  13. package/lib/pgConverters.test.js.map +1 -0
  14. package/lib/pgDb.d.ts +23 -31
  15. package/lib/pgDb.js +122 -103
  16. package/lib/pgDb.js.map +1 -1
  17. package/lib/pgDb.test.d.ts +1 -0
  18. package/lib/pgDb.test.js +1126 -0
  19. package/lib/pgDb.test.js.map +1 -0
  20. package/lib/pgDbInterface.d.ts +22 -0
  21. package/lib/pgDbInterface.js +11 -0
  22. package/lib/pgDbInterface.js.map +1 -0
  23. package/lib/pgDbOperators.d.ts +3 -3
  24. package/lib/pgDbOperators.js +4 -7
  25. package/lib/pgDbOperators.js.map +1 -1
  26. package/lib/pgDbOperators.test.d.ts +1 -0
  27. package/lib/pgDbOperators.test.js +313 -0
  28. package/lib/pgDbOperators.test.js.map +1 -0
  29. package/lib/pgSchema.d.ts +2 -3
  30. package/lib/pgSchema.js.map +1 -1
  31. package/lib/pgSchemaInterface.d.ts +0 -0
  32. package/lib/pgSchemaInterface.js +2 -0
  33. package/lib/pgSchemaInterface.js.map +1 -0
  34. package/lib/pgTable.d.ts +13 -38
  35. package/lib/pgTable.js +54 -54
  36. package/lib/pgTable.js.map +1 -1
  37. package/lib/pgTableInterface.d.ts +28 -0
  38. package/lib/pgTableInterface.js +4 -0
  39. package/lib/pgTableInterface.js.map +1 -0
  40. package/lib/pgUtils.d.ts +16 -6
  41. package/lib/pgUtils.js +162 -31
  42. package/lib/pgUtils.js.map +1 -1
  43. package/lib/queryAble.d.ts +16 -53
  44. package/lib/queryAble.js +58 -50
  45. package/lib/queryAble.js.map +1 -1
  46. package/lib/queryAbleInterface.d.ts +55 -0
  47. package/lib/queryAbleInterface.js +7 -0
  48. package/lib/queryAbleInterface.js.map +1 -0
  49. package/lib/queryWhere.d.ts +2 -2
  50. package/lib/queryWhere.js +19 -23
  51. package/lib/queryWhere.js.map +1 -1
  52. package/lib/test/pgDbOperatorSpec.d.ts +1 -0
  53. package/lib/test/pgDbOperatorSpec.js +326 -0
  54. package/lib/test/pgDbOperatorSpec.js.map +1 -0
  55. package/lib/test/pgDbSpec.d.ts +1 -0
  56. package/lib/test/pgDbSpec.js +1139 -0
  57. package/lib/test/pgDbSpec.js.map +1 -0
  58. package/lib/test/pgServiceRestartTest.d.ts +1 -0
  59. package/lib/test/pgServiceRestartTest.js +1532 -0
  60. package/lib/test/pgServiceRestartTest.js.map +1 -0
  61. package/package.json +21 -14
  62. package/{src/tsconfig.json → tsconfig.json} +11 -11
  63. package/spec/resources/init.sql +0 -122
  64. package/spec/resources/throw_exception.sql +0 -5
  65. package/spec/resources/tricky.sql +0 -13
  66. package/spec/run.js +0 -5
  67. package/spec/support/jasmine.json +0 -9
  68. package/src/bin/generateInterface.ts +0 -54
  69. package/src/connectionOptions.ts +0 -42
  70. package/src/index.ts +0 -6
  71. package/src/pgConverters.ts +0 -55
  72. package/src/pgDb.ts +0 -820
  73. package/src/pgDbLogger.ts +0 -13
  74. package/src/pgDbOperators.ts +0 -62
  75. package/src/pgSchema.ts +0 -15
  76. package/src/pgTable.ts +0 -401
  77. package/src/pgUtils.ts +0 -176
  78. package/src/queryAble.ts +0 -393
  79. package/src/queryWhere.ts +0 -326
  80. package/src/test/pgDbOperatorSpec.ts +0 -492
  81. package/src/test/pgDbSpec.ts +0 -1339
  82. package/src/test/pgServiceRestartTest.ts +0 -1500
package/src/pgDb.ts DELETED
@@ -1,820 +0,0 @@
1
- import { QueryAble, ResultFieldType } from "./queryAble";
2
- import { PgTable } from "./pgTable";
3
- import { PgSchema } from "./pgSchema";
4
- import * as PgConverters from "./pgConverters";
5
- import { pgUtils } from "./pgUtils";
6
- import * as _ from 'lodash';
7
- import * as pg from 'pg';
8
- import * as readline from 'readline';
9
- import * as fs from 'fs';
10
- import { PgDbLogger } from './pgDbLogger';
11
- import { ConnectionOptions } from './connectionOptions';
12
- import * as EventEmitter from 'events';
13
-
14
- const CONNECTION_URL_REGEXP = /^postgres:\/\/(?:([^:]+)(?::([^@]*))?@)?([^\/:]+)?(?::([^\/]+))?\/(.*)$/;
15
- const SQL_TOKENIZER_REGEXP = /''|'|""|"|;|\$|--|\/\*|\*\/|(.+?)/g;
16
- const SQL_$_ESCAPE_REGEXP = /\$[^$]*\$/g;
17
-
18
- /** looks like we only get back those that we have access to */
19
- const LIST_SCHEMAS_TABLES =
20
- `SELECT table_schema as schema, table_name as name
21
- FROM information_schema.tables
22
- WHERE table_schema NOT IN ('pg_catalog', 'pg_constraint', 'information_schema')`;
23
- const GET_OID_FOR_COLUMN_TYPE_FOR_SCHEMA = "SELECT t.oid FROM pg_catalog.pg_type t, pg_namespace n WHERE typname=:typeName and n.oid=t.typnamespace and n.nspname=:schemaName;";
24
- const GET_OID_FOR_COLUMN_TYPE = "SELECT t.oid FROM pg_catalog.pg_type t WHERE typname=:typeName";
25
-
26
- /** looks like we only get back those that we have access to */
27
- const GET_SCHEMAS_PROCEDURES = `SELECT
28
- n.nspname as "schema",
29
- p.proname as "name",
30
- (not p.proretset) as "return_single_row",
31
- (t.typtype in ('b', 'd', 'e', 'r')) as "return_single_value"
32
- FROM pg_proc p
33
- inner join pg_namespace n on (p.pronamespace = n.oid)
34
- inner join pg_type t on (p.prorettype = t.oid)
35
- left outer join pg_trigger tr on (tr.tgfoid = p.oid)
36
- WHERE n.nspname NOT IN ('pg_catalog', 'pg_constraint', 'information_schema')
37
- AND tr.oid is null;`;
38
- const GET_CURRENT_SCHEMAS = "SELECT current_schemas(false)";
39
- //const LIST_ARRAY_TYPE_FIELDS = 'SELECT a.atttypid as oid FROM pg_attribute a WHERE a.attndims>0 AND a.atttypid>200000';
40
- //const LIST_ARRAY_TYPE_FIELDS = 'SELECT a.atttypid as type_oid FROM pg_attribute a WHERE a.tttypid in (1005,1007,1016,1021,1022) OR (a.attndims>0 AND a.atttypid>200000)';
41
-
42
- /*
43
- SELECT c.nspname as schema_name, b.relname as table_name, a.attname as column_name, a.atttypid as type_oid, format_type(a.atttypid, a.atttypmod)
44
- FROM pg_attribute a
45
- JOIN pg_class b ON (a.attrelid = b.relfilenode)
46
- JOIN pg_namespace c ON (b.relnamespace=c.oid)
47
- WHERE a.attndims>0 AND a.atttypid>200000;
48
- */
49
-
50
- /*
51
- SELECT * FROM pg_catalog.pg_type t where t.typname like '%tz';
52
- SELECT t.oid FROM pg_catalog.pg_type t WHERE t.typname in ('timestamptz', 'timetz');
53
-
54
- reltype -> only include the table columns (this is zero for indexes)
55
- a.attndims>0 -> not reliable (truncate/create table like.. not set it correctly)
56
- */
57
- /** We get back fields as well that we don't have access to, thus we need to filter those schemas that we have permission for
58
- * ... TODO check it for tables */
59
- const LIST_SPECIAL_TYPE_FIELDS =
60
- `SELECT c.nspname as schema_name, b.relname as table_name, a.attname as column_name, a.atttypid as typid
61
- FROM pg_attribute a
62
- JOIN pg_class b ON (a.attrelid = b.oid)
63
- JOIN pg_type t ON (a.atttypid = t.oid)
64
- JOIN pg_namespace c ON (b.relnamespace=c.oid)
65
- WHERE (a.atttypid in (114, 3802, 1082, 1083, 1114, 1184, 1266, 3614) or t.typcategory='A')
66
- AND reltype>0 `;
67
- //AND c.nspname not in ('pg_catalog', 'pg_constraint', 'information_schema')
68
-
69
- export enum FieldType { JSON, ARRAY, TIME, TSVECTOR }
70
-
71
- export enum TranzactionIsolationLevel {
72
- serializable = 'SERIALIZABLE',
73
- repeatableRead = 'REPEATABLE READ',
74
- readCommitted = 'READ COMMITTED',
75
- readUncommitted = 'READ UNCOMMITTED'
76
- }
77
-
78
- export type PostProcessResultFunc = (res: any[], fields: ResultFieldType[], logger: PgDbLogger) => void;
79
-
80
- /** LISTEN callback parameter */
81
- export interface Notification {
82
- processId: number,
83
- channel: string,
84
- payload?: string
85
- }
86
-
87
- export class PgDb extends QueryAble {
88
- protected static instances: { [index: string]: Promise<PgDb> };
89
- /*protected*/
90
- pool;
91
- connection;
92
-
93
- /*protected*/
94
- config: ConnectionOptions;
95
- /*protected*/
96
- defaultSchemas; // for this.tables and this.fn
97
-
98
- db;
99
- schemas: { [name: string]: PgSchema };
100
- tables: { [name: string]: PgTable<any> } = {};
101
- fn: { [name: string]: (...any) => any } = {};
102
- [name: string]: any | PgSchema;
103
- /* protected */
104
- pgdbTypeParsers = {};
105
- /* protected */
106
- knownOids: Record<number, boolean> = {};
107
- /* protected */
108
- postProcessResult: PostProcessResultFunc;
109
-
110
- private constructor(pgdb: { defaultSchemas?, config?, schemas?, pool?, pgdbTypeParsers?, knownOids?, getLogger?: () => any, postProcessResult?: PostProcessResultFunc } = {}) {
111
- super();
112
- this.schemas = {};
113
- this.config = pgdb.config;
114
- this.pool = pgdb.pool;
115
- this.postProcessResult = pgdb.postProcessResult;
116
- this.pgdbTypeParsers = pgdb.pgdbTypeParsers || {};
117
- this.knownOids = pgdb.knownOids || {};
118
- this.db = this;
119
- if (pgdb.getLogger) {
120
- this.setLogger(pgdb.getLogger());
121
- }
122
-
123
- for (let schemaName in pgdb.schemas) {
124
- let schema = new PgSchema(this, schemaName);
125
- this.schemas[schemaName] = schema;
126
- if (!(schemaName in this))
127
- this[schemaName] = schema;
128
- for (let tableName in pgdb.schemas[schemaName].tables) {
129
- schema.tables[tableName] = new PgTable(schema, pgdb.schemas[schemaName][tableName].desc, pgdb.schemas[schemaName][tableName].fieldTypes);
130
- if (!(tableName in schema))
131
- schema[tableName] = schema.tables[tableName];
132
- }
133
- }
134
-
135
- this.defaultSchemas = pgdb.defaultSchemas;
136
- this.setDefaultTablesAndFunctions();
137
- }
138
-
139
- setPostProcessResult(f: (res: any[], fields: ResultFieldType[], logger: PgDbLogger) => void) {
140
- this.postProcessResult = f;
141
- }
142
-
143
- /** If planned to used as a static singleton */
144
- static async getInstance(config: ConnectionOptions): Promise<PgDb> {
145
- if (config.connectionString) {
146
- let res = CONNECTION_URL_REGEXP.exec(config.connectionString);
147
- if (res) {
148
- config.user = res[1];
149
- config.password = res[2] ? res[2] : '';
150
- config.host = res[3] ? res[3] : 'localhost';
151
- config.port = res[4] ? +res[4] : 5432;
152
- config.database = res[5];
153
- }
154
- }
155
- let connectionString = `postgres://${config.user}@${config.host}:${config.port}/${config.database}`; // without password!
156
- if (!PgDb.instances) {
157
- PgDb.instances = {};
158
- }
159
- if (PgDb.instances[connectionString]) {
160
- return PgDb.instances[connectionString];
161
- } else {
162
- let pgdb = new PgDb({ config: config });
163
- PgDb.instances[connectionString] = pgdb.init();
164
- return PgDb.instances[connectionString];
165
- }
166
- }
167
-
168
- async close() {
169
- for (let cs in PgDb.instances) {
170
- let db = await PgDb.instances[cs];
171
- if (db.pool == this.pool) {
172
- delete PgDb.instances[cs];
173
- }
174
- }
175
- await this.pool.end((err: Error) => { });
176
- }
177
-
178
- static async connect(config: ConnectionOptions): Promise<PgDb> {
179
- if (config.connectionString) {
180
- let res = CONNECTION_URL_REGEXP.exec(config.connectionString);
181
- if (res) {
182
- config.user = res[1];
183
- if (res[2]) config.password = res[2];
184
- config.host = res[3] ? res[3] : 'localhost';
185
- config.port = res[4] ? +res[4] : 5432;
186
- config.database = res[5];
187
- }
188
- }
189
- let pgdb = new PgDb({ config: config });
190
- return pgdb.init();
191
- }
192
-
193
- private async init(): Promise<PgDb> {
194
- this.pool = new pg.Pool(_.omit(this.config, ['logger', 'skipUndefined']));
195
- if (this.config.logger)
196
- this.setLogger(this.config.logger);
197
-
198
- this.pool.on('error', (e, client) => {
199
- // if a client is idle in the pool
200
- // and receives an error - for example when your PostgreSQL server restarts
201
- // the pool will catch the error & let you handle it here
202
- this.getLogger(true).error('pool error', e);
203
- });
204
- await this.reload();
205
- this.getLogger().log('Successfully connected to Db');
206
- return this;
207
- }
208
-
209
- async reload() {
210
- await this.initSchemasAndTables();
211
- await this.initFieldTypes();
212
- }
213
-
214
- private async initSchemasAndTables() {
215
- let schemasAndTables = await this.query(LIST_SCHEMAS_TABLES);
216
- let functions = await this.query(GET_SCHEMAS_PROCEDURES);
217
-
218
- this.defaultSchemas = await this.queryOneField(GET_CURRENT_SCHEMAS);
219
-
220
- let oldSchemaNames = Object.keys(this.schemas);
221
- for (let sc of oldSchemaNames) {
222
- if (this[sc] === this.schemas[sc])
223
- delete this[sc];
224
- }
225
- this.schemas = {};
226
- for (let r of schemasAndTables) {
227
- let schema = this.schemas[r.schema] = this.schemas[r.schema] || new PgSchema(this, r.schema);
228
- if (!(r.schema in this))
229
- this[r.schema] = schema;
230
- schema.tables[r.name] = new PgTable(schema, r);
231
- if (!(r.name in schema))
232
- schema[r.name] = schema.tables[r.name];
233
- }
234
-
235
- for (let r of functions) {
236
- let schema = this.schemas[r.schema] = this.schemas[r.schema] || new PgSchema(this, r.schema);
237
- if (!(r.schema in this))
238
- this[r.schema] = schema;
239
- schema.fn[r.name] = pgUtils.createFunctionCaller(schema, r);
240
- }
241
-
242
- // this.getLogger(true).log('defaultSchemas: ' + defaultSchemas);
243
- this.setDefaultTablesAndFunctions();
244
- }
245
-
246
- private setDefaultTablesAndFunctions() {
247
- this.tables = {};
248
- this.fn = {};
249
-
250
- if (!this.defaultSchemas) return;
251
- for (let sc of this.defaultSchemas) {
252
- let schema = this.schemas[sc];
253
- // this.getLogger(true).log('copy schame to default', sc, schema && Object.keys(schema.tables), schema && Object.keys(schema.fn));
254
- if (!schema)
255
- continue;
256
- for (let table in schema.tables)
257
- this.tables[table] = this.tables[table] || schema.tables[table];
258
- for (let fn in schema.fn)
259
- this.fn[fn] = this.fn[fn] || schema.fn[fn];
260
- }
261
- }
262
-
263
- private async initFieldTypes() {
264
- //--- init field types -------------------------------------------
265
- let schemaNames = "'" + Object.keys(this.schemas).join("', '") + "'";
266
- if (schemaNames == "''") {
267
- this.getLogger(true).error("No readable schema found!");
268
- return;
269
- }
270
- let specialTypeFields: { schema_name: string, table_name: string, column_name: string, typid: number }[]
271
- = await this.query(LIST_SPECIAL_TYPE_FIELDS + ' AND c.nspname in (' + schemaNames + ')');
272
-
273
- for (let r of specialTypeFields) {
274
- if (this.schemas[r.schema_name][r.table_name]) {
275
- this.schemas[r.schema_name][r.table_name].fieldTypes[r.column_name] =
276
- ([3802, 114].indexOf(r.typid) > -1) ? FieldType.JSON :
277
- ([3614].indexOf(r.typid) > -1) ? FieldType.TSVECTOR :
278
- ([1082, 1083, 1114, 1184, 1266].indexOf(r.typid) > -1) ? FieldType.TIME :
279
- FieldType.ARRAY;
280
- }
281
- }
282
-
283
- // https://web.archive.org/web/20160613215445/https://doxygen.postgresql.org/include_2catalog_2pg__type_8h_source.html
284
- // https://github.com/lib/pq/blob/master/oid/types.go
285
-
286
- let builtInArrayTypeParsers: { oidList: number[], parser: (string) => any }[] = [
287
- {
288
- oidList: [
289
- 1000 // bool[]
290
- ],
291
- parser: PgConverters.arraySplitToBool
292
- },
293
- {
294
- oidList: [
295
- 1005, // smallInt[] int2[]
296
- 1007, // integer[] int4[]
297
- 1021 // real[] float4[]
298
- ],
299
- parser: PgConverters.arraySplitToNum
300
- },
301
- {
302
- oidList: [
303
- 1009, // text[]
304
- 1015 // varchar[]
305
- ],
306
- parser: PgConverters.arraySplit
307
- },
308
- {
309
- oidList: [
310
- 199, // json[]
311
- 3807 // jsonb[]
312
- ],
313
- parser: PgConverters.arraySplitToJson
314
- },
315
- {
316
- oidList: [
317
- 1115, // timestamp[]
318
- 1182, // date[]
319
- 1183, // time[]
320
- 1185, // timestamptz[]
321
- 1270 // timetz[]
322
- ],
323
- parser: PgConverters.arraySplitToDate
324
- }
325
- ];
326
-
327
- builtInArrayTypeParsers.forEach(parserObj => {
328
- parserObj.oidList.forEach(oid => {
329
- pg.types.setTypeParser(oid, parserObj.parser);
330
- delete this.pgdbTypeParsers[oid];
331
- this.knownOids[oid] = true;
332
- });
333
- });
334
-
335
- for (let r of specialTypeFields) {
336
- if (this.knownOids[r.typid] && !this.pgdbTypeParsers[r.typid]) {
337
- continue;
338
- }
339
- switch (r.typid) {
340
- case 114: // json
341
- case 3802: // jsonb
342
- case 1082: // date
343
- case 1083: // time
344
- case 1114: // timestamp
345
- case 1184: // timestamptz
346
- case 1266: // timetz
347
- case 3614: // tsvector
348
- break;
349
- case 1016: // bigInt[] int8[]
350
- case 1022: // double[] float8[]
351
- //pg.types.setTypeParser(r.typid, arraySplitToNumWithValidation);
352
- //delete this.pgdbTypeParsers[r.typid];
353
- break;
354
- default:
355
- //best guess otherwise user need to specify
356
- pg.types.setTypeParser(r.typid, PgConverters.arraySplit);
357
- delete this.pgdbTypeParsers[r.typid];
358
- }
359
- }
360
-
361
- //has to set outside of pgjs as it doesnt support exceptions (stop everything immediately)
362
- await this.setPgDbTypeParser('int8', PgConverters.numWithValidation); //int8 - 20
363
- await this.setPgDbTypeParser('float8', PgConverters.numWithValidation); //float8 - 701
364
- await this.setPgDbTypeParser('_int8', PgConverters.stringArrayToNumWithValidation);
365
- await this.setPgDbTypeParser('_float8', PgConverters.stringArrayToNumWithValidation);
366
-
367
- let allUsedTypeFields = await this.queryOneColumn(`
368
- SELECT a.atttypid as typid
369
- FROM pg_attribute a
370
- JOIN pg_class b ON (a.attrelid = b.oid)
371
- JOIN pg_type t ON (a.atttypid = t.oid)
372
- JOIN pg_namespace c ON (b.relnamespace=c.oid)
373
- WHERE
374
- reltype>0 AND
375
- c.nspname in (${schemaNames})`
376
- );
377
- allUsedTypeFields.forEach(oid => this.knownOids[oid] = true);
378
- }
379
-
380
- /**
381
- * if schemaName is null, it will be applied for all schemas
382
- */
383
- async setTypeParser(typeName: string, parser: (string) => any, schemaName?: string): Promise<void> {
384
- try {
385
- if (schemaName) {
386
- let oid = await this.queryOneField(GET_OID_FOR_COLUMN_TYPE_FOR_SCHEMA, { typeName, schemaName });
387
- pg.types.setTypeParser(oid, parser);
388
- delete this.pgdbTypeParsers[oid];
389
- this.knownOids[oid] = true;
390
- } else {
391
- let list = await this.queryOneColumn(GET_OID_FOR_COLUMN_TYPE, { typeName });
392
- list.forEach(oid => {
393
- pg.types.setTypeParser(oid, parser);
394
- delete this.pgdbTypeParsers[oid];
395
- this.knownOids[oid] = true;
396
- });
397
- }
398
- } catch (e) {
399
- throw Error('Not existing type: ' + typeName);
400
- }
401
- }
402
-
403
- async setPgDbTypeParser(typeName: string, parser: (string) => any, schemaName?: string): Promise<void> {
404
- try {
405
- if (schemaName) {
406
- let oid = await this.queryOneField(GET_OID_FOR_COLUMN_TYPE_FOR_SCHEMA, { typeName, schemaName });
407
- this.pgdbTypeParsers[oid] = parser;
408
- this.knownOids[oid] = true;
409
- } else {
410
- let list = await this.queryOneColumn(GET_OID_FOR_COLUMN_TYPE, { typeName });
411
- list.forEach(oid => {
412
- this.pgdbTypeParsers[oid] = parser;
413
- this.knownOids[oid] = true;
414
- });
415
- }
416
- } catch (e) {
417
- throw Error('Not existing type: ' + typeName);
418
- }
419
- }
420
-
421
- async resetMissingParsers(connection, oidList: number[]): Promise<void> {
422
- let unknownOids = oidList.filter(oid => !this.knownOids[oid]);
423
- if (unknownOids.length) {
424
- let fieldsData = await connection.query(
425
- `select oid, typcategory from pg_type where oid = ANY($1)`,
426
- [unknownOids]
427
- );
428
-
429
- fieldsData.rows.forEach(fieldData => {
430
- if (fieldData.typcategory == 'A') {
431
- this.pgdbTypeParsers[fieldData.oid] = PgConverters.arraySplit;
432
- }
433
- this.knownOids[fieldData.oid] = true;
434
- });
435
- }
436
- }
437
-
438
- async dedicatedConnectionBegin(): Promise<PgDb> {
439
- if (this.needToFixConnectionForListen()) {
440
- await this.runRestartConnectionForListen();
441
- }
442
- let pgDb = new PgDb(this);
443
- pgDb.connection = await this.pool.connect();
444
- pgDb.connection.on('error', () => { }); //When there is an error, then the actual called query will throw exception
445
- return pgDb;
446
- }
447
-
448
- async dedicatedConnectionEnd(): Promise<PgDb> {
449
- if (this.connection) {
450
- try {
451
- await this.connection.release();
452
- } catch (err) {
453
- this.getLogger().error('Error while dedicated connection end.', err);
454
- }
455
- this.connection = null;
456
- }
457
- return this;
458
- }
459
-
460
- /**
461
- * transaction save point
462
- * https://www.postgresql.org/docs/current/sql-savepoint.html
463
- */
464
- async savePoint(name: string): Promise<PgDb> {
465
- if (this.isTransactionActive()) {
466
- name = (name || '').replace(/"/g, '');
467
- await this.query(`SAVEPOINT "${name}"`);
468
- } else {
469
- throw Error('No active transaction');
470
- }
471
- return this;
472
- }
473
-
474
- /**
475
- * "RELEASE SAVEPOINT" - remove transaction save point
476
- * https://www.postgresql.org/docs/current/sql-savepoint.html
477
- */
478
- async savePointRelease(name: string): Promise<PgDb> {
479
- if (this.isTransactionActive()) {
480
- name = (name || '').replace(/"/g, '');
481
- await this.query(`RELEASE SAVEPOINT "${name}"`);
482
- } else {
483
- throw Error('No active transaction');
484
- }
485
- return this;
486
- }
487
-
488
- async transactionBegin(options?: { isolationLevel?: TranzactionIsolationLevel, deferrable?: boolean, readOnly?: boolean }): Promise<PgDb> {
489
- let pgDb = this.connection ? this : await this.dedicatedConnectionBegin();
490
- let q = 'BEGIN'
491
- if (options?.isolationLevel) {
492
- q += ' ISOLATION LEVEL ' + options.isolationLevel;
493
- }
494
- if (options?.readOnly) {
495
- q += ' READ ONLY';
496
- }
497
- if (options?.deferrable) {
498
- q += ' DEFERRABLE ';
499
- }
500
-
501
- await pgDb.query(q);
502
- return pgDb;
503
- }
504
-
505
- async transactionCommit(): Promise<PgDb> {
506
- await this.query('COMMIT');
507
- return this.dedicatedConnectionEnd();
508
- }
509
-
510
- async transactionRollback(options?: { savePoint?: string }): Promise<PgDb> {
511
- if (options?.savePoint) {
512
- let name = (options.savePoint || '').replace(/"/g, '');
513
- await this.query(`ROLLBACK TO SAVEPOINT "${name}"`);
514
- return this;
515
- } else {
516
- await this.query('ROLLBACK');
517
- return this.dedicatedConnectionEnd();
518
- }
519
- }
520
-
521
- isTransactionActive(): boolean {
522
- return this.connection != null;
523
- }
524
-
525
- async execute(fileName: string, statementTransformerFunction?: (string) => string): Promise<void> {
526
- let isTransactionInPlace = this.isTransactionActive();
527
- // statements must be run in a dedicated connection
528
- let pgdb = isTransactionInPlace ? this : await this.dedicatedConnectionBegin();
529
-
530
- /** run statements one after the other */
531
- let runStatementList = (statementList) => {
532
- //this.getLogger(true).log('consumer start', commands.length);
533
- return new Promise((resolve, reject) => {
534
- let currentStatement = 0;
535
- let runStatement = () => {
536
- //this.getLogger(true).log('commnads length', commands.length, i);
537
- if (statementList.length == currentStatement) {
538
- resolve(undefined);
539
- } else {
540
- let statement = statementList[currentStatement++];
541
- if (statementTransformerFunction) {
542
- statement = statementTransformerFunction(statement);
543
- }
544
- this.getLogger(true).log('run', statementList[currentStatement - 1]);
545
- pgdb.query(statement)
546
- .then(() => runStatement(), reject)
547
- .catch(reject);
548
- }
549
- };
550
- runStatement();
551
- }).catch((e) => {
552
- this.getLogger(true).error(e);
553
- throw e;
554
- });
555
- };
556
-
557
- let lineCounter = 0;
558
- let promise = new Promise<void>((resolve, reject) => {
559
- let statementList = [];
560
- let tmp = '', t: RegExpExecArray;
561
- let consumer;
562
- let inQuotedString: string;
563
- let rl = readline.createInterface({
564
- input: fs.createReadStream(fileName),
565
- terminal: false
566
- }).on('line', (line) => {
567
- lineCounter++;
568
- try {
569
- //console.log('Line: ' + line);
570
- while (t = SQL_TOKENIZER_REGEXP.exec(line)) {
571
- if (!inQuotedString && (t[0] == '"' || t[0] == "'") || inQuotedString == '"' || inQuotedString == "'") {
572
- if (!inQuotedString) {
573
- inQuotedString = t[0];
574
- } else if (inQuotedString == t[0]) {
575
- inQuotedString = null;
576
- }
577
- tmp += t[0];
578
- } else if (!inQuotedString && t[0] == '$' || inQuotedString && inQuotedString[0] == '$') {
579
- if (!inQuotedString) {
580
- let s = line.slice(SQL_TOKENIZER_REGEXP.lastIndex - 1);
581
- let token = s.match(SQL_$_ESCAPE_REGEXP);
582
- if (!token) {
583
- throw Error('Invalid sql in line: ' + line);
584
- }
585
- inQuotedString = token[0];
586
- SQL_TOKENIZER_REGEXP.lastIndex += inQuotedString.length - 1;
587
- tmp += inQuotedString;
588
- } else {
589
- tmp += t[0];
590
- if (tmp.endsWith(inQuotedString)) {
591
- inQuotedString = null;
592
- }
593
- }
594
- } else if (!inQuotedString && t[0] == '/*' || inQuotedString == '/*') {
595
- if (!inQuotedString) {
596
- inQuotedString = t[0];
597
- } else if (t[0] == '*/') {
598
- inQuotedString = null;
599
- }
600
- } else if (!inQuotedString && t[0] == '--') {
601
- line = '';
602
- } else if (!inQuotedString && t[0] == ';') {
603
- //console.log('push ' + tmp);
604
- if (tmp.trim() != '') {
605
- statementList.push(tmp);
606
- if (!consumer) {
607
- consumer = runStatementList(statementList).then(() => {
608
- // console.log('consumer done');
609
- consumer = null;
610
- statementList.length = 0;
611
- rl.resume();
612
- }, reject);
613
- rl.pause();
614
- }
615
- }
616
- tmp = '';
617
- } else {
618
- tmp += t[0];
619
- }
620
- }
621
- if (tmp && line) {
622
- tmp += '\n';
623
- }
624
- } catch (e) {
625
- reject(e);
626
- }
627
- }).on('close', () => {
628
- if (inQuotedString) {
629
- reject(Error('Invalid SQL, unterminated string'));
630
- }
631
-
632
- //if the last statement did't have ';'
633
- if (tmp.trim() != '') {
634
- statementList.push(tmp);
635
- }
636
- if (!consumer) {
637
- if (statementList.length) {
638
- consumer = runStatementList(statementList).catch(reject);
639
- } else {
640
- resolve();
641
- }
642
- }
643
- if (consumer) {
644
- consumer = consumer.then(resolve, reject);
645
- }
646
- });
647
- });
648
- let error;
649
- return promise
650
- .catch((e) => {
651
- error = e;
652
- this.getLogger(true).error('Error at line ' + lineCounter + ' in ' + fileName + '. ' + e);
653
- })
654
- .then(() => {
655
- // finally
656
- //if transaction was in place, don't touch it
657
- if (!isTransactionInPlace) {
658
- return pgdb.dedicatedConnectionEnd();
659
- }
660
- }).catch((e) => {
661
- this.getLogger(true).error(e);
662
- }).then(() => {
663
- if (error) {
664
- throw error;
665
- }
666
- // console.log('connection released');
667
- });
668
- }
669
-
670
- private listeners = new EventEmitter();
671
- private connectionForListen;
672
- private _needToRestartConnectionForListen = false;
673
- private restartConnectionForListen: Promise<Error> = null;
674
-
675
- /**
676
- * LISTEN to a channel for a NOTIFY (https://www.postgresql.org/docs/current/sql-listen.html)
677
- * One connection will be dedicated for listening if there are any listeners.
678
- * When there is no other callback for a channel, LISTEN command is executed
679
- */
680
- async listen(channel: string, callback: (notification: Notification) => void) {
681
- let restartConnectionError: Error = null;
682
- if (this.needToFixConnectionForListen()) {
683
- restartConnectionError = await this.runRestartConnectionForListen();
684
- }
685
- if (this.listeners.listenerCount(channel)) {
686
- this.listeners.on(channel, callback);
687
- } else {
688
- if (restartConnectionError) {
689
- throw restartConnectionError;
690
- }
691
- try {
692
- if (!this.connectionForListen) {
693
- await this.initConnectionForListen();
694
- }
695
- await this.connectionForListen.query(`LISTEN "${channel}"`);
696
- } catch (err) {
697
- this._needToRestartConnectionForListen = true;
698
- throw err;
699
- }
700
- this.listeners.on(channel, callback);
701
- }
702
- }
703
-
704
- /**
705
- * Remove a callback which listening on a channel
706
- * When all callback is removed from a channel UNLISTEN command is executed
707
- * When all callback is removed from all channel, dedicated connection is released
708
- */
709
- async unlisten(channel: string, callback?: (Notification) => void) {
710
- let restartConnectionError: Error = null;
711
- if (this.needToFixConnectionForListen()) {
712
- restartConnectionError = await this.runRestartConnectionForListen();
713
- }
714
- if (callback && this.listeners.listenerCount(channel) > 1) {
715
- this.listeners.removeListener(channel, callback);
716
- } else {
717
- if (restartConnectionError) {
718
- throw restartConnectionError;
719
- }
720
- try {
721
- await this.internalQuery({ connection: this.connectionForListen, sql: `UNLISTEN "${channel}"` });
722
- if (this.listeners.eventNames().length == 1) {
723
- this.connectionForListen.removeAllListeners('notification');
724
- this.connectionForListen.release();
725
- this.connectionForListen = null;
726
- }
727
- } catch (err) {
728
- this._needToRestartConnectionForListen = true;
729
- throw err;
730
- }
731
- this.listeners.removeAllListeners(channel);
732
- }
733
- }
734
-
735
- /**
736
- * Notify a channel (https://www.postgresql.org/docs/current/sql-notify.html)
737
- */
738
- async notify(channel: string, payload?: string) {
739
- if (this.needToFixConnectionForListen()) {
740
- let restartConnectionError = await this.runRestartConnectionForListen();
741
- if (restartConnectionError) {
742
- throw restartConnectionError;
743
- }
744
- }
745
- let hasConnectionForListen = !!this.connectionForListen;
746
- let connection = this.connectionForListen || this.connection;
747
- //let sql = 'NOTIFY ' + channel + ', :payload';
748
- let sql = 'SELECT pg_notify(:channel, :payload)';
749
- let params = { channel, payload };
750
- try {
751
- return this.internalQuery({ connection, sql, params });
752
- } catch (err) {
753
- if (hasConnectionForListen) {
754
- this._needToRestartConnectionForListen = true;
755
- }
756
- throw err;
757
- }
758
- }
759
-
760
- async runRestartConnectionForListen(): Promise<Error> {
761
- if (!this._needToRestartConnectionForListen) {
762
- return null;
763
- }
764
- let errorResult: Error = null;
765
- if (!this.restartConnectionForListen) {
766
- this.restartConnectionForListen = (async () => {
767
- let eventNames = this.listeners.eventNames();
768
- try {
769
- await this.connectionForListen.release();
770
- } catch (e) {
771
- }
772
- this.connectionForListen = null;
773
- let error: Error;
774
- if (eventNames.length) {
775
- try {
776
- await this.initConnectionForListen();
777
- for (let channel of eventNames) {
778
- await this.connectionForListen.query(`LISTEN "${channel as string}"`);
779
- }
780
- } catch (err) {
781
- error = err;
782
- }
783
- }
784
- return error;
785
- })();
786
- errorResult = await this.restartConnectionForListen;
787
- this.restartConnectionForListen = null;
788
- } else {
789
- errorResult = await this.restartConnectionForListen;
790
- }
791
- if (!errorResult) {
792
- this._needToRestartConnectionForListen = false;
793
- }
794
- return errorResult;
795
- }
796
-
797
- needToFixConnectionForListen(): boolean {
798
- return this._needToRestartConnectionForListen;
799
- }
800
-
801
- private async tryToFixConnectionForListenActively() {
802
- await new Promise(r => setTimeout(r, 1000));
803
- let error = await this.runRestartConnectionForListen();
804
- if (error) {
805
- await this.tryToFixConnectionForListenActively();
806
- }
807
- }
808
-
809
- private async initConnectionForListen() {
810
- this.connectionForListen = await this.pool.connect();
811
- this.connectionForListen.on('notification', (notification: Notification) => this.listeners.emit(notification.channel, notification));
812
- this.connectionForListen.on('error', (e) => {
813
- this._needToRestartConnectionForListen = true;
814
- this.tryToFixConnectionForListenActively();
815
- });
816
- }
817
- }
818
-
819
-
820
- export default PgDb;