prostgles-server 3.0.45 → 3.0.47

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 (48) hide show
  1. package/README.md +20 -21
  2. package/dist/DboBuilder/QueryBuilder/Functions.d.ts +64 -0
  3. package/dist/DboBuilder/QueryBuilder/Functions.d.ts.map +1 -0
  4. package/dist/DboBuilder/QueryBuilder/Functions.js +940 -0
  5. package/dist/DboBuilder/QueryBuilder/Functions.js.map +1 -0
  6. package/dist/DboBuilder/QueryBuilder/QueryBuilder.d.ts +1 -62
  7. package/dist/DboBuilder/QueryBuilder/QueryBuilder.d.ts.map +1 -1
  8. package/dist/DboBuilder/QueryBuilder/QueryBuilder.js +6 -896
  9. package/dist/DboBuilder/QueryBuilder/QueryBuilder.js.map +1 -1
  10. package/dist/DboBuilder/TableHandler.d.ts.map +1 -1
  11. package/dist/DboBuilder/TableHandler.js +5 -6
  12. package/dist/DboBuilder/TableHandler.js.map +1 -1
  13. package/dist/DboBuilder/ViewHandler.d.ts.map +1 -1
  14. package/dist/DboBuilder/ViewHandler.js +4 -3
  15. package/dist/DboBuilder/ViewHandler.js.map +1 -1
  16. package/dist/DboBuilder.d.ts +1 -1
  17. package/dist/DboBuilder.d.ts.map +1 -1
  18. package/dist/DboBuilder.js.map +1 -1
  19. package/dist/FileManager.js +1 -1
  20. package/dist/FileManager.js.map +1 -1
  21. package/dist/PubSubManager.d.ts.map +1 -1
  22. package/dist/PubSubManager.js +1 -13
  23. package/dist/PubSubManager.js.map +1 -1
  24. package/lib/DboBuilder/QueryBuilder/Functions.d.ts +64 -0
  25. package/lib/DboBuilder/QueryBuilder/Functions.d.ts.map +1 -0
  26. package/lib/DboBuilder/QueryBuilder/Functions.js +939 -0
  27. package/lib/DboBuilder/QueryBuilder/Functions.ts +1071 -0
  28. package/lib/DboBuilder/QueryBuilder/QueryBuilder.d.ts +1 -62
  29. package/lib/DboBuilder/QueryBuilder/QueryBuilder.d.ts.map +1 -1
  30. package/lib/DboBuilder/QueryBuilder/QueryBuilder.js +6 -896
  31. package/lib/DboBuilder/QueryBuilder/QueryBuilder.ts +1 -1025
  32. package/lib/DboBuilder/TableHandler.d.ts.map +1 -1
  33. package/lib/DboBuilder/TableHandler.js +5 -6
  34. package/lib/DboBuilder/TableHandler.ts +7 -8
  35. package/lib/DboBuilder/ViewHandler.d.ts.map +1 -1
  36. package/lib/DboBuilder/ViewHandler.js +4 -3
  37. package/lib/DboBuilder/ViewHandler.ts +2 -1
  38. package/lib/DboBuilder.d.ts +1 -1
  39. package/lib/DboBuilder.d.ts.map +1 -1
  40. package/lib/DboBuilder.ts +5 -9
  41. package/lib/FileManager.js +1 -1
  42. package/lib/FileManager.ts +1 -1
  43. package/lib/PubSubManager.d.ts.map +1 -1
  44. package/lib/PubSubManager.js +1 -13
  45. package/lib/PubSubManager.ts +1 -13
  46. package/package.json +1 -1
  47. package/tests/client/PID.txt +1 -1
  48. package/tests/server/package-lock.json +1 -1
@@ -0,0 +1,940 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.COMPUTED_FIELDS = exports.FUNCTIONS = exports.parseFunction = void 0;
4
+ const prostgles_types_1 = require("prostgles-types");
5
+ const DboBuilder_1 = require("../../DboBuilder");
6
+ const ViewHandler_1 = require("../ViewHandler");
7
+ const QueryBuilder_1 = require("./QueryBuilder");
8
+ const parseFunction = (funcData) => {
9
+ const { func, args, functions, allowedFields } = funcData;
10
+ /* Function is computed column. No checks needed */
11
+ if (typeof func !== "string") {
12
+ const computedCol = exports.COMPUTED_FIELDS.find(c => c.name === func.name);
13
+ if (!computedCol)
14
+ throw `Unexpected function: computed column spec not found for ${JSON.stringify(func.name)}`;
15
+ return func;
16
+ }
17
+ const funcName = func;
18
+ const makeErr = (msg) => {
19
+ return `Issue with function ${JSON.stringify({ [funcName]: args })}: \n${msg}`;
20
+ };
21
+ /* Find function */
22
+ const funcDef = functions.find(f => f.name === funcName);
23
+ if (!funcDef) {
24
+ const sf = functions.filter(f => f.name.toLowerCase().slice(1).startsWith(funcName.toLowerCase())).sort((a, b) => (a.name.length - b.name.length));
25
+ const hint = (sf.length ? `. \n Maybe you meant: \n | ${sf.map(s => s.name + " " + (s.description || "")).join(" \n | ")} ?` : "");
26
+ throw "\n Function " + funcName + " does not exist or is not allowed " + hint;
27
+ }
28
+ /* Validate fields */
29
+ const fields = funcDef.getFields(args);
30
+ if (fields !== "*") {
31
+ fields.forEach(fieldKey => {
32
+ if (typeof fieldKey !== "string" || !allowedFields.includes(fieldKey)) {
33
+ throw makeErr(`getFields() => field name ${JSON.stringify(fieldKey)} is invalid or disallowed`);
34
+ }
35
+ });
36
+ if ((funcDef.minCols ?? 0) > fields.length) {
37
+ throw makeErr(`Less columns provided than necessary (minCols=${funcDef.minCols})`);
38
+ }
39
+ }
40
+ if (funcDef.numArgs && funcDef.minCols !== 0 && fields !== "*" && Array.isArray(fields) && !fields.length) {
41
+ throw `\n Function "${funcDef.name}" expects at least a field name but has not been provided with one`;
42
+ }
43
+ return funcDef;
44
+ };
45
+ exports.parseFunction = parseFunction;
46
+ const MAX_COL_NUM = 1600;
47
+ const asValue = (v, castAs = "") => DboBuilder_1.pgp.as.format("$1" + castAs, [v]);
48
+ const parseUnix = (colName, tableAlias, allColumns) => {
49
+ const col = allColumns.find(c => c.name === colName);
50
+ if (!col)
51
+ throw `Unexpected: column ${colName} not found`;
52
+ const escapedName = (0, QueryBuilder_1.asNameAlias)(colName, tableAlias);
53
+ if (col.udt_name === "int8") {
54
+ return `to_timestamp(${escapedName}/1000.0)`;
55
+ }
56
+ return escapedName;
57
+ };
58
+ const JSON_Funcs = [
59
+ {
60
+ name: "$jsonb_set",
61
+ description: "[columnName: string, path: (string | number)[], new_value?: any, create_missing?: boolean ] Returns target value (columnName) with the section designated by path replaced by new_value, or with new_value added if create_missing is true (default is true) and the item designated by path does not exist",
62
+ singleColArg: false,
63
+ numArgs: 4,
64
+ type: "function",
65
+ getFields: ([column]) => column,
66
+ getQuery: ({ args: [colName, path = [], new_value, create_missing = true], tableAlias, allowedFields }) => {
67
+ if (!allowedFields.includes(colName)) {
68
+ throw `Unexpected: column ${colName} not found`;
69
+ }
70
+ if (!path || !Array.isArray(path) || !path.every(v => ["number", "string"].includes(typeof v))) {
71
+ throw "Expecting: [columnName: string, path: (string | number)[], new_value?: any, create_missing?: boolean ]";
72
+ }
73
+ const escapedName = (0, QueryBuilder_1.asNameAlias)(colName, tableAlias);
74
+ return `jsonb_set(${escapedName}, ${asValue(path)}, ${asValue(new_value)}, ${create_missing})`;
75
+ }
76
+ },
77
+ ...[
78
+ ["jsonb_array_length", "Returns the number of elements in the outermost JSON array"],
79
+ ["jsonb_each", "Expands the outermost JSON object into a set of key/value pairs"],
80
+ ["jsonb_each_text", "Expands the outermost JSON object into a set of key/value pairs. The returned values will be of type text"],
81
+ ["jsonb_object_keys", "Returns set of keys in the outermost JSON object"],
82
+ ["jsonb_strip_nulls", "Returns from_json with all object fields that have null values omitted. Other null values are untouched"],
83
+ ["jsonb_pretty", "Returns from_json as indented JSON text "],
84
+ ["jsonb_to_record", "Builds an arbitrary record from a JSON object"],
85
+ ["jsonb_array_elements", "Expands a JSON array to a set of JSON values"],
86
+ ["jsonb_array_elements_text", "Expands a JSON array to a set of text values "],
87
+ ["jsonb_typeof", "Returns the type of the outermost JSON value as a text string. Possible types are object, array, string, number, boolean, and null "],
88
+ ].map(([name, description]) => ({
89
+ name: "$" + name,
90
+ description,
91
+ singleColArg: true,
92
+ numArgs: 1,
93
+ type: "function",
94
+ getFields: ([col]) => col,
95
+ getQuery: ({ args: [colName], tableAlias }) => {
96
+ const escapedName = (0, QueryBuilder_1.asNameAlias)(colName, tableAlias);
97
+ return `${name}(${escapedName})`;
98
+ }
99
+ }))
100
+ ];
101
+ const FTS_Funcs =
102
+ /* Full text search
103
+ https://www.postgresql.org/docs/current/textsearch-dictionaries.html#TEXTSEARCH-SIMPLE-DICTIONARY
104
+ */
105
+ [
106
+ "simple",
107
+ // "synonym", // replace word with a synonym
108
+ "english",
109
+ // "english_stem",
110
+ // "english_hunspell",
111
+ ""
112
+ ].map(type => ({
113
+ name: "$ts_headline" + (type ? ("_" + type) : ""),
114
+ description: ` :[column_name <string>, search_term: <string | { to_tsquery: string } > ] -> sha512 hash of the of column content`,
115
+ type: "function",
116
+ singleColArg: true,
117
+ numArgs: 2,
118
+ getFields: ([column]) => [column],
119
+ getQuery: ({ allColumns, args, tableAlias }) => {
120
+ const col = (0, prostgles_types_1.asName)(args[0]);
121
+ let qVal = args[1], qType = "to_tsquery";
122
+ let _type = type ? (asValue(type) + ",") : "";
123
+ const searchTypes = prostgles_types_1.TextFilter_FullTextSearchFilterKeys;
124
+ /* { to_tsquery: 'search term' } */
125
+ if ((0, DboBuilder_1.isPlainObject)(qVal)) {
126
+ const keys = Object.keys(qVal);
127
+ if (!keys.length)
128
+ throw "Bad arg";
129
+ if (keys.length !== 1 || !searchTypes.includes(keys[0]))
130
+ throw "Expecting a an object with a single key named one of: " + searchTypes.join(", ");
131
+ qType = keys[0];
132
+ qVal = asValue(qVal[qType]);
133
+ /* 'search term' */
134
+ }
135
+ else if (typeof qVal === "string") {
136
+ qVal = DboBuilder_1.pgp.as.format(qType + "($1)", [qVal]);
137
+ }
138
+ else
139
+ throw "Bad second arg. Exepcting search string or { to_tsquery: 'search string' }";
140
+ const res = `ts_headline(${_type} ${col}::text, ${qVal}, 'ShortWord=1 ' )`;
141
+ // console.log(res)
142
+ return res;
143
+ }
144
+ }));
145
+ let PostGIS_Funcs = [
146
+ {
147
+ fname: "ST_DWithin",
148
+ description: `:[column_name, { lat?: number; lng?: number; geojson?: object; srid?: number; use_spheroid?: boolean; distance: number; }]
149
+ -> Returns true if the geometries are within a given distance
150
+ For geometry: The distance is specified in units defined by the spatial reference system of the geometries. For this function to make sense, the source geometries must be in the same coordinate system (have the same SRID).
151
+ For geography: units are in meters and distance measurement defaults to use_spheroid=true. For faster evaluation use use_spheroid=false to measure on the sphere.
152
+ `
153
+ },
154
+ {
155
+ fname: "<->",
156
+ description: `:[column_name, { lat?: number; lng?: number; geojson?: object; srid?: number; use_spheroid?: boolean }]
157
+ -> The <-> operator returns the 2D distance between two geometries. Used in the "ORDER BY" clause provides index-assisted nearest-neighbor result sets. For PostgreSQL below 9.5 only gives centroid distance of bounding boxes and for PostgreSQL 9.5+, does true KNN distance search giving true distance between geometries, and distance sphere for geographies.`
158
+ },
159
+ {
160
+ fname: "ST_Distance",
161
+ description: ` :[column_name, { lat?: number; lng?: number; geojson?: object; srid?: number; use_spheroid?: boolean }]
162
+ -> For geometry types returns the minimum 2D Cartesian (planar) distance between two geometries, in projected units (spatial ref units).
163
+ -> For geography types defaults to return the minimum geodesic distance between two geographies in meters, compute on the spheroid determined by the SRID. If use_spheroid is false, a faster spherical calculation is used.
164
+ `,
165
+ }, {
166
+ fname: "ST_DistanceSpheroid",
167
+ description: ` :[column_name, { lat?: number; lng?: number; geojson?: object; srid?: number; spheroid?: string; }] -> Returns minimum distance in meters between two lon/lat geometries given a particular spheroid. See the explanation of spheroids given for ST_LengthSpheroid.
168
+
169
+ `,
170
+ }, {
171
+ fname: "ST_DistanceSphere",
172
+ description: ` :[column_name, { lat?: number; lng?: number; geojson?: object; srid?: number }] -> Returns linear distance in meters between two lon/lat points. Uses a spherical earth and radius of 6370986 meters. Faster than ST_DistanceSpheroid, but less accurate. Only implemented for points.`,
173
+ }
174
+ ].map(({ fname, description }) => ({
175
+ name: "$" + fname,
176
+ description,
177
+ type: "function",
178
+ singleColArg: true,
179
+ numArgs: 1,
180
+ getFields: (args) => [args[0]],
181
+ getQuery: ({ allColumns, args, tableAlias }) => {
182
+ const arg2 = args[1], mErr = () => { throw `${fname}: Expecting a second argument like: { lat?: number; lng?: number; geojson?: object; srid?: number; use_spheroid?: boolean }`; };
183
+ if (!(0, DboBuilder_1.isPlainObject)(arg2))
184
+ mErr();
185
+ const col = allColumns.find(c => c.name === args[0]);
186
+ if (!col)
187
+ throw new Error("Col not found: " + args[0]);
188
+ const { lat, lng, srid = 4326, geojson, text, use_spheroid, distance, spheroid = 'SPHEROID["WGS 84",6378137,298.257223563]', debug } = arg2;
189
+ let geomQ = "", extraParams = "";
190
+ if (typeof text === "string") {
191
+ geomQ = `ST_GeomFromText(${asValue(text)})`;
192
+ }
193
+ else if ([lat, lng].every(v => Number.isFinite(v))) {
194
+ geomQ = `ST_Point(${asValue(lng)}, ${asValue(lat)})`;
195
+ }
196
+ else if ((0, DboBuilder_1.isPlainObject)(geojson)) {
197
+ geomQ = `ST_GeomFromGeoJSON(${geojson})`;
198
+ }
199
+ else
200
+ mErr();
201
+ if (Number.isFinite(srid)) {
202
+ geomQ = `ST_SetSRID(${geomQ}, ${asValue(srid)})`;
203
+ }
204
+ let colCast = "";
205
+ const colIsGeog = col.udt_name === "geography";
206
+ let geomQCast = colIsGeog ? "::geography" : "::geometry";
207
+ /**
208
+ * float ST_Distance(geometry g1, geometry g2);
209
+ * float ST_Distance(geography geog1, geography geog2, boolean use_spheroid=true);
210
+ */
211
+ if (fname === "ST_Distance") {
212
+ if (typeof use_spheroid === "boolean") {
213
+ extraParams = ", " + asValue(use_spheroid);
214
+ }
215
+ colCast = (colIsGeog || use_spheroid) ? "::geography" : "::geometry";
216
+ geomQCast = (colIsGeog || use_spheroid) ? "::geography" : "::geometry";
217
+ /**
218
+ * boolean ST_DWithin(geometry g1, geometry g2, double precision distance_of_srid);
219
+ * boolean ST_DWithin(geography gg1, geography gg2, double precision distance_meters, boolean use_spheroid = true);
220
+ */
221
+ }
222
+ else if (fname === "ST_DWithin") {
223
+ colCast = colIsGeog ? "::geography" : "::geometry";
224
+ geomQCast = colIsGeog ? "::geography" : "::geometry";
225
+ if (typeof distance !== "number")
226
+ throw `ST_DWithin: distance param missing or not a number`;
227
+ extraParams = ", " + asValue(distance);
228
+ /**
229
+ * float ST_DistanceSpheroid(geometry geomlonlatA, geometry geomlonlatB, spheroid measurement_spheroid);
230
+ */
231
+ }
232
+ else if (fname === "ST_DistanceSpheroid") {
233
+ colCast = "::geometry";
234
+ geomQCast = "::geometry";
235
+ if (typeof spheroid !== "string")
236
+ throw `ST_DistanceSpheroid: spheroid param must be string`;
237
+ extraParams = `, ${asValue(spheroid)}`;
238
+ /**
239
+ * float ST_DistanceSphere(geometry geomlonlatA, geometry geomlonlatB);
240
+ */
241
+ }
242
+ else if (fname === "ST_DistanceSphere") {
243
+ colCast = "::geometry";
244
+ geomQCast = "::geometry";
245
+ extraParams = "";
246
+ /**
247
+ * double precision <->( geometry A , geometry B );
248
+ * double precision <->( geography A , geography B );
249
+ */
250
+ }
251
+ else if (fname === "<->") {
252
+ colCast = colIsGeog ? "::geography" : "::geometry";
253
+ geomQCast = colIsGeog ? "::geography" : "::geometry";
254
+ const q = DboBuilder_1.pgp.as.format(`${(0, QueryBuilder_1.asNameAlias)(args[0], tableAlias)}${colCast} <-> ${geomQ}${geomQCast}`);
255
+ if (debug)
256
+ throw q;
257
+ return q;
258
+ }
259
+ const q = DboBuilder_1.pgp.as.format(`${fname}(${(0, QueryBuilder_1.asNameAlias)(args[0], tableAlias)}${colCast} , ${geomQ}${geomQCast} ${extraParams})`);
260
+ if (debug)
261
+ throw q;
262
+ return q;
263
+ }
264
+ }));
265
+ PostGIS_Funcs = PostGIS_Funcs.concat([
266
+ "ST_AsText", "ST_AsEWKT", "ST_AsEWKB", "ST_AsBinary", "ST_AsMVT", "ST_AsMVTGeom",
267
+ "ST_AsGeoJSON", "ST_Simplify",
268
+ "ST_SnapToGrid",
269
+ ]
270
+ .map(fname => {
271
+ const res = {
272
+ name: "$" + fname,
273
+ description: ` :[column_name, precision?] -> json GeoJSON output of a geometry column`,
274
+ type: "function",
275
+ singleColArg: true,
276
+ numArgs: 1,
277
+ getFields: (args) => [args[0]],
278
+ getQuery: ({ allowedFields, args, tableAlias }) => {
279
+ let secondArg = "";
280
+ const otherArgs = args.slice(1);
281
+ if (otherArgs.length)
282
+ secondArg = ", " + otherArgs.map(arg => asValue(arg)).join(", ");
283
+ const escTabelName = (0, QueryBuilder_1.asNameAlias)(args[0], tableAlias) + "::geometry";
284
+ let result = DboBuilder_1.pgp.as.format(fname + "(" + escTabelName + secondArg + (fname === "ST_AsGeoJSON" ? ")::jsonb" : ")"));
285
+ if (fname.startsWith("ST_SnapToGrid") || fname.startsWith("ST_Simplify")) {
286
+ let r = `ST_AsGeoJSON(${result})::jsonb`;
287
+ return r;
288
+ }
289
+ return result;
290
+ }
291
+ };
292
+ return res;
293
+ }));
294
+ PostGIS_Funcs = PostGIS_Funcs.concat(["ST_Extent", "ST_3DExtent", "ST_XMin_Agg", "ST_XMax_Agg", "ST_YMin_Agg", "ST_YMax_Agg", "ST_ZMin_Agg", "ST_ZMax_Agg"]
295
+ .map(fname => {
296
+ const res = {
297
+ name: "$" + fname,
298
+ description: ` :[column_name] -> ST_Extent returns a bounding box that encloses a set of geometries.
299
+ The ST_Extent function is an "aggregate" function in the terminology of SQL.
300
+ That means that it operates on lists of data, in the same way the SUM() and AVG() functions do.`,
301
+ type: "aggregation",
302
+ singleColArg: true,
303
+ numArgs: 1,
304
+ getFields: (args) => [args[0]],
305
+ getQuery: ({ allowedFields, args, tableAlias }) => {
306
+ const escTabelName = (0, QueryBuilder_1.asNameAlias)(args[0], tableAlias) + "::geometry";
307
+ if (fname.includes("Extent")) {
308
+ return `${fname}(${escTabelName})`;
309
+ }
310
+ return `${fname.endsWith("_Agg") ? fname.slice(0, -4) : fname}(ST_Collect(${escTabelName}))`;
311
+ }
312
+ };
313
+ return res;
314
+ }));
315
+ PostGIS_Funcs = PostGIS_Funcs.concat(["ST_Length", "ST_X", "ST_Y", "ST_Z"].map(fname => ({
316
+ name: "$" + fname,
317
+ type: "function",
318
+ singleColArg: true,
319
+ numArgs: 1,
320
+ getFields: (args) => [args[0]],
321
+ getQuery: ({ allColumns, args, tableAlias }) => {
322
+ const colName = args[0];
323
+ const escapedColName = (0, QueryBuilder_1.asNameAlias)(colName, tableAlias);
324
+ const col = allColumns.find(c => c.name === colName);
325
+ if (!col)
326
+ throw new Error("Col not found: " + colName);
327
+ return `${fname}(${escapedColName})`;
328
+ }
329
+ })));
330
+ /**
331
+ * Each function expects a column at the very least
332
+ */
333
+ exports.FUNCTIONS = [
334
+ // Hashing
335
+ {
336
+ name: "$md5_multi",
337
+ description: ` :[...column_names] -> md5 hash of the column content`,
338
+ type: "function",
339
+ singleColArg: false,
340
+ numArgs: MAX_COL_NUM,
341
+ getFields: (args) => args,
342
+ getQuery: ({ allowedFields, args, tableAlias }) => {
343
+ const q = DboBuilder_1.pgp.as.format("md5(" + args.map(fname => "COALESCE( " + (0, QueryBuilder_1.asNameAlias)(fname, tableAlias) + "::text, '' )").join(" || ") + ")");
344
+ return q;
345
+ }
346
+ },
347
+ {
348
+ name: "$md5_multi_agg",
349
+ description: ` :[...column_names] -> md5 hash of the string aggregation of column content`,
350
+ type: "aggregation",
351
+ singleColArg: false,
352
+ numArgs: MAX_COL_NUM,
353
+ getFields: (args) => args,
354
+ getQuery: ({ allowedFields, args, tableAlias }) => {
355
+ const q = DboBuilder_1.pgp.as.format("md5(string_agg(" + args.map(fname => "COALESCE( " + (0, QueryBuilder_1.asNameAlias)(fname, tableAlias) + "::text, '' )").join(" || ") + ", ','))");
356
+ return q;
357
+ }
358
+ },
359
+ {
360
+ name: "$sha256_multi",
361
+ description: ` :[...column_names] -> sha256 hash of the of column content`,
362
+ type: "function",
363
+ singleColArg: false,
364
+ numArgs: MAX_COL_NUM,
365
+ getFields: (args) => args,
366
+ getQuery: ({ allowedFields, args, tableAlias }) => {
367
+ const q = DboBuilder_1.pgp.as.format("encode(sha256((" + args.map(fname => "COALESCE( " + (0, QueryBuilder_1.asNameAlias)(fname, tableAlias) + ", '' )").join(" || ") + ")::text::bytea), 'hex')");
368
+ return q;
369
+ }
370
+ },
371
+ {
372
+ name: "$sha256_multi_agg",
373
+ description: ` :[...column_names] -> sha256 hash of the string aggregation of column content`,
374
+ type: "aggregation",
375
+ singleColArg: false,
376
+ numArgs: MAX_COL_NUM,
377
+ getFields: (args) => args,
378
+ getQuery: ({ allowedFields, args, tableAlias }) => {
379
+ const q = DboBuilder_1.pgp.as.format("encode(sha256(string_agg(" + args.map(fname => "COALESCE( " + (0, QueryBuilder_1.asNameAlias)(fname, tableAlias) + ", '' )").join(" || ") + ", ',')::text::bytea), 'hex')");
380
+ return q;
381
+ }
382
+ },
383
+ {
384
+ name: "$sha512_multi",
385
+ description: ` :[...column_names] -> sha512 hash of the of column content`,
386
+ type: "function",
387
+ singleColArg: false,
388
+ numArgs: MAX_COL_NUM,
389
+ getFields: (args) => args,
390
+ getQuery: ({ allowedFields, args, tableAlias }) => {
391
+ const q = DboBuilder_1.pgp.as.format("encode(sha512((" + args.map(fname => "COALESCE( " + (0, QueryBuilder_1.asNameAlias)(fname, tableAlias) + ", '' )").join(" || ") + ")::text::bytea), 'hex')");
392
+ return q;
393
+ }
394
+ },
395
+ {
396
+ name: "$sha512_multi_agg",
397
+ description: ` :[...column_names] -> sha512 hash of the string aggregation of column content`,
398
+ type: "aggregation",
399
+ singleColArg: false,
400
+ numArgs: MAX_COL_NUM,
401
+ getFields: (args) => args,
402
+ getQuery: ({ allowedFields, args, tableAlias }) => {
403
+ const q = DboBuilder_1.pgp.as.format("encode(sha512(string_agg(" + args.map(fname => "COALESCE( " + (0, QueryBuilder_1.asNameAlias)(fname, tableAlias) + ", '' )").join(" || ") + ", ',')::text::bytea), 'hex')");
404
+ return q;
405
+ }
406
+ },
407
+ ...FTS_Funcs,
408
+ ...JSON_Funcs,
409
+ ...PostGIS_Funcs,
410
+ {
411
+ name: "$left",
412
+ description: ` :[column_name, number] -> substring`,
413
+ type: "function",
414
+ numArgs: 2,
415
+ singleColArg: false,
416
+ getFields: (args) => [args[0]],
417
+ getQuery: ({ allowedFields, args, tableAlias }) => {
418
+ return DboBuilder_1.pgp.as.format("LEFT(" + (0, QueryBuilder_1.asNameAlias)(args[0], tableAlias) + ", $1)", [args[1]]);
419
+ }
420
+ },
421
+ {
422
+ name: "$unnest_words",
423
+ description: ` :[column_name] -> Splits string at spaces`,
424
+ type: "function",
425
+ numArgs: 1,
426
+ singleColArg: true,
427
+ getFields: (args) => [args[0]],
428
+ getQuery: ({ allowedFields, args, tableAlias }) => {
429
+ return DboBuilder_1.pgp.as.format("unnest(string_to_array(" + (0, QueryBuilder_1.asNameAlias)(args[0], tableAlias) + "::TEXT , ' '))"); //, [args[1]]
430
+ }
431
+ },
432
+ {
433
+ name: "$right",
434
+ description: ` :[column_name, number] -> substring`,
435
+ type: "function",
436
+ numArgs: 2,
437
+ singleColArg: false,
438
+ getFields: (args) => [args[0]],
439
+ getQuery: ({ allowedFields, args, tableAlias }) => {
440
+ return DboBuilder_1.pgp.as.format("RIGHT(" + (0, QueryBuilder_1.asNameAlias)(args[0], tableAlias) + ", $1)", [args[1]]);
441
+ }
442
+ },
443
+ {
444
+ name: "$to_char",
445
+ type: "function",
446
+ description: ` :[column_name, format<string>] -> format dates and strings. Eg: [current_timestamp, 'HH12:MI:SS']`,
447
+ singleColArg: false,
448
+ numArgs: 2,
449
+ getFields: (args) => [args[0]],
450
+ getQuery: ({ allowedFields, args, tableAlias }) => {
451
+ if (args.length === 3) {
452
+ return DboBuilder_1.pgp.as.format("to_char(" + (0, QueryBuilder_1.asNameAlias)(args[0], tableAlias) + ", $2, $3)", [args[0], args[1], args[2]]);
453
+ }
454
+ return DboBuilder_1.pgp.as.format("to_char(" + (0, QueryBuilder_1.asNameAlias)(args[0], tableAlias) + ", $2)", [args[0], args[1]]);
455
+ }
456
+ },
457
+ /**
458
+ * Date trunc utils
459
+ */
460
+ ...[
461
+ "microsecond",
462
+ "millisecond",
463
+ "second",
464
+ "minute",
465
+ "hour",
466
+ "day",
467
+ "week",
468
+ "month",
469
+ "quarter",
470
+ "year",
471
+ "decade",
472
+ "century",
473
+ "millennium"
474
+ ].map(k => ({ val: 0, unit: k }))
475
+ .concat([
476
+ { val: 6, unit: 'month' },
477
+ { val: 4, unit: 'month' },
478
+ { val: 2, unit: 'month' },
479
+ { val: 8, unit: 'hour' },
480
+ { val: 4, unit: 'hour' },
481
+ { val: 2, unit: 'hour' },
482
+ { val: 30, unit: 'minute' },
483
+ { val: 15, unit: 'minute' },
484
+ { val: 6, unit: 'minute' },
485
+ { val: 5, unit: 'minute' },
486
+ { val: 4, unit: 'minute' },
487
+ { val: 3, unit: 'minute' },
488
+ { val: 2, unit: 'minute' },
489
+ { val: 30, unit: 'second' },
490
+ { val: 15, unit: 'second' },
491
+ { val: 10, unit: 'second' },
492
+ { val: 8, unit: 'second' },
493
+ { val: 6, unit: 'second' },
494
+ { val: 5, unit: 'second' },
495
+ { val: 4, unit: 'second' },
496
+ { val: 3, unit: 'second' },
497
+ { val: 2, unit: 'second' },
498
+ { val: 500, unit: 'millisecond' },
499
+ { val: 250, unit: 'millisecond' },
500
+ { val: 100, unit: 'millisecond' },
501
+ { val: 50, unit: 'millisecond' },
502
+ { val: 25, unit: 'millisecond' },
503
+ { val: 10, unit: 'millisecond' },
504
+ { val: 5, unit: 'millisecond' },
505
+ { val: 2, unit: 'millisecond' },
506
+ ]).map(({ val, unit }) => ({
507
+ name: "$date_trunc_" + (val || "") + unit,
508
+ type: "function",
509
+ description: ` :[column_name] -> round down timestamp to closest ${val || ""} ${unit} `,
510
+ singleColArg: true,
511
+ numArgs: 1,
512
+ getFields: (args) => [args[0]],
513
+ getQuery: ({ allColumns, args, tableAlias }) => {
514
+ const col = parseUnix(args[0], tableAlias, allColumns);
515
+ if (!val)
516
+ return `date_trunc(${asValue(unit)}, ${col})`;
517
+ const prevInt = {
518
+ month: "year",
519
+ hour: "day",
520
+ minute: "hour",
521
+ second: "minute"
522
+ };
523
+ let res = `(date_trunc(${asValue(prevInt[unit] || "hour")}, ${col}) + date_part(${asValue(unit, "::text")}, ${col})::int / ${val} * interval ${asValue(val + " " + unit)})`;
524
+ // console.log(res);
525
+ return res;
526
+ }
527
+ })),
528
+ /* Date funcs date_part */
529
+ ...["date_trunc", "date_part"].map(funcName => ({
530
+ name: "$" + funcName,
531
+ type: "function",
532
+ numArgs: 2,
533
+ description: ` :[unit<string>, column_name] -> ` + (funcName === "date_trunc" ? ` round down timestamp to closest unit value. ` : ` extract date unit as float8. `) + ` E.g. ['hour', col] `,
534
+ singleColArg: false,
535
+ getFields: (args) => [args[1]],
536
+ getQuery: ({ allColumns, args, tableAlias }) => {
537
+ return `${funcName}(${asValue(args[0])}, ${parseUnix(args[1], tableAlias, allColumns)})`;
538
+ }
539
+ })),
540
+ /* Handy date funcs */
541
+ ...[
542
+ ["date", "YYYY-MM-DD"],
543
+ ["datetime", "YYYY-MM-DD HH24:MI"],
544
+ ["datetime_", "YYYY_MM_DD__HH24_MI"],
545
+ ["timedate", "HH24:MI YYYY-MM-DD"],
546
+ ["time", "HH24:MI"],
547
+ ["time12", "HH:MI"],
548
+ ["timeAM", "HH:MI AM"],
549
+ ["dy", "dy"],
550
+ ["Dy", "Dy"],
551
+ ["day", "day"],
552
+ ["Day", "Day"],
553
+ ["DayNo", "DD"],
554
+ ["DD", "DD"],
555
+ ["dowUS", "D"],
556
+ ["D", "D"],
557
+ ["dow", "ID"],
558
+ ["ID", "ID"],
559
+ ["MonthNo", "MM"],
560
+ ["MM", "MM"],
561
+ ["mon", "mon"],
562
+ ["Mon", "Mon"],
563
+ ["month", "month"],
564
+ ["Month", "Month"],
565
+ ["year", "yyyy"],
566
+ ["yyyy", "yyyy"],
567
+ ["yy", "yy"],
568
+ ["yr", "yy"],
569
+ ].map(([funcName, txt]) => ({
570
+ name: "$" + funcName,
571
+ type: "function",
572
+ description: ` :[column_name] -> get timestamp formated as ` + txt,
573
+ singleColArg: true,
574
+ numArgs: 1,
575
+ getFields: (args) => [args[0]],
576
+ getQuery: ({ allColumns, args, tableAlias }) => {
577
+ return DboBuilder_1.pgp.as.format("trim(to_char(" + parseUnix(args[0], tableAlias, allColumns) + ", $2))", [args[0], txt]);
578
+ }
579
+ })),
580
+ /* Basic 1 arg col funcs */
581
+ ...["upper", "lower", "length", "reverse", "trim", "initcap", "round", "ceil", "floor", "sign", "md5"].map(funcName => ({
582
+ name: "$" + funcName,
583
+ type: "function",
584
+ numArgs: 1,
585
+ singleColArg: true,
586
+ getFields: (args) => [args[0]],
587
+ getQuery: ({ allowedFields, args, tableAlias }) => {
588
+ return funcName + "(" + (0, QueryBuilder_1.asNameAlias)(args[0], tableAlias) + ")";
589
+ }
590
+ })),
591
+ /**
592
+ * Interval funcs
593
+ * (col1, col2?, trunc )
594
+ * */
595
+ ...["age", "ageNow", "difference"].map(funcName => ({
596
+ name: "$" + funcName,
597
+ type: "function",
598
+ numArgs: 1,
599
+ singleColArg: true,
600
+ getFields: (args) => args.slice(0, 2).filter(a => typeof a === "string"),
601
+ getQuery: ({ allowedFields, args, tableAlias, allColumns }) => {
602
+ const validColCount = args.slice(0, 2).filter(a => typeof a === "string").length;
603
+ const trunc = args[2];
604
+ const allowedTruncs = ["second", "minute", "hour", "day", "month", "year"];
605
+ if (trunc && !allowedTruncs.includes(trunc))
606
+ throw new Error("Incorrect trunc provided. Allowed values: " + allowedTruncs);
607
+ if (funcName === "difference" && validColCount !== 2)
608
+ throw new Error("Must have two column names");
609
+ if (![1, 2].includes(validColCount))
610
+ throw new Error("Must have one or two column names");
611
+ const [leftField, rightField] = args;
612
+ const leftQ = parseUnix(leftField, tableAlias, allColumns);
613
+ let rightQ = rightField ? parseUnix(rightField, tableAlias, allColumns) : "";
614
+ let query = "";
615
+ if (funcName === "ageNow" && validColCount === 1) {
616
+ query = `age(now(), ${leftQ})`;
617
+ }
618
+ else if (funcName === "age" || funcName === "ageNow") {
619
+ if (rightQ)
620
+ rightQ = ", " + rightQ;
621
+ query = `age(${leftQ} ${rightQ})`;
622
+ }
623
+ else {
624
+ query = `${leftQ} - ${rightQ}`;
625
+ }
626
+ return trunc ? `date_trunc(${asValue(trunc)}, ${query})` : query;
627
+ }
628
+ })),
629
+ /* pgcrypto funcs */
630
+ ...["crypt"].map(funcName => ({
631
+ name: "$" + funcName,
632
+ type: "function",
633
+ numArgs: 1,
634
+ singleColArg: false,
635
+ getFields: (args) => [args[1]],
636
+ getQuery: ({ allowedFields, args, tableAlias }) => {
637
+ const value = asValue(args[0]) + "", seedColumnName = (0, QueryBuilder_1.asNameAlias)(args[1], tableAlias);
638
+ return `crypt(${value}, ${seedColumnName}::text)`;
639
+ }
640
+ })),
641
+ /* Text col and value funcs */
642
+ ...["position", "position_lower"].map(funcName => ({
643
+ name: "$" + funcName,
644
+ type: "function",
645
+ numArgs: 1,
646
+ singleColArg: false,
647
+ getFields: (args) => [args[1]],
648
+ getQuery: ({ allowedFields, args, tableAlias }) => {
649
+ let a1 = asValue(args[0]), a2 = (0, QueryBuilder_1.asNameAlias)(args[1], tableAlias);
650
+ if (funcName === "position_lower") {
651
+ a1 = `LOWER(${a1}::text)`;
652
+ a2 = `LOWER(${a2}::text)`;
653
+ }
654
+ return `position( ${a1} IN ${a2} )`;
655
+ }
656
+ })),
657
+ ...["template_string"].map(funcName => ({
658
+ name: "$" + funcName,
659
+ type: "function",
660
+ numArgs: 1,
661
+ minCols: 0,
662
+ singleColArg: false,
663
+ getFields: (args) => [],
664
+ getQuery: ({ allowedFields, args, tableAlias }) => {
665
+ if (typeof args[0] !== "string")
666
+ throw "First argument must be a string. E.g.: '{col1} ..text {col2} ...' ";
667
+ const rawValue = args[0];
668
+ let finalValue = rawValue;
669
+ const usedColumns = allowedFields.filter(fName => rawValue.includes(`{${fName}}`));
670
+ usedColumns.forEach((colName, idx) => {
671
+ finalValue = finalValue.split(`{${colName}}`).join(`%${idx + 1}$s`);
672
+ });
673
+ finalValue = asValue(finalValue);
674
+ if (usedColumns.length) {
675
+ return `format(${finalValue}, ${usedColumns.map(c => `${(0, QueryBuilder_1.asNameAlias)(c, tableAlias)}::TEXT`).join(", ")})`;
676
+ }
677
+ return `format(${finalValue})`;
678
+ }
679
+ })),
680
+ /** Custom highlight -> myterm => ['some text and', ['myterm'], ' and some other text']
681
+ * (fields: "*" | string[], term: string, { edgeTruncate: number = -1; noFields: boolean = false }) => string | (string | [string])[]
682
+ * edgeTruncate = maximum extra characters left and right of matches
683
+ * noFields = exclude field names in search
684
+ * */
685
+ {
686
+ name: "$term_highlight",
687
+ description: ` :[column_names<string[] | "*">, search_term<string>, opts?<{ returnIndex?: number; edgeTruncate?: number; noFields?: boolean }>] -> get case-insensitive text match highlight`,
688
+ type: "function",
689
+ numArgs: 1,
690
+ singleColArg: true,
691
+ canBeUsedForFilter: true,
692
+ getFields: (args) => args[0],
693
+ getQuery: ({ allowedFields, args, tableAlias, allColumns }) => {
694
+ const cols = ViewHandler_1.ViewHandler._parseFieldFilter(args[0], false, allowedFields);
695
+ let term = args[1];
696
+ const rawTerm = args[1];
697
+ let { edgeTruncate, noFields = false, returnType, matchCase = false } = args[2] || {};
698
+ if (!(0, prostgles_types_1.isEmpty)(args[2])) {
699
+ const keys = Object.keys(args[2]);
700
+ const validKeys = ["edgeTruncate", "noFields", "returnType", "matchCase"];
701
+ const bad_keys = keys.filter(k => !validKeys.includes(k));
702
+ if (bad_keys.length)
703
+ throw "Invalid options provided for $term_highlight. Expecting one of: " + validKeys.join(", ");
704
+ }
705
+ if (!cols.length)
706
+ throw "Cols are empty/invalid";
707
+ if (typeof term !== "string")
708
+ throw "Non string term provided: " + term;
709
+ if (edgeTruncate !== undefined && (!Number.isInteger(edgeTruncate) || edgeTruncate < -1))
710
+ throw "Invalid edgeTruncate. expecting a positive integer";
711
+ if (typeof noFields !== "boolean")
712
+ throw "Invalid noFields. expecting boolean";
713
+ const RETURN_TYPES = ["index", "boolean", "object"];
714
+ if (returnType && !RETURN_TYPES.includes(returnType)) {
715
+ throw `returnType can only be one of: ${RETURN_TYPES}`;
716
+ }
717
+ const makeTextMatcherArray = (rawText, _term) => {
718
+ let matchText = rawText, term = _term;
719
+ if (!matchCase) {
720
+ matchText = `LOWER(${rawText})`;
721
+ term = `LOWER(${term})`;
722
+ }
723
+ let leftStr = `substr(${rawText}, 1, position(${term} IN ${matchText}) - 1 )`, rightStr = `substr(${rawText}, position(${term} IN ${matchText}) + length(${term}) )`;
724
+ if (edgeTruncate) {
725
+ leftStr = `RIGHT(${leftStr}, ${asValue(edgeTruncate)})`;
726
+ rightStr = `LEFT(${rightStr}, ${asValue(edgeTruncate)})`;
727
+ }
728
+ return `
729
+ CASE WHEN position(${term} IN ${matchText}) > 0 AND ${term} <> ''
730
+ THEN array_to_json(ARRAY[
731
+ to_json( ${leftStr}::TEXT ),
732
+ array_to_json(
733
+ ARRAY[substr(${rawText}, position(${term} IN ${matchText}), length(${term}) )::TEXT ]
734
+ ),
735
+ to_json(${rightStr}::TEXT )
736
+ ])
737
+ ELSE
738
+ array_to_json(ARRAY[(${rawText})::TEXT])
739
+ END
740
+ `;
741
+ };
742
+ let colRaw = "( " + cols.map(c => `${noFields ? "" : (asValue(c + ": ") + " || ")} COALESCE(${(0, QueryBuilder_1.asNameAlias)(c, tableAlias)}::TEXT, '')`).join(" || ', ' || ") + " )";
743
+ let col = colRaw;
744
+ term = asValue(term);
745
+ if (!matchCase) {
746
+ col = "LOWER" + col;
747
+ term = `LOWER(${term})`;
748
+ }
749
+ let leftStr = `substr(${colRaw}, 1, position(${term} IN ${col}) - 1 )`, rightStr = `substr(${colRaw}, position(${term} IN ${col}) + length(${term}) )`;
750
+ if (edgeTruncate) {
751
+ leftStr = `RIGHT(${leftStr}, ${asValue(edgeTruncate)})`;
752
+ rightStr = `LEFT(${rightStr}, ${asValue(edgeTruncate)})`;
753
+ }
754
+ // console.log(col);
755
+ let res = "";
756
+ if (returnType === "index") {
757
+ res = `CASE WHEN position(${term} IN ${col}) > 0 THEN position(${term} IN ${col}) - 1 ELSE -1 END`;
758
+ // } else if(returnType === "boolean"){
759
+ // res = `CASE WHEN position(${term} IN ${col}) > 0 THEN TRUE ELSE FALSE END`;
760
+ }
761
+ else if (returnType === "object" || returnType === "boolean") {
762
+ const hasChars = Boolean(rawTerm && /[a-z]/i.test(rawTerm));
763
+ let validCols = cols.map(c => {
764
+ const colInfo = allColumns.find(ac => ac.name === c);
765
+ return {
766
+ key: c,
767
+ colInfo
768
+ };
769
+ })
770
+ .filter(c => c.colInfo && c.colInfo.udt_name !== "bytea");
771
+ let _cols = validCols.filter(c =>
772
+ /** Exclude numeric columns when the search tern contains a character */
773
+ !hasChars ||
774
+ (0, DboBuilder_1.postgresToTsType)(c.colInfo.udt_name) !== "number");
775
+ /** This will break GROUP BY (non-integer constant in GROUP BY) */
776
+ if (!_cols.length) {
777
+ if (validCols.length && hasChars)
778
+ throw `You're searching the impossible: characters in numeric fields. Use this to prevent making such a request in future: /[a-z]/i.test(your_term) `;
779
+ return (returnType === "boolean") ? "FALSE" : "NULL";
780
+ }
781
+ res = `CASE
782
+ ${_cols
783
+ .map(c => {
784
+ const colNameEscaped = (0, QueryBuilder_1.asNameAlias)(c.key, tableAlias);
785
+ let colSelect = `${colNameEscaped}::TEXT`;
786
+ const isTstamp = c.colInfo?.udt_name.startsWith("timestamp");
787
+ if (isTstamp || c.colInfo?.udt_name === "date") {
788
+ colSelect = `( CASE WHEN ${colNameEscaped} IS NULL THEN ''
789
+ ELSE concat_ws(' ',
790
+ trim(to_char(${colNameEscaped}, 'YYYY-MM-DD HH24:MI:SS')),
791
+ trim(to_char(${colNameEscaped}, 'Day Month')),
792
+ 'Q' || trim(to_char(${colNameEscaped}, 'Q')),
793
+ 'WK' || trim(to_char(${colNameEscaped}, 'WW'))
794
+ ) END)`;
795
+ }
796
+ let colTxt = `COALESCE(${colSelect}, '')`; // position(${term} IN ${colTxt}) > 0
797
+ if (returnType === "boolean") {
798
+ return `
799
+ WHEN ${colTxt} ${matchCase ? "LIKE" : "ILIKE"} ${asValue('%' + rawTerm + '%')}
800
+ THEN TRUE
801
+ `;
802
+ }
803
+ return `
804
+ WHEN ${colTxt} ${matchCase ? "LIKE" : "ILIKE"} ${asValue('%' + rawTerm + '%')}
805
+ THEN json_build_object(
806
+ ${asValue(c.key)},
807
+ ${makeTextMatcherArray(colTxt, term)}
808
+ )::jsonb
809
+ `;
810
+ }).join(" ")}
811
+ ELSE ${(returnType === "boolean") ? "FALSE" : "NULL"}
812
+
813
+ END`;
814
+ // console.log(res)
815
+ }
816
+ else {
817
+ /* If no match or empty search THEN return full row as string within first array element */
818
+ res = `CASE WHEN position(${term} IN ${col}) > 0 AND ${term} <> '' THEN array_to_json(ARRAY[
819
+ to_json( ${leftStr}::TEXT ),
820
+ array_to_json(
821
+ ARRAY[substr(${colRaw}, position(${term} IN ${col}), length(${term}) )::TEXT ]
822
+ ),
823
+ to_json(${rightStr}::TEXT )
824
+ ]) ELSE array_to_json(ARRAY[(${colRaw})::TEXT]) END`;
825
+ }
826
+ return res;
827
+ }
828
+ },
829
+ /* Aggs */
830
+ ...["max", "min", "count", "avg", "json_agg", "jsonb_agg", "string_agg", "array_agg", "sum"].map(aggName => ({
831
+ name: "$" + aggName,
832
+ type: "aggregation",
833
+ numArgs: 1,
834
+ singleColArg: true,
835
+ getFields: (args) => [args[0]],
836
+ getQuery: ({ allowedFields, args, tableAlias }) => {
837
+ let extraArgs = "";
838
+ if (args.length > 1) {
839
+ extraArgs = DboBuilder_1.pgp.as.format(", $1:csv", args.slice(1));
840
+ }
841
+ return aggName + "(" + (0, QueryBuilder_1.asNameAlias)(args[0], tableAlias) + `${extraArgs})`;
842
+ }
843
+ })),
844
+ /* More aggs */
845
+ {
846
+ name: "$countAll",
847
+ type: "aggregation",
848
+ description: `agg :[] COUNT of all rows `,
849
+ singleColArg: true,
850
+ numArgs: 0,
851
+ getFields: (args) => [],
852
+ getQuery: ({ allowedFields, args, tableAlias }) => {
853
+ return "COUNT(*)";
854
+ }
855
+ },
856
+ {
857
+ name: "$diff_perc",
858
+ type: "aggregation",
859
+ numArgs: 1,
860
+ singleColArg: true,
861
+ getFields: (args) => [args[0]],
862
+ getQuery: ({ allowedFields, args, tableAlias }) => {
863
+ const col = (0, QueryBuilder_1.asNameAlias)(args[0], tableAlias);
864
+ return `round( ( ( MAX(${col}) - MIN(${col}) )::float/MIN(${col}) ) * 100, 2)`;
865
+ }
866
+ }
867
+ ];
868
+ /* The difference between a function and computed field is that the computed field does not require any arguments */
869
+ exports.COMPUTED_FIELDS = [
870
+ /**
871
+ * Used instead of row id. Must be used as a last resort. Use all non pseudo or domain data type columns first!
872
+ */
873
+ {
874
+ name: "$rowhash",
875
+ type: "computed",
876
+ // description: ` order hash of row content `,
877
+ getQuery: ({ allowedFields, tableAlias, ctidField }) => {
878
+ return "md5(" +
879
+ allowedFields
880
+ /* CTID not available in AFTER trigger */
881
+ // .concat(ctidField? [ctidField] : [])
882
+ .sort()
883
+ .map(f => (0, QueryBuilder_1.asNameAlias)(f, tableAlias))
884
+ .map(f => `md5(coalesce(${f}::text, 'dd'))`)
885
+ .join(" || ") +
886
+ `)`;
887
+ }
888
+ }
889
+ // ,{
890
+ // name: "ctid",
891
+ // type: "computed",
892
+ // // description: ` order hash of row content `,
893
+ // getQuery: ({ allowedFields, tableAlias, ctidField }) => {
894
+ // return asNameAlias("ctid", tableAlias);
895
+ // }
896
+ // }
897
+ ];
898
+ /*
899
+
900
+
901
+ get key val pairs:
902
+ obj.key.path value
903
+
904
+
905
+ WITH RECURSIVE extract_all AS (
906
+ select
907
+ key as path,
908
+ jsonb_typeof(value) as type,
909
+ CASE WHEN trim(jsonb_typeof(value)) = 'array' THEN jsonb_typeof(value->0) END as elem_type,
910
+ value
911
+ from (SELECT * FROM mytable LIMIT 1) zzzzz
912
+ cross join lateral jsonb_each(jdata)
913
+ union all
914
+ select
915
+ path || '.' || coalesce(obj_key, (arr_key- 1)::text),
916
+ jsonb_typeof(coalesce(obj_value, arr_value)) as type,
917
+ CASE WHEN jsonb_typeof(coalesce(obj_value, arr_value)) = 'array' THEN jsonb_typeof(coalesce(obj_value, arr_value)->0) END as elem_type,
918
+ coalesce(obj_value, arr_value)
919
+ from extract_all
920
+ left join lateral
921
+ jsonb_each(case jsonb_typeof(value) when 'object' then value end)
922
+ as o(obj_key, obj_value)
923
+ on jsonb_typeof(value) = 'object'
924
+ left join lateral
925
+ jsonb_array_elements(case jsonb_typeof(value) when 'array' then value end)
926
+ with ordinality as a(arr_value, arr_key)
927
+ on jsonb_typeof(value) = 'array'
928
+ where obj_key is not null or arr_key is not null
929
+ )
930
+ SELECT *, array_length(string_to_array(path, '.'), 1) - 1 as depth
931
+ FROM extract_all t1
932
+ WHERE NOT EXISTS ( --Keep only leaf values
933
+ SELECT 1
934
+ FROM extract_all t2
935
+ WHERE length(t1.path) < length(t2.path)
936
+ AND starts_with(t2.path, t1.path)
937
+ );
938
+
939
+ */
940
+ //# sourceMappingURL=Functions.js.map