pogi 3.0.0-beta2 → 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 (55) hide show
  1. package/.env +5 -0
  2. package/lib/connectionOptions.d.ts +1 -4
  3. package/lib/pgDb.d.ts +8 -8
  4. package/lib/pgDb.js +7 -7
  5. package/lib/pgDb.js.map +1 -1
  6. package/lib/pgDbInterface.d.ts +1 -32
  7. package/lib/pgDbInterface.js +8 -8
  8. package/lib/pgDbInterface.js.map +1 -1
  9. package/lib/pgSchema.d.ts +7 -9
  10. package/lib/pgSchema.js.map +1 -1
  11. package/lib/pgSchemaInterface.d.ts +0 -12
  12. package/lib/pgSchemaInterface.js +0 -1
  13. package/lib/pgTable.d.ts +4 -4
  14. package/lib/pgTableInterface.d.ts +0 -74
  15. package/lib/pgTableInterface.js.map +1 -1
  16. package/lib/pgUtils.d.ts +5 -5
  17. package/lib/pgUtils.js.map +1 -1
  18. package/lib/queryAble.d.ts +3 -4
  19. package/lib/queryAble.js +3 -3
  20. package/lib/queryAble.js.map +1 -1
  21. package/lib/queryAbleInterface.d.ts +4 -4
  22. package/lib/test/pgDbOperatorSpec.d.ts +1 -0
  23. package/lib/test/pgDbOperatorSpec.js +326 -0
  24. package/lib/test/pgDbOperatorSpec.js.map +1 -0
  25. package/lib/test/pgDbSpec.d.ts +1 -0
  26. package/lib/test/pgDbSpec.js +1139 -0
  27. package/lib/test/pgDbSpec.js.map +1 -0
  28. package/lib/test/pgServiceRestartTest.d.ts +1 -0
  29. package/lib/test/pgServiceRestartTest.js +1532 -0
  30. package/lib/test/pgServiceRestartTest.js.map +1 -0
  31. package/package.json +1 -1
  32. package/src/bin/generateInterface.ts +0 -54
  33. package/src/connectionOptions.ts +0 -77
  34. package/src/index.d.ts +0 -7
  35. package/src/index.ts +0 -6
  36. package/src/pgConverters.test.ts +0 -10
  37. package/src/pgConverters.ts +0 -59
  38. package/src/pgDb.test.ts +0 -1324
  39. package/src/pgDb.ts +0 -842
  40. package/src/pgDbInterface.ts +0 -57
  41. package/src/pgDbLogger.ts +0 -13
  42. package/src/pgDbOperators.test.ts +0 -478
  43. package/src/pgDbOperators.ts +0 -85
  44. package/src/pgSchema.ts +0 -16
  45. package/src/pgSchemaInterface.ts +0 -12
  46. package/src/pgTable.ts +0 -369
  47. package/src/pgTableInterface.ts +0 -131
  48. package/src/pgUtils.ts +0 -300
  49. package/src/queryAble.ts +0 -365
  50. package/src/queryAbleInterface.ts +0 -104
  51. package/src/queryWhere.ts +0 -325
  52. package/src/test/init.sql +0 -122
  53. package/src/test/pgServiceRestartTest.ts +0 -1500
  54. package/src/test/throw_exception.sql +0 -5
  55. package/src/test/tricky.sql +0 -13
package/src/pgUtils.ts DELETED
@@ -1,300 +0,0 @@
1
- import { QueryOptions, IQueryAble } from "./queryAbleInterface";
2
- import { ResultFieldType } from "./pgDbInterface";
3
- import { FieldType } from "./pgDb";
4
- import { PgDbLogger } from "./pgDbLogger";
5
- import { IPgTable } from "./pgTableInterface";
6
- import * as _ from 'lodash';
7
- import util = require('util');
8
- import * as pg from 'pg';
9
- import { ForceEscapeColumnsOptions } from "./connectionOptions";
10
-
11
- const NAMED_PARAMS_REGEXP = /(?:^|[^:]):(!?[a-zA-Z0-9_]+)/g; // do not convert "::type cast"
12
- const ASC_DESC_REGEXP = /^\s*(.+?)(?:\s+(asc|desc))?\s*$/i;
13
-
14
- export let pgUtils = {
15
-
16
- logError(logger: PgDbLogger, options: { error?: string | Error, sql: string, params: any, connection?: pg.PoolClient | null }) {
17
- let { error, sql, params, connection } = options;
18
- logger.error(error, sql, util.inspect(logger.paramSanitizer ? logger.paramSanitizer(params) : params, false, null), connection ? connection.processID : null);
19
- },
20
-
21
- quoteFieldNameInsecure(f: string) {
22
- return f.indexOf('"') == -1 && f.indexOf('(') == -1 ? '"' + f + '"' : f;
23
- },
24
-
25
- quoteFieldName(f: string) {
26
- if (typeof f === 'string' && f.length) {
27
- return `"${f
28
- .replace(/^\s*"*/, '') // trim "
29
- .replace(/"*\s*$/, '')
30
- .replace(/"/g, '""')}"`;
31
- } else {
32
- throw new Error(`Invalid field: ${f}`);
33
- }
34
- },
35
-
36
- quoteFieldNameOrPositionInsecure(f: string | number): string {
37
- if (Number.isInteger(+f)) {
38
- if (!Number.isInteger(+f) || +f < 1) throw new Error(`Invalid field: ${f}`);
39
- return '' + f;
40
- } else if (typeof f === 'string' && f.length) {
41
- return f.indexOf('"') == -1 && f.indexOf('(') == -1 ? '"' + f + '"' : f;
42
- } else {
43
- throw new Error(`Invalid field: ${f}`);
44
- }
45
- },
46
-
47
- /** ex. for order by column position can be use, which needs no quote */
48
- quoteFieldNameOrPosition(f: string | number): string {
49
- if (Number.isInteger(+f)) {
50
- if (!Number.isInteger(+f) || +f < 1) throw new Error(`Invalid field: ${f}`);
51
- return '' + f;
52
- } else if (typeof f === 'string' && f.length) {
53
- return `"${f
54
- .replace(/^\s*"*\s*/, '') // trim "
55
- .replace(/\s*"*\s*$/, '')
56
- .replace(/"/g, '""')}"`;
57
- } else {
58
- throw new Error(`Invalid field: ${f}`);
59
- }
60
- },
61
- /**
62
- * https://www.postgresql.org/docs/current/functions-json.html
63
- * column->'a' ,
64
- * column -> 3
65
- */
66
- quoteFieldNameJsonbOrPosition(f: string | number): string {
67
- // treat numeric json keys as array indices, otherwise quote it
68
- if (Number.isInteger(+f)) {
69
- return '' + f;
70
- } if (typeof f === 'string' && f.length) {
71
- return `'${f
72
- .replace(/^\s*'*/, '') // trim "
73
- .replace(/'*\s*$/, '')
74
- .replace(/'/g, "''")}'`;
75
- } else {
76
- throw new Error(`Invalid field: ${f}`);
77
- }
78
- },
79
-
80
- processQueryFields<T>(options: QueryOptions, pgTable?: IPgTable<T>): string {
81
- let escapeColumns = (
82
- (pgTable?.db.config.forceEscapeColumns === true || (pgTable?.db.config.forceEscapeColumns as ForceEscapeColumnsOptions)?.select === true)
83
- && options.forceEscapeColumns !== false && (options.forceEscapeColumns as ForceEscapeColumnsOptions)?.select !== false)
84
- || options.forceEscapeColumns === true
85
- || (options.forceEscapeColumns as ForceEscapeColumnsOptions)?.select;
86
-
87
- let s = options && options.distinct ? ' DISTINCT ' : ' ';
88
- if (options && options.fields) {
89
- if (Array.isArray(options.fields)) {
90
- if (escapeColumns) {
91
- return s + options.fields.map(pgUtils.quoteFieldName).join(', ');
92
- }
93
- return s + options.fields.map(pgUtils.quoteFieldNameInsecure).join(', ');
94
- } else {
95
- return s + options.fields;
96
- }
97
- } else {
98
- return s + ' *';
99
- }
100
- },
101
-
102
- /**
103
- * :named -> $1 (not works with DDL (schema, table, column))
104
- * :!named -> "value" (for DDL (schema, table, column))
105
- * do not touch ::type cast
106
- */
107
- processNamedParams(sql: string, params: Object) {
108
- let sql2 = [];
109
- let params2 = [];
110
-
111
- let p = NAMED_PARAMS_REGEXP.exec(sql);
112
- let lastIndex = 0;
113
- while (p) {
114
- let ddl = false;
115
- let name = p[1];
116
- if (name[0] == '!') {
117
- name = name.slice(1);
118
- ddl = true;
119
- }
120
-
121
- if (!(name in params)) {
122
- throw new Error(`No ${p[1]} in params (keys: ${Object.keys(params)})`);
123
- }
124
- sql2.push(sql.slice(lastIndex, NAMED_PARAMS_REGEXP.lastIndex - p[1].length - 1));
125
-
126
- if (ddl) {
127
- sql2.push(pgUtils.quoteFieldName(params[name]));
128
- } else {
129
- params2.push(params[name]);
130
- sql2.push('$' + params2.length);
131
- }
132
- lastIndex = NAMED_PARAMS_REGEXP.lastIndex;
133
- p = NAMED_PARAMS_REGEXP.exec(sql);
134
- }
135
- sql2.push(sql.slice(lastIndex));
136
-
137
- return {
138
- sql: sql2.join(''),
139
- params: params2
140
- }
141
- },
142
-
143
- handleColumnEscapeGroupBy<T>(options: QueryOptions, pgTable?: IPgTable<T>): string {
144
- if (!options.groupBy) return '';
145
- let escapeColumns = (
146
- (pgTable?.db.config.forceEscapeColumns === true || (pgTable?.db.config.forceEscapeColumns as ForceEscapeColumnsOptions)?.groupBy === true)
147
- && options.forceEscapeColumns !== false && (options.forceEscapeColumns as ForceEscapeColumnsOptions)?.groupBy !== false)
148
- || options.forceEscapeColumns === true
149
- || (options.forceEscapeColumns as ForceEscapeColumnsOptions)?.groupBy;
150
-
151
- if (escapeColumns) {
152
- if (Array.isArray(options.groupBy)) {
153
- return ' GROUP BY ' + options.groupBy.map(pgUtils.quoteFieldNameOrPosition).join(',');
154
- } else {
155
- return ' GROUP BY ' + pgUtils.quoteFieldNameOrPosition(options.groupBy);
156
- }
157
- } else {
158
- if (Array.isArray(options.groupBy)) {
159
- return ' GROUP BY ' + options.groupBy.map(pgUtils.quoteFieldNameOrPositionInsecure).join(',');
160
- } else {
161
- return ' GROUP BY ' + pgUtils.quoteFieldNameOrPositionInsecure(options.groupBy);
162
- }
163
- }
164
- },
165
-
166
- handleColumnEscapeOrderBy<T>(options: QueryOptions, pgTable: IPgTable<T>): string {
167
- if (!options.orderBy) return '';
168
- let sql = '';
169
- let escapeColumns = (
170
- (pgTable?.db.config.forceEscapeColumns === true || (pgTable?.db.config.forceEscapeColumns as ForceEscapeColumnsOptions)?.orderBy === true)
171
- && options.forceEscapeColumns !== false && (options.forceEscapeColumns as ForceEscapeColumnsOptions)?.orderBy !== false)
172
- || options.forceEscapeColumns === true
173
- || (options.forceEscapeColumns as ForceEscapeColumnsOptions)?.orderBy;
174
-
175
- let orderBy = typeof options.orderBy === 'string' ? options.orderBy.split(',') : options.orderBy;
176
- if (Array.isArray(orderBy)) {
177
- let orderBy2: string[];
178
-
179
- if (escapeColumns) {
180
- orderBy2 = orderBy.map(v => {
181
- if (typeof v === 'number') return pgUtils.quoteFieldNameOrPosition(v);
182
- else if (typeof v !== 'string' || !v.length) throw new Error(`Invalid orderBy: ${v}`);
183
- if (v[0] == '+') return pgUtils.quoteFieldNameOrPosition(v.slice(1));
184
- if (v[0] == '-') return pgUtils.quoteFieldNameOrPosition(v.slice(1)) + ' desc';
185
- let o = ASC_DESC_REGEXP.exec(v);
186
- if (!o) throw new Error(`Invalid orderBy: ${v}`);
187
- return `${pgUtils.quoteFieldNameOrPosition(o[1])} ${o[2] ?? ''}`;
188
- });
189
- } else {
190
- orderBy2 = orderBy.map(v => {
191
- if (typeof v === 'number') return pgUtils.quoteFieldNameOrPositionInsecure(v);
192
- else if (typeof v !== 'string' || !v.length) throw new Error(`Invalid orderBy: ${v}`);
193
- if (v[0] == '+') return pgUtils.quoteFieldNameOrPositionInsecure(v.slice(1));
194
- if (v[0] == '-') return pgUtils.quoteFieldNameOrPositionInsecure(v.slice(1)) + ' desc';
195
- let o = ASC_DESC_REGEXP.exec(v);
196
- if (!o) throw new Error(`Invalid orderBy: ${v}`);
197
- return `${pgUtils.quoteFieldNameOrPositionInsecure(o[1])} ${o[2] ?? ''}`;
198
- });
199
- }
200
- sql += ' ORDER BY ' + orderBy2.join(',');
201
- } else {
202
- throw new Error(`Invalid orderBy: ${options.orderBy}`);
203
- }
204
- return sql;
205
- },
206
-
207
- processQueryOptions<T>(options: QueryOptions, pgTable: IPgTable<T>): string {
208
- options = options || {};
209
- let sql = '';
210
-
211
- if (options.groupBy) {
212
- sql += pgUtils.handleColumnEscapeGroupBy(options, pgTable);
213
- }
214
- if (options.orderBy) {
215
- sql += pgUtils.handleColumnEscapeOrderBy(options, pgTable);
216
-
217
- if (options.orderByNullsFirst != null) {
218
- sql += ' NULLS ' + options.orderByNullsFirst ? 'FIRST' : 'LAST';
219
- }
220
- }
221
- if (options.limit) {
222
- if (!Number.isInteger(options.limit) || options.limit < 0) throw new Error(`Invalid limit: ${options.limit}`);
223
- sql += ` LIMIT ${options.limit}`;
224
- }
225
- if (options.offset) {
226
- if (!Number.isInteger(options.offset) || options.offset < 0) throw new Error(`Invalid offset: ${options.offset}`);
227
- sql += ` OFFSET ${options.offset}`;
228
- }
229
- if (options.forUpdate) {
230
- sql += ' FOR UPDATE';
231
- }
232
- return sql;
233
- },
234
-
235
- /**
236
- * NOTE-DATE: there are 2 approaches to keep tz (the time correctly):
237
- * 1) use Date.toISOString() function, but then the $x placeholder should be TIMESTAMP WITH TIME ZONE $x
238
- * 2) use Date, and then no need to change the placeholder $x
239
- * lets use 2)
240
- */
241
- transformInsertUpdateParams(param: any, fieldType: FieldType) {
242
- return (param != null && fieldType == FieldType.JSON) ? JSON.stringify(param) :
243
- (param != null && fieldType == FieldType.TIME && !(param instanceof Date)) ? new Date(param) : param;
244
- },
245
-
246
- postProcessResult(res: any[], fields: ResultFieldType[], pgdbTypeParsers: { [oid: number]: (s: string) => any }) {
247
- if (res) {
248
- if (res[0] && !Array.isArray(res[0])) {
249
- if (Object.keys(res[0]).length != fields.length) {
250
- throw new Error("Name collision for the query, two or more fields have the same name.");
251
- }
252
- }
253
- pgUtils.convertTypes(res, fields, pgdbTypeParsers);
254
- }
255
- },
256
-
257
- convertTypes(res: any[], fields: ResultFieldType[], pgdbTypeParsers: { [oid: number]: (s: string) => any }) {
258
- let isArrayMode = Array.isArray(res[0]);
259
- fields.forEach((field, i) => {
260
- if (pgdbTypeParsers[field.dataTypeID]) {
261
- if (isArrayMode) {
262
- res.forEach(e => e[i] = e[i] == null ? null : pgdbTypeParsers[field.dataTypeID](e[i]));
263
- } else {
264
- res.forEach(e => e[field.name] = e[field.name] == null ? null : pgdbTypeParsers[field.dataTypeID](e[field.name]));
265
- }
266
- }
267
- });
268
- },
269
-
270
- createFunctionCaller(q: IQueryAble, fn: { schema: string, name: string, return_single_row: boolean, return_single_value: boolean }) {
271
- return async (...args: any[]) => {
272
- let placeHolders: string[] = [];
273
- let params: any[] = [];
274
- args.forEach((arg) => {
275
- placeHolders.push('$' + (placeHolders.length + 1));
276
- params.push(arg);
277
- });
278
- let res = await q.query(`SELECT "${fn.schema}"."${fn.name}"(${placeHolders.join(',')})`, params);
279
-
280
- if (fn.return_single_value) {
281
- let keys = res[0] ? Object.keys(res[0]) : [];
282
- if (keys.length != 1) {
283
- throw new Error(`Return type error. schema: ${fn.schema} fn: ${fn.name} expected return type: single value, current value:` + JSON.stringify(res))
284
- }
285
- res = res.map((r) => r[keys[0]]);
286
- }
287
- if (fn.return_single_row) {
288
- if (res.length != 1) {
289
- throw new Error(`Return type error. schema: ${fn.schema} fn: ${fn.name} expected return type: single value, current value:` + JSON.stringify(res))
290
- }
291
- return res[0];
292
- } else {
293
- return res;
294
- }
295
- }
296
- },
297
- escapeForLike(s: string): string {
298
- return s.replace(/([\\%_])/g, '\\$1');
299
- }
300
- };
package/src/queryAble.ts DELETED
@@ -1,365 +0,0 @@
1
- import { PgDbLogger } from "./pgDbLogger";
2
- import { pgUtils } from "./pgUtils";
3
- import * as stream from "stream";
4
- import { IPgSchema } from "./pgSchemaInterface";
5
- import * as pg from 'pg';
6
- import util = require('util');
7
- import QueryStream = require('pg-query-stream');
8
- import through = require('through');
9
- import {ResultFieldType, IPgDb} from "./pgDbInterface";
10
- import {SqlQueryOptions, IQueryAble, PgRowResult } from "./queryAbleInterface"
11
-
12
-
13
- let defaultLogger = {
14
- log: () => { },
15
- error: () => { }
16
- };
17
-
18
- export abstract class QueryAble implements IQueryAble {
19
- db!: IPgDb & IQueryAble; // assigned in async init
20
- schema!: IPgSchema;
21
- /*protected*/ logger!: PgDbLogger;
22
-
23
- public static connectionErrorListener = () => { }
24
-
25
- setLogger(logger: PgDbLogger) {
26
- this.logger = logger;
27
- }
28
-
29
- getLogger(useConsoleAsDefault = false) {
30
- return this.logger || this.schema && this.schema.logger || this.db.logger || (useConsoleAsDefault ? console : defaultLogger);
31
- }
32
-
33
- /** alias to {@link query} */
34
- async run(sql: string, params?: any[] | {}, options?: SqlQueryOptions): Promise<any[]> {
35
- return this.query(sql, params, options);
36
- }
37
-
38
- /**
39
- * Params can be
40
- * 1) array, then sql should have $1 $2 for placeholders
41
- * 2) object, then sql should have:
42
- * :example -> for params in statements (set/where), will be transformed to $1 $2 ...
43
- * :!example -> for DDL names (schema, table, column), will be replaced in the query
44
- * e.g. query('select * from a.b where id=$1;',['the_stage_is_set']);
45
- * e.g. query('select * from :!schema.:!table where id=:id;',{schema:'a',table:'b', id:'the_stage_is_set'});
46
- */
47
- async query(sql: string, params?: any[] | {} | null, options?: SqlQueryOptions): Promise<any[]> {
48
- let connection = this.db.connection;
49
- let logger = (options && options.logger || this.getLogger(false));
50
- return this.internalQuery({ connection, sql, params, logger });
51
- }
52
-
53
- protected async internalQuery(options: { connection: pg.PoolClient | null, sql: string, params?: any, logger?: PgDbLogger }): Promise<any[]>;
54
- protected async internalQuery(options: { connection: pg.PoolClient | null, sql: string, params?: any, logger?: PgDbLogger, rowMode: true }): Promise<PgRowResult>;
55
- protected async internalQuery(options: { connection: pg.PoolClient | null, sql: string, params?: any, logger?: PgDbLogger, rowMode?: boolean }): Promise<any[] | PgRowResult> {
56
- if (this.db.needToFixConnectionForListen()) {
57
- await this.db.runRestartConnectionForListen();
58
- }
59
- let { connection, sql, params, logger } = options;
60
- logger = logger || this.getLogger(false);
61
-
62
- try {
63
- if (params && !Array.isArray(params)) {
64
- let p = pgUtils.processNamedParams(sql, params);
65
- sql = p.sql;
66
- params = p.params;
67
- }
68
- let query = options?.rowMode ? { text: sql, values: params, rowMode: 'array' } : { text: sql, values: params };
69
- let res;
70
- if (connection) {
71
- logger.log('reused connection', sql, util.inspect(params, false, null), connection.processID);
72
- res = await connection.query(query);
73
- await this.checkAndFixOids(connection, res.fields);
74
- } else {
75
- connection = await this.db.pool.connect();
76
- logger.log('new connection', sql, util.inspect(params, false, null), connection.processID);
77
-
78
- connection.on('error', QueryAble.connectionErrorListener);
79
- res = await connection.query(query);
80
- await this.checkAndFixOids(connection, res.fields);
81
-
82
- connection.off('error', QueryAble.connectionErrorListener);
83
- connection.release();
84
- connection = null;
85
- }
86
- this.postProcessFields(res.rows, res.fields, logger);
87
- return options?.rowMode ? { columns: (res.fields || []).map(f => f.name), rows: res.rows || [] } : res.rows;
88
- } catch (e) {
89
- pgUtils.logError(logger, { error: <Error>e, sql, params, connection });
90
- if (connection) {
91
- try {
92
- //If any problem has happened in a dedicated connection, (wrong sql format or non-accessible postgres server)
93
- //close the connection to be a free connection in the pool,
94
- //but keep the db.connection member non - null to crash in all of the following commands
95
- connection.release();
96
- } catch (e) {
97
- logger.error('connection error', (<Error>e).message);
98
- }
99
- }
100
- throw e;
101
- }
102
- }
103
-
104
- /**
105
- * Same as query but response is two array: columns and rows and rows are arrays also not objects
106
- * This is useful for queries which have colliding column names
107
- */
108
- async queryAsRows(sql: string, params?: any[] | {}, options?: SqlQueryOptions): Promise<PgRowResult> {
109
- let connection = this.db.connection;
110
- let logger = (options && options.logger || this.getLogger(false));
111
- return this.internalQuery({ connection, sql, params, logger, rowMode: true });
112
- }
113
-
114
- /**
115
- * If the callback function return true, the connection will be closed.
116
- */
117
- async queryWithOnCursorCallback(sql: string, params: any[] | Record<string, any> | null, options: SqlQueryOptions | null, callback: (res: any) => any): Promise<void> {
118
- if (this.db.needToFixConnectionForListen()) {
119
- await this.db.runRestartConnectionForListen();
120
- }
121
- let connection = this.db.connection!;
122
- let logger = this.getLogger(true);
123
- let positionedParams: any[] | null | undefined;
124
-
125
- try {
126
- if (params && !Array.isArray(params)) {
127
- let p = pgUtils.processNamedParams(sql, params);
128
- sql = p.sql;
129
- params = p.params;
130
- } else {
131
- positionedParams = params;
132
- }
133
-
134
- let queryInternal = async () => {
135
- this.getLogger(false).log(sql, util.inspect(positionedParams, false, null), connection.processID);
136
- let fieldsToFix: ResultFieldType[] | undefined;
137
- let isFirst = true;
138
-
139
- let query = new QueryStream(sql, positionedParams);
140
- let stream = connection.query(query);
141
- await new Promise((resolve, reject) => {
142
- query.handleError = reject;
143
- stream.on('data', (res: any) => {
144
- try {
145
- let fields = stream._result && stream._result.fields || stream.cursor._result && stream.cursor._result.fields;
146
- if (isFirst) {
147
- if (this.hasUnknownOids(fields)) {
148
- fieldsToFix = fields;
149
- stream.destroy();
150
- return;
151
- }
152
- isFirst = false;
153
- }
154
- this.postProcessFields([res], fields, this.getLogger(false));
155
-
156
- if (callback(res)) {
157
- stream.destroy();
158
- }
159
- } catch (e) {
160
- reject(e);
161
- }
162
- });
163
-
164
- stream.on('close', resolve);
165
- stream.on('error', reject);
166
- });
167
- if (fieldsToFix) {
168
- await this.checkAndFixOids(connection, fieldsToFix);
169
- query = new QueryStream(sql, positionedParams);
170
- stream = connection.query(query);
171
- await new Promise((resolve, reject) => {
172
- query.handleError = reject;
173
- stream.on('data', (res: any) => {
174
- try {
175
- let fields = stream._result && stream._result.fields || stream.cursor._result && stream.cursor._result.fields;
176
- this.postProcessFields([res], fields, this.getLogger(false));
177
-
178
- if (callback(res)) {
179
- stream.destroy();
180
- }
181
- } catch (e) {
182
- reject(e);
183
- }
184
- });
185
-
186
- stream.on('close', resolve);
187
- stream.on('error', reject);
188
- });
189
- }
190
- }
191
-
192
- if (connection) {
193
- await queryInternal();
194
- } else {
195
- connection = await this.db.pool.connect();
196
- logger.log('new connection', sql, util.inspect(positionedParams, false, null), connection.processID);
197
- connection.on('error', QueryAble.connectionErrorListener);
198
- await queryInternal();
199
-
200
- connection.off('error', QueryAble.connectionErrorListener);
201
- connection.release();
202
- }
203
- } catch (e) {
204
- pgUtils.logError(logger, { error: <Error>e, sql, params, connection });
205
- if (connection) {
206
- try {
207
- connection.release();
208
- } catch (e) {
209
- logger.error('connection error', (<Error>e).message);
210
- }
211
- }
212
- throw e;
213
- }
214
- }
215
-
216
- async queryAsStream(sql: string, params?: any[] | Record<string, any> | null, options?: SqlQueryOptions | null): Promise<stream.Readable> {
217
- if (this.db.needToFixConnectionForListen()) {
218
- await this.db.runRestartConnectionForListen();
219
- }
220
- let connection = this.db.connection;
221
- let logger = (options && options.logger || this.getLogger(false));
222
- let pgStream: any;
223
- let queriable = this;
224
- let isFirst = true;
225
- let convertTypeFilter = through(function (this: stream, data) {
226
- try {
227
- let fields = pgStream._result && pgStream._result.fields || pgStream.cursor._result && pgStream.cursor._result.fields;
228
- if (isFirst) {
229
- if (queriable.hasUnknownOids(fields)) {
230
- throw new Error('[337] Query returns fields with unknown oid.');
231
- }
232
- isFirst = false;
233
- }
234
- queriable.postProcessFields([data], fields, queriable.db.pgdbTypeParsers);
235
-
236
- this.emit('data', data);
237
- } catch (err) {
238
- this.emit('error', err);
239
- }
240
- });
241
- convertTypeFilter.on('error', (e: Error) => {
242
- if (connection) {
243
- try {
244
- connection.release();
245
- } catch (e) {
246
- logger.error('connection error', (<Error>e).message);
247
- }
248
- }
249
- connection = null;
250
- pgUtils.logError(logger, { error: e, sql, params, connection });
251
- });
252
-
253
- let positionedParams: any[] | undefined | null;
254
-
255
- try {
256
- if (params && !Array.isArray(params)) {
257
- let p = pgUtils.processNamedParams(sql, params);
258
- sql = p.sql;
259
- params = p.params;
260
- } else {
261
- positionedParams = params;
262
- }
263
-
264
- if (connection) {
265
- logger.log(sql, util.inspect(positionedParams, false, null), connection.processID);
266
- let query = new QueryStream(sql, positionedParams);
267
- query.handleError = (err: Error) => {
268
- convertTypeFilter.emit('error', err);
269
- };
270
- pgStream = connection.query(query);
271
- return pgStream.pipe(convertTypeFilter);
272
- } else {
273
- connection = await this.db.pool.connect();
274
- logger.log('new connection', sql, util.inspect(positionedParams, false, null), connection.processID);
275
- connection.on('error', QueryAble.connectionErrorListener);
276
-
277
- let query = new QueryStream(sql, positionedParams);
278
- query.handleError = (err: Error, _connection: pg.PoolClient) => {
279
- convertTypeFilter.emit('error', err);
280
- };
281
- pgStream = connection.query(query);
282
- pgStream.on('close', () => {
283
- if (connection) {
284
- connection.off('error', QueryAble.connectionErrorListener);
285
- connection.release();
286
- }
287
- connection = null;
288
- });
289
- pgStream.on('error', (e: Error) => {
290
- pgUtils.logError(logger, { error: e, sql, params: positionedParams, connection });
291
-
292
- if (connection) {
293
- connection.off('error', QueryAble.connectionErrorListener);
294
- connection.release();
295
- }
296
- connection = null;
297
- });
298
- return pgStream.pipe(convertTypeFilter);
299
- }
300
- } catch (e) {
301
- pgUtils.logError(logger, { error: <Error>e, sql, params: positionedParams, connection });
302
- throw e;
303
- }
304
- }
305
-
306
- async queryOne(sql: string, params?: any[] | {}, options?: SqlQueryOptions): Promise<any> {
307
- let res = await this.query(sql, params, options);
308
- if (res.length > 1) {
309
- let logger = (options && options.logger || this.getLogger(false));
310
- let error = Error('More then one rows exists');
311
- pgUtils.logError(logger, { error, sql, params, connection: this.db.connection });
312
- throw error;
313
- }
314
- return res[0];
315
- }
316
-
317
- async queryFirst(sql: string, params?: any[] | {}, options?: SqlQueryOptions): Promise<any> {
318
- let res = await this.query(sql, params, options);
319
- return res[0];
320
- }
321
-
322
- /** @return one record's one field */
323
- async queryOneField(sql: string, params?: any[] | {}, options?: SqlQueryOptions): Promise<any> {
324
- let res = await this.query(sql, params, options);
325
- if (!res.length) {
326
- return null;
327
- }
328
- let fieldName = Object.keys(res[0])[0];
329
- if (res.length > 1) {
330
- let logger = (options && options.logger || this.getLogger(false));
331
- let error = Error('More then one field exists!');
332
- pgUtils.logError(logger, { error, sql, params, connection: this.db.connection });
333
- throw error;
334
- }
335
- return res.length == 1 ? res[0][fieldName] : null;
336
- }
337
-
338
- /** @return one column for the matching records */
339
- async queryOneColumn(sql: string, params?: any[] | {}, options?: SqlQueryOptions): Promise<any[]> {
340
- let res = await this.query(sql, params, options);
341
- if (!res.length) {
342
- return [];
343
- }
344
- let fieldName = Object.keys(res[0])[0];
345
- return res.map(r => r[fieldName]);
346
- }
347
-
348
- private postProcessFields(rows: any[], fields: ResultFieldType[], logger: PgDbLogger) {
349
- pgUtils.postProcessResult(rows, fields, this.db.pgdbTypeParsers);
350
- if (this.db.postProcessResult) this.db.postProcessResult(rows, fields, logger);
351
- }
352
-
353
- private async checkAndFixOids(connection: pg.PoolClient, fields: ResultFieldType[]) {
354
- if (fields) {
355
- let oidList = fields.map(field => field.dataTypeID);
356
- return this.db.resetMissingParsers(connection, oidList);
357
- }
358
- }
359
-
360
- private hasUnknownOids(fields: ResultFieldType[]): boolean {
361
- let oidList = fields.map(field => field.dataTypeID);
362
- let unknownOids = oidList.filter(oid => !this.db.knownOids[oid]);
363
- return !!unknownOids.length;
364
- }
365
- }