prostgles-server 4.2.626 → 4.2.628

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 (91) hide show
  1. package/debug/prostgles-types-4.0.285.tgz +0 -0
  2. package/dist/Audit/getAuditTableRules.js +1 -1
  3. package/dist/Audit/getAuditTableRules.js.map +1 -1
  4. package/dist/DBSchemaBuilder/DBSchemaBuilder.d.ts +4 -4
  5. package/dist/DBSchemaBuilder/DBSchemaBuilder.d.ts.map +1 -1
  6. package/dist/DBSchemaBuilder/getClientDBGeneratedSchemas.js +1 -1
  7. package/dist/DBSchemaBuilder/getClientDBGeneratedSchemas.js.map +1 -1
  8. package/dist/DboBuilder/QueryBuilder/Functions/Functions.d.ts +3 -1
  9. package/dist/DboBuilder/QueryBuilder/Functions/Functions.d.ts.map +1 -1
  10. package/dist/DboBuilder/QueryBuilder/Functions/Functions.js +18 -11
  11. package/dist/DboBuilder/QueryBuilder/Functions/Functions.js.map +1 -1
  12. package/dist/DboBuilder/QueryBuilder/Functions/HASHING_FUNCTIONS.d.ts.map +1 -1
  13. package/dist/DboBuilder/QueryBuilder/Functions/HASHING_FUNCTIONS.js +16 -21
  14. package/dist/DboBuilder/QueryBuilder/Functions/HASHING_FUNCTIONS.js.map +1 -1
  15. package/dist/DboBuilder/QueryBuilder/Functions/utils.d.ts +1 -0
  16. package/dist/DboBuilder/QueryBuilder/Functions/utils.d.ts.map +1 -1
  17. package/dist/DboBuilder/QueryBuilder/Functions/utils.js +3 -1
  18. package/dist/DboBuilder/QueryBuilder/Functions/utils.js.map +1 -1
  19. package/dist/DboBuilder/QueryBuilder/QueryBuilder.d.ts +18 -2
  20. package/dist/DboBuilder/QueryBuilder/QueryBuilder.d.ts.map +1 -1
  21. package/dist/DboBuilder/QueryBuilder/QueryBuilder.js +54 -16
  22. package/dist/DboBuilder/QueryBuilder/QueryBuilder.js.map +1 -1
  23. package/dist/DboBuilder/QueryBuilder/getNewQuery.d.ts +1 -1
  24. package/dist/DboBuilder/QueryBuilder/getNewQuery.d.ts.map +1 -1
  25. package/dist/DboBuilder/QueryBuilder/getNewQuery.js +40 -6
  26. package/dist/DboBuilder/QueryBuilder/getNewQuery.js.map +1 -1
  27. package/dist/DboBuilder/QueryBuilder/parseJoinSelect.d.ts.map +1 -1
  28. package/dist/DboBuilder/QueryBuilder/parseJoinSelect.js +2 -12
  29. package/dist/DboBuilder/QueryBuilder/parseJoinSelect.js.map +1 -1
  30. package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.d.ts.map +1 -1
  31. package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.js +7 -6
  32. package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.js.map +1 -1
  33. package/dist/DboBuilder/ViewHandler/find.d.ts.map +1 -1
  34. package/dist/DboBuilder/ViewHandler/find.js +2 -29
  35. package/dist/DboBuilder/ViewHandler/find.js.map +1 -1
  36. package/dist/DboBuilder/ViewHandler/parseComplexFilter.d.ts +4 -1
  37. package/dist/DboBuilder/ViewHandler/parseComplexFilter.d.ts.map +1 -1
  38. package/dist/DboBuilder/ViewHandler/parseComplexFilter.js +9 -2
  39. package/dist/DboBuilder/ViewHandler/parseComplexFilter.js.map +1 -1
  40. package/dist/DboBuilder/ViewHandler/subscribe.d.ts.map +1 -1
  41. package/dist/DboBuilder/ViewHandler/subscribe.js +8 -3
  42. package/dist/DboBuilder/ViewHandler/subscribe.js.map +1 -1
  43. package/dist/DboBuilder/ViewHandler/validateSelectParams.d.ts +3 -0
  44. package/dist/DboBuilder/ViewHandler/validateSelectParams.d.ts.map +1 -0
  45. package/dist/DboBuilder/ViewHandler/validateSelectParams.js +48 -0
  46. package/dist/DboBuilder/ViewHandler/validateSelectParams.js.map +1 -0
  47. package/dist/DboBuilder/getCondition.d.ts.map +1 -1
  48. package/dist/DboBuilder/getCondition.js +9 -3
  49. package/dist/DboBuilder/getCondition.js.map +1 -1
  50. package/dist/DboBuilder/getSubscribeRelatedTables.d.ts.map +1 -1
  51. package/dist/DboBuilder/getSubscribeRelatedTables.js +7 -2
  52. package/dist/DboBuilder/getSubscribeRelatedTables.js.map +1 -1
  53. package/dist/PubSubManager/PubSubManager.d.ts +1 -1
  54. package/dist/PubSubManager/PubSubManager.d.ts.map +1 -1
  55. package/dist/PubSubManager/PubSubManager.js +1 -1
  56. package/dist/PubSubManager/PubSubManager.js.map +1 -1
  57. package/dist/PubSubManager/addSub.js +1 -1
  58. package/dist/PubSubManager/addSub.js.map +1 -1
  59. package/dist/StorageClient/fileVersionUtils.d.ts +3 -3
  60. package/dist/StorageClient/fileVersionUtils.d.ts.map +1 -1
  61. package/dist/StorageClient/fileVersionUtils.js.map +1 -1
  62. package/dist/TableConfig/initTableConfig.d.ts.map +1 -1
  63. package/dist/TableConfig/initTableConfig.js +1 -4
  64. package/dist/TableConfig/initTableConfig.js.map +1 -1
  65. package/dist/index.d.ts +1 -1
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js.map +1 -1
  68. package/dist/typeTests/DBOFullyTyped.spec.js.map +1 -1
  69. package/lib/Audit/getAuditTableRules.ts +1 -1
  70. package/lib/DBSchemaBuilder/DBSchemaBuilder.ts +6 -7
  71. package/lib/DBSchemaBuilder/getClientDBGeneratedSchemas.ts +1 -1
  72. package/lib/DboBuilder/QueryBuilder/Functions/Functions.ts +38 -13
  73. package/lib/DboBuilder/QueryBuilder/Functions/HASHING_FUNCTIONS.ts +28 -24
  74. package/lib/DboBuilder/QueryBuilder/Functions/utils.ts +10 -0
  75. package/lib/DboBuilder/QueryBuilder/QueryBuilder.ts +95 -18
  76. package/lib/DboBuilder/QueryBuilder/getNewQuery.ts +58 -10
  77. package/lib/DboBuilder/QueryBuilder/parseJoinSelect.ts +2 -13
  78. package/lib/DboBuilder/TableHandler/onDeleteFromFileTable.ts +9 -8
  79. package/lib/DboBuilder/ViewHandler/find.ts +2 -32
  80. package/lib/DboBuilder/ViewHandler/parseComplexFilter.ts +10 -2
  81. package/lib/DboBuilder/ViewHandler/subscribe.ts +10 -3
  82. package/lib/DboBuilder/ViewHandler/validateSelectParams.ts +51 -0
  83. package/lib/DboBuilder/getCondition.ts +11 -3
  84. package/lib/DboBuilder/getSubscribeRelatedTables.ts +10 -3
  85. package/lib/PubSubManager/PubSubManager.ts +1 -1
  86. package/lib/PubSubManager/addSub.ts +1 -1
  87. package/lib/StorageClient/fileVersionUtils.ts +3 -3
  88. package/lib/TableConfig/initTableConfig.ts +1 -6
  89. package/lib/index.ts +5 -1
  90. package/lib/typeTests/DBOFullyTyped.spec.ts +1 -1
  91. package/package.json +2 -2
@@ -4,7 +4,7 @@ import { asName, includes, isObject, TextFilter_FullTextSearchFilterKeys } from
4
4
  import { asNameAlias } from "../../../utils/asNameAlias";
5
5
  import { HASHING_FUNCTIONS } from "./HASHING_FUNCTIONS";
6
6
  import { TEXT_FUNCTIONS } from "./TEXT_FUNCTIONS";
7
- import { asFunction } from "./utils";
7
+ import { asFunction, getAggregateQuery } from "./utils";
8
8
  const pgp = pgPromise();
9
9
 
10
10
  type GetQueryArgs = {
@@ -13,13 +13,15 @@ type GetQueryArgs = {
13
13
  args: any[];
14
14
  tableAliasRaw?: string;
15
15
  ctidField?: string;
16
+ aggregateFilter?: string;
17
+ aggregateOrderBy?: string;
16
18
  };
17
19
 
18
20
  export type FieldSpec = {
19
21
  name: string;
20
22
  type: "column" | "computed";
21
23
  /**
22
- * allowedFields passed for multicol functions (e.g.: $rowhash)
24
+ * allowedFields passed for multicolumn functions (e.g.: $rowhash)
23
25
  */
24
26
  getQuery: (params: Omit<GetQueryArgs, "args">) => string;
25
27
  };
@@ -454,12 +456,18 @@ PostGIS_Funcs = PostGIS_Funcs.concat(
454
456
  singleColArg: true,
455
457
  numArgs: 1,
456
458
  getFields: (args: any[]) => [args[0]],
457
- getQuery: ({ args, tableAliasRaw: tableAlias }) => {
458
- const escTabelName = asNameAlias(args[0], tableAlias) + "::geometry";
459
+ getQuery: ({ args, tableAliasRaw: tableAlias, aggregateFilter, aggregateOrderBy }) => {
460
+ const escapeTableName = asNameAlias(args[0], tableAlias) + "::geometry";
459
461
  if (fname.includes("Extent")) {
460
- return `${fname}(${escTabelName})`;
462
+ return getAggregateQuery(fname, escapeTableName, aggregateFilter, aggregateOrderBy);
461
463
  }
462
- return `${fname.endsWith("_Agg") ? fname.slice(0, -4) : fname}(ST_Collect(${escTabelName}))`;
464
+ const collectedGeometry = getAggregateQuery(
465
+ "ST_Collect",
466
+ escapeTableName,
467
+ aggregateFilter,
468
+ aggregateOrderBy,
469
+ );
470
+ return `${fname.endsWith("_Agg") ? fname.slice(0, -4) : fname}(${collectedGeometry})`;
463
471
  },
464
472
  };
465
473
  return res;
@@ -753,13 +761,23 @@ export const FUNCTIONS: FunctionSpec[] = [
753
761
  type: "aggregation",
754
762
  numArgs: 1,
755
763
  singleColArg: true,
756
- getFields: (args: any[]) => [args[0]],
757
- getQuery: ({ args, tableAliasRaw: tableAlias }) => {
764
+ minCols: aggName === "count" ? 0 : undefined,
765
+ getFields: (args: any[]) => (aggName === "count" && !args.length ? [] : [args[0]]),
766
+ getQuery: ({ args, tableAliasRaw: tableAlias, aggregateFilter, aggregateOrderBy }) => {
758
767
  let extraArgs = "";
759
768
  if (args.length > 1) {
760
769
  extraArgs = pgp.as.format(", $1:csv", args.slice(1));
761
770
  }
762
- return aggName + "(" + asNameAlias(args[0], tableAlias) + `${extraArgs})`;
771
+ const firstArg =
772
+ args.length ? asNameAlias(args[0], tableAlias)
773
+ : aggregateOrderBy ? "1"
774
+ : "*";
775
+ return getAggregateQuery(
776
+ aggName,
777
+ `${firstArg}${extraArgs}`,
778
+ aggregateFilter,
779
+ aggregateOrderBy,
780
+ );
763
781
  },
764
782
  }),
765
783
  ),
@@ -784,8 +802,13 @@ export const FUNCTIONS: FunctionSpec[] = [
784
802
  singleColArg: true,
785
803
  numArgs: 0,
786
804
  getFields: (args: any[]) => [],
787
- getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias }) => {
788
- return "COUNT(*)";
805
+ getQuery: ({ aggregateFilter, aggregateOrderBy }) => {
806
+ return getAggregateQuery(
807
+ "COUNT",
808
+ aggregateOrderBy ? "1" : "*",
809
+ aggregateFilter,
810
+ aggregateOrderBy,
811
+ );
789
812
  },
790
813
  }),
791
814
  asFunction({
@@ -794,9 +817,11 @@ export const FUNCTIONS: FunctionSpec[] = [
794
817
  numArgs: 1,
795
818
  singleColArg: true,
796
819
  getFields: (args: any[]) => [args[0]],
797
- getQuery: ({ allowedFields, args, tableAliasRaw: tableAlias }) => {
820
+ getQuery: ({ args, tableAliasRaw: tableAlias, aggregateFilter, aggregateOrderBy }) => {
798
821
  const col = asNameAlias(args[0], tableAlias);
799
- return `round( ( ( MAX(${col}) - MIN(${col}) )::float/MIN(${col}) ) * 100, 2)`;
822
+ const max = getAggregateQuery("MAX", col, aggregateFilter, aggregateOrderBy);
823
+ const min = getAggregateQuery("MIN", col, aggregateFilter, aggregateOrderBy);
824
+ return `round( ( ( ${max} - ${min} )::float/${min} ) * 100, 2)`;
800
825
  },
801
826
  }),
802
827
  ];
@@ -1,6 +1,7 @@
1
1
  import pgPromise from "pg-promise";
2
2
  import { asNameAlias } from "../../../utils/asNameAlias";
3
3
  import type { FunctionSpec } from "./Functions";
4
+ import { getAggregateQuery } from "./utils";
4
5
 
5
6
  const pgp = pgPromise();
6
7
 
@@ -33,15 +34,16 @@ export const HASHING_FUNCTIONS: FunctionSpec[] = [
33
34
  singleColArg: false,
34
35
  numArgs: MAX_COL_NUM,
35
36
  getFields: (args: any[]) => args,
36
- getQuery: ({ args, tableAliasRaw: tableAlias }) => {
37
- const q = pgp.as.format(
38
- "md5(string_agg(" +
39
- args
40
- .map((fname) => "COALESCE( " + asNameAlias(fname, tableAlias) + "::text, '' )")
41
- .join(" || ") +
42
- ", ','))",
37
+ getQuery: ({ args, tableAliasRaw: tableAlias, aggregateFilter, aggregateOrderBy }) => {
38
+ const stringAgg = getAggregateQuery(
39
+ "string_agg",
40
+ args
41
+ .map((fname) => "COALESCE( " + asNameAlias(fname, tableAlias) + "::text, '' )")
42
+ .join(" || ") + ", ','",
43
+ aggregateFilter,
44
+ aggregateOrderBy,
43
45
  );
44
- return q;
46
+ return pgp.as.format(`md5(${stringAgg})`);
45
47
  },
46
48
  },
47
49
 
@@ -70,15 +72,16 @@ export const HASHING_FUNCTIONS: FunctionSpec[] = [
70
72
  singleColArg: false,
71
73
  numArgs: MAX_COL_NUM,
72
74
  getFields: (args: any[]) => args,
73
- getQuery: ({ args, tableAliasRaw: tableAlias }) => {
74
- const q = pgp.as.format(
75
- "encode(sha256(string_agg(" +
76
- args
77
- .map((fname) => "COALESCE( " + asNameAlias(fname, tableAlias) + ", '' )")
78
- .join(" || ") +
79
- ", ',')::text::bytea), 'hex')",
75
+ getQuery: ({ args, tableAliasRaw: tableAlias, aggregateFilter, aggregateOrderBy }) => {
76
+ const stringAgg = getAggregateQuery(
77
+ "string_agg",
78
+ args
79
+ .map((fname) => "COALESCE( " + asNameAlias(fname, tableAlias) + ", '' )")
80
+ .join(" || ") + ", ','",
81
+ aggregateFilter,
82
+ aggregateOrderBy,
80
83
  );
81
- return q;
84
+ return pgp.as.format(`encode(sha256(${stringAgg}::text::bytea), 'hex')`);
82
85
  },
83
86
  },
84
87
  {
@@ -106,15 +109,16 @@ export const HASHING_FUNCTIONS: FunctionSpec[] = [
106
109
  singleColArg: false,
107
110
  numArgs: MAX_COL_NUM,
108
111
  getFields: (args: any[]) => args,
109
- getQuery: ({ args, tableAliasRaw: tableAlias }) => {
110
- const q = pgp.as.format(
111
- "encode(sha512(string_agg(" +
112
- args
113
- .map((fname) => "COALESCE( " + asNameAlias(fname, tableAlias) + ", '' )")
114
- .join(" || ") +
115
- ", ',')::text::bytea), 'hex')",
112
+ getQuery: ({ args, tableAliasRaw: tableAlias, aggregateFilter, aggregateOrderBy }) => {
113
+ const stringAgg = getAggregateQuery(
114
+ "string_agg",
115
+ args
116
+ .map((fname) => "COALESCE( " + asNameAlias(fname, tableAlias) + ", '' )")
117
+ .join(" || ") + ", ','",
118
+ aggregateFilter,
119
+ aggregateOrderBy,
116
120
  );
117
- return q;
121
+ return pgp.as.format(`encode(sha512(${stringAgg}::text::bytea), 'hex')`);
118
122
  },
119
123
  },
120
124
  ];
@@ -1,3 +1,13 @@
1
1
  import type { FunctionSpec } from "./Functions";
2
2
 
3
3
  export const asFunction = (def: FunctionSpec) => def;
4
+
5
+ export const getAggregateQuery = (
6
+ name: string,
7
+ args: string,
8
+ aggregateFilter?: string,
9
+ aggregateOrderBy?: string,
10
+ ) =>
11
+ `${name}(${args}${aggregateOrderBy ? ` ${aggregateOrderBy}` : ""})${
12
+ aggregateFilter ? ` FILTER (WHERE ${aggregateFilter})` : ""
13
+ }`;
@@ -4,13 +4,15 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
 
6
6
  import type {
7
+ AnyObject,
7
8
  ColumnInfo,
9
+ OrderBy,
8
10
  PG_COLUMN_UDT_DATA_TYPE,
9
11
  Select,
10
12
  ValidatedColumnInfo,
11
13
  } from "prostgles-types";
12
- import { getKeys, isEmpty, isObject, postgresToTsType } from "prostgles-types";
13
- import type { PGIdentifier, SortItem } from "../DboBuilder";
14
+ import { getKeys, includes, isEmpty, isObject, postgresToTsType } from "prostgles-types";
15
+ import type { ExistsFilterConfig, PGIdentifier, SortItem } from "../DboBuilder";
14
16
 
15
17
  import type { Awaitable } from "../../PublishParser/publishTypesAndUtils";
16
18
  import { asNameAlias } from "../../utils/asNameAlias";
@@ -30,6 +32,8 @@ export type SelectItem = {
30
32
  tsDataType?: ValidatedColumnInfo["tsDataType"];
31
33
  alias: string;
32
34
  selected: boolean;
35
+ dependencyFields?: string[];
36
+ dependencyExists?: ExistsFilterConfig[];
33
37
  } & (
34
38
  | {
35
39
  type: "column";
@@ -87,6 +91,36 @@ export const parseFunctionObject = (funcData: unknown): { funcName: string; args
87
91
  return { funcName, args };
88
92
  };
89
93
 
94
+ type AggregateOptions = {
95
+ $filter?: AnyObject;
96
+ $orderBy?: OrderBy;
97
+ };
98
+
99
+ type ParsedAggregateOptions = {
100
+ filter?: string;
101
+ orderBy?: string;
102
+ dependencyFields: string[];
103
+ dependencyExists: ExistsFilterConfig[];
104
+ };
105
+
106
+ const aggregateOptionKeys = ["$filter", "$orderBy"] as const;
107
+ const parseSelectFunctionObject = (funcData: Record<string, unknown>) => {
108
+ const functionData = Object.fromEntries(
109
+ Object.entries(funcData).filter(([key]) => !includes(aggregateOptionKeys, key)),
110
+ );
111
+ return {
112
+ ...parseFunctionObject(functionData),
113
+ aggregateOptions: {
114
+ ...(funcData.$filter !== undefined && {
115
+ $filter: funcData.$filter as AnyObject,
116
+ }),
117
+ ...(funcData.$orderBy !== undefined && {
118
+ $orderBy: funcData.$orderBy as OrderBy,
119
+ }),
120
+ } satisfies AggregateOptions,
121
+ };
122
+ };
123
+
90
124
  export class SelectItemBuilder {
91
125
  select: SelectItemValidated[] = [];
92
126
  private allFields: string[];
@@ -97,6 +131,7 @@ export class SelectItemBuilder {
97
131
  private functions: FunctionSpec[];
98
132
  private allowedFieldsIncludingComputed: string[];
99
133
  private columns: ColumnInfo[];
134
+ private parseAggregateOptions?: (options: AggregateOptions) => Promise<ParsedAggregateOptions>;
100
135
 
101
136
  constructor(params: {
102
137
  allowedFields: string[];
@@ -106,6 +141,7 @@ export class SelectItemBuilder {
106
141
  allFields: string[];
107
142
  isView: boolean;
108
143
  columns: ColumnInfo[];
144
+ parseAggregateOptions?: (options: AggregateOptions) => Promise<ParsedAggregateOptions>;
109
145
  }) {
110
146
  this.allFields = params.allFields;
111
147
  this.allowedFields = params.allowedFields;
@@ -113,6 +149,7 @@ export class SelectItemBuilder {
113
149
  this.computedFields = params.computedFields;
114
150
  this.functions = params.functions;
115
151
  this.columns = params.columns;
152
+ this.parseAggregateOptions = params.parseAggregateOptions;
116
153
  this.allowedFieldsIncludingComputed = this.allowedFields.concat(
117
154
  this.computedFields.map((cf) => cf.name),
118
155
  );
@@ -164,7 +201,12 @@ export class SelectItemBuilder {
164
201
  this.select.push({ ...item, fields });
165
202
  };
166
203
 
167
- private addFunction = (func: FunctionSpec | string, args: any[], alias: string) => {
204
+ private addFunction = (
205
+ func: FunctionSpec | string,
206
+ args: any[],
207
+ alias: string,
208
+ aggregateOptions: AggregateOptions = {},
209
+ ): void | Promise<void> => {
168
210
  const funcDef = parseFunction({
169
211
  func,
170
212
  args,
@@ -172,6 +214,27 @@ export class SelectItemBuilder {
172
214
  allowedFields: this.allowedFieldsIncludingComputed,
173
215
  });
174
216
 
217
+ if (Object.keys(aggregateOptions).length) {
218
+ if (funcDef.type !== "aggregation") {
219
+ throw `Aggregate options $filter and $orderBy are only allowed on aggregate functions`;
220
+ }
221
+ if (!this.parseAggregateOptions) {
222
+ throw "Aggregate options are not supported in this query";
223
+ }
224
+ return this.parseAggregateOptions(aggregateOptions).then((parsedOptions) => {
225
+ this.addFunctionItem(funcDef, args, alias, parsedOptions);
226
+ });
227
+ }
228
+
229
+ this.addFunctionItem(funcDef, args, alias);
230
+ };
231
+
232
+ private addFunctionItem = (
233
+ funcDef: FunctionSpec,
234
+ args: any[],
235
+ alias: string,
236
+ aggregateOptions?: ParsedAggregateOptions,
237
+ ) => {
175
238
  const fieldFilter = funcDef.getFields(args);
176
239
  this.addItem({
177
240
  type: funcDef.type,
@@ -184,11 +247,15 @@ export class SelectItemBuilder {
184
247
  args,
185
248
  tableAliasRaw: tableAlias,
186
249
  ctidField: undefined,
250
+ aggregateFilter: aggregateOptions?.filter,
251
+ aggregateOrderBy: aggregateOptions?.orderBy,
187
252
 
188
253
  /* CTID not available in AFTER trigger */
189
254
  // ctidField: this.isView? undefined : "ctid"
190
255
  }),
191
256
  selected: true,
257
+ dependencyFields: aggregateOptions?.dependencyFields,
258
+ dependencyExists: aggregateOptions?.dependencyExists,
192
259
  });
193
260
  };
194
261
 
@@ -204,7 +271,7 @@ export class SelectItemBuilder {
204
271
  singleColArg: false,
205
272
  getFields: (_args: any[]) => [],
206
273
  };
207
- this.addFunction(cf, [], compCol.name);
274
+ void this.addFunction(cf, [], compCol.name);
208
275
  return;
209
276
  }
210
277
  }
@@ -271,14 +338,16 @@ export class SelectItemBuilder {
271
338
  } else {
272
339
  await Promise.all(
273
340
  selectKeys.map(async (key) => {
274
- const val: unknown = userSelect[key as keyof typeof userSelect];
341
+ const userSelectValue: unknown = userSelect[key as keyof typeof userSelect];
275
342
  const throwErr: (message: string) => never = (message) => {
276
343
  console.trace(message);
277
- throw "Unexpected select -> " + JSON.stringify({ [key]: val }) + "\n" + message;
344
+ throw (
345
+ "Unexpected select -> " + JSON.stringify({ [key]: userSelectValue }) + "\n" + message
346
+ );
278
347
  };
279
348
 
280
349
  /* Included fields */
281
- if ([1, true].includes(val as number | boolean)) {
350
+ if (userSelectValue === 1 || userSelectValue === true) {
282
351
  if (key === "*") {
283
352
  this.allowedFields.map((key) => this.addColumn(key, true));
284
353
  } else {
@@ -286,18 +355,26 @@ export class SelectItemBuilder {
286
355
  }
287
356
 
288
357
  /* Aggregations and functions */
289
- } else if (typeof val === "string" || isObject(val)) {
358
+ } else if (typeof userSelectValue === "string" || isObject(userSelectValue)) {
359
+ const functionEntries =
360
+ isObject(userSelectValue) ?
361
+ Object.entries(userSelectValue).filter(
362
+ ([key]) => !includes(aggregateOptionKeys, key),
363
+ )
364
+ : [];
290
365
  /* Function shorthand notation
291
366
  { id: "$max" } === { id: { $max: ["id"] } } === SELECT MAX(id) AS id
292
367
  */
293
368
  if (
294
- (typeof val === "string" && val !== "*") ||
295
- (isObject(val) &&
296
- Object.keys(val).length === 1 &&
297
- Array.isArray(Object.values(val)[0]))
369
+ (typeof userSelectValue === "string" && userSelectValue !== "*") ||
370
+ (isObject(userSelectValue) &&
371
+ functionEntries.length === 1 &&
372
+ Array.isArray(functionEntries[0]![1]))
298
373
  ) {
299
- let funcName: string | undefined, args: any[] | undefined;
300
- if (typeof val === "string") {
374
+ let funcName: string | undefined,
375
+ args: any[] | undefined,
376
+ aggregateOptions: AggregateOptions = {};
377
+ if (typeof userSelectValue === "string") {
301
378
  /* Shorthand notation -> it is expected that the key is the column name used as the only argument */
302
379
  try {
303
380
  this.checkField(key, true);
@@ -306,22 +383,22 @@ export class SelectItemBuilder {
306
383
  ` Shorthand function notation error: the specified column ( ${key} ) is invalid or disallowed. \n Use correct column name or full aliased function notation, e.g.: -> { alias: { $func_name: ["column_name"] } } `,
307
384
  );
308
385
  }
309
- funcName = val;
386
+ funcName = userSelectValue;
310
387
  args = [key];
311
388
 
312
389
  /** Function full notation { $funcName: ["colName", ...args] } */
313
390
  } else {
314
- ({ funcName, args } = parseFunctionObject(val));
391
+ ({ funcName, args, aggregateOptions } = parseSelectFunctionObject(userSelectValue));
315
392
  }
316
393
 
317
- this.addFunction(funcName, args, key);
394
+ await this.addFunction(funcName, args, key, aggregateOptions);
318
395
 
319
396
  /* Join */
320
397
  } else {
321
398
  if (!joinParse) {
322
399
  throw "Joins disallowed";
323
400
  }
324
- const joinSelect = val;
401
+ const joinSelect = userSelectValue;
325
402
 
326
403
  const parsedJoin = parseJoinSelect(joinSelect);
327
404
 
@@ -1,7 +1,13 @@
1
1
  import type { SelectParams } from "prostgles-types";
2
- import { asName, isEmpty, omitKeys } from "prostgles-types";
2
+ import { asName, isEmpty, isObject, omitKeys } from "prostgles-types";
3
3
  import type { ParsedTableRule } from "../../PublishParser/PublishParser";
4
- import type { Filter, LocalParams, PGIdentifier, ValidatedTableRules } from "../DboBuilder";
4
+ import {
5
+ prepareOrderByQuery,
6
+ type Filter,
7
+ type LocalParams,
8
+ type PGIdentifier,
9
+ type ValidatedTableRules,
10
+ } from "../DboBuilder";
5
11
  import type { ViewHandler } from "../ViewHandler/ViewHandler";
6
12
  import { parseJoinPath } from "../ViewHandler/parseJoinPath";
7
13
  import { prepareSortItems } from "../ViewHandler/prepareSortItems";
@@ -21,22 +27,31 @@ export const getNewQuery = async (
21
27
  ): Promise<NewQuery> => {
22
28
  const { columns } = viewHandler;
23
29
 
24
- if (localParams?.isRemoteRequest && !tableRules?.select?.fields) {
30
+ const selectRule = tableRules?.select;
31
+ if (tableRules && !selectRule?.fields) {
25
32
  throw `INTERNAL ERROR: publish.${viewHandler.name}.select.fields rule missing`;
26
33
  }
27
34
 
35
+ /** Only missing tableRules means this is an unrestricted local query */
28
36
  const allowedOrderByFields =
29
- !tableRules ?
37
+ !selectRule ?
30
38
  viewHandler.column_names.slice(0)
31
- : viewHandler.parseFieldFilter(tableRules.select?.orderByFields ?? tableRules.select?.fields);
39
+ : viewHandler.parseFieldFilter(selectRule.orderByFields);
32
40
  const allowedSelectFields =
33
- !tableRules ?
41
+ !selectRule ?
34
42
  viewHandler.column_names.slice(0)
35
- : viewHandler.parseFieldFilter(tableRules.select?.fields);
43
+ : viewHandler.parseFieldFilter(selectRule.fields);
44
+ const allowedFilterFields =
45
+ !selectRule ?
46
+ viewHandler.column_names.slice(0)
47
+ : viewHandler.parseFieldFilter(selectRule.filterFields);
36
48
 
37
49
  const joinQueries: NewQueryJoin[] = [];
38
50
 
39
51
  const { select: userSelect = "*" } = selectParams;
52
+ const aggregateTableAlias =
53
+ selectParams.joinExpressionAlias ??
54
+ ({ raw: ROOT_TABLE_ALIAS, escaped: ROOT_TABLE_ALIAS } satisfies PGIdentifier);
40
55
 
41
56
  const selectItemBuilder = new SelectItemBuilder({
42
57
  allowedFields: allowedSelectFields,
@@ -46,6 +61,39 @@ export const getNewQuery = async (
46
61
  functions: FUNCTIONS,
47
62
  allFields: viewHandler.column_names.slice(0),
48
63
  columns,
64
+ parseAggregateOptions: async ({ $filter, $orderBy }) => {
65
+ if ($filter !== undefined && !isObject($filter)) {
66
+ throw `Invalid aggregate $filter. Expecting an object`;
67
+ }
68
+ const filterInfo =
69
+ $filter === undefined ? undefined : (
70
+ await prepareWhere(viewHandler, {
71
+ filter: $filter,
72
+ select: undefined,
73
+ filterFields: allowedFilterFields,
74
+ tableAlias: aggregateTableAlias,
75
+ localParams,
76
+ addWhere: false,
77
+ tableRule: tableRules,
78
+ })
79
+ );
80
+ const orderByItems = prepareSortItems(
81
+ $orderBy,
82
+ allowedOrderByFields,
83
+ aggregateTableAlias,
84
+ [],
85
+ [],
86
+ );
87
+ return {
88
+ filter: filterInfo?.condition,
89
+ orderBy: prepareOrderByQuery(orderByItems, aggregateTableAlias.raw)[0],
90
+ dependencyFields: [
91
+ ...(filterInfo?.columnsUsed ?? []),
92
+ ...orderByItems.map(({ key }) => key),
93
+ ],
94
+ dependencyExists: filterInfo?.exists ?? [],
95
+ };
96
+ },
49
97
  });
50
98
 
51
99
  await selectItemBuilder.parseUserSelectWithJoins(
@@ -151,9 +199,9 @@ export const getNewQuery = async (
151
199
  ...omitKeys(commonWhereParams, ["forcedFilter", "selectParams"]),
152
200
  filter: selectParams.having,
153
201
  tableAlias:
154
- selectParams.joinExpressionAlias ? source.alias : (
155
- { raw: ROOT_TABLE_ALIAS, escaped: ROOT_TABLE_ALIAS }
156
- ),
202
+ selectParams.joinExpressionAlias ?
203
+ source.alias
204
+ : { raw: ROOT_TABLE_ALIAS, escaped: ROOT_TABLE_ALIAS },
157
205
  isHaving: true,
158
206
  })
159
207
  );
@@ -5,18 +5,7 @@ import type {
5
5
  RawJoinPath,
6
6
  SimpleJoinSelect,
7
7
  } from "prostgles-types";
8
- import { getKeys, includes } from "prostgles-types";
9
-
10
- const JOIN_KEYS = ["$innerJoin", "$leftJoin"] as const;
11
- const JOIN_PARAM_KEYS = getKeys({
12
- $condition: 1,
13
- filter: 1,
14
- having: 1,
15
- limit: 1,
16
- offset: 1,
17
- orderBy: 1,
18
- select: 1,
19
- } satisfies Record<keyof Omit<DetailedJoinSelect, (typeof JOIN_KEYS)[number]>, 1>);
8
+ import { getKeys, includes, JOIN_KEYS, JOIN_PARAMS } from "prostgles-types";
20
9
 
21
10
  export type ParsedJoin =
22
11
  | {
@@ -47,7 +36,7 @@ export const parseJoinSelect = (joinParams: JoinSelect): ParsedJoin | string =>
47
36
  } else if (joinKey) {
48
37
  /* Full option join { field_name: db.innerJoin.table_name(filter, select) } */
49
38
  const invalidParams = Object.keys(joinParams).filter(
50
- (k) => !includes([...JOIN_PARAM_KEYS, ...JOIN_KEYS], k),
39
+ (k) => !includes([...JOIN_PARAMS, ...JOIN_KEYS], k),
51
40
  );
52
41
  if (invalidParams.length) {
53
42
  throw "Invalid join params: " + invalidParams.join(", ");
@@ -36,32 +36,33 @@ export const onDeleteFromFileTable = async (
36
36
 
37
37
  const fileIds =
38
38
  config.versioning ?
39
- ((await getFileTableHandler(dbx, config).find(filterOpts.filter, {
39
+ await getFileTableHandler(dbx, config).find(filterOpts.filter, {
40
40
  select: { id: 1 },
41
41
  limit: null,
42
- })) as { id: string }[])
42
+ })
43
43
  : [];
44
44
  const versionFiles =
45
45
  fileIds.length ?
46
- ((await getFileRevisionsTableHandler(dbx, config).find(
46
+ await getFileRevisionsTableHandler(dbx, config).find(
47
47
  { file_id: { $in: fileIds.map(({ id }) => id) } },
48
48
  { select: { storage_key: 1 }, limit: null },
49
- )) as { storage_key: string }[])
49
+ )
50
50
  : [];
51
51
 
52
- const files = await t.any<Record<string, unknown> & { _prostgles_storage_key: string }>(
52
+ const KEY = "_prostgles_storage_key" as const;
53
+ const files = await t.any<Record<string, unknown> & { [KEY]: string }>(
53
54
  `DELETE FROM ${asName(tableName)} ${filterOpts.where}
54
55
  ${returningQuery ? `${returningQuery},` : "RETURNING"}
55
- COALESCE(storage_key, id) AS _prostgles_storage_key`,
56
+ COALESCE(storage_key, id) AS ${KEY}`,
56
57
  );
57
58
  const storageKeys = new Set([
58
59
  ...versionFiles.map(({ storage_key }) => storage_key),
59
- ...files.map(({ _prostgles_storage_key }) => _prostgles_storage_key),
60
+ ...files.map((row) => row[KEY]),
60
61
  ]);
61
62
  for (const storageKey of storageKeys) {
62
63
  onCommit(({ db }) => deleteUnreferencedFile(db, config, storageKey));
63
64
  }
64
- const result = files.map(({ _prostgles_storage_key, ...row }) => row);
65
+ const result = files.map(({ [KEY]: _, ...row }) => row);
65
66
  if (!returningQuery) return undefined;
66
67
  return (isOneOrNone ? result[0] : result) as AnyObject[] | undefined;
67
68
  };
@@ -11,6 +11,7 @@ import {
11
11
  import { getNewQuery } from "../QueryBuilder/getNewQuery";
12
12
  import { getSelectQuery } from "../QueryBuilder/getSelectQuery";
13
13
  import { getReturnTypeQuery } from "./getReturnTypeQuery";
14
+ import { validateSelectParams } from "./validateSelectParams";
14
15
  import type { ViewHandler } from "./ViewHandler";
15
16
 
16
17
  export type Param3 = {
@@ -30,44 +31,13 @@ export const find = async function (
30
31
 
31
32
  const command = limit === 1 && returnType === "row" ? "findOne" : "find";
32
33
  try {
33
- const allowedReturnTypes = Object.keys({
34
- row: 1,
35
- statement: 1,
36
- value: 1,
37
- values: 1,
38
- "statement-no-rls": 1,
39
- "statement-where": 1,
40
- } satisfies Record<Required<SelectParams>["returnType"], 1>);
34
+ validateSelectParams(selectParams);
41
35
 
42
36
  const { returnType } = selectParams || {};
43
- if (returnType && !allowedReturnTypes.includes(returnType)) {
44
- throw `returnType (${returnType}) can only be ${allowedReturnTypes.join(" OR ")}`;
45
- }
46
37
 
47
38
  const { testRule = false } = localParams || {};
48
39
 
49
40
  if (testRule) return [];
50
- if (selectParams) {
51
- const validParamNames = Object.keys({
52
- select: 1,
53
- orderBy: 1,
54
- offset: 1,
55
- limit: 1,
56
- returnType: 1,
57
- groupBy: 1,
58
- having: 1,
59
- abortSignal: 1,
60
- } satisfies Record<keyof SelectParams, 1>);
61
-
62
- const invalidParams = Object.keys(selectParams).filter((k) => !validParamNames.includes(k));
63
- if (invalidParams.length)
64
- throw (
65
- "Invalid params: " +
66
- invalidParams.join(", ") +
67
- " \n Expecting: " +
68
- validParamNames.join(", ")
69
- );
70
- }
71
41
 
72
42
  /* Validate publish */
73
43
  if (tableRules) {