@dbsp/nql 1.3.0 → 1.4.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 +4 -0
- package/dist/index.js +1153 -34
- 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, isNqlBindingRef, getNqlBindingRefName
|
|
2
|
-
import { NQL_SELECT_AGGREGATE_FUNCTIONS, NQL_SELECT_JSON_FUNCTIONS, NQL_SELECT_SCALAR_FUNCTIONS,
|
|
1
|
+
import { NQL_INTERNAL_COMPILER_OPTIONS, 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';
|
|
3
3
|
import { createToken, Lexer, CstParser } from 'chevrotain';
|
|
4
4
|
|
|
5
5
|
// src/compiler/index.ts
|
|
@@ -51,6 +51,7 @@ var ColumnValidator = class _ColumnValidator {
|
|
|
51
51
|
}
|
|
52
52
|
schema;
|
|
53
53
|
knownCteTables = /* @__PURE__ */ new Set();
|
|
54
|
+
virtualBindingTables = /* @__PURE__ */ new Map();
|
|
54
55
|
/**
|
|
55
56
|
* Register CTE names so validateTable() skips validation for them.
|
|
56
57
|
* CTE names are not physical tables in the schema, but are valid FROM targets.
|
|
@@ -64,6 +65,28 @@ var ColumnValidator = class _ColumnValidator {
|
|
|
64
65
|
clearKnownCteTables() {
|
|
65
66
|
this.knownCteTables.clear();
|
|
66
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Register an NQL binding as a virtual table with a concrete output schema.
|
|
70
|
+
* Binding columns are logical output names, so matching is exact.
|
|
71
|
+
*/
|
|
72
|
+
addVirtualBindingTable(name, columns) {
|
|
73
|
+
this.virtualBindingTables.set(name, columns);
|
|
74
|
+
}
|
|
75
|
+
/** Clear all registered virtual binding tables (used between compilations). */
|
|
76
|
+
clearVirtualBindingTables() {
|
|
77
|
+
this.virtualBindingTables.clear();
|
|
78
|
+
}
|
|
79
|
+
isVirtualBindingTable(name) {
|
|
80
|
+
return name !== void 0 && this.virtualBindingTables.has(name);
|
|
81
|
+
}
|
|
82
|
+
getVirtualBindingColumns(name) {
|
|
83
|
+
return this.virtualBindingTables.get(name);
|
|
84
|
+
}
|
|
85
|
+
getTableColumns(name) {
|
|
86
|
+
const virtualColumns = this.virtualBindingTables.get(name);
|
|
87
|
+
if (virtualColumns) return virtualColumns;
|
|
88
|
+
return this.schema.getTable(name)?.columns.map((column) => column.name);
|
|
89
|
+
}
|
|
67
90
|
/**
|
|
68
91
|
* Convert camelCase to snake_case for column name matching.
|
|
69
92
|
* NQL queries may use either form (e.g., viewCount or view_count).
|
|
@@ -79,14 +102,40 @@ var ColumnValidator = class _ColumnValidator {
|
|
|
79
102
|
if (queryCol === schemaCol) return true;
|
|
80
103
|
return _ColumnValidator.toSnakeCase(queryCol) === _ColumnValidator.toSnakeCase(schemaCol);
|
|
81
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Resolve a user-authored column spelling to the model column name.
|
|
107
|
+
* Uses the same exact-or-snake/camel equivalence as validateColumn().
|
|
108
|
+
*/
|
|
109
|
+
resolveColumnName(table, column) {
|
|
110
|
+
if (column === "*") return column;
|
|
111
|
+
const virtualColumns = this.virtualBindingTables.get(table);
|
|
112
|
+
if (virtualColumns) {
|
|
113
|
+
return virtualColumns.find((c) => c === column);
|
|
114
|
+
}
|
|
115
|
+
const tableInfo = this.schema.getTable(table);
|
|
116
|
+
if (!tableInfo) return void 0;
|
|
117
|
+
return tableInfo.columns.find(
|
|
118
|
+
(c) => _ColumnValidator.columnsMatch(column, c.name)
|
|
119
|
+
)?.name;
|
|
120
|
+
}
|
|
82
121
|
validateColumn(table, column) {
|
|
83
122
|
if (column === "*") return;
|
|
123
|
+
const virtualColumns = this.virtualBindingTables.get(table);
|
|
124
|
+
if (virtualColumns) {
|
|
125
|
+
const exists = virtualColumns.some((c) => c === column);
|
|
126
|
+
if (!exists) {
|
|
127
|
+
const available = virtualColumns.join(", ") || "(none)";
|
|
128
|
+
throw new NqlSemanticException(
|
|
129
|
+
NqlErrorCodes.SEM_UNKNOWN_COLUMN,
|
|
130
|
+
`Column '${column}' is not projected by NQL binding '${table}'. Available columns: ${available}`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
84
135
|
const tableInfo = this.schema.getTable(table);
|
|
85
136
|
if (!tableInfo) return;
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
);
|
|
89
|
-
if (!exists) {
|
|
137
|
+
const resolvedColumn = this.resolveColumnName(table, column);
|
|
138
|
+
if (resolvedColumn === void 0) {
|
|
90
139
|
const available = tableInfo.columns.map((c) => c.name).join(", ");
|
|
91
140
|
throw new NqlSemanticException(
|
|
92
141
|
NqlErrorCodes.SEM_UNKNOWN_COLUMN,
|
|
@@ -95,6 +144,7 @@ var ColumnValidator = class _ColumnValidator {
|
|
|
95
144
|
}
|
|
96
145
|
}
|
|
97
146
|
validateTable(table) {
|
|
147
|
+
if (this.virtualBindingTables.has(table)) return;
|
|
98
148
|
if (this.knownCteTables.has(table)) return;
|
|
99
149
|
const tableInfo = this.schema.getTable(table);
|
|
100
150
|
if (!tableInfo) {
|
|
@@ -105,10 +155,30 @@ var ColumnValidator = class _ColumnValidator {
|
|
|
105
155
|
}
|
|
106
156
|
}
|
|
107
157
|
resolveRelationTarget(sourceTable, relationName) {
|
|
158
|
+
if (this.virtualBindingTables.has(sourceTable)) {
|
|
159
|
+
throw new NqlSemanticException(
|
|
160
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
161
|
+
`Query '${sourceTable}' reads from an NQL binding and cannot use relation filters (${relationName}). Relation constructs require a physical model table, not a CTE binding.`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
108
164
|
const relations = this.schema.getRelationsFrom(sourceTable);
|
|
109
165
|
const rel = relations.find((r) => r.name === relationName);
|
|
110
166
|
return rel?.target;
|
|
111
167
|
}
|
|
168
|
+
assertNoBindingRelationConstruct(bindingName, construct, detail) {
|
|
169
|
+
if (!this.isVirtualBindingTable(bindingName)) return;
|
|
170
|
+
throw new NqlSemanticException(
|
|
171
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
172
|
+
`Query '${bindingName}' reads from an NQL binding and cannot ${construct} (${detail}). Relation constructs require a physical model table, not a CTE binding.`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
assertNoBindingRelationPath(bindingName, path) {
|
|
176
|
+
if (!this.isVirtualBindingTable(bindingName)) return;
|
|
177
|
+
throw new NqlSemanticException(
|
|
178
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
179
|
+
`Query '${bindingName}' reads from an NQL binding and cannot reference relation path '${path}'. Relation paths require a physical model table, not a CTE binding.`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
112
182
|
};
|
|
113
183
|
var FORBIDDEN_PARAM_NAMES = /* @__PURE__ */ new Set([
|
|
114
184
|
"__proto__",
|
|
@@ -197,6 +267,50 @@ function expressionToField(expr, aliasContext) {
|
|
|
197
267
|
}
|
|
198
268
|
return null;
|
|
199
269
|
}
|
|
270
|
+
function isBindingTable(ctx, table) {
|
|
271
|
+
if (table === void 0) return false;
|
|
272
|
+
return ctx.bindingOutputColumns.has(table) || ctx.validator?.isVirtualBindingTable?.(table) === true;
|
|
273
|
+
}
|
|
274
|
+
function bindingColumns(ctx, table) {
|
|
275
|
+
if (table === void 0) return void 0;
|
|
276
|
+
if (ctx.bindingOutputColumns.has(table)) {
|
|
277
|
+
return ctx.bindingOutputColumns.get(table);
|
|
278
|
+
}
|
|
279
|
+
return ctx.validator?.getVirtualBindingColumns?.(table);
|
|
280
|
+
}
|
|
281
|
+
function getKnownBindingColumns(ctx, table) {
|
|
282
|
+
return bindingColumns(ctx, table);
|
|
283
|
+
}
|
|
284
|
+
function assertNoBindingRelationConstruct(ctx, bindingName, construct, detail) {
|
|
285
|
+
if (!isBindingTable(ctx, bindingName)) return;
|
|
286
|
+
throw new NqlSemanticException(
|
|
287
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
288
|
+
`Query '${bindingName}' reads from an NQL binding and cannot ${construct} (${detail}). Relation constructs require a physical model table, not a CTE binding.`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
function assertNoBindingRelationPath(ctx, bindingName, path) {
|
|
292
|
+
if (!isBindingTable(ctx, bindingName)) return;
|
|
293
|
+
throw new NqlSemanticException(
|
|
294
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
295
|
+
`Query '${bindingName}' reads from an NQL binding and cannot reference relation path '${path}'. Relation paths require a physical model table, not a CTE binding.`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
function validateColumnForTable(ctx, table, column) {
|
|
299
|
+
if (table === void 0 || column === "*") return;
|
|
300
|
+
if (isBindingTable(ctx, table)) {
|
|
301
|
+
const columns = bindingColumns(ctx, table);
|
|
302
|
+
if (columns === void 0) return;
|
|
303
|
+
if (!columns.some((c) => c === column)) {
|
|
304
|
+
const available = columns.join(", ") || "(none)";
|
|
305
|
+
throw new NqlSemanticException(
|
|
306
|
+
NqlErrorCodes.SEM_UNKNOWN_COLUMN,
|
|
307
|
+
`Column '${column}' is not projected by NQL binding '${table}'. Available columns: ${available}`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
ctx.validator?.validateColumn(table, column);
|
|
313
|
+
}
|
|
200
314
|
function expressionToValue(expr, ctx) {
|
|
201
315
|
switch (expr.type) {
|
|
202
316
|
case "namedParam":
|
|
@@ -216,6 +330,22 @@ function expressionToValue(expr, ctx) {
|
|
|
216
330
|
case "null":
|
|
217
331
|
return null;
|
|
218
332
|
case "path":
|
|
333
|
+
if (ctx) {
|
|
334
|
+
const field = expr.segments.join(".");
|
|
335
|
+
const isBindingReference = expr.segments.length === 1 && isBindingTable(ctx, field);
|
|
336
|
+
if (isBindingReference) {
|
|
337
|
+
return createNqlBindingRef(field);
|
|
338
|
+
}
|
|
339
|
+
const sourceTable = ctx.currentFromTable;
|
|
340
|
+
const isVirtualBindingSource = sourceTable !== void 0 && isBindingTable(ctx, sourceTable);
|
|
341
|
+
if (isVirtualBindingSource) {
|
|
342
|
+
if (field.includes(".")) {
|
|
343
|
+
assertNoBindingRelationPath(ctx, sourceTable, field);
|
|
344
|
+
} else {
|
|
345
|
+
validateColumnForTable(ctx, sourceTable, field);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
219
349
|
return createNqlBindingRef(expr.segments.join("."));
|
|
220
350
|
case "function": {
|
|
221
351
|
return {
|
|
@@ -382,6 +512,20 @@ function isAggregateFunction(name) {
|
|
|
382
512
|
);
|
|
383
513
|
}
|
|
384
514
|
function validateWhereField(ctx, field, aliasContext, originalExpr) {
|
|
515
|
+
if (!aliasContext && !field.includes(".") && ctx.currentHavingAliases?.has(field)) {
|
|
516
|
+
throw new NqlSemanticException(
|
|
517
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
518
|
+
`HAVING cannot reference SELECT alias '${field}'. PostgreSQL does not allow SELECT aliases in HAVING; repeat the aggregate expression or filter the result in an outer query.`
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
if (!aliasContext && ctx.currentFromTable && isBindingTable(ctx, ctx.currentFromTable)) {
|
|
522
|
+
if (field.includes(".")) {
|
|
523
|
+
assertNoBindingRelationPath(ctx, ctx.currentFromTable, field);
|
|
524
|
+
} else {
|
|
525
|
+
validateColumnForTable(ctx, ctx.currentFromTable, field);
|
|
526
|
+
}
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
385
529
|
if (!ctx.validator) return;
|
|
386
530
|
if (aliasContext) {
|
|
387
531
|
const isInnerScope = originalExpr?.type === "path" && originalExpr.segments.length > 1 && originalExpr.segments[0] === aliasContext;
|
|
@@ -400,8 +544,14 @@ function validateWhereField(ctx, field, aliasContext, originalExpr) {
|
|
|
400
544
|
ctx.validator.validateColumn(ctx.currentRelationTarget, field);
|
|
401
545
|
return;
|
|
402
546
|
}
|
|
547
|
+
if (ctx.currentFromTable && field.includes(".")) {
|
|
548
|
+
if (isBindingTable(ctx, ctx.currentFromTable)) {
|
|
549
|
+
assertNoBindingRelationPath(ctx, ctx.currentFromTable, field);
|
|
550
|
+
}
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
403
553
|
if (ctx.currentFromTable && !field.includes(".")) {
|
|
404
|
-
|
|
554
|
+
validateColumnForTable(ctx, ctx.currentFromTable, field);
|
|
405
555
|
}
|
|
406
556
|
}
|
|
407
557
|
function coerceToStringKey(expr, contextLabel, ctx) {
|
|
@@ -768,30 +918,317 @@ function applyIncludeLimit(includes, path, limit) {
|
|
|
768
918
|
}
|
|
769
919
|
|
|
770
920
|
// src/compiler/compile-query.ts
|
|
921
|
+
var activeBindingScopes = /* @__PURE__ */ new WeakMap();
|
|
922
|
+
var PORTABLE_BINDING_FINAL_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
923
|
+
...NQL_SELECT_AGGREGATE_FUNCTIONS,
|
|
924
|
+
...NQL_SELECT_VALUE_FUNCTIONS
|
|
925
|
+
]);
|
|
771
926
|
function compileNestedQuery(query, ctx, fns, bindings) {
|
|
772
927
|
const savedContext = {
|
|
773
928
|
currentFromTable: ctx.currentFromTable,
|
|
774
|
-
currentRelationTarget: ctx.currentRelationTarget
|
|
929
|
+
currentRelationTarget: ctx.currentRelationTarget,
|
|
930
|
+
currentHavingAliases: ctx.currentHavingAliases
|
|
775
931
|
};
|
|
776
932
|
try {
|
|
777
|
-
|
|
778
|
-
|
|
933
|
+
const nestedBindings = bindings ?? activeBindingScopes.get(ctx);
|
|
934
|
+
if (nestedBindings) {
|
|
935
|
+
return compileQuery(query, ctx, fns, nestedBindings);
|
|
779
936
|
}
|
|
780
937
|
return fns.compileQuery(query, ctx);
|
|
781
938
|
} finally {
|
|
782
939
|
ctx.currentFromTable = savedContext.currentFromTable;
|
|
783
940
|
ctx.currentRelationTarget = savedContext.currentRelationTarget;
|
|
941
|
+
ctx.currentHavingAliases = savedContext.currentHavingAliases;
|
|
784
942
|
}
|
|
785
943
|
}
|
|
786
944
|
function compileQuery(query, ctx, fns, bindings) {
|
|
945
|
+
const hadPreviousBindingScope = activeBindingScopes.has(ctx);
|
|
946
|
+
const previousBindingScope = activeBindingScopes.get(ctx);
|
|
947
|
+
if (bindings) {
|
|
948
|
+
activeBindingScopes.set(ctx, bindings);
|
|
949
|
+
}
|
|
950
|
+
try {
|
|
951
|
+
return compileQueryInternal(query, ctx, fns, bindings);
|
|
952
|
+
} finally {
|
|
953
|
+
if (hadPreviousBindingScope && previousBindingScope) {
|
|
954
|
+
activeBindingScopes.set(ctx, previousBindingScope);
|
|
955
|
+
} else {
|
|
956
|
+
activeBindingScopes.delete(ctx);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
function collectSelectAliasesFromQuery(query) {
|
|
961
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
962
|
+
for (const clause of query.clauses) {
|
|
963
|
+
if (clause.type !== "select") continue;
|
|
964
|
+
for (const item of clause.items) {
|
|
965
|
+
if (item.type === "expression" && item.alias) {
|
|
966
|
+
aliases.add(item.alias);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
return aliases;
|
|
971
|
+
}
|
|
972
|
+
function throwUnsupportedBindingFinal(bindingName, feature) {
|
|
973
|
+
throw new NqlSemanticException(
|
|
974
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
975
|
+
`Query '${bindingName}' reads from an NQL binding and cannot use ${feature} in a binding-final query (#183). Binding-final queries are restricted to portable common-ground SQL over projected binding columns: SELECT (including plain DISTINCT), WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET, and UNION/INTERSECT/EXCEPT.`
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
function validateBindingFinalPath(expr, ctx, bindingName) {
|
|
979
|
+
const { segments } = expr;
|
|
980
|
+
if (segments.length === 1) {
|
|
981
|
+
if (ctx.currentHavingAliases?.has(segments[0])) {
|
|
982
|
+
throw new NqlSemanticException(
|
|
983
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
984
|
+
`HAVING cannot reference SELECT alias '${segments[0]}'. PostgreSQL does not allow SELECT aliases in HAVING; repeat the aggregate expression or filter the result in an outer query.`
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
validateColumnForTable(ctx, bindingName, segments[0]);
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
const firstSegment = segments[0];
|
|
991
|
+
if (ctx.pseudoColumnKeywords.has(firstSegment.toLowerCase())) {
|
|
992
|
+
assertNoBindingRelationConstruct(
|
|
993
|
+
ctx,
|
|
994
|
+
bindingName,
|
|
995
|
+
"use pseudo-column traversals",
|
|
996
|
+
firstSegment
|
|
997
|
+
);
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
assertNoBindingRelationPath(ctx, bindingName, segments.join("."));
|
|
1001
|
+
}
|
|
1002
|
+
function validateBindingFinalFunction(expr, ctx, bindingName) {
|
|
1003
|
+
const fn = expr.name.toLowerCase();
|
|
1004
|
+
if (!PORTABLE_BINDING_FINAL_FUNCTIONS.has(fn)) {
|
|
1005
|
+
throwUnsupportedBindingFinal(bindingName, `function ${expr.name}()`);
|
|
1006
|
+
}
|
|
1007
|
+
for (const arg of expr.args) {
|
|
1008
|
+
validateBindingFinalExpression(arg, ctx, bindingName);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
function validateBindingFinalExpression(expr, ctx, bindingName) {
|
|
1012
|
+
switch (expr.type) {
|
|
1013
|
+
case "path":
|
|
1014
|
+
validateBindingFinalPath(expr, ctx, bindingName);
|
|
1015
|
+
break;
|
|
1016
|
+
case "binary":
|
|
1017
|
+
validateBindingFinalExpression(expr.left, ctx, bindingName);
|
|
1018
|
+
validateBindingFinalExpression(expr.right, ctx, bindingName);
|
|
1019
|
+
break;
|
|
1020
|
+
case "unary":
|
|
1021
|
+
validateBindingFinalExpression(expr.operand, ctx, bindingName);
|
|
1022
|
+
break;
|
|
1023
|
+
case "comparison":
|
|
1024
|
+
validateBindingFinalExpression(expr.left, ctx, bindingName);
|
|
1025
|
+
validateBindingFinalExpression(expr.right, ctx, bindingName);
|
|
1026
|
+
break;
|
|
1027
|
+
case "in":
|
|
1028
|
+
validateBindingFinalExpression(expr.expression, ctx, bindingName);
|
|
1029
|
+
if (Array.isArray(expr.values)) {
|
|
1030
|
+
for (const value of expr.values) {
|
|
1031
|
+
validateBindingFinalExpression(value, ctx, bindingName);
|
|
1032
|
+
}
|
|
1033
|
+
} else if (expr.values.type === "dateRange") ; else ;
|
|
1034
|
+
break;
|
|
1035
|
+
case "between":
|
|
1036
|
+
validateBindingFinalExpression(expr.expression, ctx, bindingName);
|
|
1037
|
+
validateBindingFinalExpression(expr.low, ctx, bindingName);
|
|
1038
|
+
validateBindingFinalExpression(expr.high, ctx, bindingName);
|
|
1039
|
+
break;
|
|
1040
|
+
case "isNull":
|
|
1041
|
+
validateBindingFinalExpression(expr.expression, ctx, bindingName);
|
|
1042
|
+
break;
|
|
1043
|
+
case "function":
|
|
1044
|
+
validateBindingFinalFunction(expr, ctx, bindingName);
|
|
1045
|
+
break;
|
|
1046
|
+
case "case":
|
|
1047
|
+
if (expr.subject) {
|
|
1048
|
+
validateBindingFinalExpression(expr.subject, ctx, bindingName);
|
|
1049
|
+
}
|
|
1050
|
+
for (const when of expr.whenClauses) {
|
|
1051
|
+
validateBindingFinalExpression(when.condition, ctx, bindingName);
|
|
1052
|
+
validateBindingFinalExpression(when.result, ctx, bindingName);
|
|
1053
|
+
}
|
|
1054
|
+
if (expr.elseClause) {
|
|
1055
|
+
validateBindingFinalExpression(expr.elseClause, ctx, bindingName);
|
|
1056
|
+
}
|
|
1057
|
+
break;
|
|
1058
|
+
case "relationFilter":
|
|
1059
|
+
assertNoBindingRelationConstruct(
|
|
1060
|
+
ctx,
|
|
1061
|
+
bindingName,
|
|
1062
|
+
"use relation filters",
|
|
1063
|
+
expr.relation.join(".")
|
|
1064
|
+
);
|
|
1065
|
+
break;
|
|
1066
|
+
case "any":
|
|
1067
|
+
throwUnsupportedBindingFinal(bindingName, "ANY(:param)");
|
|
1068
|
+
break;
|
|
1069
|
+
case "rangeOp":
|
|
1070
|
+
case "rangeLiteral":
|
|
1071
|
+
throwUnsupportedBindingFinal(bindingName, "PostgreSQL range operators");
|
|
1072
|
+
break;
|
|
1073
|
+
case "jsonAccess":
|
|
1074
|
+
case "jsonComparison":
|
|
1075
|
+
throwUnsupportedBindingFinal(bindingName, "PostgreSQL JSON operators");
|
|
1076
|
+
break;
|
|
1077
|
+
case "window":
|
|
1078
|
+
throwUnsupportedBindingFinal(bindingName, "window functions");
|
|
1079
|
+
break;
|
|
1080
|
+
case "exists":
|
|
1081
|
+
throwUnsupportedBindingFinal(bindingName, "EXISTS subqueries");
|
|
1082
|
+
break;
|
|
1083
|
+
case "subquery":
|
|
1084
|
+
throwUnsupportedBindingFinal(bindingName, "scalar subqueries");
|
|
1085
|
+
break;
|
|
1086
|
+
case "variable":
|
|
1087
|
+
throwUnsupportedBindingFinal(bindingName, `variable '${expr.name}'`);
|
|
1088
|
+
break;
|
|
1089
|
+
case "namedParam":
|
|
1090
|
+
case "string":
|
|
1091
|
+
case "number":
|
|
1092
|
+
case "boolean":
|
|
1093
|
+
case "null":
|
|
1094
|
+
case "dateRange":
|
|
1095
|
+
break;
|
|
1096
|
+
/* v8 ignore next — defensive: default-reject future expression shapes in binding-final queries -- @preserve */
|
|
1097
|
+
default:
|
|
1098
|
+
throwUnsupportedBindingFinal(
|
|
1099
|
+
bindingName,
|
|
1100
|
+
`expression type '${expr.type ?? "unknown"}'`
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
function validateBindingFinalSelectClause(clause, ctx, bindingName) {
|
|
1105
|
+
const distinctOn = clause.distinctOn;
|
|
1106
|
+
if (distinctOn !== void 0) {
|
|
1107
|
+
throwUnsupportedBindingFinal(bindingName, "DISTINCT ON");
|
|
1108
|
+
}
|
|
1109
|
+
for (const item of clause.items) {
|
|
1110
|
+
switch (item.type) {
|
|
1111
|
+
case "star":
|
|
1112
|
+
break;
|
|
1113
|
+
case "relationStar":
|
|
1114
|
+
assertNoBindingRelationConstruct(
|
|
1115
|
+
ctx,
|
|
1116
|
+
bindingName,
|
|
1117
|
+
"select relation columns",
|
|
1118
|
+
item.relation.join(".")
|
|
1119
|
+
);
|
|
1120
|
+
break;
|
|
1121
|
+
case "expression":
|
|
1122
|
+
if (item.expression.type === "path" && item.expression.segments.length > 1) {
|
|
1123
|
+
const segments = item.expression.segments;
|
|
1124
|
+
const firstSegment = segments[0];
|
|
1125
|
+
if (ctx.pseudoColumnKeywords.has(firstSegment.toLowerCase())) {
|
|
1126
|
+
assertNoBindingRelationConstruct(
|
|
1127
|
+
ctx,
|
|
1128
|
+
bindingName,
|
|
1129
|
+
"use pseudo-column traversals",
|
|
1130
|
+
firstSegment
|
|
1131
|
+
);
|
|
1132
|
+
} else {
|
|
1133
|
+
assertNoBindingRelationConstruct(
|
|
1134
|
+
ctx,
|
|
1135
|
+
bindingName,
|
|
1136
|
+
"select relation columns",
|
|
1137
|
+
segments.slice(0, -1).join(".")
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
} else {
|
|
1141
|
+
validateBindingFinalExpression(item.expression, ctx, bindingName);
|
|
1142
|
+
}
|
|
1143
|
+
break;
|
|
1144
|
+
/* v8 ignore next — defensive: default-reject future select item shapes in binding-final queries -- @preserve */
|
|
1145
|
+
default:
|
|
1146
|
+
throwUnsupportedBindingFinal(
|
|
1147
|
+
bindingName,
|
|
1148
|
+
`SELECT item type '${item.type ?? "unknown"}'`
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
function validateBindingFinalQuery(query, ctx) {
|
|
1154
|
+
const groupByIndex = query.clauses.findIndex((c) => c.type === "groupBy");
|
|
1155
|
+
const havingAliases = collectSelectAliasesFromQuery(query);
|
|
1156
|
+
for (let i = 0; i < query.clauses.length; i++) {
|
|
1157
|
+
const clause = query.clauses[i];
|
|
1158
|
+
switch (clause.type) {
|
|
1159
|
+
case "where": {
|
|
1160
|
+
if (groupByIndex >= 0 && i > groupByIndex) {
|
|
1161
|
+
const previousHavingAliases = ctx.currentHavingAliases;
|
|
1162
|
+
ctx.currentHavingAliases = havingAliases;
|
|
1163
|
+
try {
|
|
1164
|
+
validateBindingFinalExpression(clause.condition, ctx, query.table);
|
|
1165
|
+
} finally {
|
|
1166
|
+
ctx.currentHavingAliases = previousHavingAliases;
|
|
1167
|
+
}
|
|
1168
|
+
} else {
|
|
1169
|
+
validateBindingFinalExpression(clause.condition, ctx, query.table);
|
|
1170
|
+
}
|
|
1171
|
+
break;
|
|
1172
|
+
}
|
|
1173
|
+
case "select":
|
|
1174
|
+
validateBindingFinalSelectClause(clause, ctx, query.table);
|
|
1175
|
+
break;
|
|
1176
|
+
case "groupBy":
|
|
1177
|
+
for (const expr of clause.expressions) {
|
|
1178
|
+
validateBindingFinalExpression(expr, ctx, query.table);
|
|
1179
|
+
}
|
|
1180
|
+
break;
|
|
1181
|
+
case "orderBy":
|
|
1182
|
+
for (const item of clause.items) {
|
|
1183
|
+
validateBindingFinalExpression(item.expression, ctx, query.table);
|
|
1184
|
+
}
|
|
1185
|
+
break;
|
|
1186
|
+
case "limit":
|
|
1187
|
+
if (clause.relation) {
|
|
1188
|
+
assertNoBindingRelationConstruct(
|
|
1189
|
+
ctx,
|
|
1190
|
+
query.table,
|
|
1191
|
+
"use relation include limits",
|
|
1192
|
+
clause.relation
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
break;
|
|
1196
|
+
case "offset":
|
|
1197
|
+
case "bind":
|
|
1198
|
+
case "setOperation":
|
|
1199
|
+
break;
|
|
1200
|
+
case "flat":
|
|
1201
|
+
throwUnsupportedBindingFinal(query.table, "flat relation include mode");
|
|
1202
|
+
break;
|
|
1203
|
+
case "lock":
|
|
1204
|
+
throwUnsupportedBindingFinal(
|
|
1205
|
+
query.table,
|
|
1206
|
+
"row-level locks (FOR UPDATE / SKIP LOCKED), because locks over a CTE binding are silently ineffective"
|
|
1207
|
+
);
|
|
1208
|
+
break;
|
|
1209
|
+
/* v8 ignore next — defensive: default-reject future clauses in binding-final queries -- @preserve */
|
|
1210
|
+
default:
|
|
1211
|
+
throwUnsupportedBindingFinal(
|
|
1212
|
+
query.table,
|
|
1213
|
+
`clause '${clause.type ?? "unknown"}'`
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
function compileQueryInternal(query, ctx, fns, bindings) {
|
|
1219
|
+
const bindingSource = bindings?.get(query.table);
|
|
1220
|
+
const isBindingSource = bindingSource !== void 0 || isBindingTable(ctx, query.table);
|
|
1221
|
+
ctx.currentFromTable = query.table;
|
|
1222
|
+
ctx.validator?.validateTable(query.table);
|
|
1223
|
+
if (isBindingSource) {
|
|
1224
|
+
validateBindingFinalQuery(query, ctx);
|
|
1225
|
+
}
|
|
787
1226
|
const setClauseIndex = query.clauses.findIndex(
|
|
788
1227
|
(c) => c.type === "setOperation"
|
|
789
1228
|
);
|
|
790
1229
|
if (setClauseIndex >= 0) {
|
|
791
1230
|
return compileSetOperation(query, setClauseIndex, ctx, fns, bindings);
|
|
792
1231
|
}
|
|
793
|
-
ctx.currentFromTable = query.table;
|
|
794
|
-
ctx.validator?.validateTable(query.table);
|
|
795
1232
|
let groupByIndex = -1;
|
|
796
1233
|
for (let i = 0; i < query.clauses.length; i++) {
|
|
797
1234
|
if (query.clauses[i]?.type === "groupBy") {
|
|
@@ -799,6 +1236,7 @@ function compileQuery(query, ctx, fns, bindings) {
|
|
|
799
1236
|
break;
|
|
800
1237
|
}
|
|
801
1238
|
}
|
|
1239
|
+
const havingAliases = collectSelectAliasesFromQuery(query);
|
|
802
1240
|
const whereConditions = [];
|
|
803
1241
|
const havingConditions = [];
|
|
804
1242
|
let select;
|
|
@@ -816,13 +1254,33 @@ function compileQuery(query, ctx, fns, bindings) {
|
|
|
816
1254
|
const clause = query.clauses[i];
|
|
817
1255
|
switch (clause.type) {
|
|
818
1256
|
case "where": {
|
|
819
|
-
const condition = resolveBindingsInWhere(
|
|
820
|
-
fns.compileExpression(clause.condition, ctx, fns),
|
|
821
|
-
bindings
|
|
822
|
-
);
|
|
823
1257
|
if (groupByIndex >= 0 && i > groupByIndex) {
|
|
1258
|
+
const previousHavingAliases = ctx.currentHavingAliases;
|
|
1259
|
+
ctx.currentHavingAliases = havingAliases;
|
|
1260
|
+
const condition = (() => {
|
|
1261
|
+
try {
|
|
1262
|
+
return resolveBindingsInWhere(
|
|
1263
|
+
fns.compileExpression(
|
|
1264
|
+
clause.condition,
|
|
1265
|
+
ctx,
|
|
1266
|
+
fns
|
|
1267
|
+
),
|
|
1268
|
+
bindings
|
|
1269
|
+
);
|
|
1270
|
+
} finally {
|
|
1271
|
+
ctx.currentHavingAliases = previousHavingAliases;
|
|
1272
|
+
}
|
|
1273
|
+
})();
|
|
824
1274
|
havingConditions.push(condition);
|
|
825
1275
|
} else if (currentIncludeBatch && currentIncludeBatch.length > 0) {
|
|
1276
|
+
const condition = resolveBindingsInWhere(
|
|
1277
|
+
fns.compileExpression(
|
|
1278
|
+
clause.condition,
|
|
1279
|
+
ctx,
|
|
1280
|
+
fns
|
|
1281
|
+
),
|
|
1282
|
+
bindings
|
|
1283
|
+
);
|
|
826
1284
|
const targetInclude = currentIncludeBatch[currentIncludeBatch.length - 1];
|
|
827
1285
|
const mutableInclude = targetInclude;
|
|
828
1286
|
if (targetInclude.where) {
|
|
@@ -834,6 +1292,14 @@ function compileQuery(query, ctx, fns, bindings) {
|
|
|
834
1292
|
mutableInclude.where = condition;
|
|
835
1293
|
}
|
|
836
1294
|
} else {
|
|
1295
|
+
const condition = resolveBindingsInWhere(
|
|
1296
|
+
fns.compileExpression(
|
|
1297
|
+
clause.condition,
|
|
1298
|
+
ctx,
|
|
1299
|
+
fns
|
|
1300
|
+
),
|
|
1301
|
+
bindings
|
|
1302
|
+
);
|
|
837
1303
|
whereConditions.push(condition);
|
|
838
1304
|
}
|
|
839
1305
|
break;
|
|
@@ -909,6 +1375,12 @@ function compileQuery(query, ctx, fns, bindings) {
|
|
|
909
1375
|
}
|
|
910
1376
|
}
|
|
911
1377
|
if (includeLimits.size > 0) {
|
|
1378
|
+
if (isBindingSource) {
|
|
1379
|
+
throw new NqlSemanticException(
|
|
1380
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1381
|
+
`Query '${query.table}' reads from an NQL binding and cannot use relation include limits (${[...includeLimits.keys()].join(", ")}). Relation includes require a physical model table, not a CTE binding.`
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
912
1384
|
for (const [relation, limitCount] of includeLimits) {
|
|
913
1385
|
const rootRelation = relation.split(".")[0];
|
|
914
1386
|
const targetInclude = allIncludes.find(
|
|
@@ -935,7 +1407,7 @@ function compileQuery(query, ctx, fns, bindings) {
|
|
|
935
1407
|
} else if (havingConditions.length > 1) {
|
|
936
1408
|
having = { kind: "and", conditions: havingConditions };
|
|
937
1409
|
}
|
|
938
|
-
|
|
1410
|
+
const compiledQuery = {
|
|
939
1411
|
type: "select",
|
|
940
1412
|
from: query.table,
|
|
941
1413
|
...select !== void 0 && { select },
|
|
@@ -949,6 +1421,7 @@ function compileQuery(query, ctx, fns, bindings) {
|
|
|
949
1421
|
...offset !== void 0 && { offset },
|
|
950
1422
|
...lock !== void 0 && { lock }
|
|
951
1423
|
};
|
|
1424
|
+
return compiledQuery;
|
|
952
1425
|
}
|
|
953
1426
|
function getExplicitColumnCount(intent) {
|
|
954
1427
|
if ("kind" in intent && intent.kind === "setOperation") {
|
|
@@ -972,6 +1445,218 @@ function getExplicitColumnCount(intent) {
|
|
|
972
1445
|
return void 0;
|
|
973
1446
|
}
|
|
974
1447
|
}
|
|
1448
|
+
function expressionOutputColumn(expr) {
|
|
1449
|
+
switch (expr.kind) {
|
|
1450
|
+
case "column":
|
|
1451
|
+
if (expr.column === "*") return void 0;
|
|
1452
|
+
return expr.as ?? expr.column;
|
|
1453
|
+
case "columnAlias":
|
|
1454
|
+
return expr.alias;
|
|
1455
|
+
case "relationColumn":
|
|
1456
|
+
if (expr.column === "*") return void 0;
|
|
1457
|
+
return expr.as;
|
|
1458
|
+
case "aggregate":
|
|
1459
|
+
case "function":
|
|
1460
|
+
case "subquery":
|
|
1461
|
+
case "arithmetic":
|
|
1462
|
+
case "literal":
|
|
1463
|
+
case "case":
|
|
1464
|
+
case "jsonExtract":
|
|
1465
|
+
case "jsonPathExtract":
|
|
1466
|
+
case "customOp":
|
|
1467
|
+
case "customFn":
|
|
1468
|
+
case "array":
|
|
1469
|
+
case "unary":
|
|
1470
|
+
case "param":
|
|
1471
|
+
return expr.as;
|
|
1472
|
+
case "coalesce":
|
|
1473
|
+
case "raw":
|
|
1474
|
+
case "pseudoColumn":
|
|
1475
|
+
return expr.as;
|
|
1476
|
+
case "window":
|
|
1477
|
+
return expr.alias;
|
|
1478
|
+
case "comparison":
|
|
1479
|
+
case "jsonContains":
|
|
1480
|
+
case "jsonExists":
|
|
1481
|
+
case "ref":
|
|
1482
|
+
case "cast":
|
|
1483
|
+
case "namedArg":
|
|
1484
|
+
case "star":
|
|
1485
|
+
return void 0;
|
|
1486
|
+
/* v8 ignore next — defensive: exhaustive switch -- @preserve */
|
|
1487
|
+
default:
|
|
1488
|
+
return void 0;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
function addUnique(columns, seen, column) {
|
|
1492
|
+
if (seen.has(column)) return;
|
|
1493
|
+
seen.add(column);
|
|
1494
|
+
columns.push(column);
|
|
1495
|
+
}
|
|
1496
|
+
function resolveSourceOutputColumns(intent, ctx, bindingName) {
|
|
1497
|
+
const bindingColumns2 = getKnownBindingColumns(ctx, intent.from);
|
|
1498
|
+
if (bindingColumns2 !== void 0) return bindingColumns2;
|
|
1499
|
+
const columns = ctx.validator?.getTableColumns(intent.from);
|
|
1500
|
+
if (columns !== void 0) return columns;
|
|
1501
|
+
throw new NqlSemanticException(
|
|
1502
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1503
|
+
`Cannot compute output schema for NQL binding '${bindingName}' from SELECT * on '${intent.from}' without a concrete table schema.`
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
function getQueryOutputSchema(intent, ctx, bindingName) {
|
|
1507
|
+
const columns = [];
|
|
1508
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1509
|
+
const addColumn = (column) => addUnique(columns, seen, column);
|
|
1510
|
+
const addSourceColumns = () => {
|
|
1511
|
+
for (const column of resolveSourceOutputColumns(intent, ctx, bindingName)) {
|
|
1512
|
+
addColumn(column);
|
|
1513
|
+
}
|
|
1514
|
+
};
|
|
1515
|
+
const { select } = intent;
|
|
1516
|
+
if (!select || select.type === "all") {
|
|
1517
|
+
addSourceColumns();
|
|
1518
|
+
return { columns };
|
|
1519
|
+
}
|
|
1520
|
+
if (select.type === "fields") {
|
|
1521
|
+
for (const field of select.fields) {
|
|
1522
|
+
if (field === "*") {
|
|
1523
|
+
addSourceColumns();
|
|
1524
|
+
} else {
|
|
1525
|
+
addColumn(field);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
return { columns };
|
|
1529
|
+
}
|
|
1530
|
+
if (select.type === "aggregate") {
|
|
1531
|
+
for (const field of select.fields ?? []) {
|
|
1532
|
+
addColumn(field);
|
|
1533
|
+
}
|
|
1534
|
+
for (const aggregate of select.aggregates) {
|
|
1535
|
+
if (!aggregate.as) {
|
|
1536
|
+
throw new NqlSemanticException(
|
|
1537
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1538
|
+
`Cannot compute output schema for NQL binding '${bindingName}': aggregate '${aggregate.function}' must use an alias.`
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
addColumn(aggregate.as);
|
|
1542
|
+
}
|
|
1543
|
+
return { columns };
|
|
1544
|
+
}
|
|
1545
|
+
for (const expr of select.columns) {
|
|
1546
|
+
if (expr.kind === "column" && expr.column === "*") {
|
|
1547
|
+
addSourceColumns();
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
if (expr.kind === "relationColumn" && expr.column === "*") {
|
|
1551
|
+
throw new NqlSemanticException(
|
|
1552
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1553
|
+
`Cannot compute output schema for NQL binding '${bindingName}' from relation SELECT * '${expr.relation}.*'. Use explicit aliases for binding outputs.`
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
const outputColumn = expressionOutputColumn(expr);
|
|
1557
|
+
if (!outputColumn) {
|
|
1558
|
+
throw new NqlSemanticException(
|
|
1559
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
1560
|
+
`Cannot compute output schema for NQL binding '${bindingName}': selected expression must use an alias.`
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1563
|
+
addColumn(outputColumn);
|
|
1564
|
+
}
|
|
1565
|
+
return { columns };
|
|
1566
|
+
}
|
|
1567
|
+
function validateNqlExpressionPaths(expr, ctx) {
|
|
1568
|
+
switch (expr.type) {
|
|
1569
|
+
case "path":
|
|
1570
|
+
if (expr.segments.length === 1) {
|
|
1571
|
+
if (ctx.currentFromTable && isBindingTable(ctx, ctx.currentFromTable)) {
|
|
1572
|
+
validateColumnForTable(ctx, ctx.currentFromTable, expr.segments[0]);
|
|
1573
|
+
}
|
|
1574
|
+
} else if (ctx.currentFromTable && isBindingTable(ctx, ctx.currentFromTable)) {
|
|
1575
|
+
assertNoBindingRelationPath(
|
|
1576
|
+
ctx,
|
|
1577
|
+
ctx.currentFromTable,
|
|
1578
|
+
expr.segments.join(".")
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1581
|
+
break;
|
|
1582
|
+
case "binary":
|
|
1583
|
+
validateNqlExpressionPaths(expr.left, ctx);
|
|
1584
|
+
validateNqlExpressionPaths(expr.right, ctx);
|
|
1585
|
+
break;
|
|
1586
|
+
case "unary":
|
|
1587
|
+
validateNqlExpressionPaths(expr.operand, ctx);
|
|
1588
|
+
break;
|
|
1589
|
+
case "comparison":
|
|
1590
|
+
case "jsonComparison":
|
|
1591
|
+
validateNqlExpressionPaths(expr.left, ctx);
|
|
1592
|
+
validateNqlExpressionPaths(expr.right, ctx);
|
|
1593
|
+
break;
|
|
1594
|
+
case "rangeOp":
|
|
1595
|
+
validateNqlExpressionPaths(expr.left, ctx);
|
|
1596
|
+
if (expr.scalar) {
|
|
1597
|
+
validateNqlExpressionPaths(expr.scalar, ctx);
|
|
1598
|
+
}
|
|
1599
|
+
break;
|
|
1600
|
+
case "in":
|
|
1601
|
+
validateNqlExpressionPaths(expr.expression, ctx);
|
|
1602
|
+
if (Array.isArray(expr.values)) {
|
|
1603
|
+
for (const value of expr.values) {
|
|
1604
|
+
validateNqlExpressionPaths(value, ctx);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
break;
|
|
1608
|
+
case "any":
|
|
1609
|
+
validateNqlExpressionPaths(expr.column, ctx);
|
|
1610
|
+
break;
|
|
1611
|
+
case "between":
|
|
1612
|
+
validateNqlExpressionPaths(expr.expression, ctx);
|
|
1613
|
+
validateNqlExpressionPaths(expr.low, ctx);
|
|
1614
|
+
validateNqlExpressionPaths(expr.high, ctx);
|
|
1615
|
+
break;
|
|
1616
|
+
case "isNull":
|
|
1617
|
+
validateNqlExpressionPaths(expr.expression, ctx);
|
|
1618
|
+
break;
|
|
1619
|
+
case "function":
|
|
1620
|
+
for (const arg of expr.args) {
|
|
1621
|
+
validateNqlExpressionPaths(arg, ctx);
|
|
1622
|
+
}
|
|
1623
|
+
break;
|
|
1624
|
+
case "window":
|
|
1625
|
+
for (const arg of expr.args) {
|
|
1626
|
+
validateNqlExpressionPaths(arg, ctx);
|
|
1627
|
+
}
|
|
1628
|
+
for (const item of expr.orderBy) {
|
|
1629
|
+
validateNqlExpressionPaths(item.expression, ctx);
|
|
1630
|
+
}
|
|
1631
|
+
for (const partitionBy of expr.partitionBy) {
|
|
1632
|
+
validateNqlExpressionPaths(partitionBy, ctx);
|
|
1633
|
+
}
|
|
1634
|
+
break;
|
|
1635
|
+
case "case":
|
|
1636
|
+
if (expr.subject) {
|
|
1637
|
+
validateNqlExpressionPaths(expr.subject, ctx);
|
|
1638
|
+
}
|
|
1639
|
+
for (const when of expr.whenClauses) {
|
|
1640
|
+
validateNqlExpressionPaths(when.condition, ctx);
|
|
1641
|
+
validateNqlExpressionPaths(when.result, ctx);
|
|
1642
|
+
}
|
|
1643
|
+
if (expr.elseClause) {
|
|
1644
|
+
validateNqlExpressionPaths(expr.elseClause, ctx);
|
|
1645
|
+
}
|
|
1646
|
+
break;
|
|
1647
|
+
case "jsonAccess":
|
|
1648
|
+
validateNqlExpressionPaths(expr.base, ctx);
|
|
1649
|
+
break;
|
|
1650
|
+
case "relationFilter":
|
|
1651
|
+
assertNoBindingRelationConstruct(
|
|
1652
|
+
ctx,
|
|
1653
|
+
ctx.currentFromTable,
|
|
1654
|
+
"use relation filters",
|
|
1655
|
+
expr.relation.join(".")
|
|
1656
|
+
);
|
|
1657
|
+
break;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
975
1660
|
function compileSetOperation(query, setClauseIndex, ctx, fns, bindings) {
|
|
976
1661
|
const setClause = query.clauses[setClauseIndex];
|
|
977
1662
|
if (setClauseIndex < query.clauses.length - 1) {
|
|
@@ -981,6 +1666,7 @@ function compileSetOperation(query, setClauseIndex, ctx, fns, bindings) {
|
|
|
981
1666
|
);
|
|
982
1667
|
}
|
|
983
1668
|
const leftQuery = {
|
|
1669
|
+
type: "query",
|
|
984
1670
|
table: query.table,
|
|
985
1671
|
clauses: query.clauses.slice(0, setClauseIndex)
|
|
986
1672
|
};
|
|
@@ -1016,10 +1702,11 @@ function compileSetOperation(query, setClauseIndex, ctx, fns, bindings) {
|
|
|
1016
1702
|
}
|
|
1017
1703
|
function compileGroupByClause(clause, ctx) {
|
|
1018
1704
|
return clause.expressions.map((expr) => {
|
|
1705
|
+
validateNqlExpressionPaths(expr, ctx);
|
|
1019
1706
|
if (expr.type === "path") {
|
|
1020
1707
|
const field = expr.segments.join(".");
|
|
1021
1708
|
if (ctx.currentFromTable && !field.includes(".")) {
|
|
1022
|
-
|
|
1709
|
+
validateColumnForTable(ctx, ctx.currentFromTable, field);
|
|
1023
1710
|
}
|
|
1024
1711
|
return field;
|
|
1025
1712
|
}
|
|
@@ -1030,10 +1717,13 @@ function compileOrderByClause(clause, ctx) {
|
|
|
1030
1717
|
return clause.items.map((item) => compileOrderItem(item, ctx));
|
|
1031
1718
|
}
|
|
1032
1719
|
function compileOrderItem(item, ctx) {
|
|
1720
|
+
validateNqlExpressionPaths(item.expression, ctx);
|
|
1033
1721
|
const field = expressionToField(item.expression);
|
|
1034
1722
|
if (field) {
|
|
1035
|
-
if (ctx.currentFromTable &&
|
|
1036
|
-
|
|
1723
|
+
if (ctx.currentFromTable && field.includes(".") && isBindingTable(ctx, ctx.currentFromTable)) {
|
|
1724
|
+
assertNoBindingRelationPath(ctx, ctx.currentFromTable, field);
|
|
1725
|
+
} else if (ctx.currentFromTable && !field.includes(".") && !field.includes("(")) {
|
|
1726
|
+
validateColumnForTable(ctx, ctx.currentFromTable, field);
|
|
1037
1727
|
}
|
|
1038
1728
|
return { field, direction: item.direction };
|
|
1039
1729
|
}
|
|
@@ -1318,6 +2008,13 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
1318
2008
|
return intent2;
|
|
1319
2009
|
}
|
|
1320
2010
|
}
|
|
2011
|
+
const havingAggregateComparison = compileHavingAggregateComparison(
|
|
2012
|
+
comp,
|
|
2013
|
+
ctx,
|
|
2014
|
+
aliasContext,
|
|
2015
|
+
outerAliases
|
|
2016
|
+
);
|
|
2017
|
+
if (havingAggregateComparison) return havingAggregateComparison;
|
|
1321
2018
|
const field = expressionToField(comp.left, aliasContext);
|
|
1322
2019
|
if (!field) {
|
|
1323
2020
|
throw new NqlSemanticException(
|
|
@@ -1344,6 +2041,26 @@ function compileComparison(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
|
1344
2041
|
};
|
|
1345
2042
|
return intent;
|
|
1346
2043
|
}
|
|
2044
|
+
function compileHavingAggregateComparison(comp, ctx, aliasContext, outerAliases) {
|
|
2045
|
+
if (ctx.currentHavingAliases === void 0 || aliasContext || comp.left.type !== "function") {
|
|
2046
|
+
return null;
|
|
2047
|
+
}
|
|
2048
|
+
const fn = comp.left.name.toLowerCase();
|
|
2049
|
+
if (fn !== "count" || comp.left.args.length !== 0) {
|
|
2050
|
+
return null;
|
|
2051
|
+
}
|
|
2052
|
+
const exprIntent = {
|
|
2053
|
+
kind: "aggregate",
|
|
2054
|
+
function: "count",
|
|
2055
|
+
field: "*"
|
|
2056
|
+
};
|
|
2057
|
+
return {
|
|
2058
|
+
kind: "expression",
|
|
2059
|
+
expr: exprIntent,
|
|
2060
|
+
operator: mapComparisonOperator(comp.operator),
|
|
2061
|
+
value: resolveFilterValue(comp.right, ctx, aliasContext, outerAliases)
|
|
2062
|
+
};
|
|
2063
|
+
}
|
|
1347
2064
|
function compileRange(expr, ctx, _fns, aliasContext, outerAliases) {
|
|
1348
2065
|
const rangeExpr = expr;
|
|
1349
2066
|
const field = expressionToField(rangeExpr.left, aliasContext);
|
|
@@ -1617,11 +2334,19 @@ function compileRelationFilter(expr, ctx, fns, aliasContext, outerAliases) {
|
|
|
1617
2334
|
const relFilter = expr;
|
|
1618
2335
|
const nestedOuterAliases = aliasContext ? [...outerAliases ?? [], aliasContext] : outerAliases ?? [];
|
|
1619
2336
|
const prevRelationTarget = ctx.currentRelationTarget;
|
|
1620
|
-
if (ctx.currentFromTable &&
|
|
1621
|
-
|
|
2337
|
+
if (ctx.currentFromTable && relFilter.relation[0]) {
|
|
2338
|
+
assertNoBindingRelationConstruct(
|
|
2339
|
+
ctx,
|
|
1622
2340
|
ctx.currentFromTable,
|
|
1623
|
-
|
|
2341
|
+
"use relation filters",
|
|
2342
|
+
relFilter.relation.join(".")
|
|
1624
2343
|
);
|
|
2344
|
+
if (ctx.validator) {
|
|
2345
|
+
ctx.currentRelationTarget = ctx.validator.resolveRelationTarget(
|
|
2346
|
+
ctx.currentFromTable,
|
|
2347
|
+
relFilter.relation[0]
|
|
2348
|
+
);
|
|
2349
|
+
}
|
|
1625
2350
|
}
|
|
1626
2351
|
const where = compileExpression(
|
|
1627
2352
|
relFilter.condition,
|
|
@@ -1744,23 +2469,31 @@ var SELECT_SCALAR_FUNCTION_NAMES = /* @__PURE__ */ new Set([
|
|
|
1744
2469
|
...NQL_SELECT_SCALAR_FUNCTIONS
|
|
1745
2470
|
]);
|
|
1746
2471
|
function validateSelectPathExpression(expr, ctx) {
|
|
1747
|
-
if (!ctx.currentFromTable
|
|
2472
|
+
if (!ctx.currentFromTable) return;
|
|
1748
2473
|
const { segments } = expr;
|
|
1749
2474
|
if (segments.length === 1) {
|
|
1750
|
-
|
|
2475
|
+
validateColumnForTable(ctx, ctx.currentFromTable, segments[0]);
|
|
1751
2476
|
return;
|
|
1752
2477
|
}
|
|
1753
2478
|
const firstSegmentLower = segments[0].toLowerCase();
|
|
1754
2479
|
if (ctx.pseudoColumnKeywords.has(firstSegmentLower)) {
|
|
2480
|
+
assertNoBindingRelationConstruct(
|
|
2481
|
+
ctx,
|
|
2482
|
+
ctx.currentFromTable,
|
|
2483
|
+
"use pseudo-column traversals",
|
|
2484
|
+
segments[0]
|
|
2485
|
+
);
|
|
1755
2486
|
let i = 1;
|
|
1756
2487
|
while (i < segments.length && ctx.pseudoColumnKeywords.has(segments[i].toLowerCase())) {
|
|
1757
2488
|
i++;
|
|
1758
2489
|
}
|
|
1759
2490
|
if (i < segments.length) {
|
|
1760
|
-
|
|
2491
|
+
validateColumnForTable(ctx, ctx.currentFromTable, segments[i]);
|
|
1761
2492
|
}
|
|
1762
2493
|
return;
|
|
1763
2494
|
}
|
|
2495
|
+
assertNoBindingRelationPath(ctx, ctx.currentFromTable, segments.join("."));
|
|
2496
|
+
if (!ctx.validator) return;
|
|
1764
2497
|
const targetTable = ctx.validator.resolveRelationTarget(
|
|
1765
2498
|
ctx.currentFromTable,
|
|
1766
2499
|
segments[0]
|
|
@@ -1790,6 +2523,12 @@ function compileSelectClause(clause, ctx, fns) {
|
|
|
1790
2523
|
} else if (item.type === "relationStar") {
|
|
1791
2524
|
hasExpressions = true;
|
|
1792
2525
|
const relation = item.relation.join(".");
|
|
2526
|
+
assertNoBindingRelationConstruct(
|
|
2527
|
+
ctx,
|
|
2528
|
+
ctx.currentFromTable,
|
|
2529
|
+
"select relation columns",
|
|
2530
|
+
relation
|
|
2531
|
+
);
|
|
1793
2532
|
expressions.push({
|
|
1794
2533
|
kind: "relationColumn",
|
|
1795
2534
|
relation,
|
|
@@ -1935,14 +2674,14 @@ function compileSelectExpression(item, ctx, fns) {
|
|
|
1935
2674
|
const partitionBy = windowExpr.partitionBy.length > 0 ? windowExpr.partitionBy.map((e) => {
|
|
1936
2675
|
const f = expressionToValidatedField(e, ctx) ?? expressionToSql(e);
|
|
1937
2676
|
if (ctx.currentFromTable && !f.includes(".") && !f.includes("(")) {
|
|
1938
|
-
|
|
2677
|
+
validateColumnForTable(ctx, ctx.currentFromTable, f);
|
|
1939
2678
|
}
|
|
1940
2679
|
return f;
|
|
1941
2680
|
}) : void 0;
|
|
1942
2681
|
const orderBy = windowExpr.orderBy.length > 0 ? windowExpr.orderBy.map((o) => {
|
|
1943
2682
|
const f = expressionToValidatedField(o.expression, ctx) ?? expressionToSql(o.expression);
|
|
1944
2683
|
if (ctx.currentFromTable && !f.includes(".") && !f.includes("(")) {
|
|
1945
|
-
|
|
2684
|
+
validateColumnForTable(ctx, ctx.currentFromTable, f);
|
|
1946
2685
|
}
|
|
1947
2686
|
return { field: f, direction: o.direction };
|
|
1948
2687
|
}) : void 0;
|
|
@@ -2060,6 +2799,12 @@ function compileMultiSegmentPath(expr, item, ctx) {
|
|
|
2060
2799
|
const segments = expr.segments;
|
|
2061
2800
|
const firstSegmentLower = segments[0].toLowerCase();
|
|
2062
2801
|
if (ctx.pseudoColumnKeywords.has(firstSegmentLower)) {
|
|
2802
|
+
assertNoBindingRelationConstruct(
|
|
2803
|
+
ctx,
|
|
2804
|
+
ctx.currentFromTable,
|
|
2805
|
+
"use pseudo-column traversals",
|
|
2806
|
+
segments[0]
|
|
2807
|
+
);
|
|
2063
2808
|
const firstSegment = firstSegmentLower;
|
|
2064
2809
|
const depthHint = expr.depthHint;
|
|
2065
2810
|
if (depthHint !== void 0) {
|
|
@@ -2087,7 +2832,7 @@ function compileMultiSegmentPath(expr, item, ctx) {
|
|
|
2087
2832
|
}
|
|
2088
2833
|
const targetColumn = segments[i];
|
|
2089
2834
|
if (ctx.currentFromTable) {
|
|
2090
|
-
|
|
2835
|
+
validateColumnForTable(ctx, ctx.currentFromTable, targetColumn);
|
|
2091
2836
|
}
|
|
2092
2837
|
const defaultAlias = segments.map((s) => s.toLowerCase()).join(".");
|
|
2093
2838
|
if (traversals.length === 1) {
|
|
@@ -2109,6 +2854,12 @@ function compileMultiSegmentPath(expr, item, ctx) {
|
|
|
2109
2854
|
}
|
|
2110
2855
|
const column = segments[segments.length - 1];
|
|
2111
2856
|
const relation = segments.slice(0, -1).join(".");
|
|
2857
|
+
assertNoBindingRelationConstruct(
|
|
2858
|
+
ctx,
|
|
2859
|
+
ctx.currentFromTable,
|
|
2860
|
+
"select relation columns",
|
|
2861
|
+
relation
|
|
2862
|
+
);
|
|
2112
2863
|
if (ctx.currentFromTable && ctx.validator) {
|
|
2113
2864
|
const targetTable = ctx.validator.resolveRelationTarget(
|
|
2114
2865
|
ctx.currentFromTable,
|
|
@@ -2269,6 +3020,220 @@ function allowsInternalParams(options) {
|
|
|
2269
3020
|
const internalOptions = options?.[NQL_INTERNAL_COMPILER_OPTIONS];
|
|
2270
3021
|
return internalOptions?.allowInternalParams === true;
|
|
2271
3022
|
}
|
|
3023
|
+
function isUnresolvedSelectAllOutputSchemaError(error) {
|
|
3024
|
+
return error instanceof NqlSemanticException && error.message.includes("from SELECT *") && error.message.includes("without a concrete table schema");
|
|
3025
|
+
}
|
|
3026
|
+
function programSequenceStepFromResult(result, bindName, final, bindingDependencies) {
|
|
3027
|
+
if (result.query) {
|
|
3028
|
+
return {
|
|
3029
|
+
kind: "query",
|
|
3030
|
+
query: result.query,
|
|
3031
|
+
...bindName !== void 0 && { bindName },
|
|
3032
|
+
final,
|
|
3033
|
+
bindingDependencies
|
|
3034
|
+
};
|
|
3035
|
+
}
|
|
3036
|
+
if (result.mutation) {
|
|
3037
|
+
return {
|
|
3038
|
+
kind: "mutation",
|
|
3039
|
+
mutation: result.mutation,
|
|
3040
|
+
...bindName !== void 0 && { bindName },
|
|
3041
|
+
final,
|
|
3042
|
+
bindingDependencies
|
|
3043
|
+
};
|
|
3044
|
+
}
|
|
3045
|
+
return void 0;
|
|
3046
|
+
}
|
|
3047
|
+
function stringArraysEqual(left, right) {
|
|
3048
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
3049
|
+
}
|
|
3050
|
+
function isNqlAstRecord(value) {
|
|
3051
|
+
return typeof value === "object" && value !== null;
|
|
3052
|
+
}
|
|
3053
|
+
function addReadBindingReference(references, readBindingNames, name) {
|
|
3054
|
+
if (typeof name === "string" && readBindingNames.has(name)) {
|
|
3055
|
+
references.add(name);
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
3058
|
+
function withoutLocalCteNames(readBindingNames, ctes) {
|
|
3059
|
+
if (ctes.length === 0) return readBindingNames;
|
|
3060
|
+
const localCteNames = new Set(ctes.map((cte) => cte.name));
|
|
3061
|
+
if (![...localCteNames].some((name) => readBindingNames.has(name))) {
|
|
3062
|
+
return readBindingNames;
|
|
3063
|
+
}
|
|
3064
|
+
return new Set(
|
|
3065
|
+
[...readBindingNames].filter((name) => !localCteNames.has(name))
|
|
3066
|
+
);
|
|
3067
|
+
}
|
|
3068
|
+
function collectQueryReadBindingReferences(query, readBindingNames, references) {
|
|
3069
|
+
addReadBindingReference(references, readBindingNames, query.table);
|
|
3070
|
+
collectNodeReadBindingReferences(query.clauses, readBindingNames, references);
|
|
3071
|
+
}
|
|
3072
|
+
function collectWithQueryReadBindingReferences(withQuery, readBindingNames, references) {
|
|
3073
|
+
const scopedReadBindingNames = withoutLocalCteNames(
|
|
3074
|
+
readBindingNames,
|
|
3075
|
+
withQuery.ctes
|
|
3076
|
+
);
|
|
3077
|
+
for (const cte of withQuery.ctes) {
|
|
3078
|
+
collectQueryReadBindingReferences(
|
|
3079
|
+
cte.query,
|
|
3080
|
+
scopedReadBindingNames,
|
|
3081
|
+
references
|
|
3082
|
+
);
|
|
3083
|
+
}
|
|
3084
|
+
collectQueryReadBindingReferences(
|
|
3085
|
+
withQuery.query,
|
|
3086
|
+
scopedReadBindingNames,
|
|
3087
|
+
references
|
|
3088
|
+
);
|
|
3089
|
+
}
|
|
3090
|
+
function collectInListReadBindingReference(values, readBindingNames, references) {
|
|
3091
|
+
if (!Array.isArray(values) || values.length !== 1) return;
|
|
3092
|
+
const value = values[0];
|
|
3093
|
+
if (!isNqlAstRecord(value) || value.type !== "path") return;
|
|
3094
|
+
const segments = value.segments;
|
|
3095
|
+
if (!Array.isArray(segments) || segments.length !== 1) return;
|
|
3096
|
+
addReadBindingReference(references, readBindingNames, segments[0]);
|
|
3097
|
+
}
|
|
3098
|
+
function collectNodeReadBindingReferences(node, readBindingNames, references) {
|
|
3099
|
+
if (Array.isArray(node)) {
|
|
3100
|
+
for (const item of node) {
|
|
3101
|
+
collectNodeReadBindingReferences(item, readBindingNames, references);
|
|
3102
|
+
}
|
|
3103
|
+
return;
|
|
3104
|
+
}
|
|
3105
|
+
if (!isNqlAstRecord(node)) return;
|
|
3106
|
+
switch (node.type) {
|
|
3107
|
+
case "query":
|
|
3108
|
+
collectQueryReadBindingReferences(
|
|
3109
|
+
node,
|
|
3110
|
+
readBindingNames,
|
|
3111
|
+
references
|
|
3112
|
+
);
|
|
3113
|
+
return;
|
|
3114
|
+
case "withQuery":
|
|
3115
|
+
collectWithQueryReadBindingReferences(
|
|
3116
|
+
node,
|
|
3117
|
+
readBindingNames,
|
|
3118
|
+
references
|
|
3119
|
+
);
|
|
3120
|
+
return;
|
|
3121
|
+
case "mutationPipeline":
|
|
3122
|
+
collectNodeReadBindingReferences(
|
|
3123
|
+
node.mutation,
|
|
3124
|
+
readBindingNames,
|
|
3125
|
+
references
|
|
3126
|
+
);
|
|
3127
|
+
collectNodeReadBindingReferences(
|
|
3128
|
+
node.clauses,
|
|
3129
|
+
readBindingNames,
|
|
3130
|
+
references
|
|
3131
|
+
);
|
|
3132
|
+
return;
|
|
3133
|
+
case "insert_from":
|
|
3134
|
+
case "upsert_from":
|
|
3135
|
+
addReadBindingReference(references, readBindingNames, node.source);
|
|
3136
|
+
collectNodeReadBindingReferences(
|
|
3137
|
+
node.where,
|
|
3138
|
+
readBindingNames,
|
|
3139
|
+
references
|
|
3140
|
+
);
|
|
3141
|
+
return;
|
|
3142
|
+
case "setOperation":
|
|
3143
|
+
addReadBindingReference(references, readBindingNames, node.boundName);
|
|
3144
|
+
collectNodeReadBindingReferences(
|
|
3145
|
+
node.right,
|
|
3146
|
+
readBindingNames,
|
|
3147
|
+
references
|
|
3148
|
+
);
|
|
3149
|
+
return;
|
|
3150
|
+
case "in":
|
|
3151
|
+
collectNodeReadBindingReferences(
|
|
3152
|
+
node.expression,
|
|
3153
|
+
readBindingNames,
|
|
3154
|
+
references
|
|
3155
|
+
);
|
|
3156
|
+
collectInListReadBindingReference(
|
|
3157
|
+
node.values,
|
|
3158
|
+
readBindingNames,
|
|
3159
|
+
references
|
|
3160
|
+
);
|
|
3161
|
+
collectNodeReadBindingReferences(
|
|
3162
|
+
node.values,
|
|
3163
|
+
readBindingNames,
|
|
3164
|
+
references
|
|
3165
|
+
);
|
|
3166
|
+
return;
|
|
3167
|
+
case "subquery":
|
|
3168
|
+
collectNodeReadBindingReferences(
|
|
3169
|
+
node.query,
|
|
3170
|
+
readBindingNames,
|
|
3171
|
+
references
|
|
3172
|
+
);
|
|
3173
|
+
return;
|
|
3174
|
+
}
|
|
3175
|
+
for (const child of Object.values(node)) {
|
|
3176
|
+
collectNodeReadBindingReferences(child, readBindingNames, references);
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
function collectStatementReadBindingReferences(stmt, readBindingNames) {
|
|
3180
|
+
const references = /* @__PURE__ */ new Set();
|
|
3181
|
+
if (readBindingNames.size === 0) return references;
|
|
3182
|
+
collectNodeReadBindingReferences(stmt, readBindingNames, references);
|
|
3183
|
+
return references;
|
|
3184
|
+
}
|
|
3185
|
+
function collectCompileResultReadBindingReferences(result, readBindingNames, references) {
|
|
3186
|
+
if (result.query) {
|
|
3187
|
+
addReadBindingReference(references, readBindingNames, result.query.from);
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
function addTransitiveBindingDependency(bindName, bindingDependencies, ordered, seen) {
|
|
3191
|
+
if (seen.has(bindName)) return;
|
|
3192
|
+
seen.add(bindName);
|
|
3193
|
+
for (const dependency of bindingDependencies.get(bindName) ?? []) {
|
|
3194
|
+
addTransitiveBindingDependency(
|
|
3195
|
+
dependency,
|
|
3196
|
+
bindingDependencies,
|
|
3197
|
+
ordered,
|
|
3198
|
+
seen
|
|
3199
|
+
);
|
|
3200
|
+
}
|
|
3201
|
+
ordered.push(bindName);
|
|
3202
|
+
}
|
|
3203
|
+
function collectTransitiveReadBindingDependencies(directReferences, bindingDependencies) {
|
|
3204
|
+
const ordered = [];
|
|
3205
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3206
|
+
for (const reference of directReferences) {
|
|
3207
|
+
addTransitiveBindingDependency(
|
|
3208
|
+
reference,
|
|
3209
|
+
bindingDependencies,
|
|
3210
|
+
ordered,
|
|
3211
|
+
seen
|
|
3212
|
+
);
|
|
3213
|
+
}
|
|
3214
|
+
return ordered;
|
|
3215
|
+
}
|
|
3216
|
+
function rejectReadBindingReferenceAcrossMutation(bindName, definitionIndex, mutationIndex, referenceIndex, statementCount) {
|
|
3217
|
+
throw new NqlSemanticException(
|
|
3218
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
3219
|
+
`read binding referenced across a mutation (#186): binding '${bindName}' is defined by read-only statement ${definitionIndex + 1} of ${statementCount} and referenced by statement ${referenceIndex + 1} after mutation statement ${mutationIndex + 1}. Read-only bindings are not materialized snapshots; move the reference before the mutation or bind the mutation RETURNING result instead.`
|
|
3220
|
+
);
|
|
3221
|
+
}
|
|
3222
|
+
function rejectInvalidReadBindingDependencies(statementBindingDependencies, readBindingDefinitions, lastMutationStatement, referenceIndex, statementCount) {
|
|
3223
|
+
for (const referencedBindName of statementBindingDependencies) {
|
|
3224
|
+
const definitionIndex = readBindingDefinitions.get(referencedBindName);
|
|
3225
|
+
if (definitionIndex === void 0) continue;
|
|
3226
|
+
if (lastMutationStatement > definitionIndex) {
|
|
3227
|
+
rejectReadBindingReferenceAcrossMutation(
|
|
3228
|
+
referencedBindName,
|
|
3229
|
+
definitionIndex,
|
|
3230
|
+
lastMutationStatement,
|
|
3231
|
+
referenceIndex,
|
|
3232
|
+
statementCount
|
|
3233
|
+
);
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
2272
3237
|
var NqlCompiler = class {
|
|
2273
3238
|
ctx;
|
|
2274
3239
|
fns;
|
|
@@ -2292,9 +3257,11 @@ var NqlCompiler = class {
|
|
|
2292
3257
|
this.ctx = {
|
|
2293
3258
|
currentFromTable: void 0,
|
|
2294
3259
|
currentRelationTarget: void 0,
|
|
3260
|
+
currentHavingAliases: void 0,
|
|
2295
3261
|
pseudoColumnKeywords,
|
|
2296
3262
|
recursiveKeywords,
|
|
2297
3263
|
validator,
|
|
3264
|
+
bindingOutputColumns: /* @__PURE__ */ new Map(),
|
|
2298
3265
|
params,
|
|
2299
3266
|
maxAnyItems: maxAnyItemsRaw ?? MAX_ANY_ITEMS,
|
|
2300
3267
|
allowUnfilteredMutations: options?.allowUnfilteredMutations ?? false,
|
|
@@ -2310,6 +3277,14 @@ var NqlCompiler = class {
|
|
|
2310
3277
|
* Compile an NQL program to IntentAST.
|
|
2311
3278
|
*/
|
|
2312
3279
|
compile(program) {
|
|
3280
|
+
try {
|
|
3281
|
+
return this.compileProgram(program);
|
|
3282
|
+
} finally {
|
|
3283
|
+
this.ctx.bindingOutputColumns.clear();
|
|
3284
|
+
this.ctx.validator?.clearVirtualBindingTables();
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
compileProgram(program) {
|
|
2313
3288
|
if (program.statements.length === 0) {
|
|
2314
3289
|
return {};
|
|
2315
3290
|
}
|
|
@@ -2326,30 +3301,121 @@ var NqlCompiler = class {
|
|
|
2326
3301
|
}
|
|
2327
3302
|
}
|
|
2328
3303
|
const bindings = /* @__PURE__ */ new Map();
|
|
3304
|
+
const bindingOutputSchemas = /* @__PURE__ */ new Map();
|
|
2329
3305
|
const mutationBindings = /* @__PURE__ */ new Map();
|
|
2330
3306
|
const materializedBindStatements = /* @__PURE__ */ new Set();
|
|
3307
|
+
const seenBindNames = /* @__PURE__ */ new Map();
|
|
3308
|
+
const nqlProgramSequence = [];
|
|
3309
|
+
const readBindingDefinitions = /* @__PURE__ */ new Map();
|
|
3310
|
+
const definedBindingNames = /* @__PURE__ */ new Set();
|
|
3311
|
+
const bindingDependencies = /* @__PURE__ */ new Map();
|
|
3312
|
+
let lastMutationStatement = -1;
|
|
2331
3313
|
let lastResult = {};
|
|
2332
3314
|
for (let i = 0; i < program.statements.length; i++) {
|
|
2333
3315
|
const stmt = program.statements[i];
|
|
2334
|
-
lastResult = this.compileSingleStatement(stmt, bindings);
|
|
2335
3316
|
const bindName = extractBindName(stmt);
|
|
3317
|
+
if (bindName) {
|
|
3318
|
+
const previousStatement = seenBindNames.get(bindName);
|
|
3319
|
+
if (previousStatement !== void 0) {
|
|
3320
|
+
throw new NqlSemanticException(
|
|
3321
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
3322
|
+
`NQL binding name '${bindName}' is used more than once (statements ${previousStatement + 1} and ${i + 1}). NQL binding names must be unique.`
|
|
3323
|
+
);
|
|
3324
|
+
}
|
|
3325
|
+
seenBindNames.set(bindName, i);
|
|
3326
|
+
}
|
|
3327
|
+
const readBindingReferences = collectStatementReadBindingReferences(
|
|
3328
|
+
stmt,
|
|
3329
|
+
definedBindingNames
|
|
3330
|
+
);
|
|
3331
|
+
let statementBindingDependencies = collectTransitiveReadBindingDependencies(
|
|
3332
|
+
readBindingReferences,
|
|
3333
|
+
bindingDependencies
|
|
3334
|
+
);
|
|
3335
|
+
rejectInvalidReadBindingDependencies(
|
|
3336
|
+
statementBindingDependencies,
|
|
3337
|
+
readBindingDefinitions,
|
|
3338
|
+
lastMutationStatement,
|
|
3339
|
+
i,
|
|
3340
|
+
program.statements.length
|
|
3341
|
+
);
|
|
3342
|
+
lastResult = this.compileSingleStatement(stmt, bindings);
|
|
3343
|
+
collectCompileResultReadBindingReferences(
|
|
3344
|
+
lastResult,
|
|
3345
|
+
definedBindingNames,
|
|
3346
|
+
readBindingReferences
|
|
3347
|
+
);
|
|
3348
|
+
statementBindingDependencies = collectTransitiveReadBindingDependencies(
|
|
3349
|
+
readBindingReferences,
|
|
3350
|
+
bindingDependencies
|
|
3351
|
+
);
|
|
3352
|
+
rejectInvalidReadBindingDependencies(
|
|
3353
|
+
statementBindingDependencies,
|
|
3354
|
+
readBindingDefinitions,
|
|
3355
|
+
lastMutationStatement,
|
|
3356
|
+
i,
|
|
3357
|
+
program.statements.length
|
|
3358
|
+
);
|
|
3359
|
+
if (stmt.type === "mutationPipeline") {
|
|
3360
|
+
lastMutationStatement = i;
|
|
3361
|
+
}
|
|
2336
3362
|
if (bindName) {
|
|
2337
3363
|
if (lastResult.query) {
|
|
3364
|
+
const outputSchema = this.registerQueryBindingOutputSchema(
|
|
3365
|
+
bindName,
|
|
3366
|
+
lastResult.query,
|
|
3367
|
+
bindingOutputSchemas
|
|
3368
|
+
);
|
|
2338
3369
|
bindings.set(bindName, lastResult.query);
|
|
3370
|
+
if (outputSchema) {
|
|
3371
|
+
this.ctx.validator?.addVirtualBindingTable(
|
|
3372
|
+
bindName,
|
|
3373
|
+
outputSchema.columns
|
|
3374
|
+
);
|
|
3375
|
+
}
|
|
2339
3376
|
materializedBindStatements.add(i);
|
|
3377
|
+
readBindingDefinitions.set(bindName, i);
|
|
3378
|
+
definedBindingNames.add(bindName);
|
|
3379
|
+
bindingDependencies.set(bindName, statementBindingDependencies);
|
|
2340
3380
|
} else if (lastResult.mutation?.returning?.length) {
|
|
2341
|
-
|
|
3381
|
+
const canonicalBinding = this.canonicalizeMutationBinding(
|
|
3382
|
+
bindName,
|
|
3383
|
+
lastResult.mutation
|
|
3384
|
+
);
|
|
3385
|
+
lastResult = { mutation: canonicalBinding.mutation };
|
|
3386
|
+
mutationBindings.set(bindName, canonicalBinding.mutation);
|
|
3387
|
+
const outputSchema = canonicalBinding.outputSchema;
|
|
3388
|
+
const bindingFields = outputSchema?.columns ?? canonicalBinding.mutation.returning ?? [];
|
|
3389
|
+
this.ctx.bindingOutputColumns.set(bindName, outputSchema?.columns);
|
|
2342
3390
|
bindings.set(bindName, {
|
|
2343
3391
|
type: "select",
|
|
2344
|
-
from:
|
|
3392
|
+
from: canonicalBinding.mutation.table,
|
|
2345
3393
|
select: {
|
|
2346
3394
|
type: "fields",
|
|
2347
|
-
fields: [...
|
|
3395
|
+
fields: [...bindingFields]
|
|
2348
3396
|
}
|
|
2349
3397
|
});
|
|
3398
|
+
if (outputSchema) {
|
|
3399
|
+
bindingOutputSchemas.set(bindName, outputSchema);
|
|
3400
|
+
this.ctx.validator?.addVirtualBindingTable(
|
|
3401
|
+
bindName,
|
|
3402
|
+
outputSchema.columns
|
|
3403
|
+
);
|
|
3404
|
+
}
|
|
2350
3405
|
materializedBindStatements.add(i);
|
|
3406
|
+
definedBindingNames.add(bindName);
|
|
3407
|
+
bindingDependencies.set(bindName, []);
|
|
2351
3408
|
}
|
|
2352
3409
|
}
|
|
3410
|
+
const sequenceStep = programSequenceStepFromResult(
|
|
3411
|
+
lastResult,
|
|
3412
|
+
bindName,
|
|
3413
|
+
i === program.statements.length - 1,
|
|
3414
|
+
statementBindingDependencies
|
|
3415
|
+
);
|
|
3416
|
+
if (sequenceStep !== void 0) {
|
|
3417
|
+
nqlProgramSequence.push(sequenceStep);
|
|
3418
|
+
}
|
|
2353
3419
|
}
|
|
2354
3420
|
for (let i = 0; i < program.statements.length - 1; i++) {
|
|
2355
3421
|
const bindName = extractBindName(program.statements[i]);
|
|
@@ -2360,15 +3426,68 @@ var NqlCompiler = class {
|
|
|
2360
3426
|
}
|
|
2361
3427
|
}
|
|
2362
3428
|
const hasMutationBindings = mutationBindings.size > 0;
|
|
3429
|
+
const hasBindingOutputSchemas = bindingOutputSchemas.size > 0;
|
|
3430
|
+
const hasNqlProgramSequence = nqlProgramSequence.length === program.statements.length;
|
|
2363
3431
|
if (bindings.size > 0) {
|
|
2364
3432
|
return {
|
|
2365
3433
|
...lastResult,
|
|
2366
3434
|
bindings,
|
|
2367
|
-
...
|
|
3435
|
+
...hasBindingOutputSchemas && { bindingOutputSchemas },
|
|
3436
|
+
...hasMutationBindings && { mutationBindings },
|
|
3437
|
+
...hasNqlProgramSequence && { nqlProgramSequence }
|
|
2368
3438
|
};
|
|
2369
3439
|
}
|
|
2370
3440
|
return lastResult;
|
|
2371
3441
|
}
|
|
3442
|
+
registerQueryBindingOutputSchema(bindName, query, bindingOutputSchemas) {
|
|
3443
|
+
try {
|
|
3444
|
+
const outputSchema = getQueryOutputSchema(query, this.ctx, bindName);
|
|
3445
|
+
bindingOutputSchemas.set(bindName, outputSchema);
|
|
3446
|
+
this.ctx.bindingOutputColumns.set(bindName, outputSchema.columns);
|
|
3447
|
+
return outputSchema;
|
|
3448
|
+
} catch (error) {
|
|
3449
|
+
if (!this.ctx.validator && isUnresolvedSelectAllOutputSchemaError(error)) {
|
|
3450
|
+
this.ctx.bindingOutputColumns.set(bindName, void 0);
|
|
3451
|
+
return void 0;
|
|
3452
|
+
}
|
|
3453
|
+
throw error;
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
canonicalizeMutationBinding(bindName, mutation) {
|
|
3457
|
+
const outputSchema = this.getMutationBindingOutputSchema(
|
|
3458
|
+
bindName,
|
|
3459
|
+
mutation
|
|
3460
|
+
);
|
|
3461
|
+
const returning = mutation.returning;
|
|
3462
|
+
if (returning === void 0 || returning.length === 0 || returning.includes("*") || outputSchema === void 0 || stringArraysEqual(returning, outputSchema.columns)) {
|
|
3463
|
+
return { mutation, outputSchema };
|
|
3464
|
+
}
|
|
3465
|
+
return {
|
|
3466
|
+
mutation: {
|
|
3467
|
+
...mutation,
|
|
3468
|
+
returning: outputSchema.columns
|
|
3469
|
+
},
|
|
3470
|
+
outputSchema
|
|
3471
|
+
};
|
|
3472
|
+
}
|
|
3473
|
+
getMutationBindingOutputSchema(bindName, mutation) {
|
|
3474
|
+
const returning = mutation.returning;
|
|
3475
|
+
if (!returning || returning.length === 0) return void 0;
|
|
3476
|
+
if (returning.includes("*")) {
|
|
3477
|
+
const columns = this.ctx.validator?.getTableColumns(mutation.table);
|
|
3478
|
+
if (columns !== void 0) return { columns };
|
|
3479
|
+
if (!this.ctx.validator) return void 0;
|
|
3480
|
+
throw new NqlSemanticException(
|
|
3481
|
+
NqlErrorCodes.SEM_INVALID_SYNTAX,
|
|
3482
|
+
`Cannot compute output schema for NQL binding '${bindName}' from mutation RETURNING * on '${mutation.table}' without a concrete table schema.`
|
|
3483
|
+
);
|
|
3484
|
+
}
|
|
3485
|
+
return {
|
|
3486
|
+
columns: returning.map(
|
|
3487
|
+
(column) => this.ctx.validator?.resolveColumnName(mutation.table, column) ?? column
|
|
3488
|
+
)
|
|
3489
|
+
};
|
|
3490
|
+
}
|
|
2372
3491
|
compileSingleStatement(stmt, bindings) {
|
|
2373
3492
|
if (stmt.type === "withQuery") {
|
|
2374
3493
|
return compileWithQuery(stmt, this.ctx, this.fns);
|