@dbsp/nql 1.5.0 → 1.7.0
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.
- package/dist/index.d.ts +54 -43
- package/dist/index.js +828 -127
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { NQL_INTERNAL_COMPILER_OPTIONS, markNqlTrustedRelationFilter, createNqlBindingRef, isNqlBindingRef, getNqlBindingRefName } from '@dbsp/types/internal';
|
|
2
|
-
import { NQL_SELECT_AGGREGATE_FUNCTIONS, NQL_SELECT_VALUE_FUNCTIONS, NQL_SELECT_JSON_FUNCTIONS, NQL_SELECT_SCALAR_FUNCTIONS, isNqlSelectWindowFunctionAllowed, isRankingWindowFunction, isParamIntent } from '@dbsp/types';
|
|
1
|
+
import { NQL_INTERNAL_COMPILER_OPTIONS, explainUnsupportedNqlBindingIncludeHop, markNqlTrustedRelationFilter, createNqlBindingRef, isNqlBindingRef, getNqlBindingRefName } from '@dbsp/types/internal';
|
|
2
|
+
import { NQL_SELECT_AGGREGATE_FUNCTIONS, NQL_SELECT_VALUE_FUNCTIONS, NQL_SELECT_JSON_FUNCTIONS, NQL_SELECT_SCALAR_FUNCTIONS, toColumnList, isNqlSelectWindowFunctionAllowed, isRankingWindowFunction, isParamIntent } from '@dbsp/types';
|
|
3
3
|
import { createToken, Lexer, CstParser } from 'chevrotain';
|
|
4
4
|
|
|
5
5
|
// src/compiler/index.ts
|
|
@@ -43,6 +43,28 @@ var NqlSemanticException = class extends Error {
|
|
|
43
43
|
this.relatedSymbol = relatedSymbol;
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
|
+
var DEFAULT_RELATION_TARGET_COLUMN = "id";
|
|
47
|
+
function relationForeignKeys(relation) {
|
|
48
|
+
if (!relation) return void 0;
|
|
49
|
+
const columns = toColumnList(relation.foreignKey);
|
|
50
|
+
return columns.length > 0 ? columns : void 0;
|
|
51
|
+
}
|
|
52
|
+
function relationCardinality(relation) {
|
|
53
|
+
return relation?.type === "hasMany" || relation?.type === "belongsToMany" ? "many" : "one";
|
|
54
|
+
}
|
|
55
|
+
function scalarRelationJoinColumns(relation) {
|
|
56
|
+
if (!relation) return void 0;
|
|
57
|
+
if (relation.type !== "belongsTo" && relation.type !== "hasOne" && relation.type !== "hasMany") {
|
|
58
|
+
return void 0;
|
|
59
|
+
}
|
|
60
|
+
const fkColumns = relationForeignKeys(relation);
|
|
61
|
+
if (!fkColumns) return void 0;
|
|
62
|
+
const sourceKeys = toColumnList(relation.sourceKey);
|
|
63
|
+
const targetKeys = toColumnList(relation.targetKey);
|
|
64
|
+
const sourceJoinColumn = relation.type === "belongsTo" ? fkColumns : sourceKeys.length > 0 ? sourceKeys : [DEFAULT_RELATION_TARGET_COLUMN];
|
|
65
|
+
const targetJoinColumn = relation.type === "belongsTo" ? targetKeys.length > 0 ? targetKeys : [DEFAULT_RELATION_TARGET_COLUMN] : fkColumns;
|
|
66
|
+
return { sourceJoinColumn, targetJoinColumn };
|
|
67
|
+
}
|
|
46
68
|
|
|
47
69
|
// src/compiler/column-validator.ts
|
|
48
70
|
var ColumnValidator = class _ColumnValidator {
|
|
@@ -92,6 +114,12 @@ var ColumnValidator = class _ColumnValidator {
|
|
|
92
114
|
if (virtualColumns) return virtualColumns;
|
|
93
115
|
return this.schema.getTable(name)?.columns.map((column) => column.name);
|
|
94
116
|
}
|
|
117
|
+
getPhysicalTableColumns(name) {
|
|
118
|
+
return this.schema.getTable(name)?.columns.map((column) => column.name);
|
|
119
|
+
}
|
|
120
|
+
getPseudoColumns(name) {
|
|
121
|
+
return this.schema.getTable(name)?.pseudoColumns ?? [];
|
|
122
|
+
}
|
|
95
123
|
hasPhysicalTable(name) {
|
|
96
124
|
return !this.virtualBindingTables.has(name) && !!this.schema.getTable(name);
|
|
97
125
|
}
|
|
@@ -110,6 +138,88 @@ var ColumnValidator = class _ColumnValidator {
|
|
|
110
138
|
getVirtualBindingScalarRelation(bindingName, relationName) {
|
|
111
139
|
return this.virtualBindingRelationFilters.get(bindingName)?.scalarRelations?.find((relation) => relation.relation === relationName);
|
|
112
140
|
}
|
|
141
|
+
static virtualRelationIncludeShape(relation) {
|
|
142
|
+
const relationType = relation.relationType;
|
|
143
|
+
const foreignKey = relationType === "belongsTo" ? relation.sourceColumn : relation.targetColumn;
|
|
144
|
+
return {
|
|
145
|
+
type: relationType,
|
|
146
|
+
foreignKey,
|
|
147
|
+
source: relation.sourceTable,
|
|
148
|
+
target: relation.targetTable
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
resolveVirtualBindingScalarRelationForInclude(bindingName, relationPath) {
|
|
152
|
+
const relationName = relationPath.join(".");
|
|
153
|
+
if (relationPath.length < 1) {
|
|
154
|
+
throw new NqlSemanticException(
|
|
155
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
156
|
+
`Query '${bindingName}' reads from an NQL binding and cannot use relation include '${relationName}' (ref-#192): the relation path must name a source-table relation.`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
const relation = relationPath[0];
|
|
160
|
+
if (!relation) {
|
|
161
|
+
throw new NqlSemanticException(
|
|
162
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
163
|
+
`Query '${bindingName}' reads from an NQL binding and cannot use relation include '${relationName}' (ref-#192): the relation path must name a source-table relation.`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
const virtualRelation = this.getVirtualBindingScalarRelation(
|
|
167
|
+
bindingName,
|
|
168
|
+
relation
|
|
169
|
+
);
|
|
170
|
+
if (virtualRelation) {
|
|
171
|
+
const resolvedFirstHop = this.getRelation(virtualRelation.sourceTable, relation) ?? _ColumnValidator.virtualRelationIncludeShape(virtualRelation);
|
|
172
|
+
const unsupportedFirstHopReason = explainUnsupportedNqlBindingIncludeHop(
|
|
173
|
+
relation,
|
|
174
|
+
resolvedFirstHop,
|
|
175
|
+
{ relation }
|
|
176
|
+
);
|
|
177
|
+
if (unsupportedFirstHopReason) {
|
|
178
|
+
throw new NqlSemanticException(
|
|
179
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
180
|
+
`Query '${bindingName}' reads from an NQL binding and cannot use relation include '${relationName}' (ref-#192): ${unsupportedFirstHopReason}.`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
let sourceTable = virtualRelation.targetTable;
|
|
184
|
+
for (let i = 1; i < relationPath.length; i++) {
|
|
185
|
+
const tailRelation = relationPath[i];
|
|
186
|
+
if (!tailRelation) {
|
|
187
|
+
throw new NqlSemanticException(
|
|
188
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
189
|
+
`Query '${bindingName}' reads from an NQL binding and cannot use relation include '${relationName}' (ref-#192): relation path segment ${i + 1} is empty.`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
const resolvedTail = this.getRelation(sourceTable, tailRelation);
|
|
193
|
+
if (!resolvedTail) {
|
|
194
|
+
throw new NqlSemanticException(
|
|
195
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
196
|
+
`Query '${bindingName}' reads from an NQL binding and cannot use relation include '${relationName}' (ref-#192): tail relation '${tailRelation}' is not declared on table '${sourceTable}'.`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
const unsupportedReason = explainUnsupportedNqlBindingIncludeHop(
|
|
200
|
+
tailRelation,
|
|
201
|
+
resolvedTail,
|
|
202
|
+
{ relation: tailRelation }
|
|
203
|
+
);
|
|
204
|
+
if (unsupportedReason) {
|
|
205
|
+
throw new NqlSemanticException(
|
|
206
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
207
|
+
`Query '${bindingName}' reads from an NQL binding and cannot use relation include '${relationName}' (ref-#192): tail ${unsupportedReason}.`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
sourceTable = resolvedTail.target;
|
|
211
|
+
}
|
|
212
|
+
return virtualRelation;
|
|
213
|
+
}
|
|
214
|
+
const reason = this.explainVirtualBindingScalarRelationRejection(
|
|
215
|
+
bindingName,
|
|
216
|
+
relation
|
|
217
|
+
);
|
|
218
|
+
throw new NqlSemanticException(
|
|
219
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
220
|
+
`Query '${bindingName}' reads from an NQL binding and cannot use relation include '${relation}' (ref-#192): ${reason}.`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
113
223
|
explainVirtualBindingRelationRejection(bindingName, relationName) {
|
|
114
224
|
const metadata = this.virtualBindingRelationFilters.get(bindingName);
|
|
115
225
|
if (!metadata) {
|
|
@@ -124,27 +234,24 @@ var ColumnValidator = class _ColumnValidator {
|
|
|
124
234
|
if (!relation) {
|
|
125
235
|
return `relation '${relationName}' is not declared on source table '${sourceTable}'`;
|
|
126
236
|
}
|
|
127
|
-
const
|
|
128
|
-
const fkColumns = typeof fk === "string" ? [fk] : Array.isArray(fk) ? [...fk] : [];
|
|
237
|
+
const fkColumns = toColumnList(relation.foreignKey);
|
|
129
238
|
if (relation.type !== "belongsTo") {
|
|
130
239
|
return `relation '${relationName}' is '${relation.type ?? "unknown"}'; A-lite only supports relations whose FK column is on the binding source table`;
|
|
131
240
|
}
|
|
132
|
-
if (fkColumns.length
|
|
133
|
-
return `relation '${relationName}' must have
|
|
241
|
+
if (fkColumns.length === 0) {
|
|
242
|
+
return `relation '${relationName}' must have at least one FK column for A-lite binding relation filters`;
|
|
134
243
|
}
|
|
135
|
-
const fkColumn
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const available2 = this.virtualBindingTables.get(bindingName)?.join(", ");
|
|
144
|
-
return `relation '${relationName}' FK column '${fkColumn}' is not projected as a direct source-column projection by binding '${bindingName}'${available2 ? ` (available columns: ${available2})` : ""}`;
|
|
244
|
+
for (const fkColumn of fkColumns) {
|
|
245
|
+
const directProjection = metadata.directProjectionLineage?.find(
|
|
246
|
+
(projection) => projection.sourceTable === sourceTable && _ColumnValidator.columnsMatch(projection.sourceColumn, fkColumn)
|
|
247
|
+
);
|
|
248
|
+
if (!directProjection) {
|
|
249
|
+
const available2 = this.virtualBindingTables.get(bindingName)?.join(", ");
|
|
250
|
+
return `relation '${relationName}' FK column '${fkColumn}' is not projected as a direct source-column projection by binding '${bindingName}'${available2 ? ` (available columns: ${available2})` : ""}`;
|
|
251
|
+
}
|
|
145
252
|
}
|
|
146
253
|
const available = this.virtualBindingTables.get(bindingName)?.join(", ");
|
|
147
|
-
return `relation '${relationName}' FK
|
|
254
|
+
return `relation '${relationName}' FK columns '${fkColumns.join(", ")}' are not available through binding '${bindingName}'${available ? ` (available columns: ${available})` : ""}`;
|
|
148
255
|
}
|
|
149
256
|
explainVirtualBindingScalarRelationRejection(bindingName, relationName) {
|
|
150
257
|
const metadata = this.virtualBindingRelationFilters.get(bindingName);
|
|
@@ -156,33 +263,131 @@ var ColumnValidator = class _ColumnValidator {
|
|
|
156
263
|
if (!sourceTable) {
|
|
157
264
|
return "the binding source table could not be proven";
|
|
158
265
|
}
|
|
266
|
+
const recursiveReason = this.explainVirtualBindingRecursiveScalarRelationRejection(
|
|
267
|
+
bindingName,
|
|
268
|
+
relationName,
|
|
269
|
+
sourceTable,
|
|
270
|
+
metadata
|
|
271
|
+
);
|
|
272
|
+
if (recursiveReason) return recursiveReason;
|
|
159
273
|
const relation = this.getRelation(sourceTable, relationName);
|
|
160
274
|
if (!relation) {
|
|
161
275
|
return `relation '${relationName}' is not declared on source table '${sourceTable}'`;
|
|
162
276
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
277
|
+
if (relation.type === "belongsToMany") {
|
|
278
|
+
if (relation.recursive !== void 0 || relation.source !== void 0 && relation.source === relation.target) {
|
|
279
|
+
return `relation '${relationName}' is recursive/self-referential; binding relation columns for recursive relations require recursive CTE handling and are not supported (ref-#193)`;
|
|
280
|
+
}
|
|
281
|
+
if (typeof relation.through !== "string" || relation.through.length === 0) {
|
|
282
|
+
return `relation '${relationName}' is many-to-many but does not declare a resolvable junction table (ref-#192)`;
|
|
283
|
+
}
|
|
284
|
+
const sourceColumns2 = toColumnList(relation.sourceKey).length > 0 ? toColumnList(relation.sourceKey) : ["id"];
|
|
285
|
+
const throughSourceColumns = toColumnList(relation.foreignKey);
|
|
286
|
+
const throughTargetColumns = toColumnList(relation.otherKey);
|
|
287
|
+
const targetColumns = toColumnList(relation.targetKey).length > 0 ? toColumnList(relation.targetKey) : ["id"];
|
|
288
|
+
if (sourceColumns2.length !== 1) {
|
|
289
|
+
return `relation '${relationName}' source key is composite; binding many-to-many relation columns require a single source key column (ref-#179)`;
|
|
290
|
+
}
|
|
291
|
+
if (throughSourceColumns.length !== 1) {
|
|
292
|
+
return `relation '${relationName}' junction source FK is composite or missing; binding many-to-many relation columns require a single junction source FK column (ref-#179)`;
|
|
293
|
+
}
|
|
294
|
+
if (throughTargetColumns.length !== 1) {
|
|
295
|
+
return `relation '${relationName}' junction target FK is composite or missing; binding many-to-many relation columns require a single junction target FK column (ref-#179)`;
|
|
296
|
+
}
|
|
297
|
+
if (targetColumns.length !== 1) {
|
|
298
|
+
return `relation '${relationName}' target key is composite; binding many-to-many relation columns require a single target key column (ref-#179)`;
|
|
299
|
+
}
|
|
300
|
+
const [sourceColumn] = sourceColumns2;
|
|
301
|
+
if (sourceColumn === void 0) {
|
|
302
|
+
return `relation '${relationName}' source key is missing; binding many-to-many relation columns require a single source key column (ref-#179)`;
|
|
303
|
+
}
|
|
304
|
+
const directProjection = metadata.directProjectionLineage?.find(
|
|
305
|
+
(projection) => projection.sourceTable === sourceTable && _ColumnValidator.columnsMatch(projection.sourceColumn, sourceColumn)
|
|
306
|
+
);
|
|
307
|
+
if (!directProjection) {
|
|
308
|
+
const available3 = this.virtualBindingTables.get(bindingName)?.join(", ");
|
|
309
|
+
return `relation '${relationName}' source key column '${sourceColumn}' is not projected as a direct source-column projection by binding '${bindingName}'${available3 ? ` (available columns: ${available3})` : ""}`;
|
|
310
|
+
}
|
|
311
|
+
const available2 = this.virtualBindingTables.get(bindingName)?.join(", ");
|
|
312
|
+
return `relation '${relationName}' many-to-many source column '${sourceColumn}' is not available through binding '${bindingName}'${available2 ? ` (available columns: ${available2})` : ""}`;
|
|
174
313
|
}
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
314
|
+
const unsupportedReason = explainUnsupportedNqlBindingIncludeHop(
|
|
315
|
+
relationName,
|
|
316
|
+
relation
|
|
178
317
|
);
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
318
|
+
if (unsupportedReason) return unsupportedReason;
|
|
319
|
+
const fkColumns = toColumnList(relation.foreignKey);
|
|
320
|
+
const sourceColumns = relation.type === "belongsTo" ? fkColumns : toColumnList(relation.sourceKey).length > 0 ? toColumnList(relation.sourceKey) : ["id"];
|
|
321
|
+
for (const sourceColumn of sourceColumns) {
|
|
322
|
+
const directProjection = metadata.directProjectionLineage?.find(
|
|
323
|
+
(projection) => projection.sourceTable === sourceTable && _ColumnValidator.columnsMatch(projection.sourceColumn, sourceColumn)
|
|
324
|
+
);
|
|
325
|
+
if (!directProjection) {
|
|
326
|
+
const available2 = this.virtualBindingTables.get(bindingName)?.join(", ");
|
|
327
|
+
const sourceColumnLabel = relation.type === "belongsTo" ? "FK column" : "source key column";
|
|
328
|
+
return `relation '${relationName}' ${sourceColumnLabel} '${sourceColumn}' is not projected as a direct source-column projection by binding '${bindingName}'${available2 ? ` (available columns: ${available2})` : ""}`;
|
|
329
|
+
}
|
|
183
330
|
}
|
|
184
331
|
const available = this.virtualBindingTables.get(bindingName)?.join(", ");
|
|
185
|
-
return `relation '${relationName}' source
|
|
332
|
+
return `relation '${relationName}' source columns '${sourceColumns.join(", ")}' are not available through binding '${bindingName}'${available ? ` (available columns: ${available})` : ""}`;
|
|
333
|
+
}
|
|
334
|
+
static recursiveDirection(value) {
|
|
335
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
336
|
+
const direction = value.direction;
|
|
337
|
+
if (direction === "up" || direction === "ancestors") return "up";
|
|
338
|
+
if (direction === "down" || direction === "descendants") return "down";
|
|
339
|
+
return void 0;
|
|
340
|
+
}
|
|
341
|
+
explainVirtualBindingRecursiveScalarRelationRejection(bindingName, relationName, sourceTable, metadata) {
|
|
342
|
+
const lowerRelationName = relationName.toLowerCase();
|
|
343
|
+
const pseudoColumn = this.getPseudoColumns(sourceTable).find(
|
|
344
|
+
(candidate) => candidate.ascendantKeyword?.toLowerCase() === lowerRelationName || candidate.descendantKeyword?.toLowerCase() === lowerRelationName
|
|
345
|
+
);
|
|
346
|
+
if (!pseudoColumn) return void 0;
|
|
347
|
+
const direction = pseudoColumn.ascendantKeyword?.toLowerCase() === lowerRelationName ? "up" : "down";
|
|
348
|
+
const recursiveRelations = this.getRelationsFrom(sourceTable).filter(
|
|
349
|
+
(relation) => relation.recursive !== void 0 && relation.source === relation.target && relation.source === sourceTable && _ColumnValidator.recursiveDirection(relation.recursive) === direction
|
|
350
|
+
);
|
|
351
|
+
if (recursiveRelations.length === 0) {
|
|
352
|
+
return `recursive traversal '${relationName}' is missing recursive relation metadata for direction '${direction}' (ref-#193)`;
|
|
353
|
+
}
|
|
354
|
+
for (const relation of recursiveRelations) {
|
|
355
|
+
const relationFkColumns = toColumnList(relation.foreignKey);
|
|
356
|
+
const fkColumns = relationFkColumns.length > 0 ? relationFkColumns : toColumnList(pseudoColumn.foreignKeyColumn);
|
|
357
|
+
if (fkColumns.length !== 1) {
|
|
358
|
+
return `relation '${relation.name}' self-ref FK is composite or missing; binding recursive relation columns require a single self-ref FK column (ref-#193)`;
|
|
359
|
+
}
|
|
360
|
+
const relationTargetColumns = toColumnList(relation.targetKey);
|
|
361
|
+
const targetColumns = relationTargetColumns.length > 0 ? relationTargetColumns : pseudoColumn.targetColumn !== void 0 ? [pseudoColumn.targetColumn] : [DEFAULT_RELATION_TARGET_COLUMN];
|
|
362
|
+
if (targetColumns.length !== 1) {
|
|
363
|
+
return `relation '${relation.name}' target key is composite; binding recursive relation columns require a single target key column (ref-#193)`;
|
|
364
|
+
}
|
|
365
|
+
const selfRefColumn = fkColumns[0];
|
|
366
|
+
const targetKeyColumn = targetColumns[0];
|
|
367
|
+
if (selfRefColumn === void 0 || targetKeyColumn === void 0) {
|
|
368
|
+
return `relation '${relation.name}' recursive metadata is missing a single-column seed; binding recursive relation columns require a single self-ref FK and target key column (ref-#193)`;
|
|
369
|
+
}
|
|
370
|
+
if (pseudoColumn.foreignKeyColumn !== void 0 && !_ColumnValidator.columnsMatch(
|
|
371
|
+
pseudoColumn.foreignKeyColumn,
|
|
372
|
+
selfRefColumn
|
|
373
|
+
) || pseudoColumn.targetColumn !== void 0 && !_ColumnValidator.columnsMatch(
|
|
374
|
+
pseudoColumn.targetColumn,
|
|
375
|
+
targetKeyColumn
|
|
376
|
+
)) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
const seedColumn = direction === "up" ? selfRefColumn : targetKeyColumn;
|
|
380
|
+
const directProjection = metadata.directProjectionLineage?.find(
|
|
381
|
+
(projection) => projection.sourceTable === sourceTable && _ColumnValidator.columnsMatch(projection.sourceColumn, seedColumn)
|
|
382
|
+
);
|
|
383
|
+
if (!directProjection) {
|
|
384
|
+
const available = this.virtualBindingTables.get(bindingName)?.join(", ");
|
|
385
|
+
const seedLabel = direction === "up" ? "self-ref FK column" : "target key column";
|
|
386
|
+
return `recursive traversal '${relationName}' ${seedLabel} '${seedColumn}' is not projected as a direct source-column projection by binding '${bindingName}'${available ? ` (available columns: ${available})` : ""} (ref-#193)`;
|
|
387
|
+
}
|
|
388
|
+
return void 0;
|
|
389
|
+
}
|
|
390
|
+
return `recursive traversal '${relationName}' pseudo metadata does not match a single-column recursive self-reference (ref-#193)`;
|
|
186
391
|
}
|
|
187
392
|
/**
|
|
188
393
|
* Convert camelCase to snake_case for column name matching.
|
|
@@ -215,6 +420,14 @@ var ColumnValidator = class _ColumnValidator {
|
|
|
215
420
|
(c) => _ColumnValidator.columnsMatch(column, c.name)
|
|
216
421
|
)?.name;
|
|
217
422
|
}
|
|
423
|
+
resolvePhysicalColumnName(table, column) {
|
|
424
|
+
if (column === "*") return column;
|
|
425
|
+
const tableInfo = this.schema.getTable(table);
|
|
426
|
+
if (!tableInfo) return void 0;
|
|
427
|
+
return tableInfo.columns.find(
|
|
428
|
+
(c) => _ColumnValidator.columnsMatch(column, c.name)
|
|
429
|
+
)?.name;
|
|
430
|
+
}
|
|
218
431
|
validateColumn(table, column) {
|
|
219
432
|
if (column === "*") return;
|
|
220
433
|
const virtualColumns = this.virtualBindingTables.get(table);
|
|
@@ -401,6 +614,17 @@ function throwBindingRelationColumn182(bindingName, relationName, reason) {
|
|
|
401
614
|
`Query '${bindingName}' reads from an NQL binding and cannot select relation column '${relationName}' under A-full (ref-#182): ${reason}.`
|
|
402
615
|
);
|
|
403
616
|
}
|
|
617
|
+
function bindingRelationColumnUnsupportedReason(relationName, relation) {
|
|
618
|
+
const unsupportedReason = explainUnsupportedNqlBindingIncludeHop(
|
|
619
|
+
relationName,
|
|
620
|
+
relation
|
|
621
|
+
);
|
|
622
|
+
if (unsupportedReason) return unsupportedReason;
|
|
623
|
+
if (relationCardinality(relation) !== "one") {
|
|
624
|
+
return `relation '${relationName}' is '${relation.type ?? "unknown"}'; scalar binding relation columns require every hop to be to-one (belongsTo/hasOne) (ref-#192)`;
|
|
625
|
+
}
|
|
626
|
+
return void 0;
|
|
627
|
+
}
|
|
404
628
|
function resolveBindingRelationFilter(ctx, bindingName, relationPath) {
|
|
405
629
|
if (!isBindingTable(ctx, bindingName)) return void 0;
|
|
406
630
|
const actualBindingName = bindingName;
|
|
@@ -435,19 +659,19 @@ function resolveBindingRelationColumn(ctx, bindingName, relationPath, selectedCo
|
|
|
435
659
|
if (!isBindingTable(ctx, bindingName)) return void 0;
|
|
436
660
|
const actualBindingName = bindingName;
|
|
437
661
|
const relationName = relationPath.join(".");
|
|
438
|
-
|
|
662
|
+
const relation = relationPath[0];
|
|
663
|
+
if (!relation) {
|
|
439
664
|
throwBindingRelationColumn182(
|
|
440
665
|
actualBindingName,
|
|
441
666
|
relationName,
|
|
442
|
-
"
|
|
667
|
+
"the relation path must name a source-table relation"
|
|
443
668
|
);
|
|
444
669
|
}
|
|
445
|
-
|
|
446
|
-
if (!relation) {
|
|
670
|
+
if (!ctx.validator) {
|
|
447
671
|
throwBindingRelationColumn182(
|
|
448
672
|
actualBindingName,
|
|
449
673
|
relationName,
|
|
450
|
-
"
|
|
674
|
+
"model metadata is not available"
|
|
451
675
|
);
|
|
452
676
|
}
|
|
453
677
|
const virtualRelation = ctx.validator?.getVirtualBindingScalarRelation(
|
|
@@ -459,10 +683,129 @@ function resolveBindingRelationColumn(ctx, bindingName, relationPath, selectedCo
|
|
|
459
683
|
actualBindingName,
|
|
460
684
|
relation
|
|
461
685
|
) ?? "model metadata is not available";
|
|
462
|
-
throwBindingRelationColumn182(actualBindingName,
|
|
686
|
+
throwBindingRelationColumn182(actualBindingName, relationName, reason);
|
|
687
|
+
}
|
|
688
|
+
if (relationPath.length === 1) {
|
|
689
|
+
ctx.validator.validateColumn(virtualRelation.targetTable, selectedColumn);
|
|
690
|
+
return virtualRelation;
|
|
691
|
+
}
|
|
692
|
+
const firstHop = ctx.validator.getRelation(
|
|
693
|
+
virtualRelation.sourceTable,
|
|
694
|
+
relation
|
|
695
|
+
);
|
|
696
|
+
if (!firstHop) {
|
|
697
|
+
throwBindingRelationColumn182(
|
|
698
|
+
actualBindingName,
|
|
699
|
+
relationName,
|
|
700
|
+
`relation '${relation}' is not declared on source table '${virtualRelation.sourceTable}'`
|
|
701
|
+
);
|
|
463
702
|
}
|
|
464
|
-
|
|
465
|
-
|
|
703
|
+
const firstHopReason = bindingRelationColumnUnsupportedReason(
|
|
704
|
+
relation,
|
|
705
|
+
firstHop
|
|
706
|
+
);
|
|
707
|
+
if (firstHopReason) {
|
|
708
|
+
throwBindingRelationColumn182(
|
|
709
|
+
actualBindingName,
|
|
710
|
+
relationName,
|
|
711
|
+
firstHopReason
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
let sourceTable = virtualRelation.targetTable;
|
|
715
|
+
const hops = [];
|
|
716
|
+
for (let i = 1; i < relationPath.length; i++) {
|
|
717
|
+
const tailRelation = relationPath[i];
|
|
718
|
+
if (!tailRelation) {
|
|
719
|
+
throwBindingRelationColumn182(
|
|
720
|
+
actualBindingName,
|
|
721
|
+
relationName,
|
|
722
|
+
`relation path segment ${i + 1} is empty (ref-#192)`
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
const resolvedTail = ctx.validator.getRelation(sourceTable, tailRelation);
|
|
726
|
+
if (!resolvedTail) {
|
|
727
|
+
throwBindingRelationColumn182(
|
|
728
|
+
actualBindingName,
|
|
729
|
+
relationName,
|
|
730
|
+
`tail relation '${tailRelation}' is not declared on table '${sourceTable}' (ref-#192)`
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
const tailReason = bindingRelationColumnUnsupportedReason(
|
|
734
|
+
tailRelation,
|
|
735
|
+
resolvedTail
|
|
736
|
+
);
|
|
737
|
+
if (tailReason) {
|
|
738
|
+
throwBindingRelationColumn182(
|
|
739
|
+
actualBindingName,
|
|
740
|
+
relationName,
|
|
741
|
+
`tail ${tailReason}`
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
const joinColumns = scalarRelationJoinColumns(resolvedTail);
|
|
745
|
+
if (!joinColumns) {
|
|
746
|
+
throwBindingRelationColumn182(
|
|
747
|
+
actualBindingName,
|
|
748
|
+
relationName,
|
|
749
|
+
`tail relation '${tailRelation}' cannot be resolved to a scalar join (ref-#192)`
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
if (joinColumns.sourceJoinColumn.length !== joinColumns.targetJoinColumn.length) {
|
|
753
|
+
throwBindingRelationColumn182(
|
|
754
|
+
actualBindingName,
|
|
755
|
+
relationName,
|
|
756
|
+
`tail relation '${tailRelation}' has mismatched join column counts (${joinColumns.sourceJoinColumn.length} source, ${joinColumns.targetJoinColumn.length} target) (ref-#179)`
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
hops.push({
|
|
760
|
+
target: resolvedTail.target,
|
|
761
|
+
fkColumn: joinColumns.sourceJoinColumn,
|
|
762
|
+
joinColumn: joinColumns.targetJoinColumn
|
|
763
|
+
});
|
|
764
|
+
sourceTable = resolvedTail.target;
|
|
765
|
+
}
|
|
766
|
+
ctx.validator.validateColumn(sourceTable, selectedColumn);
|
|
767
|
+
return {
|
|
768
|
+
...virtualRelation,
|
|
769
|
+
relation: relationName,
|
|
770
|
+
hops
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function resolveBindingRecursiveRelationColumn(ctx, bindingName, traversal, selectedColumn, maxDepth) {
|
|
774
|
+
if (!isBindingTable(ctx, bindingName)) return void 0;
|
|
775
|
+
const traversalName = traversal.toLowerCase();
|
|
776
|
+
const isRecursiveTraversal = ctx.recursiveKeywords.has(traversalName) || traversalName === "ascendant" || traversalName === "descendant";
|
|
777
|
+
const actualBindingName = bindingName;
|
|
778
|
+
if (!ctx.validator) {
|
|
779
|
+
if (!isRecursiveTraversal) return void 0;
|
|
780
|
+
throwBindingRelationColumn182(
|
|
781
|
+
actualBindingName,
|
|
782
|
+
traversalName,
|
|
783
|
+
"model metadata is not available"
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
const virtualRelation = ctx.validator.getVirtualBindingScalarRelation(
|
|
787
|
+
actualBindingName,
|
|
788
|
+
traversalName
|
|
789
|
+
);
|
|
790
|
+
if (!isRecursiveTraversal && !virtualRelation?.recursive) {
|
|
791
|
+
return void 0;
|
|
792
|
+
}
|
|
793
|
+
if (!virtualRelation?.recursive) {
|
|
794
|
+
const reason = ctx.validator.explainVirtualBindingScalarRelationRejection(
|
|
795
|
+
actualBindingName,
|
|
796
|
+
traversalName
|
|
797
|
+
);
|
|
798
|
+
throwBindingRelationColumn182(actualBindingName, traversalName, reason);
|
|
799
|
+
}
|
|
800
|
+
ctx.validator.validateColumn(virtualRelation.targetTable, selectedColumn);
|
|
801
|
+
if (maxDepth === void 0) return virtualRelation;
|
|
802
|
+
return {
|
|
803
|
+
...virtualRelation,
|
|
804
|
+
recursive: {
|
|
805
|
+
...virtualRelation.recursive,
|
|
806
|
+
maxDepth
|
|
807
|
+
}
|
|
808
|
+
};
|
|
466
809
|
}
|
|
467
810
|
function assertNoBindingRelationPath(ctx, bindingName, path) {
|
|
468
811
|
if (!isBindingTable(ctx, bindingName)) return;
|
|
@@ -1099,7 +1442,12 @@ var PORTABLE_BINDING_FINAL_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
|
1099
1442
|
...NQL_SELECT_AGGREGATE_FUNCTIONS,
|
|
1100
1443
|
...NQL_SELECT_VALUE_FUNCTIONS
|
|
1101
1444
|
]);
|
|
1102
|
-
var
|
|
1445
|
+
var UnresolvedSelectAllOutputSchemaError = class extends NqlSemanticException {
|
|
1446
|
+
constructor(message) {
|
|
1447
|
+
super(NqlErrorCodes.SEM_INVALID_SYNTAX, message);
|
|
1448
|
+
this.name = "UnresolvedSelectAllOutputSchemaError";
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1103
1451
|
function compileNestedQuery(query, ctx, fns, bindings) {
|
|
1104
1452
|
const savedContext = {
|
|
1105
1453
|
currentFromTable: ctx.currentFromTable,
|
|
@@ -1182,19 +1530,24 @@ function selectContainsAggregate(select) {
|
|
|
1182
1530
|
);
|
|
1183
1531
|
}
|
|
1184
1532
|
function relationForeignKeysOnSource(relation) {
|
|
1185
|
-
if (
|
|
1186
|
-
|
|
1187
|
-
|
|
1533
|
+
if (relation?.type !== "belongsTo") return void 0;
|
|
1534
|
+
const fkColumns = toColumnList(relation.foreignKey);
|
|
1535
|
+
return fkColumns.length > 0 ? fkColumns : void 0;
|
|
1536
|
+
}
|
|
1537
|
+
function recursiveDirection(recursive) {
|
|
1538
|
+
if (recursive === null || typeof recursive !== "object") return void 0;
|
|
1539
|
+
const direction = recursive.direction;
|
|
1540
|
+
if (direction === "up" || direction === "ancestors") return "up";
|
|
1541
|
+
if (direction === "down" || direction === "descendants") return "down";
|
|
1188
1542
|
return void 0;
|
|
1189
1543
|
}
|
|
1190
|
-
function
|
|
1191
|
-
if (
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
return void 0;
|
|
1544
|
+
function recursiveMaxDepth(recursive) {
|
|
1545
|
+
if (recursive === null || typeof recursive !== "object") return void 0;
|
|
1546
|
+
const maxDepth = recursive.maxDepth;
|
|
1547
|
+
return typeof maxDepth === "number" && Number.isSafeInteger(maxDepth) && maxDepth > 0 ? maxDepth : void 0;
|
|
1195
1548
|
}
|
|
1196
|
-
function
|
|
1197
|
-
return
|
|
1549
|
+
function pseudoColumnMatchesSelfRef(pseudoColumn, sourceTable, selfRefColumn, targetKeyColumn) {
|
|
1550
|
+
return (pseudoColumn.table === void 0 || pseudoColumn.table === sourceTable) && (pseudoColumn.foreignKeyColumn === void 0 || columnsMatch(pseudoColumn.foreignKeyColumn, selfRefColumn)) && (pseudoColumn.targetColumn === void 0 || columnsMatch(pseudoColumn.targetColumn, targetKeyColumn));
|
|
1198
1551
|
}
|
|
1199
1552
|
function unsafeBindingRelationReason(intent, ctx, bindingDependencies) {
|
|
1200
1553
|
if (!ctx.validator) return "model metadata is not available";
|
|
@@ -1229,13 +1582,71 @@ function findDirectSourceProjection(sourceTable, sourceColumn, directProjectionL
|
|
|
1229
1582
|
(projection) => projection.sourceTable === sourceTable && columnsMatch(projection.sourceColumn, sourceColumn)
|
|
1230
1583
|
);
|
|
1231
1584
|
}
|
|
1585
|
+
function findDirectSourceProjections(sourceTable, sourceColumns, directProjectionLineage) {
|
|
1586
|
+
const projections = [];
|
|
1587
|
+
for (const sourceColumn of sourceColumns) {
|
|
1588
|
+
const projection = findDirectSourceProjection(
|
|
1589
|
+
sourceTable,
|
|
1590
|
+
sourceColumn,
|
|
1591
|
+
directProjectionLineage
|
|
1592
|
+
);
|
|
1593
|
+
if (!projection) return void 0;
|
|
1594
|
+
projections.push(projection);
|
|
1595
|
+
}
|
|
1596
|
+
return projections;
|
|
1597
|
+
}
|
|
1232
1598
|
function virtualRelationForBinding(relation, sourceTable, directProjectionLineage) {
|
|
1233
1599
|
const fkColumns = relationForeignKeysOnSource(relation);
|
|
1234
|
-
if (!relation || !fkColumns
|
|
1235
|
-
const
|
|
1600
|
+
if (!relation || !fkColumns) return void 0;
|
|
1601
|
+
const sourceProjections = findDirectSourceProjections(
|
|
1602
|
+
sourceTable,
|
|
1603
|
+
fkColumns,
|
|
1604
|
+
directProjectionLineage
|
|
1605
|
+
);
|
|
1606
|
+
if (!sourceProjections) return void 0;
|
|
1607
|
+
const targetKey = toColumnList(relation.targetKey);
|
|
1608
|
+
const targetColumns = targetKey.length > 0 ? targetKey : [DEFAULT_RELATION_TARGET_COLUMN];
|
|
1609
|
+
if (targetColumns.length !== fkColumns.length) return void 0;
|
|
1610
|
+
return {
|
|
1611
|
+
relation: relation.name,
|
|
1612
|
+
sourceTable,
|
|
1613
|
+
targetTable: relation.target,
|
|
1614
|
+
sourceColumn: sourceProjections.map(
|
|
1615
|
+
(projection) => projection.outputColumn
|
|
1616
|
+
),
|
|
1617
|
+
targetColumn: targetColumns,
|
|
1618
|
+
hops: [],
|
|
1619
|
+
cardinality: "one"
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
function singleResolvedColumn(columns) {
|
|
1623
|
+
return columns.length === 1 ? columns[0] : void 0;
|
|
1624
|
+
}
|
|
1625
|
+
function manyToManyVirtualRelationForBinding(relation, sourceTable, directProjectionLineage) {
|
|
1626
|
+
if (relation?.type !== "belongsToMany") return void 0;
|
|
1627
|
+
if (typeof relation.through !== "string" || relation.through.length === 0 || relation.recursive !== void 0 || relation.source !== void 0 && relation.source === relation.target) {
|
|
1628
|
+
return void 0;
|
|
1629
|
+
}
|
|
1630
|
+
const sourceKeys = toColumnList(relation.sourceKey);
|
|
1631
|
+
const sourceColumn = singleResolvedColumn(
|
|
1632
|
+
sourceKeys.length > 0 ? sourceKeys : [DEFAULT_RELATION_TARGET_COLUMN]
|
|
1633
|
+
);
|
|
1634
|
+
const targetKeys = toColumnList(relation.targetKey);
|
|
1635
|
+
const targetColumn = singleResolvedColumn(
|
|
1636
|
+
targetKeys.length > 0 ? targetKeys : [DEFAULT_RELATION_TARGET_COLUMN]
|
|
1637
|
+
);
|
|
1638
|
+
const throughSourceColumn = singleResolvedColumn(
|
|
1639
|
+
toColumnList(relation.foreignKey)
|
|
1640
|
+
);
|
|
1641
|
+
const throughTargetColumn = singleResolvedColumn(
|
|
1642
|
+
toColumnList(relation.otherKey)
|
|
1643
|
+
);
|
|
1644
|
+
if (!sourceColumn || !targetColumn || !throughSourceColumn || !throughTargetColumn) {
|
|
1645
|
+
return void 0;
|
|
1646
|
+
}
|
|
1236
1647
|
const sourceProjection = findDirectSourceProjection(
|
|
1237
1648
|
sourceTable,
|
|
1238
|
-
|
|
1649
|
+
sourceColumn,
|
|
1239
1650
|
directProjectionLineage
|
|
1240
1651
|
);
|
|
1241
1652
|
if (!sourceProjection) return void 0;
|
|
@@ -1243,34 +1654,109 @@ function virtualRelationForBinding(relation, sourceTable, directProjectionLineag
|
|
|
1243
1654
|
relation: relation.name,
|
|
1244
1655
|
sourceTable,
|
|
1245
1656
|
targetTable: relation.target,
|
|
1246
|
-
sourceColumn: sourceProjection.outputColumn,
|
|
1247
|
-
targetColumn:
|
|
1248
|
-
|
|
1657
|
+
sourceColumn: [sourceProjection.outputColumn],
|
|
1658
|
+
targetColumn: [targetColumn],
|
|
1659
|
+
hops: [],
|
|
1660
|
+
through: relation.through,
|
|
1661
|
+
throughSourceColumn,
|
|
1662
|
+
throughTargetColumn,
|
|
1663
|
+
cardinality: "many",
|
|
1664
|
+
relationType: "manyToMany"
|
|
1249
1665
|
};
|
|
1250
1666
|
}
|
|
1251
|
-
function
|
|
1252
|
-
|
|
1253
|
-
if (relation
|
|
1667
|
+
function recursiveVirtualRelationForBinding(relation, sourceTable, directProjectionLineage, pseudoColumns) {
|
|
1668
|
+
const direction = recursiveDirection(relation?.recursive);
|
|
1669
|
+
if (!relation || !direction) return void 0;
|
|
1670
|
+
if (relation.source !== relation.target || relation.source !== sourceTable) {
|
|
1254
1671
|
return void 0;
|
|
1255
1672
|
}
|
|
1256
|
-
const fkColumns =
|
|
1257
|
-
|
|
1258
|
-
const
|
|
1259
|
-
|
|
1260
|
-
|
|
1673
|
+
const fkColumns = toColumnList(relation.foreignKey);
|
|
1674
|
+
const targetKeys = toColumnList(relation.targetKey);
|
|
1675
|
+
const pseudoColumn = pseudoColumns.find((candidate) => {
|
|
1676
|
+
const candidateFkColumns = fkColumns.length > 0 ? fkColumns : toColumnList(candidate.foreignKeyColumn);
|
|
1677
|
+
const candidateTargetColumns = targetKeys.length > 0 ? targetKeys : candidate.targetColumn !== void 0 ? [candidate.targetColumn] : [DEFAULT_RELATION_TARGET_COLUMN];
|
|
1678
|
+
const candidateSelfRefColumn = singleResolvedColumn(candidateFkColumns);
|
|
1679
|
+
const candidateTargetKeyColumn = singleResolvedColumn(
|
|
1680
|
+
candidateTargetColumns
|
|
1681
|
+
);
|
|
1682
|
+
return candidateSelfRefColumn !== void 0 && candidateTargetKeyColumn !== void 0 && pseudoColumnMatchesSelfRef(
|
|
1683
|
+
candidate,
|
|
1684
|
+
sourceTable,
|
|
1685
|
+
candidateSelfRefColumn,
|
|
1686
|
+
candidateTargetKeyColumn
|
|
1687
|
+
);
|
|
1688
|
+
});
|
|
1689
|
+
if (!pseudoColumn) return void 0;
|
|
1690
|
+
const selfRefColumn = singleResolvedColumn(
|
|
1691
|
+
fkColumns.length > 0 ? fkColumns : toColumnList(pseudoColumn.foreignKeyColumn)
|
|
1692
|
+
);
|
|
1693
|
+
const targetKeyColumn = singleResolvedColumn(
|
|
1694
|
+
targetKeys.length > 0 ? targetKeys : pseudoColumn.targetColumn !== void 0 ? [pseudoColumn.targetColumn] : [DEFAULT_RELATION_TARGET_COLUMN]
|
|
1695
|
+
);
|
|
1696
|
+
if (!selfRefColumn || !targetKeyColumn) return void 0;
|
|
1697
|
+
const relationName = direction === "up" ? pseudoColumn.ascendantKeyword : pseudoColumn.descendantKeyword;
|
|
1698
|
+
if (!relationName) return void 0;
|
|
1699
|
+
const seedColumn = direction === "up" ? selfRefColumn : targetKeyColumn;
|
|
1261
1700
|
const sourceProjection = findDirectSourceProjection(
|
|
1262
1701
|
sourceTable,
|
|
1263
|
-
|
|
1702
|
+
seedColumn,
|
|
1264
1703
|
directProjectionLineage
|
|
1265
1704
|
);
|
|
1266
1705
|
if (!sourceProjection) return void 0;
|
|
1706
|
+
return {
|
|
1707
|
+
relation: relationName.toLowerCase(),
|
|
1708
|
+
sourceTable,
|
|
1709
|
+
targetTable: relation.target,
|
|
1710
|
+
sourceColumn: [sourceProjection.outputColumn],
|
|
1711
|
+
targetColumn: [direction === "up" ? targetKeyColumn : selfRefColumn],
|
|
1712
|
+
hops: [],
|
|
1713
|
+
cardinality: "many",
|
|
1714
|
+
relationType: relation.type,
|
|
1715
|
+
recursive: {
|
|
1716
|
+
direction,
|
|
1717
|
+
maxDepth: recursiveMaxDepth(relation.recursive) ?? 10,
|
|
1718
|
+
selfRefColumn,
|
|
1719
|
+
targetKeyColumn
|
|
1720
|
+
}
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
function scalarVirtualRelationForBinding(relation, sourceTable, directProjectionLineage) {
|
|
1724
|
+
if (!relation) return void 0;
|
|
1725
|
+
if (relation.type === "belongsToMany") {
|
|
1726
|
+
return manyToManyVirtualRelationForBinding(
|
|
1727
|
+
relation,
|
|
1728
|
+
sourceTable,
|
|
1729
|
+
directProjectionLineage
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
if (relation.recursive !== void 0 || relation.source !== void 0 && relation.source === relation.target) {
|
|
1733
|
+
return void 0;
|
|
1734
|
+
}
|
|
1735
|
+
if (relation.type !== "belongsTo" && relation.type !== "hasOne" && relation.type !== "hasMany") {
|
|
1736
|
+
return void 0;
|
|
1737
|
+
}
|
|
1738
|
+
const joinColumns = scalarRelationJoinColumns(relation);
|
|
1739
|
+
if (!joinColumns) return void 0;
|
|
1740
|
+
if (joinColumns.sourceJoinColumn.length !== joinColumns.targetJoinColumn.length) {
|
|
1741
|
+
return void 0;
|
|
1742
|
+
}
|
|
1743
|
+
const sourceProjections = findDirectSourceProjections(
|
|
1744
|
+
sourceTable,
|
|
1745
|
+
joinColumns.sourceJoinColumn,
|
|
1746
|
+
directProjectionLineage
|
|
1747
|
+
);
|
|
1748
|
+
if (!sourceProjections) return void 0;
|
|
1267
1749
|
return {
|
|
1268
1750
|
relation: relation.name,
|
|
1269
1751
|
sourceTable,
|
|
1270
1752
|
targetTable: relation.target,
|
|
1271
|
-
sourceColumn:
|
|
1272
|
-
|
|
1273
|
-
|
|
1753
|
+
sourceColumn: sourceProjections.map(
|
|
1754
|
+
(projection) => projection.outputColumn
|
|
1755
|
+
),
|
|
1756
|
+
targetColumn: joinColumns.targetJoinColumn,
|
|
1757
|
+
hops: [],
|
|
1758
|
+
cardinality: relationCardinality(relation),
|
|
1759
|
+
relationType: relation.type
|
|
1274
1760
|
};
|
|
1275
1761
|
}
|
|
1276
1762
|
function getBindingRelationFilterMetadata(intent, ctx, outputSchema, bindingDependencies) {
|
|
@@ -1294,6 +1780,13 @@ function getBindingRelationFilterMetadata(intent, ctx, outputSchema, bindingDepe
|
|
|
1294
1780
|
);
|
|
1295
1781
|
for (const relationName of relationNames ?? []) {
|
|
1296
1782
|
const relation = ctx.validator?.getRelation(sourceTable, relationName);
|
|
1783
|
+
const recursiveRelation = recursiveVirtualRelationForBinding(
|
|
1784
|
+
relation,
|
|
1785
|
+
sourceTable,
|
|
1786
|
+
outputSchema.directProjectionLineage ?? [],
|
|
1787
|
+
ctx.validator?.getPseudoColumns(sourceTable) ?? []
|
|
1788
|
+
);
|
|
1789
|
+
if (recursiveRelation) scalarRelations.push(recursiveRelation);
|
|
1297
1790
|
const virtualRelation = virtualRelationForBinding(
|
|
1298
1791
|
relation,
|
|
1299
1792
|
sourceTable,
|
|
@@ -1328,6 +1821,15 @@ function validateBindingFinalPath(expr, ctx, bindingName) {
|
|
|
1328
1821
|
}
|
|
1329
1822
|
const firstSegment = segments[0];
|
|
1330
1823
|
if (ctx.pseudoColumnKeywords.has(firstSegment.toLowerCase())) {
|
|
1824
|
+
if (segments.length === 2 && resolveBindingRecursiveRelationColumn(
|
|
1825
|
+
ctx,
|
|
1826
|
+
bindingName,
|
|
1827
|
+
firstSegment,
|
|
1828
|
+
segments[1],
|
|
1829
|
+
expr.depthHint
|
|
1830
|
+
)) {
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1331
1833
|
assertNoBindingRelationConstruct(
|
|
1332
1834
|
ctx,
|
|
1333
1835
|
bindingName,
|
|
@@ -1338,6 +1840,19 @@ function validateBindingFinalPath(expr, ctx, bindingName) {
|
|
|
1338
1840
|
}
|
|
1339
1841
|
assertNoBindingRelationPath(ctx, bindingName, segments.join("."));
|
|
1340
1842
|
}
|
|
1843
|
+
function resolveBindingRelationInclude(ctx, bindingName, relationPath) {
|
|
1844
|
+
if (!isBindingTable(ctx, bindingName)) return void 0;
|
|
1845
|
+
if (!bindingName || !ctx.validator) {
|
|
1846
|
+
throw new NqlSemanticException(
|
|
1847
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1848
|
+
`Query '${bindingName ?? "<unknown>"}' reads from an NQL binding and cannot use relation include '${relationPath.join(".")}' (ref-#192): model metadata is not available.`
|
|
1849
|
+
);
|
|
1850
|
+
}
|
|
1851
|
+
return ctx.validator.resolveVirtualBindingScalarRelationForInclude(
|
|
1852
|
+
bindingName,
|
|
1853
|
+
relationPath
|
|
1854
|
+
);
|
|
1855
|
+
}
|
|
1341
1856
|
function validateBindingFinalFunction(expr, ctx, bindingName, allowRelationFilters) {
|
|
1342
1857
|
const fn = expr.name.toLowerCase();
|
|
1343
1858
|
if (!PORTABLE_BINDING_FINAL_FUNCTIONS.has(fn)) {
|
|
@@ -1538,24 +2053,27 @@ function validateBindingFinalSelectClause(clause, ctx, bindingName) {
|
|
|
1538
2053
|
case "star":
|
|
1539
2054
|
break;
|
|
1540
2055
|
case "relationStar":
|
|
1541
|
-
|
|
1542
|
-
ctx,
|
|
1543
|
-
bindingName,
|
|
1544
|
-
"select relation columns",
|
|
1545
|
-
item.relation.join(".")
|
|
1546
|
-
);
|
|
2056
|
+
resolveBindingRelationInclude(ctx, bindingName, item.relation);
|
|
1547
2057
|
break;
|
|
1548
2058
|
case "expression":
|
|
1549
2059
|
if (item.expression.type === "path" && item.expression.segments.length > 1) {
|
|
1550
2060
|
const segments = item.expression.segments;
|
|
1551
2061
|
const firstSegment = segments[0];
|
|
1552
2062
|
if (ctx.pseudoColumnKeywords.has(firstSegment.toLowerCase())) {
|
|
1553
|
-
|
|
2063
|
+
if (segments.length !== 2 || !resolveBindingRecursiveRelationColumn(
|
|
1554
2064
|
ctx,
|
|
1555
2065
|
bindingName,
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
2066
|
+
firstSegment,
|
|
2067
|
+
segments[1],
|
|
2068
|
+
item.expression.depthHint
|
|
2069
|
+
)) {
|
|
2070
|
+
assertNoBindingRelationConstruct(
|
|
2071
|
+
ctx,
|
|
2072
|
+
bindingName,
|
|
2073
|
+
"use pseudo-column traversals",
|
|
2074
|
+
firstSegment
|
|
2075
|
+
);
|
|
2076
|
+
}
|
|
1559
2077
|
} else {
|
|
1560
2078
|
resolveBindingRelationColumn(
|
|
1561
2079
|
ctx,
|
|
@@ -1779,21 +2297,38 @@ function compileQueryInternal(query, ctx, fns, bindings) {
|
|
|
1779
2297
|
}
|
|
1780
2298
|
}
|
|
1781
2299
|
}
|
|
1782
|
-
if (select && select.type === "expressions"
|
|
2300
|
+
if (select && select.type === "expressions") {
|
|
1783
2301
|
const relationPaths = /* @__PURE__ */ new Set();
|
|
1784
2302
|
for (const expr of select.columns) {
|
|
1785
2303
|
if (expr.kind === "relationColumn") {
|
|
1786
|
-
|
|
2304
|
+
if (!isBindingSource || expr.column === "*") {
|
|
2305
|
+
relationPaths.add(expr.relation);
|
|
2306
|
+
}
|
|
1787
2307
|
}
|
|
1788
2308
|
}
|
|
1789
2309
|
if (relationPaths.size > 0) {
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
);
|
|
1795
|
-
|
|
1796
|
-
allIncludes.
|
|
2310
|
+
if (isBindingSource) {
|
|
2311
|
+
for (const relation of relationPaths) {
|
|
2312
|
+
resolveBindingRelationInclude(ctx, query.table, relation.split("."));
|
|
2313
|
+
}
|
|
2314
|
+
const nestedIncludes = buildNestedIncludes(relationPaths, flatMode);
|
|
2315
|
+
for (const inc of nestedIncludes) {
|
|
2316
|
+
const exists = allIncludes.some(
|
|
2317
|
+
(existing) => existing.relation === inc.relation
|
|
2318
|
+
);
|
|
2319
|
+
if (!exists) {
|
|
2320
|
+
allIncludes.push(inc);
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
} else {
|
|
2324
|
+
const nestedIncludes = buildNestedIncludes(relationPaths, flatMode);
|
|
2325
|
+
for (const inc of nestedIncludes) {
|
|
2326
|
+
const exists = allIncludes.some(
|
|
2327
|
+
(existing) => existing.relation === inc.relation
|
|
2328
|
+
);
|
|
2329
|
+
if (!exists) {
|
|
2330
|
+
allIncludes.push(inc);
|
|
2331
|
+
}
|
|
1797
2332
|
}
|
|
1798
2333
|
}
|
|
1799
2334
|
}
|
|
@@ -1931,8 +2466,7 @@ function resolveSourceOutputColumns(intent, ctx, bindingName) {
|
|
|
1931
2466
|
if (bindingColumns2 !== void 0) return bindingColumns2;
|
|
1932
2467
|
const columns = ctx.validator?.getTableColumns(intent.from);
|
|
1933
2468
|
if (columns !== void 0) return columns;
|
|
1934
|
-
throw new
|
|
1935
|
-
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
2469
|
+
throw new UnresolvedSelectAllOutputSchemaError(
|
|
1936
2470
|
`Cannot compute output schema for NQL binding '${bindingName}' from SELECT * on '${intent.from}' without a concrete table schema.`
|
|
1937
2471
|
);
|
|
1938
2472
|
}
|
|
@@ -2858,6 +3392,7 @@ function compileRelationFilter(expr, ctx, fns, aliasContext, outerAliases) {
|
|
|
2858
3392
|
targetTable: bindingRelation.targetTable,
|
|
2859
3393
|
sourceColumn: bindingRelation.sourceColumn,
|
|
2860
3394
|
targetColumn: bindingRelation.targetColumn,
|
|
3395
|
+
hops: bindingRelation.hops,
|
|
2861
3396
|
...bindingRelation.cardinality !== void 0 && {
|
|
2862
3397
|
cardinality: bindingRelation.cardinality
|
|
2863
3398
|
}
|
|
@@ -2967,6 +3502,20 @@ var SELECT_SCALAR_FUNCTION_NAMES = /* @__PURE__ */ new Set([
|
|
|
2967
3502
|
...NQL_SELECT_JSON_FUNCTIONS,
|
|
2968
3503
|
...NQL_SELECT_SCALAR_FUNCTIONS
|
|
2969
3504
|
]);
|
|
3505
|
+
function resolveBindingRelationInclude2(ctx, bindingName, relationPath) {
|
|
3506
|
+
if (!isBindingTable(ctx, bindingName)) return false;
|
|
3507
|
+
if (!bindingName || !ctx.validator) {
|
|
3508
|
+
throw new NqlSemanticException(
|
|
3509
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
3510
|
+
`Query '${bindingName ?? "<unknown>"}' reads from an NQL binding and cannot use relation include '${relationPath.join(".")}' (ref-#192): model metadata is not available.`
|
|
3511
|
+
);
|
|
3512
|
+
}
|
|
3513
|
+
ctx.validator.resolveVirtualBindingScalarRelationForInclude(
|
|
3514
|
+
bindingName,
|
|
3515
|
+
relationPath
|
|
3516
|
+
);
|
|
3517
|
+
return true;
|
|
3518
|
+
}
|
|
2970
3519
|
function validateSelectPathExpression(expr, ctx) {
|
|
2971
3520
|
if (!ctx.currentFromTable) return;
|
|
2972
3521
|
const { segments } = expr;
|
|
@@ -3022,12 +3571,14 @@ function compileSelectClause(clause, ctx, fns) {
|
|
|
3022
3571
|
} else if (item.type === "relationStar") {
|
|
3023
3572
|
hasExpressions = true;
|
|
3024
3573
|
const relation = item.relation.join(".");
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3574
|
+
if (!resolveBindingRelationInclude2(ctx, ctx.currentFromTable, item.relation)) {
|
|
3575
|
+
assertNoBindingRelationConstruct(
|
|
3576
|
+
ctx,
|
|
3577
|
+
ctx.currentFromTable,
|
|
3578
|
+
"select relation columns",
|
|
3579
|
+
relation
|
|
3580
|
+
);
|
|
3581
|
+
}
|
|
3031
3582
|
expressions.push({
|
|
3032
3583
|
kind: "relationColumn",
|
|
3033
3584
|
relation,
|
|
@@ -3298,12 +3849,6 @@ function compileMultiSegmentPath(expr, item, ctx) {
|
|
|
3298
3849
|
const segments = expr.segments;
|
|
3299
3850
|
const firstSegmentLower = segments[0].toLowerCase();
|
|
3300
3851
|
if (ctx.pseudoColumnKeywords.has(firstSegmentLower)) {
|
|
3301
|
-
assertNoBindingRelationConstruct(
|
|
3302
|
-
ctx,
|
|
3303
|
-
ctx.currentFromTable,
|
|
3304
|
-
"use pseudo-column traversals",
|
|
3305
|
-
segments[0]
|
|
3306
|
-
);
|
|
3307
3852
|
const firstSegment = firstSegmentLower;
|
|
3308
3853
|
const depthHint = expr.depthHint;
|
|
3309
3854
|
if (depthHint !== void 0) {
|
|
@@ -3330,6 +3875,44 @@ function compileMultiSegmentPath(expr, item, ctx) {
|
|
|
3330
3875
|
);
|
|
3331
3876
|
}
|
|
3332
3877
|
const targetColumn = segments[i];
|
|
3878
|
+
const bindingRelation2 = traversals.length === 1 ? resolveBindingRecursiveRelationColumn(
|
|
3879
|
+
ctx,
|
|
3880
|
+
ctx.currentFromTable,
|
|
3881
|
+
firstSegment,
|
|
3882
|
+
targetColumn,
|
|
3883
|
+
depthHint
|
|
3884
|
+
) : void 0;
|
|
3885
|
+
if (bindingRelation2) {
|
|
3886
|
+
const relationColumnIntent2 = {
|
|
3887
|
+
kind: "relationColumn",
|
|
3888
|
+
relation: bindingRelation2.relation,
|
|
3889
|
+
column: targetColumn,
|
|
3890
|
+
as: item.alias ?? `${bindingRelation2.relation}.${targetColumn}`
|
|
3891
|
+
};
|
|
3892
|
+
return markNqlTrustedRelationFilter(relationColumnIntent2, {
|
|
3893
|
+
relation: bindingRelation2.relation,
|
|
3894
|
+
targetTable: bindingRelation2.targetTable,
|
|
3895
|
+
sourceColumn: bindingRelation2.sourceColumn,
|
|
3896
|
+
targetColumn: bindingRelation2.targetColumn,
|
|
3897
|
+
hops: bindingRelation2.hops,
|
|
3898
|
+
selectedColumn: targetColumn,
|
|
3899
|
+
...bindingRelation2.cardinality !== void 0 && {
|
|
3900
|
+
cardinality: bindingRelation2.cardinality
|
|
3901
|
+
},
|
|
3902
|
+
...bindingRelation2.relationType !== void 0 && {
|
|
3903
|
+
relationType: bindingRelation2.relationType
|
|
3904
|
+
},
|
|
3905
|
+
...bindingRelation2.recursive !== void 0 && {
|
|
3906
|
+
recursive: bindingRelation2.recursive
|
|
3907
|
+
}
|
|
3908
|
+
});
|
|
3909
|
+
}
|
|
3910
|
+
assertNoBindingRelationConstruct(
|
|
3911
|
+
ctx,
|
|
3912
|
+
ctx.currentFromTable,
|
|
3913
|
+
"use pseudo-column traversals",
|
|
3914
|
+
segments[0]
|
|
3915
|
+
);
|
|
3333
3916
|
if (ctx.currentFromTable) {
|
|
3334
3917
|
validateColumnForTable(ctx, ctx.currentFromTable, targetColumn);
|
|
3335
3918
|
}
|
|
@@ -3368,7 +3951,8 @@ function compileMultiSegmentPath(expr, item, ctx) {
|
|
|
3368
3951
|
);
|
|
3369
3952
|
}
|
|
3370
3953
|
if (ctx.currentFromTable && ctx.validator) {
|
|
3371
|
-
const
|
|
3954
|
+
const lastBindingHop = bindingRelation?.hops[bindingRelation.hops.length - 1];
|
|
3955
|
+
const targetTable = lastBindingHop?.target ?? bindingRelation?.targetTable ?? ctx.validator.resolveRelationTarget(ctx.currentFromTable, segments[0]);
|
|
3372
3956
|
if (targetTable) {
|
|
3373
3957
|
ctx.validator.validateColumn(targetTable, column);
|
|
3374
3958
|
}
|
|
@@ -3384,9 +3968,25 @@ function compileMultiSegmentPath(expr, item, ctx) {
|
|
|
3384
3968
|
targetTable: bindingRelation.targetTable,
|
|
3385
3969
|
sourceColumn: bindingRelation.sourceColumn,
|
|
3386
3970
|
targetColumn: bindingRelation.targetColumn,
|
|
3971
|
+
hops: bindingRelation.hops,
|
|
3972
|
+
...bindingRelation.through !== void 0 && {
|
|
3973
|
+
through: bindingRelation.through
|
|
3974
|
+
},
|
|
3975
|
+
...bindingRelation.throughSourceColumn !== void 0 && {
|
|
3976
|
+
throughSourceColumn: bindingRelation.throughSourceColumn
|
|
3977
|
+
},
|
|
3978
|
+
...bindingRelation.throughTargetColumn !== void 0 && {
|
|
3979
|
+
throughTargetColumn: bindingRelation.throughTargetColumn
|
|
3980
|
+
},
|
|
3387
3981
|
selectedColumn: column,
|
|
3388
3982
|
...bindingRelation.cardinality !== void 0 && {
|
|
3389
3983
|
cardinality: bindingRelation.cardinality
|
|
3984
|
+
},
|
|
3985
|
+
...bindingRelation.relationType !== void 0 && {
|
|
3986
|
+
relationType: bindingRelation.relationType
|
|
3987
|
+
},
|
|
3988
|
+
...bindingRelation.recursive !== void 0 && {
|
|
3989
|
+
recursive: bindingRelation.recursive
|
|
3390
3990
|
}
|
|
3391
3991
|
}) : relationColumnIntent;
|
|
3392
3992
|
}
|
|
@@ -3535,16 +4135,17 @@ function allowsInternalParams(options) {
|
|
|
3535
4135
|
return internalOptions?.allowInternalParams === true;
|
|
3536
4136
|
}
|
|
3537
4137
|
function isUnresolvedSelectAllOutputSchemaError(error) {
|
|
3538
|
-
return error instanceof
|
|
4138
|
+
return error instanceof UnresolvedSelectAllOutputSchemaError;
|
|
3539
4139
|
}
|
|
3540
|
-
function programSequenceStepFromResult(result, bindName, final, bindingDependencies) {
|
|
4140
|
+
function programSequenceStepFromResult(result, bindName, final, bindingDependencies, snapshotReadBindings) {
|
|
3541
4141
|
if (result.query) {
|
|
3542
4142
|
return {
|
|
3543
4143
|
kind: "query",
|
|
3544
4144
|
query: result.query,
|
|
3545
4145
|
...bindName !== void 0 && { bindName },
|
|
3546
4146
|
final,
|
|
3547
|
-
bindingDependencies
|
|
4147
|
+
bindingDependencies,
|
|
4148
|
+
...bindName !== void 0 && snapshotReadBindings.has(bindName) && { snapshot: true }
|
|
3548
4149
|
};
|
|
3549
4150
|
}
|
|
3550
4151
|
if (result.mutation) {
|
|
@@ -3564,6 +4165,79 @@ function stringArraysEqual(left, right) {
|
|
|
3564
4165
|
function isNqlAstRecord(value) {
|
|
3565
4166
|
return typeof value === "object" && value !== null;
|
|
3566
4167
|
}
|
|
4168
|
+
function isExactPhysicalProjectionColumn(query, ctx, sourceColumn, outputColumn) {
|
|
4169
|
+
const resolvedColumn = ctx.validator?.resolvePhysicalColumnName(
|
|
4170
|
+
query.from,
|
|
4171
|
+
sourceColumn
|
|
4172
|
+
);
|
|
4173
|
+
return resolvedColumn !== void 0 && sourceColumn === resolvedColumn && outputColumn === resolvedColumn;
|
|
4174
|
+
}
|
|
4175
|
+
function hasDirectPhysicalColumnProjection(query, ctx) {
|
|
4176
|
+
const physicalColumns = ctx.validator?.getPhysicalTableColumns(query.from);
|
|
4177
|
+
if (physicalColumns === void 0) return false;
|
|
4178
|
+
const select = query.select;
|
|
4179
|
+
if (!select || select.type === "all") return physicalColumns.length > 0;
|
|
4180
|
+
let projectedColumnCount = 0;
|
|
4181
|
+
const addStarProjection = () => {
|
|
4182
|
+
projectedColumnCount += physicalColumns.length;
|
|
4183
|
+
return physicalColumns.length > 0;
|
|
4184
|
+
};
|
|
4185
|
+
const addDirectProjection = (sourceColumn, outputColumn) => {
|
|
4186
|
+
projectedColumnCount++;
|
|
4187
|
+
return isExactPhysicalProjectionColumn(
|
|
4188
|
+
query,
|
|
4189
|
+
ctx,
|
|
4190
|
+
sourceColumn,
|
|
4191
|
+
outputColumn
|
|
4192
|
+
);
|
|
4193
|
+
};
|
|
4194
|
+
if (select.type === "fields") {
|
|
4195
|
+
for (const field of select.fields) {
|
|
4196
|
+
if (field === "*") {
|
|
4197
|
+
if (!addStarProjection()) return false;
|
|
4198
|
+
} else if (!addDirectProjection(field, field)) {
|
|
4199
|
+
return false;
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
return projectedColumnCount > 0;
|
|
4203
|
+
}
|
|
4204
|
+
if (select.type === "aggregate") return false;
|
|
4205
|
+
for (const expr of select.columns) {
|
|
4206
|
+
if (expr.kind === "column" && expr.column === "*") {
|
|
4207
|
+
if (!addStarProjection()) return false;
|
|
4208
|
+
continue;
|
|
4209
|
+
}
|
|
4210
|
+
if (expr.kind === "column") {
|
|
4211
|
+
const outputColumn = expr.as ?? expr.column;
|
|
4212
|
+
if (!addDirectProjection(expr.column, outputColumn)) return false;
|
|
4213
|
+
continue;
|
|
4214
|
+
}
|
|
4215
|
+
if (expr.kind === "columnAlias") {
|
|
4216
|
+
if (!addDirectProjection(expr.column, expr.alias)) return false;
|
|
4217
|
+
continue;
|
|
4218
|
+
}
|
|
4219
|
+
return false;
|
|
4220
|
+
}
|
|
4221
|
+
return projectedColumnCount > 0;
|
|
4222
|
+
}
|
|
4223
|
+
function classifyReadBindingSnapshotShape(query, ctx, sourceWasBinding) {
|
|
4224
|
+
if (sourceWasBinding) {
|
|
4225
|
+
return { supported: false, reason: "a binding source" };
|
|
4226
|
+
}
|
|
4227
|
+
if (ctx.validator?.getPhysicalTableColumns(query.from) === void 0) {
|
|
4228
|
+
return {
|
|
4229
|
+
supported: false,
|
|
4230
|
+
reason: `no physical model table source '${query.from}'`
|
|
4231
|
+
};
|
|
4232
|
+
}
|
|
4233
|
+
if (!hasDirectPhysicalColumnProjection(query, ctx)) {
|
|
4234
|
+
return {
|
|
4235
|
+
supported: false,
|
|
4236
|
+
reason: "aliased/computed/aggregate columns"
|
|
4237
|
+
};
|
|
4238
|
+
}
|
|
4239
|
+
return { supported: true };
|
|
4240
|
+
}
|
|
3567
4241
|
function addReadBindingReference(references, readBindingNames, name) {
|
|
3568
4242
|
if (typeof name === "string" && readBindingNames.has(name)) {
|
|
3569
4243
|
references.add(name);
|
|
@@ -3727,23 +4401,31 @@ function collectTransitiveReadBindingDependencies(directReferences, bindingDepen
|
|
|
3727
4401
|
}
|
|
3728
4402
|
return ordered;
|
|
3729
4403
|
}
|
|
3730
|
-
function rejectReadBindingReferenceAcrossMutation(bindName,
|
|
4404
|
+
function rejectReadBindingReferenceAcrossMutation(bindName, reason) {
|
|
3731
4405
|
throw new NqlSemanticException(
|
|
3732
4406
|
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
3733
|
-
`read binding referenced across a mutation (#186):
|
|
4407
|
+
`read binding referenced across a mutation: unsupported snapshot shape (#186): snapshotting currently supports only a direct single-table column projection; binding '${bindName}' has ${reason}, so it cannot be materialized - move the reference before the mutation, or bind the mutation RETURNING result.`
|
|
3734
4408
|
);
|
|
3735
4409
|
}
|
|
3736
|
-
function
|
|
4410
|
+
function markReadBindingReferenceAcrossMutation(bindName, snapshotReadBindings, readBindingSnapshotShapes) {
|
|
4411
|
+
const snapshotShape = readBindingSnapshotShapes.get(bindName);
|
|
4412
|
+
if (snapshotShape?.supported !== true) {
|
|
4413
|
+
rejectReadBindingReferenceAcrossMutation(
|
|
4414
|
+
bindName,
|
|
4415
|
+
snapshotShape?.reason ?? "an unavailable output shape"
|
|
4416
|
+
);
|
|
4417
|
+
}
|
|
4418
|
+
snapshotReadBindings.add(bindName);
|
|
4419
|
+
}
|
|
4420
|
+
function markReadBindingDependenciesRequiringSnapshot(statementBindingDependencies, readBindingDefinitions, lastMutationStatement, snapshotReadBindings, readBindingSnapshotShapes) {
|
|
3737
4421
|
for (const referencedBindName of statementBindingDependencies) {
|
|
3738
4422
|
const definitionIndex = readBindingDefinitions.get(referencedBindName);
|
|
3739
4423
|
if (definitionIndex === void 0) continue;
|
|
3740
4424
|
if (lastMutationStatement > definitionIndex) {
|
|
3741
|
-
|
|
4425
|
+
markReadBindingReferenceAcrossMutation(
|
|
3742
4426
|
referencedBindName,
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
referenceIndex,
|
|
3746
|
-
statementCount
|
|
4427
|
+
snapshotReadBindings,
|
|
4428
|
+
readBindingSnapshotShapes
|
|
3747
4429
|
);
|
|
3748
4430
|
}
|
|
3749
4431
|
}
|
|
@@ -3823,6 +4505,8 @@ var NqlCompiler = class {
|
|
|
3823
4505
|
const seenBindNames = /* @__PURE__ */ new Map();
|
|
3824
4506
|
const nqlProgramSequence = [];
|
|
3825
4507
|
const readBindingDefinitions = /* @__PURE__ */ new Map();
|
|
4508
|
+
const readBindingSnapshotShapes = /* @__PURE__ */ new Map();
|
|
4509
|
+
const snapshotReadBindings = /* @__PURE__ */ new Set();
|
|
3826
4510
|
const definedBindingNames = /* @__PURE__ */ new Set();
|
|
3827
4511
|
const bindingDependencies = /* @__PURE__ */ new Map();
|
|
3828
4512
|
let lastMutationStatement = -1;
|
|
@@ -3848,12 +4532,12 @@ var NqlCompiler = class {
|
|
|
3848
4532
|
readBindingReferences,
|
|
3849
4533
|
bindingDependencies
|
|
3850
4534
|
);
|
|
3851
|
-
|
|
4535
|
+
markReadBindingDependenciesRequiringSnapshot(
|
|
3852
4536
|
statementBindingDependencies,
|
|
3853
4537
|
readBindingDefinitions,
|
|
3854
4538
|
lastMutationStatement,
|
|
3855
|
-
|
|
3856
|
-
|
|
4539
|
+
snapshotReadBindings,
|
|
4540
|
+
readBindingSnapshotShapes
|
|
3857
4541
|
);
|
|
3858
4542
|
lastResult = this.compileSingleStatement(stmt, bindings);
|
|
3859
4543
|
collectCompileResultReadBindingReferences(
|
|
@@ -3865,24 +4549,35 @@ var NqlCompiler = class {
|
|
|
3865
4549
|
readBindingReferences,
|
|
3866
4550
|
bindingDependencies
|
|
3867
4551
|
);
|
|
3868
|
-
|
|
4552
|
+
markReadBindingDependenciesRequiringSnapshot(
|
|
3869
4553
|
statementBindingDependencies,
|
|
3870
4554
|
readBindingDefinitions,
|
|
3871
4555
|
lastMutationStatement,
|
|
3872
|
-
|
|
3873
|
-
|
|
4556
|
+
snapshotReadBindings,
|
|
4557
|
+
readBindingSnapshotShapes
|
|
3874
4558
|
);
|
|
3875
4559
|
if (stmt.type === "mutationPipeline") {
|
|
3876
4560
|
lastMutationStatement = i;
|
|
3877
4561
|
}
|
|
3878
4562
|
if (bindName) {
|
|
3879
4563
|
if (lastResult.query) {
|
|
4564
|
+
const sourceWasBinding = definedBindingNames.has(
|
|
4565
|
+
lastResult.query.from
|
|
4566
|
+
);
|
|
3880
4567
|
const outputSchema = this.registerQueryBindingOutputSchema(
|
|
3881
4568
|
bindName,
|
|
3882
4569
|
lastResult.query,
|
|
3883
4570
|
bindingOutputSchemas,
|
|
3884
4571
|
statementBindingDependencies
|
|
3885
4572
|
);
|
|
4573
|
+
readBindingSnapshotShapes.set(
|
|
4574
|
+
bindName,
|
|
4575
|
+
classifyReadBindingSnapshotShape(
|
|
4576
|
+
lastResult.query,
|
|
4577
|
+
this.ctx,
|
|
4578
|
+
sourceWasBinding
|
|
4579
|
+
)
|
|
4580
|
+
);
|
|
3886
4581
|
bindings.set(bindName, lastResult.query);
|
|
3887
4582
|
if (outputSchema) {
|
|
3888
4583
|
this.ctx.validator?.addVirtualBindingTable(
|
|
@@ -3930,7 +4625,8 @@ var NqlCompiler = class {
|
|
|
3930
4625
|
lastResult,
|
|
3931
4626
|
bindName,
|
|
3932
4627
|
i === program.statements.length - 1,
|
|
3933
|
-
statementBindingDependencies
|
|
4628
|
+
statementBindingDependencies,
|
|
4629
|
+
snapshotReadBindings
|
|
3934
4630
|
);
|
|
3935
4631
|
if (sequenceStep !== void 0) {
|
|
3936
4632
|
nqlProgramSequence.push(sequenceStep);
|
|
@@ -3946,14 +4642,19 @@ var NqlCompiler = class {
|
|
|
3946
4642
|
}
|
|
3947
4643
|
const hasMutationBindings = mutationBindings.size > 0;
|
|
3948
4644
|
const hasBindingOutputSchemas = bindingOutputSchemas.size > 0;
|
|
3949
|
-
const
|
|
4645
|
+
const taggedNqlProgramSequence = nqlProgramSequence.map(
|
|
4646
|
+
(step) => step.kind === "query" && step.bindName !== void 0 && snapshotReadBindings.has(step.bindName) ? { ...step, snapshot: true } : step
|
|
4647
|
+
);
|
|
4648
|
+
const hasNqlProgramSequence = taggedNqlProgramSequence.length === program.statements.length;
|
|
3950
4649
|
if (bindings.size > 0) {
|
|
3951
4650
|
return {
|
|
3952
4651
|
...lastResult,
|
|
3953
4652
|
bindings,
|
|
3954
4653
|
...hasBindingOutputSchemas && { bindingOutputSchemas },
|
|
3955
4654
|
...hasMutationBindings && { mutationBindings },
|
|
3956
|
-
...hasNqlProgramSequence && {
|
|
4655
|
+
...hasNqlProgramSequence && {
|
|
4656
|
+
nqlProgramSequence: taggedNqlProgramSequence
|
|
4657
|
+
}
|
|
3957
4658
|
};
|
|
3958
4659
|
}
|
|
3959
4660
|
return lastResult;
|