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.
Files changed (83) hide show
  1. package/.vscode/launch.json +47 -15
  2. package/CHANGELOG.md +11 -0
  3. package/docs/API/PgDb.md +25 -0
  4. package/docs/notification.md +19 -0
  5. package/jest.config.js +23 -0
  6. package/lib/bin/generateInterface.js +3 -3
  7. package/lib/bin/generateInterface.js.map +1 -1
  8. package/lib/connectionOptions.d.ts +10 -0
  9. package/lib/index.d.ts +1 -1
  10. package/lib/pgConverters.d.ts +9 -8
  11. package/lib/pgConverters.js +46 -32
  12. package/lib/pgConverters.js.map +1 -1
  13. package/lib/pgConverters.test.d.ts +1 -0
  14. package/lib/pgConverters.test.js +13 -0
  15. package/lib/pgConverters.test.js.map +1 -0
  16. package/lib/pgDb.d.ts +27 -27
  17. package/lib/pgDb.js +293 -100
  18. package/lib/pgDb.js.map +1 -1
  19. package/lib/pgDb.test.d.ts +1 -0
  20. package/lib/pgDb.test.js +1126 -0
  21. package/lib/pgDb.test.js.map +1 -0
  22. package/lib/pgDbInterface.d.ts +53 -0
  23. package/lib/pgDbInterface.js +11 -0
  24. package/lib/pgDbInterface.js.map +1 -0
  25. package/lib/pgDbOperators.d.ts +3 -3
  26. package/lib/pgDbOperators.js +4 -7
  27. package/lib/pgDbOperators.js.map +1 -1
  28. package/lib/pgDbOperators.test.d.ts +1 -0
  29. package/lib/pgDbOperators.test.js +313 -0
  30. package/lib/pgDbOperators.test.js.map +1 -0
  31. package/lib/pgSchema.d.ts +10 -9
  32. package/lib/pgSchema.js.map +1 -1
  33. package/lib/pgSchemaInterface.d.ts +12 -0
  34. package/lib/pgSchemaInterface.js +3 -0
  35. package/lib/pgSchemaInterface.js.map +1 -0
  36. package/lib/pgTable.d.ts +15 -40
  37. package/lib/pgTable.js +54 -54
  38. package/lib/pgTable.js.map +1 -1
  39. package/lib/pgTableInterface.d.ts +102 -0
  40. package/lib/pgTableInterface.js +4 -0
  41. package/lib/pgTableInterface.js.map +1 -0
  42. package/lib/pgUtils.d.ts +16 -6
  43. package/lib/pgUtils.js +162 -31
  44. package/lib/pgUtils.js.map +1 -1
  45. package/lib/queryAble.d.ts +20 -53
  46. package/lib/queryAble.js +149 -80
  47. package/lib/queryAble.js.map +1 -1
  48. package/lib/queryAbleInterface.d.ts +55 -0
  49. package/lib/queryAbleInterface.js +7 -0
  50. package/lib/queryAbleInterface.js.map +1 -0
  51. package/lib/queryWhere.d.ts +2 -2
  52. package/lib/queryWhere.js +19 -23
  53. package/lib/queryWhere.js.map +1 -1
  54. package/mkdocs.yml +1 -0
  55. package/package.json +21 -11
  56. package/src/bin/generateInterface.ts +2 -2
  57. package/src/connectionOptions.ts +48 -13
  58. package/src/index.d.ts +7 -0
  59. package/src/index.ts +1 -1
  60. package/src/pgConverters.test.ts +10 -0
  61. package/src/pgConverters.ts +34 -22
  62. package/src/pgDb.test.ts +1324 -0
  63. package/src/pgDb.ts +318 -122
  64. package/src/pgDbInterface.ts +57 -0
  65. package/src/pgDbOperators.test.ts +478 -0
  66. package/src/pgDbOperators.ts +45 -22
  67. package/src/pgSchema.ts +10 -9
  68. package/src/pgSchemaInterface.ts +12 -0
  69. package/src/pgTable.ts +66 -98
  70. package/src/pgTableInterface.ts +131 -0
  71. package/src/pgUtils.ts +166 -42
  72. package/src/queryAble.ts +167 -125
  73. package/src/queryAbleInterface.ts +104 -0
  74. package/src/queryWhere.ts +42 -43
  75. package/{spec/resources → src/test}/init.sql +23 -0
  76. package/src/test/pgServiceRestartTest.ts +1500 -0
  77. package/{spec/resources → src/test}/throw_exception.sql +0 -0
  78. package/{spec/resources → src/test}/tricky.sql +0 -0
  79. package/{src/tsconfig.json → tsconfig.json} +12 -11
  80. package/spec/run.js +0 -5
  81. package/spec/support/jasmine.json +0 -9
  82. package/src/test/pgDbOperatorSpec.ts +0 -492
  83. package/src/test/pgDbSpec.ts +0 -994
package/src/pgUtils.ts CHANGED
@@ -1,28 +1,96 @@
1
- import {QueryOptions, ResultFieldType, QueryAble} from "./queryAble";
2
- import {FieldType} from "./pgDb";
3
- import {PgDbLogger} from "./pgDbLogger";
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";
4
6
  import * as _ from 'lodash';
7
+ import util = require('util');
8
+ import * as pg from 'pg';
9
+ import { ForceEscapeColumnsOptions } from "./connectionOptions";
5
10
 
6
- const util = require('util');
7
11
  const NAMED_PARAMS_REGEXP = /(?:^|[^:]):(!?[a-zA-Z0-9_]+)/g; // do not convert "::type cast"
8
- const ASC_DESC_REGEXP = /^([^" (]+)( asc| desc)?$/;
12
+ const ASC_DESC_REGEXP = /^\s*(.+?)(?:\s+(asc|desc))?\s*$/i;
9
13
 
10
14
  export let pgUtils = {
11
15
 
12
- logError(logger: PgDbLogger, options: { error?: string|Error, sql: string, params: any, connection }) {
16
+ logError(logger: PgDbLogger, options: { error?: string | Error, sql: string, params: any, connection?: pg.PoolClient | null }) {
13
17
  let { error, sql, params, connection } = options;
14
18
  logger.error(error, sql, util.inspect(logger.paramSanitizer ? logger.paramSanitizer(params) : params, false, null), connection ? connection.processID : null);
15
19
  },
16
20
 
17
- quoteField(f) {
21
+ quoteFieldNameInsecure(f: string) {
18
22
  return f.indexOf('"') == -1 && f.indexOf('(') == -1 ? '"' + f + '"' : f;
19
23
  },
20
24
 
21
- processQueryFields(options: QueryOptions): string {
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
+
22
87
  let s = options && options.distinct ? ' DISTINCT ' : ' ';
23
88
  if (options && options.fields) {
24
89
  if (Array.isArray(options.fields)) {
25
- return s + options.fields.map(pgUtils.quoteField).join(', ');
90
+ if (escapeColumns) {
91
+ return s + options.fields.map(pgUtils.quoteFieldName).join(', ');
92
+ }
93
+ return s + options.fields.map(pgUtils.quoteFieldNameInsecure).join(', ');
26
94
  } else {
27
95
  return s + options.fields;
28
96
  }
@@ -56,7 +124,7 @@ export let pgUtils = {
56
124
  sql2.push(sql.slice(lastIndex, NAMED_PARAMS_REGEXP.lastIndex - p[1].length - 1));
57
125
 
58
126
  if (ddl) {
59
- sql2.push('"' + ('' + params[name]).replace(/"/g, '""') + '"');
127
+ sql2.push(pgUtils.quoteFieldName(params[name]));
60
128
  } else {
61
129
  params2.push(params[name]);
62
130
  sql2.push('$' + params2.length);
@@ -64,7 +132,7 @@ export let pgUtils = {
64
132
  lastIndex = NAMED_PARAMS_REGEXP.lastIndex;
65
133
  p = NAMED_PARAMS_REGEXP.exec(sql);
66
134
  }
67
- sql2.push(sql.substr(lastIndex));
135
+ sql2.push(sql.slice(lastIndex));
68
136
 
69
137
  return {
70
138
  sql: sql2.join(''),
@@ -72,40 +140,93 @@ export let pgUtils = {
72
140
  }
73
141
  },
74
142
 
75
- processQueryOptions(options: QueryOptions): string {
76
- options = options || {};
77
- let sql = '';
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;
78
150
 
79
- if (options.groupBy) {
151
+ if (escapeColumns) {
80
152
  if (Array.isArray(options.groupBy)) {
81
- sql += ' GROUP BY ' + options.groupBy.map(pgUtils.quoteField).join(',');
153
+ return ' GROUP BY ' + options.groupBy.map(pgUtils.quoteFieldNameOrPosition).join(',');
82
154
  } else {
83
- sql += ' GROUP BY ' + pgUtils.quoteField(options.groupBy);
155
+ return ' GROUP BY ' + pgUtils.quoteFieldNameOrPosition(options.groupBy);
84
156
  }
85
- }
86
- if (options.orderBy) {
87
- if (typeof options.orderBy == 'string') {
88
- sql += ' ORDER BY ' + pgUtils.quoteField(options.orderBy);
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);
89
162
  }
90
- else if (Array.isArray(options.orderBy)) {
91
- let orderBy = options.orderBy.map(v =>
92
- v[0] == '+' ? pgUtils.quoteField(v.substr(1, v.length - 1)) + ' asc' :
93
- v[0] == '-' ? pgUtils.quoteField(v.substr(1, v.length - 1)) + ' desc' :
94
- v.replace(ASC_DESC_REGEXP, '"$1"$2'));
95
- sql += ' ORDER BY ' + orderBy.join(',');
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
+ });
96
189
  } else {
97
- let orderBy = [];
98
- _.forEach(options.orderBy, (v, k) => orderBy.push(pgUtils.quoteField(k) + ' ' + v));
99
- sql += ' ORDER BY ' + orderBy.join(',');
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';
100
219
  }
101
220
  }
102
221
  if (options.limit) {
103
- sql += util.format(' LIMIT %d', options.limit);
222
+ if (!Number.isInteger(options.limit) || options.limit < 0) throw new Error(`Invalid limit: ${options.limit}`);
223
+ sql += ` LIMIT ${options.limit}`;
104
224
  }
105
225
  if (options.offset) {
106
- sql += util.format(' OFFSET %d', options.offset);
226
+ if (!Number.isInteger(options.offset) || options.offset < 0) throw new Error(`Invalid offset: ${options.offset}`);
227
+ sql += ` OFFSET ${options.offset}`;
107
228
  }
108
- if (options.forUpdate){
229
+ if (options.forUpdate) {
109
230
  sql += ' FOR UPDATE';
110
231
  }
111
232
  return sql;
@@ -122,18 +243,18 @@ export let pgUtils = {
122
243
  (param != null && fieldType == FieldType.TIME && !(param instanceof Date)) ? new Date(param) : param;
123
244
  },
124
245
 
125
- postProcessResult(res: any[], fields: ResultFieldType[], pgdbTypeParsers: { [oid: number]: (s:string) => any }) {
246
+ postProcessResult(res: any[], fields: ResultFieldType[], pgdbTypeParsers: { [oid: number]: (s: string) => any }) {
126
247
  if (res) {
127
248
  if (res[0] && !Array.isArray(res[0])) {
128
249
  if (Object.keys(res[0]).length != fields.length) {
129
- throw Error("Name collision for the query, two or more fields have the same name.");
250
+ throw new Error("Name collision for the query, two or more fields have the same name.");
130
251
  }
131
252
  }
132
253
  pgUtils.convertTypes(res, fields, pgdbTypeParsers);
133
254
  }
134
255
  },
135
256
 
136
- convertTypes(res: any[], fields: ResultFieldType[], pgdbTypeParsers: { [oid: number]: (s:string) => any }) {
257
+ convertTypes(res: any[], fields: ResultFieldType[], pgdbTypeParsers: { [oid: number]: (s: string) => any }) {
137
258
  let isArrayMode = Array.isArray(res[0]);
138
259
  fields.forEach((field, i) => {
139
260
  if (pgdbTypeParsers[field.dataTypeID]) {
@@ -146,10 +267,10 @@ export let pgUtils = {
146
267
  });
147
268
  },
148
269
 
149
- createFunctionCaller(q: QueryAble, fn: { schema: string, name: string, return_single_row: boolean, return_single_value: boolean }) {
150
- return async (...args) => {
151
- let placeHolders = [];
152
- let params = [];
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[] = [];
153
274
  args.forEach((arg) => {
154
275
  placeHolders.push('$' + (placeHolders.length + 1));
155
276
  params.push(arg);
@@ -159,18 +280,21 @@ export let pgUtils = {
159
280
  if (fn.return_single_value) {
160
281
  let keys = res[0] ? Object.keys(res[0]) : [];
161
282
  if (keys.length != 1) {
162
- throw Error(`Return type error. schema: ${fn.schema} fn: ${fn.name} expected return type: single value, current value:` + JSON.stringify(res))
283
+ throw new Error(`Return type error. schema: ${fn.schema} fn: ${fn.name} expected return type: single value, current value:` + JSON.stringify(res))
163
284
  }
164
285
  res = res.map((r) => r[keys[0]]);
165
286
  }
166
287
  if (fn.return_single_row) {
167
288
  if (res.length != 1) {
168
- throw Error(`Return type error. schema: ${fn.schema} fn: ${fn.name} expected return type: single value, current value:` + JSON.stringify(res))
289
+ throw new Error(`Return type error. schema: ${fn.schema} fn: ${fn.name} expected return type: single value, current value:` + JSON.stringify(res))
169
290
  }
170
291
  return res[0];
171
292
  } else {
172
293
  return res;
173
294
  }
174
295
  }
296
+ },
297
+ escapeForLike(s: string): string {
298
+ return s.replace(/([\\%_])/g, '\\$1');
175
299
  }
176
300
  };