prostgles-server 4.2.160 → 4.2.162

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 (114) hide show
  1. package/dist/Auth/AuthHandler.d.ts +4 -4
  2. package/dist/Auth/AuthHandler.d.ts.map +1 -1
  3. package/dist/Auth/AuthHandler.js.map +1 -1
  4. package/dist/Auth/setEmailProvider.d.ts.map +1 -1
  5. package/dist/Auth/setEmailProvider.js +5 -5
  6. package/dist/Auth/setEmailProvider.js.map +1 -1
  7. package/dist/Auth/setupAuthRoutes.d.ts.map +1 -1
  8. package/dist/Auth/setupAuthRoutes.js +5 -6
  9. package/dist/Auth/setupAuthRoutes.js.map +1 -1
  10. package/lib/Auth/AuthHandler.ts +436 -0
  11. package/lib/Auth/AuthTypes.ts +280 -0
  12. package/lib/Auth/getSafeReturnURL.ts +35 -0
  13. package/lib/Auth/sendEmail.ts +83 -0
  14. package/lib/Auth/setAuthProviders.ts +128 -0
  15. package/lib/Auth/setEmailProvider.ts +85 -0
  16. package/lib/Auth/setupAuthRoutes.ts +159 -0
  17. package/lib/DBEventsManager.ts +178 -0
  18. package/lib/DBSchemaBuilder.ts +225 -0
  19. package/lib/DboBuilder/DboBuilder.ts +319 -0
  20. package/lib/DboBuilder/DboBuilderTypes.ts +361 -0
  21. package/lib/DboBuilder/QueryBuilder/Functions.ts +1153 -0
  22. package/lib/DboBuilder/QueryBuilder/QueryBuilder.ts +288 -0
  23. package/lib/DboBuilder/QueryBuilder/getJoinQuery.ts +263 -0
  24. package/lib/DboBuilder/QueryBuilder/getNewQuery.ts +271 -0
  25. package/lib/DboBuilder/QueryBuilder/getSelectQuery.ts +136 -0
  26. package/lib/DboBuilder/QueryBuilder/prepareHaving.ts +22 -0
  27. package/lib/DboBuilder/QueryStreamer.ts +250 -0
  28. package/lib/DboBuilder/TableHandler/DataValidator.ts +428 -0
  29. package/lib/DboBuilder/TableHandler/TableHandler.ts +205 -0
  30. package/lib/DboBuilder/TableHandler/delete.ts +115 -0
  31. package/lib/DboBuilder/TableHandler/insert.ts +183 -0
  32. package/lib/DboBuilder/TableHandler/insertTest.ts +78 -0
  33. package/lib/DboBuilder/TableHandler/onDeleteFromFileTable.ts +62 -0
  34. package/lib/DboBuilder/TableHandler/runInsertUpdateQuery.ts +134 -0
  35. package/lib/DboBuilder/TableHandler/update.ts +126 -0
  36. package/lib/DboBuilder/TableHandler/updateBatch.ts +49 -0
  37. package/lib/DboBuilder/TableHandler/updateFile.ts +48 -0
  38. package/lib/DboBuilder/TableHandler/upsert.ts +34 -0
  39. package/lib/DboBuilder/ViewHandler/ViewHandler.ts +393 -0
  40. package/lib/DboBuilder/ViewHandler/count.ts +38 -0
  41. package/lib/DboBuilder/ViewHandler/find.ts +153 -0
  42. package/lib/DboBuilder/ViewHandler/getExistsCondition.ts +73 -0
  43. package/lib/DboBuilder/ViewHandler/getExistsFilters.ts +74 -0
  44. package/lib/DboBuilder/ViewHandler/getInfo.ts +32 -0
  45. package/lib/DboBuilder/ViewHandler/getTableJoinQuery.ts +84 -0
  46. package/lib/DboBuilder/ViewHandler/parseComplexFilter.ts +96 -0
  47. package/lib/DboBuilder/ViewHandler/parseFieldFilter.ts +105 -0
  48. package/lib/DboBuilder/ViewHandler/parseJoinPath.ts +208 -0
  49. package/lib/DboBuilder/ViewHandler/prepareSortItems.ts +163 -0
  50. package/lib/DboBuilder/ViewHandler/prepareWhere.ts +90 -0
  51. package/lib/DboBuilder/ViewHandler/size.ts +37 -0
  52. package/lib/DboBuilder/ViewHandler/subscribe.ts +118 -0
  53. package/lib/DboBuilder/ViewHandler/validateViewRules.ts +70 -0
  54. package/lib/DboBuilder/dboBuilderUtils.ts +222 -0
  55. package/lib/DboBuilder/getColumns.ts +114 -0
  56. package/lib/DboBuilder/getCondition.ts +201 -0
  57. package/lib/DboBuilder/getSubscribeRelatedTables.ts +190 -0
  58. package/lib/DboBuilder/getTablesForSchemaPostgresSQL.ts +426 -0
  59. package/lib/DboBuilder/insertNestedRecords.ts +355 -0
  60. package/lib/DboBuilder/parseUpdateRules.ts +187 -0
  61. package/lib/DboBuilder/prepareShortestJoinPaths.ts +186 -0
  62. package/lib/DboBuilder/runSQL.ts +182 -0
  63. package/lib/DboBuilder/runTransaction.ts +50 -0
  64. package/lib/DboBuilder/sqlErrCodeToMsg.ts +254 -0
  65. package/lib/DboBuilder/uploadFile.ts +69 -0
  66. package/lib/Event_Trigger_Tags.ts +118 -0
  67. package/lib/FileManager/FileManager.ts +358 -0
  68. package/lib/FileManager/getValidatedFileType.ts +69 -0
  69. package/lib/FileManager/initFileManager.ts +187 -0
  70. package/lib/FileManager/upload.ts +62 -0
  71. package/lib/FileManager/uploadStream.ts +79 -0
  72. package/lib/Filtering.ts +463 -0
  73. package/lib/JSONBValidation/validate_jsonb_schema_sql.ts +502 -0
  74. package/lib/JSONBValidation/validation.ts +143 -0
  75. package/lib/Logging.ts +127 -0
  76. package/lib/PostgresNotifListenManager.ts +143 -0
  77. package/lib/Prostgles.ts +485 -0
  78. package/lib/ProstglesTypes.ts +196 -0
  79. package/lib/PubSubManager/PubSubManager.ts +609 -0
  80. package/lib/PubSubManager/addSub.ts +138 -0
  81. package/lib/PubSubManager/addSync.ts +141 -0
  82. package/lib/PubSubManager/getCreatePubSubManagerError.ts +72 -0
  83. package/lib/PubSubManager/getPubSubManagerInitQuery.ts +662 -0
  84. package/lib/PubSubManager/initPubSubManager.ts +79 -0
  85. package/lib/PubSubManager/notifListener.ts +173 -0
  86. package/lib/PubSubManager/orphanTriggerCheck.ts +70 -0
  87. package/lib/PubSubManager/pushSubData.ts +55 -0
  88. package/lib/PublishParser/PublishParser.ts +162 -0
  89. package/lib/PublishParser/getFileTableRules.ts +124 -0
  90. package/lib/PublishParser/getSchemaFromPublish.ts +141 -0
  91. package/lib/PublishParser/getTableRulesWithoutFileTable.ts +177 -0
  92. package/lib/PublishParser/publishTypesAndUtils.ts +399 -0
  93. package/lib/RestApi.ts +127 -0
  94. package/lib/SchemaWatch/SchemaWatch.ts +90 -0
  95. package/lib/SchemaWatch/createSchemaWatchEventTrigger.ts +3 -0
  96. package/lib/SchemaWatch/getValidatedWatchSchemaType.ts +45 -0
  97. package/lib/SchemaWatch/getWatchSchemaTagList.ts +27 -0
  98. package/lib/SyncReplication.ts +557 -0
  99. package/lib/TableConfig/TableConfig.ts +468 -0
  100. package/lib/TableConfig/getColumnDefinitionQuery.ts +111 -0
  101. package/lib/TableConfig/getConstraintDefinitionQueries.ts +95 -0
  102. package/lib/TableConfig/getFutureTableSchema.ts +64 -0
  103. package/lib/TableConfig/getPGIndexes.ts +53 -0
  104. package/lib/TableConfig/getTableColumnQueries.ts +129 -0
  105. package/lib/TableConfig/initTableConfig.ts +326 -0
  106. package/lib/index.ts +13 -0
  107. package/lib/initProstgles.ts +319 -0
  108. package/lib/onSocketConnected.ts +102 -0
  109. package/lib/runClientRequest.ts +129 -0
  110. package/lib/shortestPath.ts +122 -0
  111. package/lib/typeTests/DBoGenerated.d.ts +320 -0
  112. package/lib/typeTests/dboTypeCheck.ts +81 -0
  113. package/lib/utils.ts +15 -0
  114. package/package.json +1 -1
@@ -0,0 +1,288 @@
1
+
2
+ /*---------------------------------------------------------------------------------------------
3
+ * Copyright (c) Stefan L. All rights reserved.
4
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
5
+ *--------------------------------------------------------------------------------------------*/
6
+
7
+ import { asName, ColumnInfo, getKeys, isEmpty, isObject, JoinSelect, PG_COLUMN_UDT_DATA_TYPE, Select, ValidatedColumnInfo } from "prostgles-types";
8
+ import { isPlainObject, postgresToTsType, SortItem } from "../DboBuilder";
9
+
10
+ import { ParsedJoinPath } from "../ViewHandler/parseJoinPath";
11
+ import { ViewHandler } from "../ViewHandler/ViewHandler";
12
+ import { COMPUTED_FIELDS, FieldSpec, FunctionSpec, parseFunction } from "./Functions";
13
+
14
+ export type SelectItem = {
15
+ getFields: (args?: any[]) => string[] | "*";
16
+ getQuery: (tableAlias?: string) => string;
17
+ columnPGDataType?: string;
18
+ column_udt_type?: PG_COLUMN_UDT_DATA_TYPE;
19
+ tsDataType?: ValidatedColumnInfo["tsDataType"]
20
+ alias: string;
21
+ selected: boolean;
22
+ } & ({
23
+ type: "column";
24
+ columnName: string;
25
+ } | {
26
+ type: "function" | "aggregation" | "joinedColumn" | "computed";
27
+ columnName?: undefined;
28
+ });
29
+ export type SelectItemValidated = SelectItem & { fields: string[]; }
30
+ export type WhereOptions = Awaited<ReturnType<ViewHandler["prepareWhere"]>>;
31
+ export type NewQueryRoot = {
32
+ /**
33
+ * All fields from the table will be in nested SELECT and GROUP BY to allow order/filter by fields not in select
34
+ */
35
+ allFields: string[];
36
+
37
+ /**
38
+ * Contains user selection and all the allowed columns. Allowed columns not selected are marked with selected: false
39
+ */
40
+ select: SelectItem[];
41
+
42
+ table: string;
43
+ where: string;
44
+ whereOpts: WhereOptions;
45
+ orderByItems: SortItem[];
46
+ having: string;
47
+ limit: number | null;
48
+ offset: number;
49
+ isLeftJoin: boolean;
50
+ tableAlias?: string;
51
+ };
52
+
53
+ export type NewQueryJoin = (NewQuery & {
54
+ joinPath: ParsedJoinPath[];
55
+ joinAlias: string;
56
+ });
57
+ export type NewQuery = NewQueryRoot & {
58
+ joins?: NewQueryJoin[];
59
+ }
60
+
61
+ export const asNameAlias = (field: string, tableAlias?: string) => {
62
+ const result = asName(field);
63
+ if(tableAlias) return asName(tableAlias) + "." + result;
64
+ return result;
65
+ }
66
+
67
+ export const parseFunctionObject = (funcData: any): { funcName: string; args: any[] } => {
68
+ const makeErr = (msg: string) => `Function not specified correctly. Expecting { $funcName: ["columnName" | <value>, ...args] } object but got: ${JSON.stringify(funcData)} \n ${msg}`
69
+ if(!isObject(funcData)) throw makeErr("");
70
+ const keys = getKeys(funcData);
71
+ if(keys.length !== 1) throw makeErr("");
72
+ const funcName = keys[0]!;
73
+ const args = funcData[funcName];
74
+ if(!args || !Array.isArray(args)){
75
+ throw makeErr("Arguments missing or invalid");
76
+ }
77
+
78
+ return { funcName, args };
79
+ }
80
+
81
+
82
+ export class SelectItemBuilder {
83
+
84
+ select: SelectItemValidated[] = [];
85
+ private allFields: string[];
86
+
87
+ private allowedFields: string[];
88
+ private allowedOrderByFields: string[];
89
+ private computedFields: FieldSpec[];
90
+ private functions: FunctionSpec[];
91
+ private allowedFieldsIncludingComputed: string[];
92
+ private isView: boolean;
93
+ private columns: ColumnInfo[];
94
+
95
+ constructor(params: { allowedFields: string[]; allowedOrderByFields: string[]; computedFields: FieldSpec[]; functions: FunctionSpec[]; allFields: string[]; isView: boolean; columns: ColumnInfo[]; }){
96
+ this.allFields = params.allFields;
97
+ this.allowedFields = params.allowedFields;
98
+ this.allowedOrderByFields = params.allowedOrderByFields;
99
+ this.computedFields = params.computedFields;
100
+ this.isView = params.isView;
101
+ this.functions = params.functions;
102
+ this.columns = params.columns;
103
+ this.allowedFieldsIncludingComputed = this.allowedFields.concat(this.computedFields? this.computedFields.map(cf => cf.name) : []);
104
+ if(!this.allowedFields.length){
105
+ if(!this.columns.length){
106
+ throw "This view/table has no columns. Cannot select anything";
107
+ }
108
+ throw "allowedFields empty/missing";
109
+ }
110
+
111
+ /* Check for conflicting computed column names */
112
+ const conflictingCol = this.allFields.find(fieldName => this.computedFields.find(cf => cf.name === fieldName));
113
+ if(conflictingCol){
114
+ throw "INTERNAL ERROR: Cannot have duplicate column names ( " + conflictingCol + " ). One or more computed column names are colliding with table columns ones";
115
+ }
116
+ }
117
+
118
+ private checkField = (f: string, isSelected: boolean) => {
119
+ const allowedSelectedFields = this.allowedFieldsIncludingComputed;
120
+ const allowedNonSelectedFields = [...this.allowedFieldsIncludingComputed, ...this.allowedOrderByFields];
121
+
122
+ /** Not selected items can be part of the orderBy fields */
123
+ const allowedFields = isSelected? allowedSelectedFields : allowedNonSelectedFields;
124
+ if(!allowedFields.includes(f)){
125
+ throw "Field " + f + " is invalid or dissallowed. \nAllowed fields: " + allowedFields.join(", ");
126
+ }
127
+ return f;
128
+ }
129
+
130
+ private addItem = (item: SelectItem) => {
131
+ let fields = item.getFields();
132
+ // console.trace(fields)
133
+ if(fields === "*") fields = this.allowedFields.slice(0);
134
+ fields.map(f => this.checkField(f, item.selected));
135
+
136
+ if(this.select.find(s => s.alias === item.alias)){
137
+ throw `Cannot specify duplicate columns ( ${item.alias} ). Perhaps you're using "*" with column names?`;
138
+ }
139
+ this.select.push({ ...item, fields });
140
+ }
141
+
142
+ private addFunction = (func: FunctionSpec | string, args: any[], alias: string) => {
143
+ const funcDef = parseFunction({
144
+ func, args, functions: this.functions,
145
+ allowedFields: this.allowedFieldsIncludingComputed,
146
+ });
147
+
148
+ this.addItem({
149
+ type: funcDef.type,
150
+ alias,
151
+ getFields: () => funcDef.getFields(args),
152
+ getQuery: (tableAlias?: string) => funcDef.getQuery({ allColumns: this.columns, allowedFields: this.allowedFields, args, tableAlias,
153
+ ctidField: undefined,
154
+
155
+ /* CTID not available in AFTER trigger */
156
+ // ctidField: this.isView? undefined : "ctid"
157
+ }),
158
+ selected: true
159
+ });
160
+ }
161
+
162
+ addColumn = (fieldName: string, selected: boolean) => {
163
+
164
+ /* Check if computed col */
165
+ if(selected){
166
+ const compCol = COMPUTED_FIELDS.find(cf => cf.name === fieldName);
167
+ if(compCol && !this.select.find(s => s.alias === fieldName)){
168
+ const cf: FunctionSpec = {
169
+ ...compCol,
170
+ type: "computed",
171
+ numArgs: 0,
172
+ singleColArg: false,
173
+ getFields: (_args: any[]) => []
174
+ }
175
+ this.addFunction(cf, [], compCol.name)
176
+ return;
177
+ }
178
+ }
179
+
180
+ const colDef = this.columns.find(c => c.name === fieldName);
181
+ const alias = selected? fieldName : ("not_selected_" + fieldName);
182
+ this.addItem({
183
+ type: "column",
184
+ columnName: fieldName,
185
+ columnPGDataType: colDef?.data_type,
186
+ column_udt_type: colDef?.udt_name,
187
+ tsDataType: colDef && postgresToTsType(colDef.udt_name),
188
+ alias,
189
+ getQuery: (tableAlias) => asNameAlias(fieldName, tableAlias),
190
+ getFields: () => [fieldName],
191
+ selected
192
+ });
193
+ }
194
+
195
+ parseUserSelect = async (userSelect: Select, joinParse?: (key: string, val: JoinSelect, throwErr: (msg: string) => any) => any) => {
196
+
197
+ /* [col1, col2, col3] */
198
+ if(Array.isArray(userSelect)){
199
+ if(userSelect.find(key => typeof key !== "string")) throw "Invalid array select. Expecting an array of strings";
200
+
201
+ userSelect.map(key => this.addColumn(key, true))
202
+
203
+ /* Empty select */
204
+ } else if(userSelect === ""){
205
+ return [];
206
+
207
+ } else if(userSelect === "*"){
208
+ this.allowedFields.map(key => this.addColumn(key, true) );
209
+
210
+ } else if(isPlainObject(userSelect) && !isEmpty(userSelect)){
211
+ const selectKeys = Object.keys(userSelect),
212
+ selectValues = Object.values(userSelect);
213
+
214
+ /* Cannot include and exclude at the same time */
215
+ if(
216
+ selectValues.filter(v => [0, false].includes(v)).length
217
+ ){
218
+ if(selectValues.filter(v => ![0, false].includes(v)).length ){
219
+ throw "\nCannot include and exclude fields at the same time";
220
+ }
221
+
222
+ /* Exclude only */
223
+ this.allowedFields.filter(f => !selectKeys.includes(f)).map(key => this.addColumn(key, true) )
224
+
225
+ } else {
226
+ await Promise.all(selectKeys.map(async key => {
227
+ const val: any = userSelect[key as keyof typeof userSelect],
228
+ throwErr = (extraErr = "") => {
229
+ console.trace(extraErr)
230
+ throw "Unexpected select -> " + JSON.stringify({ [key]: val }) + "\n" + extraErr;
231
+ };
232
+
233
+ /* Included fields */
234
+ if([1, true].includes(val)){
235
+ if(key === "*"){
236
+ this.allowedFields.map(key => this.addColumn(key, true) )
237
+ } else {
238
+ this.addColumn(key, true);
239
+ }
240
+
241
+ /* Aggs and functions */
242
+ } else if(typeof val === "string" || isObject(val)) {
243
+
244
+ /* Function shorthand notation
245
+ { id: "$max" } === { id: { $max: ["id"] } } === SELECT MAX(id) AS id
246
+ */
247
+ if(
248
+ (typeof val === "string" && val !== "*") ||
249
+ isPlainObject(val) && Object.keys(val).length === 1 && Array.isArray(Object.values(val)[0])
250
+ ){
251
+
252
+ let funcName: string | undefined, args: any[] | undefined;
253
+ if(typeof val === "string") {
254
+ /* Shorthand notation -> it is expected that the key is the column name used as the only argument */
255
+ try {
256
+ this.checkField(key, true)
257
+ } catch (err){
258
+ throwErr(` Shorthand function notation error: the specifield column ( ${key} ) is invalid or dissallowed. \n Use correct column name or full aliased function notation, e.g.: -> { alias: { $func_name: ["column_name"] } } `)
259
+ }
260
+ funcName = val;
261
+ args = [key];
262
+
263
+ /** Function full notation { $funcName: ["colName", ...args] } */
264
+ } else {
265
+ ({ funcName, args } = parseFunctionObject(val));
266
+ }
267
+
268
+ this.addFunction(funcName, args, key);
269
+
270
+ /* Join */
271
+ } else {
272
+
273
+ if(!joinParse) {
274
+ throw "Joins dissalowed";
275
+ }
276
+ await joinParse(key, val as JoinSelect, throwErr);
277
+
278
+ }
279
+
280
+ } else throwErr();
281
+
282
+ }));
283
+ }
284
+ } else throw "Unexpected select -> " + JSON.stringify(userSelect);
285
+
286
+ }
287
+
288
+ }
@@ -0,0 +1,263 @@
1
+ import { isDefined, asName } from "prostgles-types";
2
+ import { ParsedJoinPath, parseJoinPath } from "../ViewHandler/parseJoinPath";
3
+ import { NewQuery, NewQueryJoin, SelectItem, asNameAlias } from "./QueryBuilder";
4
+ import { ROOT_TABLE_ALIAS, ROOT_TABLE_ROW_NUM_ID, indentLines } from "./getSelectQuery";
5
+ import { ViewHandler } from "../ViewHandler/ViewHandler";
6
+ import { getJoinOnCondition } from "../ViewHandler/getTableJoinQuery";
7
+ import { prepareOrderByQuery } from "../DboBuilder";
8
+
9
+ type Args = {
10
+ q1: NewQuery;
11
+ q2: NewQueryJoin;
12
+ selectParamsGroupBy: boolean;
13
+ }
14
+
15
+ /**
16
+ * Rename all join columns to prevent name clash
17
+ */
18
+ export const getJoinCol = (colName: string) => {
19
+ const alias = asName("prgl_join_col__" + colName);
20
+ return {
21
+ alias,
22
+ rootSelect: `${asName(colName)} AS ${alias}`,
23
+ }
24
+ }
25
+
26
+ export const JSON_AGG_FIELD_NAME = "prostgles_json_agg_result_field";
27
+ /**
28
+ * Used for LIMIT and for sorting
29
+ */
30
+ export const NESTED_ROWID_FIELD_NAME = "prostgles_rowid_field";
31
+
32
+ const getJoinTable = (tableName: string, pathIndex: number, isLastTableAlias: string | undefined) => {
33
+ const rawAlias = isLastTableAlias ?? `p${pathIndex} ${tableName}`;
34
+ return {
35
+ // name: asName(tableName), /** table names are already escaped */
36
+ name: tableName,
37
+ alias: asName(rawAlias),
38
+ rawAlias,
39
+ }
40
+ }
41
+
42
+ type GetJoinQueryResult = {
43
+ resultAlias: string;
44
+ // queryLines: string[];
45
+ firstJoinTableJoinFields: string[];
46
+ isOrJoin: boolean;
47
+ type: "cte";
48
+ joinLines: string[];
49
+ cteLines: string[];
50
+ }
51
+
52
+ /**
53
+ Returns join query. All inner join tables will be prefixed with path index unless it's the final target table which is aliased using the q2 tableAlias
54
+
55
+ LEFT JOIN (
56
+ SELECT [target table select + join fields]
57
+ FROM first_join/target_table
58
+ JOIN ..next_joins ON ...
59
+ JOIN target_table
60
+ ) target_table
61
+ ON ...condition
62
+ */
63
+ export const getJoinQuery = (viewHandler: ViewHandler, { q1, q2 }: Args): GetJoinQueryResult => {
64
+ const paths = parseJoinPath({
65
+ rootTable: q1.table,
66
+ rawPath: q2.joinPath,
67
+ viewHandler: viewHandler,
68
+ allowMultiOrJoin: true,
69
+ addShortestJoinIfMissing: true,
70
+ });
71
+
72
+ const targetTableAliasRaw = q2.tableAlias || q2.table;
73
+ const targetTableAlias = asName(targetTableAliasRaw);
74
+
75
+ const firstJoinTablePath = paths[0]!;
76
+ const firstJoinTableJoinFields = firstJoinTablePath.on.flatMap(condObj => Object.entries(condObj).map(([source, target]) => target));
77
+ const { rootSelectItems, jsonAggLimit } = getNestedSelectFields({
78
+ q: q2,
79
+ firstJoinTableAlias: getJoinTable(firstJoinTablePath.table, 0, paths.length === 1? targetTableAliasRaw : undefined).rawAlias,
80
+ _joinFields: firstJoinTableJoinFields
81
+ });
82
+
83
+ const joinType = q2.isLeftJoin? "LEFT" : "INNER";
84
+
85
+ const isOrJoin = firstJoinTablePath.on.length > 1;
86
+ const joinCondition = getJoinOnCondition({
87
+ on: firstJoinTablePath.on,
88
+ leftAlias: asName(q1.tableAlias || q1.table),
89
+ rightAlias: targetTableAlias,
90
+ getRightColName: (col) => getJoinCol(col).alias
91
+ });
92
+
93
+ const joinFields = rootSelectItems.filter(s => s.isJoinCol).map(s => s.alias);
94
+ const selectedFields = rootSelectItems.filter(s => s.selected).map(s => asNameAlias(s.alias, targetTableAliasRaw));
95
+ const rootNestedSort = q1.orderByItems.filter(d => d.nested?.joinAlias === q2.joinAlias);
96
+ const jsonAggSort = prepareOrderByQuery(q2.orderByItems, targetTableAliasRaw).join(", ");
97
+ const jsonAgg = `json_agg((SELECT x FROM (SELECT ${selectedFields}) as x )${jsonAggSort}) ${jsonAggLimit} as ${JSON_AGG_FIELD_NAME}`;
98
+
99
+ const { innerQuery } = getInnerJoinQuery({ paths, q1, q2, rootSelectItems, targetTableAliasRaw });
100
+
101
+ const requiredJoinFields = joinFields.map(field => getJoinCol(field).alias);
102
+ /**
103
+ * Used to prevent duplicates in case of OR filters
104
+ */
105
+ const rootTableIdField = `${ROOT_TABLE_ALIAS}.${ROOT_TABLE_ROW_NUM_ID}`;
106
+ const wrappingQuery = [
107
+ `SELECT `,
108
+ ...indentLines([
109
+ ...(isOrJoin? [rootTableIdField]: requiredJoinFields),
110
+ jsonAgg,
111
+ ...rootNestedSort.map(d => d.nested!.wrapperQuerySortItem)
112
+ ], { appendCommas: true }),
113
+ `FROM (`,
114
+ ...indentLines(innerQuery),
115
+ `) ${targetTableAlias}`,
116
+ ...(isOrJoin? [
117
+ `LEFT JOIN ${q1.table} ${ROOT_TABLE_ALIAS}`,
118
+ `ON ${joinCondition}`
119
+ ] : []),
120
+ `GROUP BY ${isOrJoin? rootTableIdField : requiredJoinFields}`,
121
+ ];
122
+
123
+ /**
124
+ * This is done to prevent join cte names clashing with actual table names
125
+ */
126
+ const targetTableAliasTempRename = asName(`${targetTableAlias}_prostgles_join_temp_rename`)
127
+ const cteLines = [
128
+ `${targetTableAliasTempRename} AS (`,
129
+ ...indentLines(wrappingQuery),
130
+ `)`
131
+ ];
132
+
133
+ const joinLines = [
134
+ `${joinType} JOIN ( SELECT * FROM ${targetTableAliasTempRename} ) as ${targetTableAlias}`,
135
+ isOrJoin?
136
+ `ON ${targetTableAlias}.${ROOT_TABLE_ROW_NUM_ID} = ${rootTableIdField}` :
137
+ `ON ${joinCondition}`
138
+ ];
139
+
140
+ return {
141
+ type: "cte",
142
+ resultAlias: JSON_AGG_FIELD_NAME,
143
+ // queryLines,
144
+ joinLines,
145
+ cteLines,
146
+ isOrJoin,
147
+ firstJoinTableJoinFields,
148
+ }
149
+ }
150
+
151
+
152
+ /**
153
+ * prepares the
154
+ */
155
+ const getInnerJoinQuery = ({ paths, q1, q2, targetTableAliasRaw, rootSelectItems }: {
156
+ paths: ParsedJoinPath[];
157
+ q1: NewQuery;
158
+ q2: NewQueryJoin;
159
+ targetTableAliasRaw: string;
160
+ rootSelectItems: SelectItemNested[];
161
+ }) => {
162
+
163
+ const innerQuery = paths.flatMap((path, i) => {
164
+
165
+ const isLast = i === paths.length - 1;
166
+ const targetQueryExtraQueries: string[] = [];
167
+
168
+ const prevTable = getJoinTable(!i? (q1.tableAlias? asName(q1.tableAlias) : q1.table) : paths[i-1]!.table, i-1, undefined);
169
+
170
+ const table = getJoinTable(path.table, i, isLast? targetTableAliasRaw : undefined);
171
+
172
+ if(isLast){
173
+ if(q2.where){
174
+ targetQueryExtraQueries.push(q2.where);
175
+ }
176
+
177
+ /* If aggs exist need to set groupBy add joinFields into select */
178
+ const aggs = q2.select.filter(s => s.type === "aggregation")
179
+ if (aggs.length) {
180
+ const groupByFields = rootSelectItems.map((c, i) => (c.isJoinCol || c.selected && c.type !== "aggregation")? `${i+1}` : undefined ).filter(isDefined);
181
+ if(groupByFields.length){
182
+ targetQueryExtraQueries.push(`GROUP BY ${groupByFields}`)
183
+ }
184
+ if(q2.having){
185
+ targetQueryExtraQueries.push(`HAVING ${q2.having}`)
186
+ }
187
+ }
188
+ }
189
+
190
+ const isFirst = !i;
191
+ if(isFirst){
192
+ return [
193
+ `SELECT `,
194
+ ` /* Join fields + select */`,
195
+ ...indentLines(rootSelectItems.map(s => s.query), { appendCommas: true }),
196
+ `FROM ${table.name} ${table.alias}`,
197
+ ...targetQueryExtraQueries
198
+ ]
199
+ }
200
+
201
+ return [
202
+ `INNER JOIN ${table.name} ${table.alias}`,
203
+ `ON ${getJoinOnCondition({
204
+ on: path.on,
205
+ leftAlias: prevTable.alias,
206
+ rightAlias: table.alias,
207
+ })}`,
208
+ ...targetQueryExtraQueries
209
+ ]
210
+ });
211
+
212
+ return { innerQuery }
213
+ }
214
+
215
+
216
+ type GetSelectFieldsArgs = {
217
+ q: NewQueryJoin;
218
+ firstJoinTableAlias: string;
219
+ _joinFields: string[];
220
+ }
221
+
222
+ export type SelectItemNested = SelectItem & { query: string; isJoinCol: boolean; };
223
+ const getNestedSelectFields = ({ q, firstJoinTableAlias, _joinFields }: GetSelectFieldsArgs) => {
224
+ const targetTableAlias = (q.tableAlias || q.table);
225
+
226
+ const requiredJoinFields = Array.from(new Set(_joinFields))
227
+ const selectedFields = q.select.filter(s => s.selected);
228
+ const rootSelectItems: SelectItemNested[] = selectedFields
229
+ .map(s => ({
230
+ ...s,
231
+ isJoinCol: false,
232
+ query: s.getQuery(targetTableAlias) + " AS " + asName(s.alias)
233
+ }))
234
+ .concat(requiredJoinFields.map(f => ({
235
+ type: "column",
236
+ columnName: f,
237
+ alias: f,
238
+ getFields: () => [f],
239
+ getQuery: (tableAlias) => asNameAlias(f, tableAlias),
240
+ selected: false,
241
+ isJoinCol: true,
242
+ query: `${asName(firstJoinTableAlias)}.${getJoinCol(f).rootSelect}`,
243
+ })));
244
+
245
+ const getQuery = (tableAlias?: string) => {
246
+ const partitionBy = `PARTITION BY ${requiredJoinFields.map(f => asNameAlias(f, tableAlias))}`;
247
+ return `ROW_NUMBER() OVER(${partitionBy}) AS ${NESTED_ROWID_FIELD_NAME}`
248
+ };
249
+ rootSelectItems.push({
250
+ type: "computed",
251
+ selected: false,
252
+ alias: NESTED_ROWID_FIELD_NAME,
253
+ getFields: () => [],
254
+ getQuery,
255
+ query: getQuery(firstJoinTableAlias),
256
+ isJoinCol: false,
257
+ })
258
+
259
+ return {
260
+ rootSelectItems,
261
+ jsonAggLimit: q.limit? `FILTER (WHERE ${NESTED_ROWID_FIELD_NAME} <= ${q.limit})` : ""
262
+ };
263
+ }