pogi 2.10.1 → 3.0.0-beta

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 (108) hide show
  1. package/.vscode/launch.json +47 -15
  2. package/CHANGELOG.md +20 -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 +2 -2
  7. package/lib/bin/generateInterface.js.map +1 -1
  8. package/lib/index.js +1 -0
  9. package/lib/index.js.map +1 -1
  10. package/lib/pgConverters.d.ts +2 -0
  11. package/lib/pgConverters.js +27 -10
  12. package/lib/pgConverters.js.map +1 -1
  13. package/lib/pgDb.d.ts +15 -6
  14. package/lib/pgDb.js +263 -67
  15. package/lib/pgDb.js.map +1 -1
  16. package/lib/pgTable.js +7 -7
  17. package/lib/pgTable.js.map +1 -1
  18. package/lib/pgUtils.d.ts +3 -1
  19. package/lib/pgUtils.js +61 -23
  20. package/lib/pgUtils.js.map +1 -1
  21. package/lib/queryAble.d.ts +16 -1
  22. package/lib/queryAble.js +124 -56
  23. package/lib/queryAble.js.map +1 -1
  24. package/lib/src/bin/generateInterface.d.ts +1 -0
  25. package/lib/src/bin/generateInterface.js +53 -0
  26. package/lib/src/bin/generateInterface.js.map +1 -0
  27. package/lib/src/connectionOptions.d.ts +34 -0
  28. package/lib/src/connectionOptions.js +3 -0
  29. package/lib/src/connectionOptions.js.map +1 -0
  30. package/lib/src/index.d.ts +6 -0
  31. package/lib/src/index.js +12 -0
  32. package/lib/src/index.js.map +1 -0
  33. package/lib/src/pgConverters.d.ts +9 -0
  34. package/lib/src/pgConverters.js +71 -0
  35. package/lib/src/pgConverters.js.map +1 -0
  36. package/lib/src/pgConverters.test.d.ts +1 -0
  37. package/lib/src/pgConverters.test.js +13 -0
  38. package/lib/src/pgConverters.test.js.map +1 -0
  39. package/lib/src/pgDb.d.ts +79 -0
  40. package/lib/src/pgDb.js +764 -0
  41. package/lib/src/pgDb.js.map +1 -0
  42. package/lib/src/pgDb.test.d.ts +1 -0
  43. package/lib/src/pgDb.test.js +1126 -0
  44. package/lib/src/pgDb.test.js.map +1 -0
  45. package/lib/src/pgDbInterface.js +11 -0
  46. package/lib/src/pgDbInterface.js.map +1 -0
  47. package/lib/src/pgDbLogger.d.ts +5 -0
  48. package/lib/src/pgDbLogger.js +3 -0
  49. package/lib/src/pgDbLogger.js.map +1 -0
  50. package/lib/src/pgDbOperators.d.ts +113 -0
  51. package/lib/src/pgDbOperators.js +41 -0
  52. package/lib/src/pgDbOperators.js.map +1 -0
  53. package/lib/src/pgDbOperators.test.d.ts +1 -0
  54. package/lib/src/pgDbOperators.test.js +313 -0
  55. package/lib/src/pgDbOperators.test.js.map +1 -0
  56. package/lib/src/pgSchema.d.ts +17 -0
  57. package/lib/src/pgSchema.js +16 -0
  58. package/lib/src/pgSchema.js.map +1 -0
  59. package/lib/src/pgSchemaInterface.d.ts +12 -0
  60. package/lib/src/pgSchemaInterface.js +3 -0
  61. package/lib/src/pgSchemaInterface.js.map +1 -0
  62. package/lib/src/pgTable.d.ts +105 -0
  63. package/lib/src/pgTable.js +322 -0
  64. package/lib/src/pgTable.js.map +1 -0
  65. package/lib/src/pgTableInterface.d.ts +102 -0
  66. package/lib/src/pgTableInterface.js +4 -0
  67. package/lib/src/pgTableInterface.js.map +1 -0
  68. package/lib/src/pgUtils.d.ts +41 -0
  69. package/lib/src/pgUtils.js +282 -0
  70. package/lib/src/pgUtils.js.map +1 -0
  71. package/lib/src/queryAble.d.ts +40 -0
  72. package/lib/src/queryAble.js +338 -0
  73. package/lib/src/queryAble.js.map +1 -0
  74. package/lib/src/queryAbleInterface.d.ts +59 -0
  75. package/lib/src/queryAbleInterface.js +7 -0
  76. package/lib/src/queryAbleInterface.js.map +1 -0
  77. package/lib/src/queryWhere.d.ts +8 -0
  78. package/lib/src/queryWhere.js +245 -0
  79. package/lib/src/queryWhere.js.map +1 -0
  80. package/mkdocs.yml +1 -0
  81. package/package.json +23 -14
  82. package/src/bin/generateInterface.ts +2 -2
  83. package/src/connectionOptions.ts +46 -13
  84. package/src/index.d.ts +7 -0
  85. package/src/pgConverters.test.ts +10 -0
  86. package/src/pgConverters.ts +34 -22
  87. package/src/pgDb.test.ts +1324 -0
  88. package/src/pgDb.ts +321 -125
  89. package/src/pgDbInterface.ts +57 -0
  90. package/src/pgDbOperators.test.ts +478 -0
  91. package/src/pgDbOperators.ts +45 -22
  92. package/src/pgSchema.ts +10 -9
  93. package/src/pgSchemaInterface.ts +12 -0
  94. package/src/pgTable.ts +65 -97
  95. package/src/pgTableInterface.ts +131 -0
  96. package/src/pgUtils.ts +156 -42
  97. package/src/queryAble.ts +167 -125
  98. package/src/queryAbleInterface.ts +108 -0
  99. package/src/queryWhere.ts +42 -43
  100. package/{spec/resources → src/test}/init.sql +23 -0
  101. package/src/test/pgServiceRestartTest.ts +1500 -0
  102. package/{spec/resources → src/test}/throw_exception.sql +0 -0
  103. package/{spec/resources → src/test}/tricky.sql +0 -0
  104. package/{src/tsconfig.json → tsconfig.json} +9 -6
  105. package/spec/run.js +0 -5
  106. package/spec/support/jasmine.json +0 -9
  107. package/src/test/pgDbOperatorSpec.ts +0 -492
  108. package/src/test/pgDbSpec.ts +0 -994
package/src/pgUtils.ts CHANGED
@@ -1,28 +1,92 @@
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';
5
9
 
6
- const util = require('util');
7
10
  const NAMED_PARAMS_REGEXP = /(?:^|[^:]):(!?[a-zA-Z0-9_]+)/g; // do not convert "::type cast"
8
- const ASC_DESC_REGEXP = /^([^" (]+)( asc| desc)?$/;
11
+ const ASC_DESC_REGEXP = /^\s*(.+?)(?:\s+(asc|desc))?\s*$/i;
9
12
 
10
13
  export let pgUtils = {
11
14
 
12
- logError(logger: PgDbLogger, options: { error?: string|Error, sql: string, params: any, connection }) {
15
+ logError(logger: PgDbLogger, options: { error?: string | Error, sql: string, params: any, connection?: pg.PoolClient | null }) {
13
16
  let { error, sql, params, connection } = options;
14
17
  logger.error(error, sql, util.inspect(logger.paramSanitizer ? logger.paramSanitizer(params) : params, false, null), connection ? connection.processID : null);
15
18
  },
16
19
 
17
- quoteField(f) {
20
+ quoteFieldNameInsecure(f: string) {
18
21
  return f.indexOf('"') == -1 && f.indexOf('(') == -1 ? '"' + f + '"' : f;
19
22
  },
20
23
 
21
- processQueryFields(options: QueryOptions): string {
24
+ quoteFieldName(f: string) {
25
+ if (typeof f === 'string' && f.length) {
26
+ return `"${f
27
+ .replace(/^\s*"*/, '') // trim "
28
+ .replace(/"*\s*$/, '')
29
+ .replace(/"/g, '""')}"`;
30
+ } else {
31
+ throw new Error(`Invalid field: ${f}`);
32
+ }
33
+ },
34
+
35
+ quoteFieldNameOrPositionInsecure(f: string | number): string {
36
+ if (Number.isInteger(+f)) {
37
+ if (!Number.isInteger(+f) || +f < 1) throw new Error(`Invalid field: ${f}`);
38
+ return '' + f;
39
+ } else if (typeof f === 'string' && f.length) {
40
+ return f.indexOf('"') == -1 && f.indexOf('(') == -1 ? '"' + f + '"' : f;
41
+ } else {
42
+ throw new Error(`Invalid field: ${f}`);
43
+ }
44
+ },
45
+
46
+ /** ex. for order by column position can be use, which needs no quote */
47
+ quoteFieldNameOrPosition(f: string | number): string {
48
+ if (Number.isInteger(+f)) {
49
+ if (!Number.isInteger(+f) || +f < 1) throw new Error(`Invalid field: ${f}`);
50
+ return '' + f;
51
+ } else if (typeof f === 'string' && f.length) {
52
+ return `"${f
53
+ .replace(/^\s*"*\s*/, '') // trim "
54
+ .replace(/\s*"*\s*$/, '')
55
+ .replace(/"/g, '""')}"`;
56
+ } else {
57
+ throw new Error(`Invalid field: ${f}`);
58
+ }
59
+ },
60
+ /**
61
+ * https://www.postgresql.org/docs/current/functions-json.html
62
+ * column->'a' ,
63
+ * column -> 3
64
+ */
65
+ quoteFieldNameJsonbOrPosition(f: string | number): string {
66
+ // treat numeric json keys as array indices, otherwise quote it
67
+ if (Number.isInteger(+f)) {
68
+ return '' + f;
69
+ } if (typeof f === 'string' && f.length) {
70
+ return `'${f
71
+ .replace(/^\s*'*/, '') // trim "
72
+ .replace(/'*\s*$/, '')
73
+ .replace(/'/g, "''")}'`;
74
+ } else {
75
+ throw new Error(`Invalid field: ${f}`);
76
+ }
77
+ },
78
+
79
+ processQueryFields<T>(options: QueryOptions, pgTable?: IPgTable<T>): string {
80
+ let escapeColumns = ((pgTable?.db.config.forceEscapeColumns === true || pgTable?.db.config.forceEscapeColumns?.select === true) && options.forceEscapeColumns?.select !== false && options.forceEscapeColumns !== false) ||
81
+ options.forceEscapeColumns === true || options.forceEscapeColumns?.select;
82
+
22
83
  let s = options && options.distinct ? ' DISTINCT ' : ' ';
23
84
  if (options && options.fields) {
24
85
  if (Array.isArray(options.fields)) {
25
- return s + options.fields.map(pgUtils.quoteField).join(', ');
86
+ if (escapeColumns) {
87
+ return s + options.fields.map(pgUtils.quoteFieldName).join(', ');
88
+ }
89
+ return s + options.fields.map(pgUtils.quoteFieldNameInsecure).join(', ');
26
90
  } else {
27
91
  return s + options.fields;
28
92
  }
@@ -56,7 +120,7 @@ export let pgUtils = {
56
120
  sql2.push(sql.slice(lastIndex, NAMED_PARAMS_REGEXP.lastIndex - p[1].length - 1));
57
121
 
58
122
  if (ddl) {
59
- sql2.push('"' + ('' + params[name]).replace(/"/g, '""') + '"');
123
+ sql2.push(pgUtils.quoteFieldName(params[name]));
60
124
  } else {
61
125
  params2.push(params[name]);
62
126
  sql2.push('$' + params2.length);
@@ -64,7 +128,7 @@ export let pgUtils = {
64
128
  lastIndex = NAMED_PARAMS_REGEXP.lastIndex;
65
129
  p = NAMED_PARAMS_REGEXP.exec(sql);
66
130
  }
67
- sql2.push(sql.substr(lastIndex));
131
+ sql2.push(sql.slice(lastIndex));
68
132
 
69
133
  return {
70
134
  sql: sql2.join(''),
@@ -72,40 +136,87 @@ export let pgUtils = {
72
136
  }
73
137
  },
74
138
 
75
- processQueryOptions(options: QueryOptions): string {
76
- options = options || {};
77
- let sql = '';
139
+ handleColumnEscapeGroupBy<T>(options: QueryOptions, pgTable?: IPgTable<T>): string {
140
+ if (!options.groupBy) return '';
141
+ let escapeColumns = ((pgTable?.db.config.forceEscapeColumns === true || pgTable?.db.config.forceEscapeColumns?.groupBy === true) && options.forceEscapeColumns?.groupBy !== false && options.forceEscapeColumns !== false) ||
142
+ options.forceEscapeColumns === true || options.forceEscapeColumns?.groupBy;
78
143
 
79
- if (options.groupBy) {
144
+ if (escapeColumns) {
80
145
  if (Array.isArray(options.groupBy)) {
81
- sql += ' GROUP BY ' + options.groupBy.map(pgUtils.quoteField).join(',');
146
+ return ' GROUP BY ' + options.groupBy.map(pgUtils.quoteFieldNameOrPosition).join(',');
82
147
  } else {
83
- sql += ' GROUP BY ' + pgUtils.quoteField(options.groupBy);
148
+ return ' GROUP BY ' + pgUtils.quoteFieldNameOrPosition(options.groupBy);
84
149
  }
85
- }
86
- if (options.orderBy) {
87
- if (typeof options.orderBy == 'string') {
88
- sql += ' ORDER BY ' + pgUtils.quoteField(options.orderBy);
150
+ } else {
151
+ if (Array.isArray(options.groupBy)) {
152
+ return ' GROUP BY ' + options.groupBy.map(pgUtils.quoteFieldNameOrPositionInsecure).join(',');
153
+ } else {
154
+ return ' GROUP BY ' + pgUtils.quoteFieldNameOrPositionInsecure(options.groupBy);
89
155
  }
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(',');
156
+ }
157
+ },
158
+
159
+ handleColumnEscapeOrderBy<T>(options: QueryOptions, pgTable: IPgTable<T>): string {
160
+ if (!options.orderBy) return '';
161
+ let sql = '';
162
+ let escapeColumns = ((pgTable?.db.config.forceEscapeColumns === true || pgTable?.db.config.forceEscapeColumns?.orderBy === true) && options.forceEscapeColumns?.orderBy !== false && options.forceEscapeColumns !== false) ||
163
+ options.forceEscapeColumns === true || options.forceEscapeColumns?.orderBy;
164
+
165
+ let orderBy = typeof options.orderBy === 'string' ? options.orderBy.split(',') : options.orderBy;
166
+ if (Array.isArray(orderBy)) {
167
+ let orderBy2: string[];
168
+
169
+ if (escapeColumns) {
170
+ orderBy2 = orderBy.map(v => {
171
+ if (typeof v === 'number') return pgUtils.quoteFieldNameOrPosition(v);
172
+ else if (typeof v !== 'string' || !v.length) throw new Error(`Invalid orderBy: ${v}`);
173
+ if (v[0] == '+') return pgUtils.quoteFieldNameOrPosition(v.slice(1));
174
+ if (v[0] == '-') return pgUtils.quoteFieldNameOrPosition(v.slice(1)) + ' desc';
175
+ let o = ASC_DESC_REGEXP.exec(v);
176
+ if (!o) throw new Error(`Invalid orderBy: ${v}`);
177
+ return `${pgUtils.quoteFieldNameOrPosition(o[1])} ${o[2] ?? ''}`;
178
+ });
96
179
  } else {
97
- let orderBy = [];
98
- _.forEach(options.orderBy, (v, k) => orderBy.push(pgUtils.quoteField(k) + ' ' + v));
99
- sql += ' ORDER BY ' + orderBy.join(',');
180
+ orderBy2 = orderBy.map(v => {
181
+ if (typeof v === 'number') return pgUtils.quoteFieldNameOrPositionInsecure(v);
182
+ else if (typeof v !== 'string' || !v.length) throw new Error(`Invalid orderBy: ${v}`);
183
+ if (v[0] == '+') return pgUtils.quoteFieldNameOrPositionInsecure(v.slice(1));
184
+ if (v[0] == '-') return pgUtils.quoteFieldNameOrPositionInsecure(v.slice(1)) + ' desc';
185
+ let o = ASC_DESC_REGEXP.exec(v);
186
+ if (!o) throw new Error(`Invalid orderBy: ${v}`);
187
+ return `${pgUtils.quoteFieldNameOrPositionInsecure(o[1])} ${o[2] ?? ''}`;
188
+ });
189
+ }
190
+ sql += ' ORDER BY ' + orderBy2.join(',');
191
+ } else {
192
+ throw new Error(`Invalid orderBy: ${options.orderBy}`);
193
+ }
194
+ return sql;
195
+ },
196
+
197
+ processQueryOptions<T>(options: QueryOptions, pgTable: IPgTable<T>): string {
198
+ options = options || {};
199
+ let sql = '';
200
+
201
+ if (options.groupBy) {
202
+ sql += pgUtils.handleColumnEscapeGroupBy(options, pgTable);
203
+ }
204
+ if (options.orderBy) {
205
+ sql += pgUtils.handleColumnEscapeOrderBy(options, pgTable);
206
+
207
+ if (options.orderByNullsFirst != null) {
208
+ sql += ' NULLS ' + options.orderByNullsFirst ? 'FIRST' : 'LAST';
100
209
  }
101
210
  }
102
211
  if (options.limit) {
103
- sql += util.format(' LIMIT %d', options.limit);
212
+ if (!Number.isInteger(options.limit) || options.limit < 0) throw new Error(`Invalid limit: ${options.limit}`);
213
+ sql += ` LIMIT ${options.limit}`;
104
214
  }
105
215
  if (options.offset) {
106
- sql += util.format(' OFFSET %d', options.offset);
216
+ if (!Number.isInteger(options.offset) || options.offset < 0) throw new Error(`Invalid offset: ${options.offset}`);
217
+ sql += ` OFFSET ${options.offset}`;
107
218
  }
108
- if (options.forUpdate){
219
+ if (options.forUpdate) {
109
220
  sql += ' FOR UPDATE';
110
221
  }
111
222
  return sql;
@@ -122,18 +233,18 @@ export let pgUtils = {
122
233
  (param != null && fieldType == FieldType.TIME && !(param instanceof Date)) ? new Date(param) : param;
123
234
  },
124
235
 
125
- postProcessResult(res: any[], fields: ResultFieldType[], pgdbTypeParsers: { [oid: number]: (s:string) => any }) {
236
+ postProcessResult(res: any[], fields: ResultFieldType[], pgdbTypeParsers: { [oid: number]: (s: string) => any }) {
126
237
  if (res) {
127
238
  if (res[0] && !Array.isArray(res[0])) {
128
239
  if (Object.keys(res[0]).length != fields.length) {
129
- throw Error("Name collision for the query, two or more fields have the same name.");
240
+ throw new Error("Name collision for the query, two or more fields have the same name.");
130
241
  }
131
242
  }
132
243
  pgUtils.convertTypes(res, fields, pgdbTypeParsers);
133
244
  }
134
245
  },
135
246
 
136
- convertTypes(res: any[], fields: ResultFieldType[], pgdbTypeParsers: { [oid: number]: (s:string) => any }) {
247
+ convertTypes(res: any[], fields: ResultFieldType[], pgdbTypeParsers: { [oid: number]: (s: string) => any }) {
137
248
  let isArrayMode = Array.isArray(res[0]);
138
249
  fields.forEach((field, i) => {
139
250
  if (pgdbTypeParsers[field.dataTypeID]) {
@@ -146,10 +257,10 @@ export let pgUtils = {
146
257
  });
147
258
  },
148
259
 
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 = [];
260
+ createFunctionCaller(q: IQueryAble, fn: { schema: string, name: string, return_single_row: boolean, return_single_value: boolean }) {
261
+ return async (...args: any[]) => {
262
+ let placeHolders: string[] = [];
263
+ let params: any[] = [];
153
264
  args.forEach((arg) => {
154
265
  placeHolders.push('$' + (placeHolders.length + 1));
155
266
  params.push(arg);
@@ -159,18 +270,21 @@ export let pgUtils = {
159
270
  if (fn.return_single_value) {
160
271
  let keys = res[0] ? Object.keys(res[0]) : [];
161
272
  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))
273
+ throw new Error(`Return type error. schema: ${fn.schema} fn: ${fn.name} expected return type: single value, current value:` + JSON.stringify(res))
163
274
  }
164
275
  res = res.map((r) => r[keys[0]]);
165
276
  }
166
277
  if (fn.return_single_row) {
167
278
  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))
279
+ throw new Error(`Return type error. schema: ${fn.schema} fn: ${fn.name} expected return type: single value, current value:` + JSON.stringify(res))
169
280
  }
170
281
  return res[0];
171
282
  } else {
172
283
  return res;
173
284
  }
174
285
  }
286
+ },
287
+ escapeForLike(s: string): string {
288
+ return s.replace(/([\\%_])/g, '\\$1');
175
289
  }
176
290
  };