pogi 2.10.2 → 3.0.0-beta2
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/.vscode/launch.json +47 -15
- package/CHANGELOG.md +11 -0
- package/docs/API/PgDb.md +25 -0
- package/docs/notification.md +19 -0
- package/jest.config.js +23 -0
- package/lib/bin/generateInterface.js +3 -3
- package/lib/bin/generateInterface.js.map +1 -1
- package/lib/connectionOptions.d.ts +10 -0
- package/lib/index.d.ts +1 -1
- package/lib/pgConverters.d.ts +9 -8
- package/lib/pgConverters.js +46 -32
- package/lib/pgConverters.js.map +1 -1
- package/lib/pgConverters.test.d.ts +1 -0
- package/lib/pgConverters.test.js +13 -0
- package/lib/pgConverters.test.js.map +1 -0
- package/lib/pgDb.d.ts +27 -27
- package/lib/pgDb.js +293 -100
- package/lib/pgDb.js.map +1 -1
- package/lib/pgDb.test.d.ts +1 -0
- package/lib/pgDb.test.js +1126 -0
- package/lib/pgDb.test.js.map +1 -0
- package/lib/pgDbInterface.d.ts +53 -0
- package/lib/pgDbInterface.js +11 -0
- package/lib/pgDbInterface.js.map +1 -0
- package/lib/pgDbOperators.d.ts +3 -3
- package/lib/pgDbOperators.js +4 -7
- package/lib/pgDbOperators.js.map +1 -1
- package/lib/pgDbOperators.test.d.ts +1 -0
- package/lib/pgDbOperators.test.js +313 -0
- package/lib/pgDbOperators.test.js.map +1 -0
- package/lib/pgSchema.d.ts +10 -9
- package/lib/pgSchema.js.map +1 -1
- package/lib/pgSchemaInterface.d.ts +12 -0
- package/lib/pgSchemaInterface.js +3 -0
- package/lib/pgSchemaInterface.js.map +1 -0
- package/lib/pgTable.d.ts +15 -40
- package/lib/pgTable.js +54 -54
- package/lib/pgTable.js.map +1 -1
- package/lib/pgTableInterface.d.ts +102 -0
- package/lib/pgTableInterface.js +4 -0
- package/lib/pgTableInterface.js.map +1 -0
- package/lib/pgUtils.d.ts +16 -6
- package/lib/pgUtils.js +162 -31
- package/lib/pgUtils.js.map +1 -1
- package/lib/queryAble.d.ts +20 -53
- package/lib/queryAble.js +149 -80
- package/lib/queryAble.js.map +1 -1
- package/lib/queryAbleInterface.d.ts +55 -0
- package/lib/queryAbleInterface.js +7 -0
- package/lib/queryAbleInterface.js.map +1 -0
- package/lib/queryWhere.d.ts +2 -2
- package/lib/queryWhere.js +19 -23
- package/lib/queryWhere.js.map +1 -1
- package/mkdocs.yml +1 -0
- package/package.json +21 -11
- package/src/bin/generateInterface.ts +2 -2
- package/src/connectionOptions.ts +48 -13
- package/src/index.d.ts +7 -0
- package/src/index.ts +1 -1
- package/src/pgConverters.test.ts +10 -0
- package/src/pgConverters.ts +34 -22
- package/src/pgDb.test.ts +1324 -0
- package/src/pgDb.ts +318 -122
- package/src/pgDbInterface.ts +57 -0
- package/src/pgDbOperators.test.ts +478 -0
- package/src/pgDbOperators.ts +45 -22
- package/src/pgSchema.ts +10 -9
- package/src/pgSchemaInterface.ts +12 -0
- package/src/pgTable.ts +66 -98
- package/src/pgTableInterface.ts +131 -0
- package/src/pgUtils.ts +166 -42
- package/src/queryAble.ts +167 -125
- package/src/queryAbleInterface.ts +104 -0
- package/src/queryWhere.ts +42 -43
- package/{spec/resources → src/test}/init.sql +23 -0
- package/src/test/pgServiceRestartTest.ts +1500 -0
- package/{spec/resources → src/test}/throw_exception.sql +0 -0
- package/{spec/resources → src/test}/tricky.sql +0 -0
- package/{src/tsconfig.json → tsconfig.json} +12 -11
- package/spec/run.js +0 -5
- package/spec/support/jasmine.json +0 -9
- package/src/test/pgDbOperatorSpec.ts +0 -492
- package/src/test/pgDbSpec.ts +0 -994
package/src/pgDb.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import { QueryAble
|
|
1
|
+
import { QueryAble } from "./queryAble";
|
|
2
|
+
import { IPgDb, ResultFieldType, PostProcessResultFunc, Notification, TranzactionIsolationLevel } from "./pgDbInterface";
|
|
3
|
+
import { IPgTable } from "./pgTableInterface";
|
|
2
4
|
import { PgTable } from "./pgTable";
|
|
3
5
|
import { PgSchema } from "./pgSchema";
|
|
6
|
+
import { IPgSchema } from "./pgSchemaInterface";
|
|
4
7
|
import * as PgConverters from "./pgConverters";
|
|
5
8
|
import { pgUtils } from "./pgUtils";
|
|
6
9
|
import * as _ from 'lodash';
|
|
7
10
|
import * as pg from 'pg';
|
|
8
11
|
import * as readline from 'readline';
|
|
9
12
|
import * as fs from 'fs';
|
|
10
|
-
import {PgDbLogger} from './pgDbLogger';
|
|
11
|
-
import {ConnectionOptions} from './connectionOptions';
|
|
13
|
+
import { PgDbLogger } from './pgDbLogger';
|
|
14
|
+
import { ConnectionOptions } from './connectionOptions';
|
|
12
15
|
import * as EventEmitter from 'events';
|
|
13
16
|
|
|
14
17
|
const CONNECTION_URL_REGEXP = /^postgres:\/\/(?:([^:]+)(?::([^@]*))?@)?([^\/:]+)?(?::([^\/]+))?\/(.*)$/;
|
|
@@ -57,7 +60,7 @@ const GET_CURRENT_SCHEMAS = "SELECT current_schemas(false)";
|
|
|
57
60
|
/** 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
61
|
* ... TODO check it for tables */
|
|
59
62
|
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
|
|
63
|
+
`SELECT c.nspname as schema_name, b.relname as table_name, a.attname as column_name, a.atttypid as typid, t.typcategory
|
|
61
64
|
FROM pg_attribute a
|
|
62
65
|
JOIN pg_class b ON (a.attrelid = b.oid)
|
|
63
66
|
JOIN pg_type t ON (a.atttypid = t.oid)
|
|
@@ -65,54 +68,59 @@ const LIST_SPECIAL_TYPE_FIELDS =
|
|
|
65
68
|
WHERE (a.atttypid in (114, 3802, 1082, 1083, 1114, 1184, 1266, 3614) or t.typcategory='A')
|
|
66
69
|
AND reltype>0 `;
|
|
67
70
|
//AND c.nspname not in ('pg_catalog', 'pg_constraint', 'information_schema')
|
|
71
|
+
const TYPE2OID = `SELECT t.oid, typname FROM pg_catalog.pg_type t WHERE typname in (
|
|
72
|
+
'_bool',
|
|
73
|
+
'int8','_int2','_int4','_int8','_float4','float8','_float8',
|
|
74
|
+
'_text','_varchar',
|
|
75
|
+
'json','jsonb', '_json','_jsonb',
|
|
76
|
+
'date','time','timestamp','timestamptz','timetz','_date','_time','_timestamp','_timestamptz','_timetz',
|
|
77
|
+
'tsvector')`
|
|
68
78
|
|
|
69
79
|
export enum FieldType { JSON, ARRAY, TIME, TSVECTOR }
|
|
70
80
|
|
|
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
81
|
|
|
80
|
-
/** LISTEN callback parameter */
|
|
81
|
-
export interface Notification {
|
|
82
|
-
processId: number,
|
|
83
|
-
channel: string,
|
|
84
|
-
payload?: string
|
|
85
|
-
}
|
|
86
82
|
|
|
87
|
-
export class PgDb extends QueryAble {
|
|
83
|
+
export class PgDb extends QueryAble implements IPgDb {
|
|
88
84
|
protected static instances: { [index: string]: Promise<PgDb> };
|
|
89
85
|
/*protected*/
|
|
90
|
-
pool;
|
|
91
|
-
connection;
|
|
86
|
+
pool: pg.Pool;
|
|
87
|
+
connection: pg.PoolClient | null = null;
|
|
92
88
|
|
|
93
|
-
protected connectionForListen;
|
|
94
89
|
/*protected*/
|
|
95
90
|
config: ConnectionOptions;
|
|
96
91
|
/*protected*/
|
|
97
|
-
defaultSchemas; // for this.tables and this.fn
|
|
92
|
+
defaultSchemas: string[]; // for this.tables and this.fn
|
|
98
93
|
|
|
99
|
-
db;
|
|
94
|
+
db: IPgDb;
|
|
100
95
|
schemas: { [name: string]: PgSchema };
|
|
101
96
|
tables: { [name: string]: PgTable<any> } = {};
|
|
102
|
-
fn: { [name: string]: (...any) => any } = {};
|
|
97
|
+
fn: { [name: string]: (...args: any[]) => any } = {};
|
|
103
98
|
[name: string]: any | PgSchema;
|
|
104
99
|
/* protected */
|
|
105
|
-
pgdbTypeParsers = {};
|
|
100
|
+
pgdbTypeParsers: Record<string, (s: any) => any> = {};
|
|
106
101
|
/* protected */
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
knownOids: Record<number, boolean> = {};
|
|
103
|
+
/* protected */
|
|
104
|
+
postProcessResult: PostProcessResultFunc | undefined | null;
|
|
105
|
+
|
|
106
|
+
private constructor(pgdb: {
|
|
107
|
+
defaultSchemas?: string[],
|
|
108
|
+
config?: ConnectionOptions,
|
|
109
|
+
schemas?: Record<string, PgSchema>,
|
|
110
|
+
pool?: pg.Pool,
|
|
111
|
+
pgdbTypeParsers?: Record<string, (s: any) => any>,
|
|
112
|
+
knownOids?: Record<number, boolean>,
|
|
113
|
+
getLogger?: () => any,
|
|
114
|
+
postProcessResult?: PostProcessResultFunc | null
|
|
115
|
+
}) {
|
|
110
116
|
super();
|
|
111
117
|
this.schemas = {};
|
|
112
|
-
this.config = pgdb.config;
|
|
113
|
-
|
|
118
|
+
this.config = pgdb.config || {};
|
|
119
|
+
// pool is initialized in init, because it is asynchronous, but it is needed to call after constructor
|
|
120
|
+
this.pool = pgdb.pool!;
|
|
114
121
|
this.postProcessResult = pgdb.postProcessResult;
|
|
115
122
|
this.pgdbTypeParsers = pgdb.pgdbTypeParsers || {};
|
|
123
|
+
this.knownOids = pgdb.knownOids || {};
|
|
116
124
|
this.db = this;
|
|
117
125
|
if (pgdb.getLogger) {
|
|
118
126
|
this.setLogger(pgdb.getLogger());
|
|
@@ -130,11 +138,11 @@ export class PgDb extends QueryAble {
|
|
|
130
138
|
}
|
|
131
139
|
}
|
|
132
140
|
|
|
133
|
-
this.defaultSchemas = pgdb.defaultSchemas;
|
|
141
|
+
this.defaultSchemas = pgdb.defaultSchemas || [];
|
|
134
142
|
this.setDefaultTablesAndFunctions();
|
|
135
143
|
}
|
|
136
144
|
|
|
137
|
-
setPostProcessResult(f: (res: any[], fields: ResultFieldType[], logger: PgDbLogger) => void) {
|
|
145
|
+
setPostProcessResult(f: null | ((res: any[], fields: ResultFieldType[], logger: PgDbLogger) => void)) {
|
|
138
146
|
this.postProcessResult = f;
|
|
139
147
|
}
|
|
140
148
|
|
|
@@ -154,7 +162,7 @@ export class PgDb extends QueryAble {
|
|
|
154
162
|
if (!PgDb.instances) {
|
|
155
163
|
PgDb.instances = {};
|
|
156
164
|
}
|
|
157
|
-
if (PgDb.instances[connectionString]) {
|
|
165
|
+
if (PgDb.instances[connectionString] != null) {
|
|
158
166
|
return PgDb.instances[connectionString];
|
|
159
167
|
} else {
|
|
160
168
|
let pgdb = new PgDb({ config: config });
|
|
@@ -170,7 +178,7 @@ export class PgDb extends QueryAble {
|
|
|
170
178
|
delete PgDb.instances[cs];
|
|
171
179
|
}
|
|
172
180
|
}
|
|
173
|
-
await this.pool.end();
|
|
181
|
+
await new Promise<void>((resolve) => this.pool.end(resolve));
|
|
174
182
|
}
|
|
175
183
|
|
|
176
184
|
static async connect(config: ConnectionOptions): Promise<PgDb> {
|
|
@@ -213,7 +221,7 @@ export class PgDb extends QueryAble {
|
|
|
213
221
|
let schemasAndTables = await this.query(LIST_SCHEMAS_TABLES);
|
|
214
222
|
let functions = await this.query(GET_SCHEMAS_PROCEDURES);
|
|
215
223
|
|
|
216
|
-
this.defaultSchemas =
|
|
224
|
+
this.defaultSchemas = await this.queryOneField(GET_CURRENT_SCHEMAS);
|
|
217
225
|
|
|
218
226
|
let oldSchemaNames = Object.keys(this.schemas);
|
|
219
227
|
for (let sc of oldSchemaNames) {
|
|
@@ -260,121 +268,208 @@ export class PgDb extends QueryAble {
|
|
|
260
268
|
|
|
261
269
|
private async initFieldTypes() {
|
|
262
270
|
//--- init field types -------------------------------------------
|
|
263
|
-
let schemaNames =
|
|
264
|
-
if (schemaNames=="''") {
|
|
271
|
+
let schemaNames = "'" + Object.keys(this.schemas).join("', '") + "'";
|
|
272
|
+
if (schemaNames == "''") {
|
|
265
273
|
this.getLogger(true).error("No readable schema found!");
|
|
266
274
|
return;
|
|
267
275
|
}
|
|
268
|
-
let
|
|
276
|
+
let type2oid = {};
|
|
277
|
+
let res = await this.query(TYPE2OID);
|
|
278
|
+
for (let tt of res || []) {
|
|
279
|
+
type2oid[tt.typname] = +tt.oid;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
let specialTypeFields: { schema_name: string, table_name: string, column_name: string, typid: number, typcategory: string }[]
|
|
269
283
|
= await this.query(LIST_SPECIAL_TYPE_FIELDS + ' AND c.nspname in (' + schemaNames + ')');
|
|
270
284
|
|
|
271
285
|
for (let r of specialTypeFields) {
|
|
272
286
|
if (this.schemas[r.schema_name][r.table_name]) {
|
|
273
287
|
this.schemas[r.schema_name][r.table_name].fieldTypes[r.column_name] =
|
|
274
|
-
([
|
|
275
|
-
([
|
|
276
|
-
([
|
|
288
|
+
([type2oid['json'], type2oid['jsonb']].indexOf(r.typid) > -1) ? FieldType.JSON :
|
|
289
|
+
([type2oid['tsvector']].indexOf(r.typid) > -1) ? FieldType.TSVECTOR :
|
|
290
|
+
([type2oid['date'], type2oid['time'], type2oid['timestamp'], type2oid['timestamptz'], type2oid['timetz']].indexOf(r.typid) > -1) ? FieldType.TIME :
|
|
277
291
|
FieldType.ARRAY;
|
|
278
292
|
}
|
|
279
293
|
}
|
|
280
294
|
|
|
295
|
+
// https://web.archive.org/web/20160613215445/https://doxygen.postgresql.org/include_2catalog_2pg__type_8h_source.html
|
|
296
|
+
// https://github.com/lib/pq/blob/master/oid/types.go
|
|
297
|
+
|
|
298
|
+
let builtInArrayTypeParsers: { oidList: number[], parser: (s: string) => any }[] = [
|
|
299
|
+
{
|
|
300
|
+
oidList: [
|
|
301
|
+
type2oid['_bool'] // bool[]
|
|
302
|
+
],
|
|
303
|
+
parser: PgConverters.parseBooleanArray
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
oidList: [
|
|
307
|
+
type2oid['_int2'], // smallInt[] int2[]
|
|
308
|
+
type2oid['_int4'], // integer[] int4[]
|
|
309
|
+
type2oid['_float4'], // real[] float4[]
|
|
310
|
+
],
|
|
311
|
+
parser: PgConverters.parseNumberArray
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
oidList: [
|
|
315
|
+
type2oid['_text'], // text[]
|
|
316
|
+
type2oid['_varchar'] // varchar[]
|
|
317
|
+
],
|
|
318
|
+
parser: PgConverters.parseArray
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
oidList: [
|
|
322
|
+
type2oid['_json'], // json[]
|
|
323
|
+
type2oid['_jsonb'] // jsonb[]
|
|
324
|
+
],
|
|
325
|
+
parser: PgConverters.parseJsonArray
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
oidList: [
|
|
329
|
+
type2oid['_date'], // date[]
|
|
330
|
+
type2oid['_time'],
|
|
331
|
+
type2oid['_timetz'],
|
|
332
|
+
type2oid['_timestamp'], //timestamp[]
|
|
333
|
+
type2oid['_timestamptz'],
|
|
334
|
+
],
|
|
335
|
+
parser: PgConverters.parseDateArray
|
|
336
|
+
}
|
|
337
|
+
];
|
|
338
|
+
|
|
339
|
+
builtInArrayTypeParsers.forEach(parserObj => {
|
|
340
|
+
parserObj.oidList.forEach(oid => {
|
|
341
|
+
pg.types.setTypeParser(oid, parserObj.parser);
|
|
342
|
+
delete this.pgdbTypeParsers[oid];
|
|
343
|
+
this.knownOids[oid] = true;
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
281
347
|
for (let r of specialTypeFields) {
|
|
282
|
-
|
|
348
|
+
if (this.knownOids[r.typid] && !this.pgdbTypeParsers[r.typid]) {
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
283
351
|
switch (r.typid) {
|
|
284
|
-
case
|
|
285
|
-
case
|
|
286
|
-
case
|
|
287
|
-
case
|
|
288
|
-
case
|
|
289
|
-
case
|
|
290
|
-
case
|
|
291
|
-
case
|
|
292
|
-
|
|
293
|
-
case
|
|
294
|
-
case 1007: // integer[] int4[]
|
|
295
|
-
case 1021: // real[] float4[]
|
|
296
|
-
pg.types.setTypeParser(r.typid, PgConverters.arraySplitToNum);
|
|
297
|
-
break;
|
|
298
|
-
case 1009: // text[]
|
|
299
|
-
case 1015: // varchar[]
|
|
300
|
-
pg.types.setTypeParser(r.typid, PgConverters.arraySplit);
|
|
301
|
-
break;
|
|
302
|
-
case 3807:
|
|
303
|
-
pg.types.setTypeParser(r.typid, PgConverters.arraySplitToJson);
|
|
304
|
-
break;
|
|
305
|
-
case 1016: // bigInt[] int8[]
|
|
306
|
-
case 1022: // double[] float8[]
|
|
307
|
-
//pg.types.setTypeParser(r.typid, arraySplitToNumWithValidation);
|
|
308
|
-
break;
|
|
309
|
-
case 1115: // timestamp[]
|
|
310
|
-
case 1182: // date[]
|
|
311
|
-
case 1183: // time[]
|
|
312
|
-
case 1185: // timestamptz[]
|
|
313
|
-
case 1270: // timetz[]
|
|
314
|
-
pg.types.setTypeParser(r.typid, PgConverters.arraySplitToDate);
|
|
352
|
+
case type2oid['json']:
|
|
353
|
+
case type2oid['jsonb']:
|
|
354
|
+
case type2oid['date']: // date
|
|
355
|
+
case type2oid['time']: // time
|
|
356
|
+
case type2oid['timetz']: // timetz
|
|
357
|
+
case type2oid['timestamp']: // timestamp
|
|
358
|
+
case type2oid['timestamptz']: // timestamptz
|
|
359
|
+
case type2oid['tsvector']: // tsvector
|
|
360
|
+
case type2oid['_int8']: // bigInt[] int8[]
|
|
361
|
+
case type2oid['_float8']: // double[] float8[]
|
|
315
362
|
break;
|
|
316
363
|
default:
|
|
317
364
|
//best guess otherwise user need to specify
|
|
318
|
-
pg.types.setTypeParser(r.typid, PgConverters.
|
|
365
|
+
pg.types.setTypeParser(r.typid, PgConverters.parseArray);
|
|
366
|
+
delete this.pgdbTypeParsers[r.typid];
|
|
319
367
|
}
|
|
320
368
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
369
|
+
this.pgdbTypeParsers[type2oid['int8']] = PgConverters.parseNumberWithValidation;
|
|
370
|
+
this.pgdbTypeParsers[type2oid['float8']] = PgConverters.parseNumberWithValidation;
|
|
371
|
+
this.pgdbTypeParsers[type2oid['_int8']] = PgConverters.parseNumberArrayWithValidation;
|
|
372
|
+
this.pgdbTypeParsers[type2oid['_float8']] = PgConverters.parseNumberArrayWithValidation;
|
|
373
|
+
this.knownOids[type2oid['int8']] = true;
|
|
374
|
+
this.knownOids[type2oid['float8']] = true;
|
|
375
|
+
this.knownOids[type2oid['_int8']] = true;
|
|
376
|
+
this.knownOids[type2oid['_float8']] = true;
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
let allUsedTypeFields = await this.queryOneColumn(`
|
|
380
|
+
SELECT distinct a.atttypid as typid
|
|
381
|
+
FROM pg_attribute a
|
|
382
|
+
JOIN pg_class b ON (a.attrelid = b.oid)
|
|
383
|
+
JOIN pg_type t ON (a.atttypid = t.oid)
|
|
384
|
+
JOIN pg_namespace c ON (b.relnamespace=c.oid)
|
|
385
|
+
WHERE
|
|
386
|
+
reltype>0 AND
|
|
387
|
+
c.nspname in (${schemaNames})`
|
|
388
|
+
);
|
|
389
|
+
allUsedTypeFields.forEach(oid => this.knownOids[oid] = true);
|
|
327
390
|
}
|
|
328
391
|
|
|
329
392
|
/**
|
|
330
393
|
* if schemaName is null, it will be applied for all schemas
|
|
331
394
|
*/
|
|
332
|
-
async setTypeParser(typeName: string, parser: (string) => any, schemaName?: string): Promise<void> {
|
|
395
|
+
async setTypeParser(typeName: string, parser: (s: string) => any, schemaName?: string): Promise<void> {
|
|
333
396
|
try {
|
|
334
397
|
if (schemaName) {
|
|
335
398
|
let oid = await this.queryOneField(GET_OID_FOR_COLUMN_TYPE_FOR_SCHEMA, { typeName, schemaName });
|
|
336
399
|
pg.types.setTypeParser(oid, parser);
|
|
337
400
|
delete this.pgdbTypeParsers[oid];
|
|
401
|
+
this.knownOids[oid] = true;
|
|
338
402
|
} else {
|
|
339
403
|
let list = await this.queryOneColumn(GET_OID_FOR_COLUMN_TYPE, { typeName });
|
|
340
404
|
list.forEach(oid => {
|
|
341
405
|
pg.types.setTypeParser(oid, parser);
|
|
342
406
|
delete this.pgdbTypeParsers[oid];
|
|
407
|
+
this.knownOids[oid] = true;
|
|
343
408
|
});
|
|
344
409
|
}
|
|
345
410
|
} catch (e) {
|
|
346
|
-
throw Error('Not existing type: ' + typeName);
|
|
411
|
+
throw new Error('Not existing type: ' + typeName);
|
|
347
412
|
}
|
|
348
413
|
}
|
|
349
414
|
|
|
350
|
-
async setPgDbTypeParser(typeName: string, parser: (string) => any, schemaName?: string): Promise<void> {
|
|
415
|
+
async setPgDbTypeParser(typeName: string, parser: (s: string) => any, schemaName?: string): Promise<void> {
|
|
351
416
|
try {
|
|
352
417
|
if (schemaName) {
|
|
353
418
|
let oid = await this.queryOneField(GET_OID_FOR_COLUMN_TYPE_FOR_SCHEMA, { typeName, schemaName });
|
|
354
419
|
this.pgdbTypeParsers[oid] = parser;
|
|
420
|
+
this.knownOids[oid] = true;
|
|
355
421
|
} else {
|
|
356
422
|
let list = await this.queryOneColumn(GET_OID_FOR_COLUMN_TYPE, { typeName });
|
|
357
|
-
list.forEach(oid =>
|
|
423
|
+
list.forEach(oid => {
|
|
424
|
+
this.pgdbTypeParsers[oid] = parser;
|
|
425
|
+
this.knownOids[oid] = true;
|
|
426
|
+
});
|
|
358
427
|
}
|
|
359
428
|
} catch (e) {
|
|
360
|
-
throw Error('Not existing type: ' + typeName);
|
|
429
|
+
throw new Error('Not existing type: ' + typeName);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async resetMissingParsers(connection: pg.PoolClient, oidList: number[]): Promise<void> {
|
|
434
|
+
let unknownOids = oidList.filter(oid => !this.knownOids[oid]);
|
|
435
|
+
if (unknownOids.length) {
|
|
436
|
+
let fieldsData = await connection.query(
|
|
437
|
+
`select oid, typcategory from pg_type where oid = ANY($1)`,
|
|
438
|
+
[unknownOids]
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
fieldsData.rows.forEach(fieldData => {
|
|
442
|
+
if (fieldData.typcategory == 'A') {
|
|
443
|
+
this.pgdbTypeParsers[fieldData.oid] = PgConverters.parseArray;
|
|
444
|
+
}
|
|
445
|
+
this.knownOids[fieldData.oid] = true;
|
|
446
|
+
});
|
|
361
447
|
}
|
|
362
448
|
}
|
|
363
449
|
|
|
364
450
|
async dedicatedConnectionBegin(): Promise<PgDb> {
|
|
451
|
+
if (this.needToFixConnectionForListen()) {
|
|
452
|
+
await this.runRestartConnectionForListen();
|
|
453
|
+
}
|
|
365
454
|
let pgDb = new PgDb(this);
|
|
366
455
|
pgDb.connection = await this.pool.connect();
|
|
456
|
+
pgDb.connection.on('error', QueryAble.connectionErrorListener); //When there is an error, then the actual called query will throw exception
|
|
367
457
|
return pgDb;
|
|
368
458
|
}
|
|
369
459
|
|
|
370
460
|
async dedicatedConnectionEnd(): Promise<PgDb> {
|
|
371
461
|
if (this.connection) {
|
|
372
|
-
|
|
462
|
+
this.connection.off('error', QueryAble.connectionErrorListener);
|
|
463
|
+
try {
|
|
464
|
+
await this.connection.release();
|
|
465
|
+
} catch (err) {
|
|
466
|
+
this.getLogger().error('Error while dedicated connection end.', err);
|
|
467
|
+
}
|
|
373
468
|
this.connection = null;
|
|
374
469
|
}
|
|
375
470
|
return this;
|
|
376
471
|
}
|
|
377
|
-
|
|
472
|
+
|
|
378
473
|
/**
|
|
379
474
|
* transaction save point
|
|
380
475
|
* https://www.postgresql.org/docs/current/sql-savepoint.html
|
|
@@ -384,7 +479,7 @@ export class PgDb extends QueryAble {
|
|
|
384
479
|
name = (name || '').replace(/"/g, '');
|
|
385
480
|
await this.query(`SAVEPOINT "${name}"`);
|
|
386
481
|
} else {
|
|
387
|
-
throw Error('No active transaction');
|
|
482
|
+
throw new Error('No active transaction');
|
|
388
483
|
}
|
|
389
484
|
return this;
|
|
390
485
|
}
|
|
@@ -398,12 +493,12 @@ export class PgDb extends QueryAble {
|
|
|
398
493
|
name = (name || '').replace(/"/g, '');
|
|
399
494
|
await this.query(`RELEASE SAVEPOINT "${name}"`);
|
|
400
495
|
} else {
|
|
401
|
-
throw Error('No active transaction');
|
|
496
|
+
throw new Error('No active transaction');
|
|
402
497
|
}
|
|
403
498
|
return this;
|
|
404
499
|
}
|
|
405
500
|
|
|
406
|
-
async transactionBegin(options?: { isolationLevel?: TranzactionIsolationLevel, deferrable?:boolean, readOnly?: boolean}): Promise<PgDb> {
|
|
501
|
+
async transactionBegin(options?: { isolationLevel?: TranzactionIsolationLevel, deferrable?: boolean, readOnly?: boolean }): Promise<PgDb> {
|
|
407
502
|
let pgDb = this.connection ? this : await this.dedicatedConnectionBegin();
|
|
408
503
|
let q = 'BEGIN'
|
|
409
504
|
if (options?.isolationLevel) {
|
|
@@ -440,13 +535,13 @@ export class PgDb extends QueryAble {
|
|
|
440
535
|
return this.connection != null;
|
|
441
536
|
}
|
|
442
537
|
|
|
443
|
-
async execute(fileName: string, statementTransformerFunction?: (string) => string): Promise<void> {
|
|
538
|
+
async execute(fileName: string, statementTransformerFunction?: (s: string) => string): Promise<void> {
|
|
444
539
|
let isTransactionInPlace = this.isTransactionActive();
|
|
445
540
|
// statements must be run in a dedicated connection
|
|
446
541
|
let pgdb = isTransactionInPlace ? this : await this.dedicatedConnectionBegin();
|
|
447
542
|
|
|
448
543
|
/** run statements one after the other */
|
|
449
|
-
let runStatementList = (statementList) => {
|
|
544
|
+
let runStatementList = (statementList: string[]) => {
|
|
450
545
|
//this.getLogger(true).log('consumer start', commands.length);
|
|
451
546
|
return new Promise((resolve, reject) => {
|
|
452
547
|
let currentStatement = 0;
|
|
@@ -474,10 +569,10 @@ export class PgDb extends QueryAble {
|
|
|
474
569
|
|
|
475
570
|
let lineCounter = 0;
|
|
476
571
|
let promise = new Promise<void>((resolve, reject) => {
|
|
477
|
-
let statementList = [];
|
|
478
|
-
let tmp = '', t: RegExpExecArray;
|
|
479
|
-
let consumer;
|
|
480
|
-
let inQuotedString:string;
|
|
572
|
+
let statementList: string[] = [];
|
|
573
|
+
let tmp = '', t: RegExpExecArray | null;
|
|
574
|
+
let consumer: Promise<any> | null;
|
|
575
|
+
let inQuotedString: string | null;
|
|
481
576
|
let rl = readline.createInterface({
|
|
482
577
|
input: fs.createReadStream(fileName),
|
|
483
578
|
terminal: false
|
|
@@ -498,7 +593,7 @@ export class PgDb extends QueryAble {
|
|
|
498
593
|
let s = line.slice(SQL_TOKENIZER_REGEXP.lastIndex - 1);
|
|
499
594
|
let token = s.match(SQL_$_ESCAPE_REGEXP);
|
|
500
595
|
if (!token) {
|
|
501
|
-
throw Error('Invalid sql in line: ' + line);
|
|
596
|
+
throw new Error('Invalid sql in line: ' + line);
|
|
502
597
|
}
|
|
503
598
|
inQuotedString = token[0];
|
|
504
599
|
SQL_TOKENIZER_REGEXP.lastIndex += inQuotedString.length - 1;
|
|
@@ -563,7 +658,7 @@ export class PgDb extends QueryAble {
|
|
|
563
658
|
}
|
|
564
659
|
});
|
|
565
660
|
});
|
|
566
|
-
let error;
|
|
661
|
+
let error: Error | null;
|
|
567
662
|
return promise
|
|
568
663
|
.catch((e) => {
|
|
569
664
|
error = e;
|
|
@@ -586,21 +681,37 @@ export class PgDb extends QueryAble {
|
|
|
586
681
|
}
|
|
587
682
|
|
|
588
683
|
private listeners = new EventEmitter();
|
|
684
|
+
private connectionForListen: pg.PoolClient | null = null;
|
|
685
|
+
private _needToRestartConnectionForListen = false;
|
|
686
|
+
private restartConnectionForListen: Promise<Error | null> | null = null;
|
|
589
687
|
|
|
590
688
|
/**
|
|
591
689
|
* LISTEN to a channel for a NOTIFY (https://www.postgresql.org/docs/current/sql-listen.html)
|
|
592
690
|
* One connection will be dedicated for listening if there are any listeners.
|
|
593
691
|
* When there is no other callback for a channel, LISTEN command is executed
|
|
594
692
|
*/
|
|
595
|
-
async listen(channel: string, callback: (notification:Notification) => void) {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
693
|
+
async listen(channel: string, callback: (notification: Notification) => void) {
|
|
694
|
+
let restartConnectionError: Error | null = null;
|
|
695
|
+
if (this.needToFixConnectionForListen()) {
|
|
696
|
+
restartConnectionError = await this.runRestartConnectionForListen();
|
|
599
697
|
}
|
|
600
|
-
if (
|
|
601
|
-
|
|
698
|
+
if (this.listeners.listenerCount(channel)) {
|
|
699
|
+
this.listeners.on(channel, callback);
|
|
700
|
+
} else {
|
|
701
|
+
if (restartConnectionError) {
|
|
702
|
+
throw restartConnectionError;
|
|
703
|
+
}
|
|
704
|
+
try {
|
|
705
|
+
if (!this.connectionForListen) {
|
|
706
|
+
await this.initConnectionForListen();
|
|
707
|
+
}
|
|
708
|
+
await this.connectionForListen!.query(`LISTEN "${channel}"`);
|
|
709
|
+
} catch (err) {
|
|
710
|
+
this._needToRestartConnectionForListen = true;
|
|
711
|
+
throw err;
|
|
712
|
+
}
|
|
713
|
+
this.listeners.on(channel, callback);
|
|
602
714
|
}
|
|
603
|
-
this.listeners.on(channel, callback);
|
|
604
715
|
}
|
|
605
716
|
|
|
606
717
|
/**
|
|
@@ -608,37 +719,122 @@ export class PgDb extends QueryAble {
|
|
|
608
719
|
* When all callback is removed from a channel UNLISTEN command is executed
|
|
609
720
|
* When all callback is removed from all channel, dedicated connection is released
|
|
610
721
|
*/
|
|
611
|
-
async unlisten(channel: string, callback?: (Notification) => void) {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
722
|
+
async unlisten(channel: string, callback?: (notification: Notification) => void) {
|
|
723
|
+
let restartConnectionError: Error | null = null;
|
|
724
|
+
if (this.needToFixConnectionForListen()) {
|
|
725
|
+
restartConnectionError = await this.runRestartConnectionForListen();
|
|
615
726
|
}
|
|
616
|
-
if (callback) {
|
|
727
|
+
if (callback && this.listeners.listenerCount(channel) > 1) {
|
|
617
728
|
this.listeners.removeListener(channel, callback);
|
|
618
729
|
} else {
|
|
730
|
+
if (restartConnectionError) {
|
|
731
|
+
throw restartConnectionError;
|
|
732
|
+
}
|
|
733
|
+
try {
|
|
734
|
+
await this.internalQuery({ connection: this.connectionForListen, sql: `UNLISTEN "${channel}"` });
|
|
735
|
+
if (this.connectionForListen && this.listeners.eventNames().length == 1) {
|
|
736
|
+
this.connectionForListen.removeAllListeners('notification');
|
|
737
|
+
this.connectionForListen.release();
|
|
738
|
+
this.connectionForListen = null;
|
|
739
|
+
}
|
|
740
|
+
} catch (err) {
|
|
741
|
+
this._needToRestartConnectionForListen = true;
|
|
742
|
+
throw err;
|
|
743
|
+
}
|
|
619
744
|
this.listeners.removeAllListeners(channel);
|
|
620
745
|
}
|
|
621
|
-
|
|
622
|
-
if (!this.listeners.listenerCount(channel)) {
|
|
623
|
-
await this.internalQuery({ connection: this.connectionForListen, sql: 'UNLISTEN ' + channel });
|
|
624
|
-
}
|
|
625
|
-
let allListeners = this.listeners.eventNames().reduce((sum, ename) => sum + this.listeners.listenerCount(ename), 0);
|
|
626
|
-
if (!allListeners && this.connectionForListen) {
|
|
627
|
-
this.connectionForListen.removeAllListeners('notification');
|
|
628
|
-
this.connectionForListen.release();
|
|
629
|
-
this.connectionForListen = null;
|
|
630
|
-
}
|
|
631
746
|
}
|
|
632
747
|
|
|
633
748
|
/**
|
|
634
749
|
* Notify a channel (https://www.postgresql.org/docs/current/sql-notify.html)
|
|
635
750
|
*/
|
|
636
751
|
async notify(channel: string, payload?: string) {
|
|
752
|
+
if (this.needToFixConnectionForListen()) {
|
|
753
|
+
let restartConnectionError = await this.runRestartConnectionForListen();
|
|
754
|
+
if (restartConnectionError) {
|
|
755
|
+
throw restartConnectionError;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
let hasConnectionForListen = !!this.connectionForListen;
|
|
637
759
|
let connection = this.connectionForListen || this.connection;
|
|
638
760
|
//let sql = 'NOTIFY ' + channel + ', :payload';
|
|
639
761
|
let sql = 'SELECT pg_notify(:channel, :payload)';
|
|
640
762
|
let params = { channel, payload };
|
|
641
|
-
|
|
763
|
+
try {
|
|
764
|
+
return this.internalQuery({ connection, sql, params });
|
|
765
|
+
} catch (err) {
|
|
766
|
+
if (hasConnectionForListen) {
|
|
767
|
+
this._needToRestartConnectionForListen = true;
|
|
768
|
+
}
|
|
769
|
+
throw err;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
async runRestartConnectionForListen(): Promise<Error | null> {
|
|
774
|
+
if (!this._needToRestartConnectionForListen) {
|
|
775
|
+
return null;
|
|
776
|
+
}
|
|
777
|
+
let errorResult: Error | null = null;
|
|
778
|
+
if (!this.restartConnectionForListen) {
|
|
779
|
+
this.restartConnectionForListen = (async () => {
|
|
780
|
+
let eventNames = this.listeners.eventNames();
|
|
781
|
+
if (this.connectionForListen) {
|
|
782
|
+
try {
|
|
783
|
+
this.connectionForListen.on('notification', this.notificationListener);
|
|
784
|
+
this.connectionForListen.on('error', this.errorListener);
|
|
785
|
+
} catch (e) { }
|
|
786
|
+
try {
|
|
787
|
+
await this.connectionForListen.release();
|
|
788
|
+
} catch (e) { }
|
|
789
|
+
this.connectionForListen = null;
|
|
790
|
+
}
|
|
791
|
+
let error: Error | null = null;
|
|
792
|
+
if (eventNames.length) {
|
|
793
|
+
try {
|
|
794
|
+
await this.initConnectionForListen();
|
|
795
|
+
for (let channel of eventNames) {
|
|
796
|
+
await this.connectionForListen!.query(`LISTEN "${channel as string}"`);
|
|
797
|
+
}
|
|
798
|
+
} catch (err) {
|
|
799
|
+
error = <Error>err;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return error;
|
|
803
|
+
})();
|
|
804
|
+
errorResult = await this.restartConnectionForListen;
|
|
805
|
+
this.restartConnectionForListen = null;
|
|
806
|
+
} else {
|
|
807
|
+
errorResult = await this.restartConnectionForListen;
|
|
808
|
+
}
|
|
809
|
+
if (!errorResult) {
|
|
810
|
+
this._needToRestartConnectionForListen = false;
|
|
811
|
+
}
|
|
812
|
+
return errorResult;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
needToFixConnectionForListen(): boolean {
|
|
816
|
+
return this._needToRestartConnectionForListen;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
private async tryToFixConnectionForListenActively() {
|
|
820
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
821
|
+
let error = await this.runRestartConnectionForListen();
|
|
822
|
+
if (error) {
|
|
823
|
+
await this.tryToFixConnectionForListenActively();
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
notificationListener = (notification: Notification) => this.listeners.emit(notification.channel, notification)
|
|
828
|
+
|
|
829
|
+
errorListener = (e: Error) => {
|
|
830
|
+
this._needToRestartConnectionForListen = true;
|
|
831
|
+
this.tryToFixConnectionForListenActively();
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
private async initConnectionForListen() {
|
|
835
|
+
this.connectionForListen = await this.pool.connect();
|
|
836
|
+
this.connectionForListen.on('notification', this.notificationListener);
|
|
837
|
+
this.connectionForListen.on('error', this.errorListener);
|
|
642
838
|
}
|
|
643
839
|
}
|
|
644
840
|
|