@minnowdb/core 0.7.1 → 0.7.5
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/engine/database.d.ts +6 -0
- package/dist/engine/database.js +391 -93
- package/dist/engine/optimizer.js +168 -87
- package/dist/engine/query.d.ts +98 -0
- package/dist/engine/query.js +1457 -162
- package/dist/engine/sql-domains.d.ts +11 -0
- package/dist/engine/sql-domains.js +56 -0
- package/dist/engine/sql-functions.js +129 -3
- package/dist/engine/sql-json.d.ts +2 -0
- package/dist/engine/sql-json.js +4 -0
- package/dist/engine/sql-semantics.d.ts +9 -1
- package/dist/engine/sql-semantics.js +19 -3
- package/dist/engine/vector.js +29 -13
- package/dist/plan/model.d.ts +14 -2
- package/dist/storage/indexeddb.js +9 -1
- package/dist/storage/memory.d.ts +2 -1
- package/dist/storage/memory.js +29 -4
- package/dist/storage/toolkit/record-core.js +15 -3
- package/dist/testing/sqllogictest.js +3 -1
- package/package.json +1 -1
- package/postgres-feature-profile.json +9 -4
- package/sql-feature-matrix.json +543 -6
package/dist/engine/query.js
CHANGED
|
@@ -6,12 +6,12 @@ import { SqlCompileError } from "./errors.js";
|
|
|
6
6
|
import { cachedQueryTerms, ftsBm25Row, FtsStatsAccumulator, ftsMatchTruth, renderDocumentValue, tokenize as ftsTokenize, validateFtsQuery } from "./fts.js";
|
|
7
7
|
import { QueryMemoryContext } from "./memory.js";
|
|
8
8
|
import { buildSortKeyColumn, sortKeyIndexes } from "./sort-keys.js";
|
|
9
|
-
import { coerceComparisonOperands, coercedComparable, parseSqlTimestampText, stringArgument } from "./sql-semantics.js";
|
|
9
|
+
import { coerceComparisonOperands, coercedComparable, parseSqlTimestampText, stringArgument, readUntypedText } from "./sql-semantics.js";
|
|
10
10
|
import { simpleScalarFunctions } from "./sql-functions.js";
|
|
11
|
-
import { jsonArrowStep, jsonAtPath, jsonConstructor, jsonIsValid, parseJsonPath } from "./sql-json.js";
|
|
11
|
+
import { jsonArrowStep, jsonAtPath, jsonConstructor, jsonIsValid, parseJsonPath, jsonDocumentOf } from "./sql-json.js";
|
|
12
12
|
import { optimizePlan, rewriteBoundCalendarEqualities } from "./optimizer.js";
|
|
13
13
|
import { compareSqlValues as compareValues, compileLikePattern, compileSimilarPattern, encodeSqlEqualityValue, roundSqlNumber } from "./sql-semantics.js";
|
|
14
|
-
import { arrayDomainValue, boundedJsonText, collatedDomainValue, concatenatedSqlValue, dateDomainValue, exactNumericAsNumber, exactNumericBinary, exactNumericLiteral, exactNumericValue, externalSqlDomainColumnValue, externalSqlDomainValue, intervalDomainValue, isDateDomainValue, isExactNumeric, isSqlDomainValue, jsonDomainValue, normalizeSqlDomainValue, preservedJsonDomainValue, protectedSqlTextValue, timeDomainValue, uuidDomainValue } from "./sql-domains.js";
|
|
14
|
+
import { arrayDomainValue, boundedJsonText, collatedDomainValue, concatenatedSqlValue, dateDomainValue, exactNumericAsNumber, exactNumericBinary, exactNumericLiteral, decimalScaleOfNumber, exactNumericRounded, exactNumericUnary, exactNumericValue, externalSqlDomainColumnValue, externalSqlDomainValue, intervalDomainValue, isDateDomainValue, isExactNumeric, isSqlDomainValue, jsonDomainValue, normalizeSqlDomainValue, preservedJsonDomainValue, protectedSqlTextValue, timeDomainValue, uuidDomainValue } from "./sql-domains.js";
|
|
15
15
|
import { columnarTableFromRows, prepareVectorQuery } from "./vector.js";
|
|
16
16
|
function unknownColumnDomains(columns) {
|
|
17
17
|
return columns.map(() => null);
|
|
@@ -61,6 +61,7 @@ const scalarFunctionNames = /* @__PURE__ */ new Set([
|
|
|
61
61
|
"JSON_EXISTS",
|
|
62
62
|
"JSON_OBJECT",
|
|
63
63
|
"JSON_ARRAY",
|
|
64
|
+
"TO_JSON",
|
|
64
65
|
"IS_JSON",
|
|
65
66
|
"ARRAY",
|
|
66
67
|
"MINNOW_JSON_GET",
|
|
@@ -87,13 +88,23 @@ const functionSpellings = /* @__PURE__ */ new Map([
|
|
|
87
88
|
["DATE_PART", "EXTRACT"],
|
|
88
89
|
["CEILING", "CEIL"],
|
|
89
90
|
["CHAR_LENGTH", "LENGTH"],
|
|
90
|
-
["CHARACTER_LENGTH", "LENGTH"]
|
|
91
|
+
["CHARACTER_LENGTH", "LENGTH"],
|
|
92
|
+
["POW", "POWER"],
|
|
93
|
+
["JSON_AGG", "JSON_ARRAYAGG"],
|
|
94
|
+
["JSONB_AGG", "JSON_ARRAYAGG"],
|
|
95
|
+
["JSON_BUILD_ARRAY", "JSON_ARRAY"],
|
|
96
|
+
["JSONB_BUILD_ARRAY", "JSON_ARRAY"],
|
|
97
|
+
["TO_JSONB", "TO_JSON"],
|
|
98
|
+
["ROW_TO_JSON", "TO_JSON"]
|
|
91
99
|
]);
|
|
92
100
|
const statementDatetimeAliases = /* @__PURE__ */ new Map([
|
|
93
101
|
["CURRENT_DATE", "CURRENT_DATE"],
|
|
94
102
|
["CURRENT_TIMESTAMP", "CURRENT_TIMESTAMP"],
|
|
95
103
|
["LOCALTIMESTAMP", "CURRENT_TIMESTAMP"],
|
|
96
104
|
["NOW", "CURRENT_TIMESTAMP"],
|
|
105
|
+
["TRANSACTION_TIMESTAMP", "CURRENT_TIMESTAMP"],
|
|
106
|
+
["STATEMENT_TIMESTAMP", "CURRENT_TIMESTAMP"],
|
|
107
|
+
["CLOCK_TIMESTAMP", "CURRENT_TIMESTAMP"],
|
|
97
108
|
["CURRENT_TIME", "LOCALTIME"],
|
|
98
109
|
["LOCALTIME", "LOCALTIME"]
|
|
99
110
|
]);
|
|
@@ -256,17 +267,23 @@ function buildScalarFunctionEvaluator(name) {
|
|
|
256
267
|
if (values.length > 1 && (values[1] === null || values[1] === void 0))
|
|
257
268
|
return null;
|
|
258
269
|
const digits = values.length > 1 ? numeric(values[1]) : 0;
|
|
259
|
-
if (isExactNumeric(first) && Number.isInteger(digits)
|
|
260
|
-
return
|
|
270
|
+
if (isExactNumeric(first) && Number.isInteger(digits)) {
|
|
271
|
+
return exactNumericRounded(first, digits, "round");
|
|
261
272
|
}
|
|
262
273
|
return roundSqlNumber(numeric(first), digits);
|
|
263
274
|
};
|
|
264
275
|
case "FLOOR":
|
|
265
|
-
return (values) => values[0] === null || values[0] === void 0 ? null : Math.floor(numeric(values[0]));
|
|
266
276
|
case "CEIL":
|
|
267
|
-
return (values) => values[0] === null || values[0] === void 0 ? null : Math.ceil(numeric(values[0]));
|
|
268
277
|
case "ABS":
|
|
269
|
-
return (values) =>
|
|
278
|
+
return (values) => {
|
|
279
|
+
const first = values[0];
|
|
280
|
+
if (first === null || first === void 0)
|
|
281
|
+
return null;
|
|
282
|
+
if (isExactNumeric(first))
|
|
283
|
+
return exactNumericUnary(name, first);
|
|
284
|
+
const operand = numeric(first);
|
|
285
|
+
return name === "FLOOR" ? Math.floor(operand) : name === "CEIL" ? Math.ceil(operand) : Math.abs(operand);
|
|
286
|
+
};
|
|
270
287
|
case "UPPER":
|
|
271
288
|
return (values) => {
|
|
272
289
|
const first = values[0];
|
|
@@ -355,12 +372,15 @@ function scalarFunctionValueGeneric(name, values) {
|
|
|
355
372
|
return comparable(first) === comparable(other) ? null : first;
|
|
356
373
|
}
|
|
357
374
|
case "FLOOR":
|
|
358
|
-
return Math.floor(numeric(first));
|
|
375
|
+
return isExactNumeric(first) ? exactNumericUnary("FLOOR", first) : Math.floor(numeric(first));
|
|
359
376
|
case "CEIL":
|
|
360
|
-
return Math.ceil(numeric(first));
|
|
377
|
+
return isExactNumeric(first) ? exactNumericUnary("CEIL", first) : Math.ceil(numeric(first));
|
|
361
378
|
case "MOD": {
|
|
362
379
|
if (values[1] === null || values[1] === void 0)
|
|
363
380
|
return null;
|
|
381
|
+
const exact = exactNumericBinary("%", first, values[1]);
|
|
382
|
+
if (exact !== void 0)
|
|
383
|
+
return exact;
|
|
364
384
|
const divisor = numeric(values[1]);
|
|
365
385
|
return divisor === 0 ? null : numeric(first) % divisor;
|
|
366
386
|
}
|
|
@@ -412,13 +432,13 @@ function scalarFunctionValueGeneric(name, values) {
|
|
|
412
432
|
if (values.length > 1 && (values[1] === null || values[1] === void 0))
|
|
413
433
|
return null;
|
|
414
434
|
const digits = values.length > 1 ? numeric(values[1]) : 0;
|
|
415
|
-
if (isExactNumeric(first) && Number.isInteger(digits)
|
|
416
|
-
return
|
|
435
|
+
if (isExactNumeric(first) && Number.isInteger(digits)) {
|
|
436
|
+
return exactNumericRounded(first, digits, "round");
|
|
417
437
|
}
|
|
418
438
|
return roundSqlNumber(numeric(first), digits);
|
|
419
439
|
}
|
|
420
440
|
case "ABS":
|
|
421
|
-
return Math.abs(numeric(first));
|
|
441
|
+
return isExactNumeric(first) ? exactNumericUnary("ABS", first) : Math.abs(numeric(first));
|
|
422
442
|
case "UPPER": {
|
|
423
443
|
const source = stringArgument("UPPER", first);
|
|
424
444
|
assertScalarInputLength(source, "UPPER input");
|
|
@@ -458,6 +478,8 @@ function scalarFunctionValueGeneric(name, values) {
|
|
|
458
478
|
return null;
|
|
459
479
|
return preservedJsonDomainValue(JSON.stringify(found.value));
|
|
460
480
|
}
|
|
481
|
+
case "TO_JSON":
|
|
482
|
+
return preservedJsonDomainValue(jsonDocumentOf(first));
|
|
461
483
|
case "MINNOW_JSON_GET": {
|
|
462
484
|
if (values[1] === null || values[1] === void 0)
|
|
463
485
|
return null;
|
|
@@ -767,20 +789,36 @@ const createTableTypeNames = /* @__PURE__ */ new Map([
|
|
|
767
789
|
["BOOL", "boolean"],
|
|
768
790
|
["INTEGER", "number"],
|
|
769
791
|
["INT", "number"],
|
|
792
|
+
["INT4", "number"],
|
|
770
793
|
["BIGINT", "number"],
|
|
794
|
+
["INT8", "number"],
|
|
771
795
|
["SMALLINT", "number"],
|
|
796
|
+
["INT2", "number"],
|
|
772
797
|
["REAL", "number"],
|
|
798
|
+
["FLOAT4", "number"],
|
|
773
799
|
["FLOAT", "number"],
|
|
800
|
+
["FLOAT8", "number"],
|
|
774
801
|
["TEXT", "string"],
|
|
775
802
|
["VARCHAR", "string"],
|
|
776
803
|
["CHAR", "string"],
|
|
804
|
+
["CHARACTER", "string"],
|
|
777
805
|
["STRING", "string"],
|
|
778
806
|
["TIMESTAMP", "datetime"],
|
|
779
807
|
["TIMESTAMPTZ", "datetime"],
|
|
780
808
|
["DATETIME", "datetime"]
|
|
781
809
|
]);
|
|
810
|
+
const integerTypeNames = /* @__PURE__ */ new Set([
|
|
811
|
+
"INTEGER",
|
|
812
|
+
"INT",
|
|
813
|
+
"INT4",
|
|
814
|
+
"BIGINT",
|
|
815
|
+
"INT8",
|
|
816
|
+
"SMALLINT",
|
|
817
|
+
"INT2"
|
|
818
|
+
]);
|
|
782
819
|
const clauseKeywords = /* @__PURE__ */ new Set([
|
|
783
820
|
"WHERE",
|
|
821
|
+
"FOR",
|
|
784
822
|
"NATURAL",
|
|
785
823
|
"USING",
|
|
786
824
|
"OUTER",
|
|
@@ -802,6 +840,72 @@ const clauseKeywords = /* @__PURE__ */ new Set([
|
|
|
802
840
|
"UNION",
|
|
803
841
|
"RETURNING"
|
|
804
842
|
]);
|
|
843
|
+
const bareLabelStopWords = /* @__PURE__ */ new Set([
|
|
844
|
+
...clauseKeywords,
|
|
845
|
+
"ALL",
|
|
846
|
+
"AND",
|
|
847
|
+
"ANY",
|
|
848
|
+
"ARRAY",
|
|
849
|
+
"AS",
|
|
850
|
+
"ASC",
|
|
851
|
+
"AT",
|
|
852
|
+
"BETWEEN",
|
|
853
|
+
"BOTH",
|
|
854
|
+
"CASE",
|
|
855
|
+
"CAST",
|
|
856
|
+
"CHECK",
|
|
857
|
+
"COLLATE",
|
|
858
|
+
"COLUMN",
|
|
859
|
+
"CONSTRAINT",
|
|
860
|
+
"CREATE",
|
|
861
|
+
"DEFAULT",
|
|
862
|
+
"DESC",
|
|
863
|
+
"DISTINCT",
|
|
864
|
+
"DO",
|
|
865
|
+
"ELSE",
|
|
866
|
+
"END",
|
|
867
|
+
"ESCAPE",
|
|
868
|
+
"FALSE",
|
|
869
|
+
"FILTER",
|
|
870
|
+
"FOR",
|
|
871
|
+
"FOREIGN",
|
|
872
|
+
"FROM",
|
|
873
|
+
"GRANT",
|
|
874
|
+
"ILIKE",
|
|
875
|
+
"IN",
|
|
876
|
+
"INTO",
|
|
877
|
+
"IS",
|
|
878
|
+
"ISNULL",
|
|
879
|
+
"LATERAL",
|
|
880
|
+
"LEADING",
|
|
881
|
+
"LIKE",
|
|
882
|
+
"NOT",
|
|
883
|
+
"NOTNULL",
|
|
884
|
+
"NULL",
|
|
885
|
+
"ON",
|
|
886
|
+
"ONLY",
|
|
887
|
+
"OR",
|
|
888
|
+
"OVER",
|
|
889
|
+
"OVERLAPS",
|
|
890
|
+
"PLACING",
|
|
891
|
+
"PRIMARY",
|
|
892
|
+
"REFERENCES",
|
|
893
|
+
"SELECT",
|
|
894
|
+
"SIMILAR",
|
|
895
|
+
"SOME",
|
|
896
|
+
"TABLE",
|
|
897
|
+
"THEN",
|
|
898
|
+
"TO",
|
|
899
|
+
"TRAILING",
|
|
900
|
+
"TRUE",
|
|
901
|
+
"UNIQUE",
|
|
902
|
+
"USER",
|
|
903
|
+
"VALUES",
|
|
904
|
+
"WHEN",
|
|
905
|
+
"WITH",
|
|
906
|
+
"WITHIN",
|
|
907
|
+
"WITHOUT"
|
|
908
|
+
]);
|
|
805
909
|
const aggregateNames = /* @__PURE__ */ new Set([
|
|
806
910
|
"COUNT",
|
|
807
911
|
"SUM",
|
|
@@ -830,20 +934,28 @@ function rejectSemicolons(tokens) {
|
|
|
830
934
|
function normalizeSql(sql) {
|
|
831
935
|
return { text: sql.trim().replace(/;$/, "").trim(), offset: sql.length - sql.trimStart().length };
|
|
832
936
|
}
|
|
937
|
+
function normalizeQuerySql(sql) {
|
|
938
|
+
const spelling = "SELECT * FROM ";
|
|
939
|
+
const table = /^\s*TABLE\s+(?=[A-Za-z_"])/i.exec(sql);
|
|
940
|
+
if (table === null)
|
|
941
|
+
return normalizeSql(sql);
|
|
942
|
+
const { text } = normalizeSql(spelling + sql.slice(table[0].length));
|
|
943
|
+
return { text, offset: table[0].length - spelling.length };
|
|
944
|
+
}
|
|
833
945
|
function throwLocated(error, offset, span) {
|
|
834
946
|
if (error instanceof SqlCompileError) {
|
|
835
947
|
if (offset === 0)
|
|
836
948
|
throw error;
|
|
837
|
-
throw new SqlCompileError(error.message, error.offset + offset, error.length);
|
|
949
|
+
throw new SqlCompileError(error.message, Math.max(error.offset + offset, 0), error.length);
|
|
838
950
|
}
|
|
839
951
|
if (error instanceof TypeError) {
|
|
840
|
-
throw new SqlCompileError(error.message, offset + span.start, Math.max(span.end - span.start, 0));
|
|
952
|
+
throw new SqlCompileError(error.message, Math.max(offset + span.start, 0), Math.max(span.end - span.start, 0));
|
|
841
953
|
}
|
|
842
954
|
throw error;
|
|
843
955
|
}
|
|
844
956
|
function compileQuery(sql, options = {}) {
|
|
845
957
|
validateSqlSource(sql);
|
|
846
|
-
const { text, offset } =
|
|
958
|
+
const { text, offset } = normalizeQuerySql(sql);
|
|
847
959
|
if (text.length === 0)
|
|
848
960
|
throw new SqlCompileError("Enter a SELECT query", offset, 0);
|
|
849
961
|
let parser;
|
|
@@ -858,6 +970,8 @@ function compileQuery(sql, options = {}) {
|
|
|
858
970
|
}
|
|
859
971
|
let compiled;
|
|
860
972
|
try {
|
|
973
|
+
plan = expandRowReferences(plan, () => void 0);
|
|
974
|
+
annotatePlanIntegerDivision(plan);
|
|
861
975
|
resolvePlanExactNumericConstants(plan);
|
|
862
976
|
if (options.optimize === false && planHasPendingSelectShapes(plan)) {
|
|
863
977
|
plan.preserveUnoptimizedShape = true;
|
|
@@ -896,6 +1010,94 @@ function isDefaultInsertValue(value) {
|
|
|
896
1010
|
function isDeferredInsertExpression(value) {
|
|
897
1011
|
return typeof value === "object" && value !== null && !(value instanceof Date) && "expression" in value;
|
|
898
1012
|
}
|
|
1013
|
+
function parseSettingStatement(keyword, tokens) {
|
|
1014
|
+
const words = tokens.filter((token) => token.kind !== "eof");
|
|
1015
|
+
const upperAt = (at) => {
|
|
1016
|
+
const word = words[at];
|
|
1017
|
+
return word?.kind === "identifier" && word.quoted !== true ? word.text.toUpperCase() : void 0;
|
|
1018
|
+
};
|
|
1019
|
+
if (keyword === "RESET") {
|
|
1020
|
+
const name2 = words[1];
|
|
1021
|
+
if (name2 === void 0 || name2.kind !== "identifier" && name2.kind !== "string") {
|
|
1022
|
+
throw new TypeError("RESET takes a setting name or ALL");
|
|
1023
|
+
}
|
|
1024
|
+
return { kind: "set", action: "reset", name: name2.text };
|
|
1025
|
+
}
|
|
1026
|
+
let index = 1;
|
|
1027
|
+
if (upperAt(index) === "SESSION" || upperAt(index) === "LOCAL")
|
|
1028
|
+
index += 1;
|
|
1029
|
+
if (upperAt(index) === "TIME" && upperAt(index + 1) === "ZONE") {
|
|
1030
|
+
const zone = words[index + 2];
|
|
1031
|
+
const spelled = zone?.kind === "string" ? zone.text : zone?.text.toUpperCase();
|
|
1032
|
+
const utc = spelled === void 0 || spelled.toUpperCase() === "UTC" || spelled.toUpperCase() === "DEFAULT" || spelled.toUpperCase() === "LOCAL" || spelled === "Etc/UTC" || spelled === "GMT";
|
|
1033
|
+
if (!utc) {
|
|
1034
|
+
throw new TypeError(`SET TIME ZONE ${zone?.text ?? ""} is not supported: every datetime is an instant in UTC`);
|
|
1035
|
+
}
|
|
1036
|
+
return { kind: "set", action: "set", name: "timezone" };
|
|
1037
|
+
}
|
|
1038
|
+
if (upperAt(index) === "TRANSACTION" || upperAt(index) === "CONSTRAINTS") {
|
|
1039
|
+
if (words.some((word) => word.text.toUpperCase() === "SERIALIZABLE")) {
|
|
1040
|
+
throw new TypeError("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE is not supported: the engine has one isolation level");
|
|
1041
|
+
}
|
|
1042
|
+
return { kind: "set", action: "set", name: (upperAt(index) ?? "").toLowerCase() };
|
|
1043
|
+
}
|
|
1044
|
+
const name = words[index];
|
|
1045
|
+
if (name?.kind !== "identifier") {
|
|
1046
|
+
throw new TypeError(`Expected a setting name, found ${name?.text ?? "end of input"}`);
|
|
1047
|
+
}
|
|
1048
|
+
let cursor = index + 1;
|
|
1049
|
+
while (words[cursor]?.text === ".")
|
|
1050
|
+
cursor += 2;
|
|
1051
|
+
const settingName = words.slice(index, cursor).map((token) => token.text).join("");
|
|
1052
|
+
const separator = words[cursor];
|
|
1053
|
+
if (separator === void 0 || separator.text.toUpperCase() !== "TO" && separator.text !== "=") {
|
|
1054
|
+
throw new TypeError(`SET ${settingName} takes TO or = and a value`);
|
|
1055
|
+
}
|
|
1056
|
+
if (words[cursor + 1] === void 0)
|
|
1057
|
+
throw new TypeError(`SET ${settingName} needs a value`);
|
|
1058
|
+
if (settingName.toLowerCase() === "timezone" || settingName.toLowerCase() === "time_zone") {
|
|
1059
|
+
const zone = words[cursor + 1];
|
|
1060
|
+
const spelled = zone?.kind === "string" ? zone.text : zone?.text ?? "";
|
|
1061
|
+
if (!["utc", "default", "local", "etc/utc", "gmt"].includes(spelled.toLowerCase())) {
|
|
1062
|
+
throw new TypeError(`SET ${settingName} = ${zone?.text ?? ""} is not supported: every datetime is an instant in UTC`);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
return { kind: "set", action: "set", name: settingName };
|
|
1066
|
+
}
|
|
1067
|
+
function parseTruncateStatement(tokens) {
|
|
1068
|
+
const words = tokens.filter((token) => token.kind !== "eof");
|
|
1069
|
+
let index = 1;
|
|
1070
|
+
const upperAt = (at) => {
|
|
1071
|
+
const word = words[at];
|
|
1072
|
+
return word?.kind === "identifier" ? word.text.toUpperCase() : void 0;
|
|
1073
|
+
};
|
|
1074
|
+
if (upperAt(index) === "TABLE")
|
|
1075
|
+
index += 1;
|
|
1076
|
+
if (upperAt(index) === "ONLY")
|
|
1077
|
+
index += 1;
|
|
1078
|
+
const table = words[index];
|
|
1079
|
+
if (table?.kind !== "identifier") {
|
|
1080
|
+
throw new TypeError(`Expected table name, found ${table?.text ?? "end of input"}`);
|
|
1081
|
+
}
|
|
1082
|
+
index += 1;
|
|
1083
|
+
if (words[index]?.text === ",")
|
|
1084
|
+
throw new TypeError("TRUNCATE takes one table at a time");
|
|
1085
|
+
if (upperAt(index) === "RESTART" || upperAt(index) === "CONTINUE") {
|
|
1086
|
+
if (upperAt(index + 1) !== "IDENTITY") {
|
|
1087
|
+
throw new TypeError(`Expected IDENTITY, found ${words[index + 1]?.text ?? "end of input"}`);
|
|
1088
|
+
}
|
|
1089
|
+
index += 2;
|
|
1090
|
+
}
|
|
1091
|
+
if (upperAt(index) === "CASCADE") {
|
|
1092
|
+
throw new TypeError("TRUNCATE CASCADE is not supported; truncate dependents explicitly");
|
|
1093
|
+
}
|
|
1094
|
+
if (upperAt(index) === "RESTRICT")
|
|
1095
|
+
index += 1;
|
|
1096
|
+
if (index !== words.length) {
|
|
1097
|
+
throw new TypeError(`Unexpected input after TRUNCATE: ${words[index]?.text ?? ""}`);
|
|
1098
|
+
}
|
|
1099
|
+
return { kind: "delete", table: table.text, predicates: [] };
|
|
1100
|
+
}
|
|
899
1101
|
function parseTransactionStatement(keyword, tokens) {
|
|
900
1102
|
const identifierAt = (index) => {
|
|
901
1103
|
const token = tokens[index];
|
|
@@ -927,8 +1129,30 @@ function parseTransactionStatement(keyword, tokens) {
|
|
|
927
1129
|
return { kind: "transaction", action: "rollback-to", name };
|
|
928
1130
|
}
|
|
929
1131
|
const words = tokens.filter((token) => token.kind === "identifier").map((token) => token.text.toUpperCase());
|
|
930
|
-
const action = keyword === "START" || keyword === "BEGIN" ? "begin" : keyword.toLowerCase();
|
|
931
|
-
|
|
1132
|
+
const action = keyword === "START" || keyword === "BEGIN" ? "begin" : keyword === "END" ? "commit" : keyword === "ABORT" ? "rollback" : keyword.toLowerCase();
|
|
1133
|
+
if (words.includes("SERIALIZABLE")) {
|
|
1134
|
+
throw new TypeError("BEGIN ISOLATION LEVEL SERIALIZABLE is not supported: the engine has one isolation level");
|
|
1135
|
+
}
|
|
1136
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
1137
|
+
"BEGIN",
|
|
1138
|
+
"START",
|
|
1139
|
+
"COMMIT",
|
|
1140
|
+
"END",
|
|
1141
|
+
"ROLLBACK",
|
|
1142
|
+
"ABORT",
|
|
1143
|
+
"WORK",
|
|
1144
|
+
"TRANSACTION",
|
|
1145
|
+
"READ",
|
|
1146
|
+
"ONLY",
|
|
1147
|
+
"WRITE",
|
|
1148
|
+
"NOT",
|
|
1149
|
+
"DEFERRABLE",
|
|
1150
|
+
"ISOLATION",
|
|
1151
|
+
"LEVEL",
|
|
1152
|
+
"COMMITTED",
|
|
1153
|
+
"UNCOMMITTED",
|
|
1154
|
+
"REPEATABLE"
|
|
1155
|
+
]);
|
|
932
1156
|
for (const word of words) {
|
|
933
1157
|
if (!allowed.has(word)) {
|
|
934
1158
|
throw new TypeError(`${keyword} takes no ${word}: this engine has one isolation level`);
|
|
@@ -1115,7 +1339,7 @@ function parseCreateTrigger(text, tokens) {
|
|
|
1115
1339
|
}
|
|
1116
1340
|
function compileStatement(sql) {
|
|
1117
1341
|
validateSqlSource(sql);
|
|
1118
|
-
const { text, offset } =
|
|
1342
|
+
const { text, offset } = normalizeQuerySql(sql);
|
|
1119
1343
|
if (text.length === 0)
|
|
1120
1344
|
throw new SqlCompileError("Enter a SQL statement", offset, 0);
|
|
1121
1345
|
let parser;
|
|
@@ -1123,13 +1347,25 @@ function compileStatement(sql) {
|
|
|
1123
1347
|
const tokens = tokenize(text);
|
|
1124
1348
|
const first = tokens[0];
|
|
1125
1349
|
const keyword = first?.kind === "identifier" ? first.text.toUpperCase() : "";
|
|
1126
|
-
|
|
1350
|
+
let second = tokens[1];
|
|
1127
1351
|
const isTriggerDdl = (keyword === "CREATE" || keyword === "DROP") && second?.kind === "identifier" && second.text.toUpperCase() === "TRIGGER";
|
|
1128
1352
|
if (!isTriggerDdl)
|
|
1129
1353
|
rejectSemicolons(tokens);
|
|
1130
|
-
if (keyword === "BEGIN" || keyword === "START" || keyword === "COMMIT" || keyword === "ROLLBACK" || keyword === "SAVEPOINT" || keyword === "RELEASE") {
|
|
1354
|
+
if (keyword === "BEGIN" || keyword === "START" || keyword === "COMMIT" || keyword === "END" || keyword === "ROLLBACK" || keyword === "ABORT" || keyword === "SAVEPOINT" || keyword === "RELEASE") {
|
|
1131
1355
|
return parseTransactionStatement(keyword, tokens);
|
|
1132
1356
|
}
|
|
1357
|
+
if (keyword === "TRUNCATE")
|
|
1358
|
+
return parseTruncateStatement(tokens);
|
|
1359
|
+
if (keyword === "SET" || keyword === "RESET")
|
|
1360
|
+
return parseSettingStatement(keyword, tokens);
|
|
1361
|
+
if (keyword === "SHOW") {
|
|
1362
|
+
const name = tokens[1];
|
|
1363
|
+
if (name?.kind !== "identifier" || tokens[2]?.kind !== "eof" && tokens[2]?.text !== ".") {
|
|
1364
|
+
throw new TypeError("SHOW takes one setting name");
|
|
1365
|
+
}
|
|
1366
|
+
const setting = tokens.slice(1).filter((token) => token.kind !== "eof").map((token) => token.text).join("");
|
|
1367
|
+
return { kind: "show", name: setting };
|
|
1368
|
+
}
|
|
1133
1369
|
if (keyword === "MERGE") {
|
|
1134
1370
|
parser = new Parser(tokens, text);
|
|
1135
1371
|
const statement = parser.parseMerge();
|
|
@@ -1147,6 +1383,10 @@ function compileStatement(sql) {
|
|
|
1147
1383
|
if (keyword === "CREATE") {
|
|
1148
1384
|
if (isTriggerDdl)
|
|
1149
1385
|
return parseCreateTrigger(text, tokens);
|
|
1386
|
+
while (tokens[1]?.kind === "identifier" && ["TEMP", "TEMPORARY", "UNLOGGED", "GLOBAL", "LOCAL"].includes(tokens[1].text.toUpperCase())) {
|
|
1387
|
+
tokens.splice(1, 1);
|
|
1388
|
+
}
|
|
1389
|
+
second = tokens[1];
|
|
1150
1390
|
if (second?.kind === "identifier" && second.text.toUpperCase() === "TYPE") {
|
|
1151
1391
|
const name = tokens[2];
|
|
1152
1392
|
if (name?.kind !== "identifier")
|
|
@@ -1608,6 +1848,7 @@ function bindStatementParameters(statement, params) {
|
|
|
1608
1848
|
if (clone.onConflict?.where !== void 0) {
|
|
1609
1849
|
clone.onConflict.where = bindExpression(clone.onConflict.where, values);
|
|
1610
1850
|
}
|
|
1851
|
+
bindReturningItems(clone.returningItems, values);
|
|
1611
1852
|
return clone;
|
|
1612
1853
|
}
|
|
1613
1854
|
if (clone.kind === "drop-table" || clone.kind === "create-view" || clone.kind === "drop-view" || clone.kind === "transaction") {
|
|
@@ -1643,8 +1884,15 @@ function bindStatementParameters(statement, params) {
|
|
|
1643
1884
|
predicate.left = bindExpression(predicate.left, values);
|
|
1644
1885
|
predicate.right = bindExpression(predicate.right, values);
|
|
1645
1886
|
}
|
|
1887
|
+
bindReturningItems(clone.returningItems, values);
|
|
1646
1888
|
return clone;
|
|
1647
1889
|
}
|
|
1890
|
+
function bindReturningItems(items, values) {
|
|
1891
|
+
if (items === void 0)
|
|
1892
|
+
return;
|
|
1893
|
+
for (const item of items)
|
|
1894
|
+
item.expression = bindExpression(item.expression, values);
|
|
1895
|
+
}
|
|
1648
1896
|
function inferBlockSchema(plan, schemas) {
|
|
1649
1897
|
const sources = [plan.base, ...plan.joins];
|
|
1650
1898
|
const multipleSources = sources.filter((source) => {
|
|
@@ -1711,15 +1959,42 @@ function inferBlockSchema(plan, schemas) {
|
|
|
1711
1959
|
return { kind: "interval" };
|
|
1712
1960
|
return void 0;
|
|
1713
1961
|
};
|
|
1962
|
+
const operandScale = (expression, domain) => {
|
|
1963
|
+
if (domain !== void 0)
|
|
1964
|
+
return domain.kind === "numeric" ? domain.scale : void 0;
|
|
1965
|
+
if (expression.kind === "literal") {
|
|
1966
|
+
return typeof expression.value === "number" && Number.isFinite(expression.value) ? decimalScaleOfNumber(expression.value) : void 0;
|
|
1967
|
+
}
|
|
1968
|
+
return expression.kind === "column" ? 0 : void 0;
|
|
1969
|
+
};
|
|
1970
|
+
const numericResultDomain = (operator, sides) => {
|
|
1971
|
+
const scales = sides.map(([expression, domain]) => operandScale(expression, domain));
|
|
1972
|
+
if (scales.some((scale) => scale === void 0))
|
|
1973
|
+
return { kind: "numeric" };
|
|
1974
|
+
const known = scales;
|
|
1975
|
+
return {
|
|
1976
|
+
kind: "numeric",
|
|
1977
|
+
scale: operator === "*" ? known.reduce((sum, scale) => sum + scale, 0) : Math.max(...known)
|
|
1978
|
+
};
|
|
1979
|
+
};
|
|
1714
1980
|
const inferDomain = (expression) => {
|
|
1715
1981
|
if (expression.kind === "literal")
|
|
1716
1982
|
return expression.sqlDomain;
|
|
1717
1983
|
if (expression.kind === "column")
|
|
1718
1984
|
return resolveColumnDomain(expression.reference);
|
|
1719
1985
|
if (expression.kind === "binary") {
|
|
1986
|
+
if (expression.operator === "||")
|
|
1987
|
+
return void 0;
|
|
1720
1988
|
const left = inferDomain(expression.left);
|
|
1721
1989
|
const right = inferDomain(expression.right);
|
|
1722
|
-
|
|
1990
|
+
if (left?.kind !== "numeric" && right?.kind !== "numeric")
|
|
1991
|
+
return void 0;
|
|
1992
|
+
if (expression.operator === "/")
|
|
1993
|
+
return { kind: "numeric" };
|
|
1994
|
+
return numericResultDomain(expression.operator, [
|
|
1995
|
+
[expression.left, left],
|
|
1996
|
+
[expression.right, right]
|
|
1997
|
+
]);
|
|
1723
1998
|
}
|
|
1724
1999
|
if (expression.kind === "case") {
|
|
1725
2000
|
const outcomes = [
|
|
@@ -1741,11 +2016,30 @@ function inferBlockSchema(plan, schemas) {
|
|
|
1741
2016
|
const target = expression.arguments[1];
|
|
1742
2017
|
return target?.kind === "literal" && typeof target.value === "string" ? castDomain(target.value) : void 0;
|
|
1743
2018
|
}
|
|
1744
|
-
if (expression.name === "JSON_ARRAYAGG" || expression.name === "JSON_QUERY" || expression.name === "JSON_OBJECT" || expression.name === "JSON_ARRAY" || expression.name === "MINNOW_JSON_GET") {
|
|
2019
|
+
if (expression.name === "JSON_ARRAYAGG" || expression.name === "JSON_QUERY" || expression.name === "JSON_OBJECT" || expression.name === "JSON_ARRAY" || expression.name === "TO_JSON" || expression.name === "MINNOW_JSON_GET") {
|
|
1745
2020
|
return { kind: "json" };
|
|
1746
2021
|
}
|
|
1747
2022
|
if (expression.name === "GEN_RANDOM_UUID")
|
|
1748
2023
|
return { kind: "uuid" };
|
|
2024
|
+
if (expression.name === "ROUND" || expression.name === "TRUNC") {
|
|
2025
|
+
const input = inferDomain(expression.arguments[0] ?? { kind: "literal", value: null });
|
|
2026
|
+
if (input?.kind !== "numeric")
|
|
2027
|
+
return void 0;
|
|
2028
|
+
const digits = expression.arguments[1];
|
|
2029
|
+
return digits?.kind === "literal" && typeof digits.value === "number" && Number.isInteger(digits.value) && digits.value > 0 ? { kind: "numeric", scale: digits.value } : { kind: "numeric" };
|
|
2030
|
+
}
|
|
2031
|
+
if (expression.name === "ABS") {
|
|
2032
|
+
const input = inferDomain(expression.arguments[0] ?? { kind: "literal", value: null });
|
|
2033
|
+
return input?.kind === "numeric" ? input : void 0;
|
|
2034
|
+
}
|
|
2035
|
+
if (expression.name === "FLOOR" || expression.name === "CEIL" || expression.name === "SIGN") {
|
|
2036
|
+
const input = inferDomain(expression.arguments[0] ?? { kind: "literal", value: null });
|
|
2037
|
+
return input?.kind === "numeric" ? { kind: "numeric" } : void 0;
|
|
2038
|
+
}
|
|
2039
|
+
if (expression.name === "MOD") {
|
|
2040
|
+
const sides = expression.arguments.map((argument) => [argument, inferDomain(argument)]);
|
|
2041
|
+
return sides.some(([, domain]) => domain?.kind === "numeric") ? numericResultDomain("%", sides) : void 0;
|
|
2042
|
+
}
|
|
1749
2043
|
if (expression.name === "DATE_ADD") {
|
|
1750
2044
|
const input = inferDomain(expression.arguments[0] ?? { kind: "literal", value: null });
|
|
1751
2045
|
const milliseconds = expression.arguments[2];
|
|
@@ -1756,6 +2050,7 @@ function inferBlockSchema(plan, schemas) {
|
|
|
1756
2050
|
}
|
|
1757
2051
|
return void 0;
|
|
1758
2052
|
};
|
|
2053
|
+
const exactNumericMix = (expression, left, right) => (left === "string" && right === "number" || left === "number" && right === "string") && inferDomain(expression)?.kind === "numeric";
|
|
1759
2054
|
const infer = (expression) => {
|
|
1760
2055
|
if (expression.kind === "subquery" || expression.kind === "list") {
|
|
1761
2056
|
throw new TypeError("Subqueries must be resolved before schema inference");
|
|
@@ -1780,6 +2075,10 @@ function inferBlockSchema(plan, schemas) {
|
|
|
1780
2075
|
if (type === "null")
|
|
1781
2076
|
continue;
|
|
1782
2077
|
if (resolved !== "null" && resolved !== type) {
|
|
2078
|
+
if (exactNumericMix(expression, resolved, type)) {
|
|
2079
|
+
resolved = "string";
|
|
2080
|
+
continue;
|
|
2081
|
+
}
|
|
1783
2082
|
throw new TypeError("CASE branches must produce one value type");
|
|
1784
2083
|
}
|
|
1785
2084
|
resolved = type;
|
|
@@ -1838,10 +2137,13 @@ function inferBlockSchema(plan, schemas) {
|
|
|
1838
2137
|
if (expression.name === "SUM" || expression.name === "AVG") {
|
|
1839
2138
|
return inferDomain(expression)?.kind === "numeric" ? "string" : "number";
|
|
1840
2139
|
}
|
|
1841
|
-
if (expression.name === "ROUND" || expression.name === "
|
|
2140
|
+
if (expression.name === "ROUND" || expression.name === "ABS" || expression.name === "FLOOR" || expression.name === "CEIL" || expression.name === "MOD") {
|
|
2141
|
+
return inferDomain(expression)?.kind === "numeric" ? "string" : "number";
|
|
2142
|
+
}
|
|
2143
|
+
if (expression.name === "LENGTH" || expression.name === "POWER" || expression.name === "SQRT" || expression.name === "INSTR" || expression.name === "EXTRACT" || expression.name === "OCTET_LENGTH" || expression.name === "GROUPING" || expression.name === "NEXTVAL" || expression.name === "CURRVAL" || expression.name === "RANDOM") {
|
|
1842
2144
|
return "number";
|
|
1843
2145
|
}
|
|
1844
|
-
if (expression.name === "JSON_ARRAYAGG" || expression.name === "STRING_AGG" || expression.name === "JSON_VALUE" || expression.name === "JSON_QUERY" || expression.name === "JSON_OBJECT" || expression.name === "JSON_ARRAY" || expression.name === "ARRAY" || expression.name === "MINNOW_JSON_GET" || expression.name === "MINNOW_JSON_GET_TEXT" || expression.name === "MINNOW_TUPLE_KEY" || expression.name === "MINNOW_COLLATE" || expression.name === "GEN_RANDOM_UUID") {
|
|
2146
|
+
if (expression.name === "JSON_ARRAYAGG" || expression.name === "STRING_AGG" || expression.name === "JSON_VALUE" || expression.name === "JSON_QUERY" || expression.name === "JSON_OBJECT" || expression.name === "JSON_ARRAY" || expression.name === "TO_JSON" || expression.name === "ARRAY" || expression.name === "MINNOW_JSON_GET" || expression.name === "MINNOW_JSON_GET_TEXT" || expression.name === "MINNOW_TUPLE_KEY" || expression.name === "MINNOW_COLLATE" || expression.name === "GEN_RANDOM_UUID") {
|
|
1845
2147
|
return "string";
|
|
1846
2148
|
}
|
|
1847
2149
|
if (expression.name === "JSON_EXISTS" || expression.name === "IS_JSON")
|
|
@@ -1852,6 +2154,9 @@ function inferBlockSchema(plan, schemas) {
|
|
|
1852
2154
|
const argument2 = expression.arguments[0];
|
|
1853
2155
|
return argument2 === void 0 ? "null" : infer(argument2);
|
|
1854
2156
|
}
|
|
2157
|
+
if (simple.returns === "number" && inferDomain(expression)?.kind === "numeric") {
|
|
2158
|
+
return "string";
|
|
2159
|
+
}
|
|
1855
2160
|
return simple.returns === "date" || simple.returns === "interval" ? "string" : simple.returns;
|
|
1856
2161
|
}
|
|
1857
2162
|
if (expression.name === "DATE_TRUNC")
|
|
@@ -1872,6 +2177,10 @@ function inferBlockSchema(plan, schemas) {
|
|
|
1872
2177
|
if (type === "null")
|
|
1873
2178
|
continue;
|
|
1874
2179
|
if (resolved !== "null" && resolved !== type) {
|
|
2180
|
+
if (exactNumericMix(expression, resolved, type)) {
|
|
2181
|
+
resolved = "string";
|
|
2182
|
+
continue;
|
|
2183
|
+
}
|
|
1875
2184
|
throw new TypeError(`${expression.name} arguments must produce one value type`);
|
|
1876
2185
|
}
|
|
1877
2186
|
resolved = type;
|
|
@@ -1907,6 +2216,10 @@ function inferBlockSchema(plan, schemas) {
|
|
|
1907
2216
|
if (type === "null")
|
|
1908
2217
|
continue;
|
|
1909
2218
|
if (resolved !== "null" && resolved !== type) {
|
|
2219
|
+
if (exactNumericMix(expression, resolved, type)) {
|
|
2220
|
+
resolved = "string";
|
|
2221
|
+
continue;
|
|
2222
|
+
}
|
|
1910
2223
|
throw new TypeError("COALESCE arguments must produce one value type");
|
|
1911
2224
|
}
|
|
1912
2225
|
resolved = type;
|
|
@@ -1928,7 +2241,7 @@ function inferBlockSchema(plan, schemas) {
|
|
|
1928
2241
|
if (type === "null") {
|
|
1929
2242
|
throw new TypeError(`Cannot infer a column type for output ${item.alias}`);
|
|
1930
2243
|
}
|
|
1931
|
-
const integer = item.expression
|
|
2244
|
+
const integer = integerTypedExpression(item.expression, resolveColumnInteger);
|
|
1932
2245
|
const sqlDomain = inferDomain(item.expression);
|
|
1933
2246
|
return [
|
|
1934
2247
|
{
|
|
@@ -1980,6 +2293,7 @@ function referencedColumns(plan, schemas) {
|
|
|
1980
2293
|
const sourceAliases = sources.map((source) => source.alias);
|
|
1981
2294
|
if (new Set(sourceAliases).size !== sourceAliases.length)
|
|
1982
2295
|
throw new TypeError("Table aliases must be unique");
|
|
2296
|
+
plan = unifyPlanGroupedReferences(plan, (tableName) => schemas.get(tableName));
|
|
1983
2297
|
validateGrouping(plan);
|
|
1984
2298
|
const aliases = new Map(sources.map((source) => [source.alias, source.table]));
|
|
1985
2299
|
const requested = new Map(sources.map((source) => [source.table, /* @__PURE__ */ new Set()]));
|
|
@@ -2100,6 +2414,7 @@ function createPreparedQuery(plan, tables, options = {}) {
|
|
|
2100
2414
|
assertTailParametersBound(plan);
|
|
2101
2415
|
plan = resolveStatementDatetimes(plan);
|
|
2102
2416
|
plan = bindPendingSelectShapes(plan, wildcardRowColumns(tables));
|
|
2417
|
+
plan = unifyPlanGroupedReferences(plan, wildcardRowColumns(tables));
|
|
2103
2418
|
validateGrouping(plan);
|
|
2104
2419
|
plan = expandSourceColumnAliases(plan, wildcardRowColumns(tables));
|
|
2105
2420
|
plan = expandNaturalJoins(plan, wildcardRowColumns(tables));
|
|
@@ -2145,6 +2460,7 @@ function createPreparedColumnarQuery(plan, tables, memory = new QueryMemoryConte
|
|
|
2145
2460
|
plan = expandNaturalJoins(plan, columnarColumns);
|
|
2146
2461
|
plan = expandQualifiedWildcards(plan, columnarColumns);
|
|
2147
2462
|
plan = expandDistinctWildcard(plan, columnarColumns);
|
|
2463
|
+
plan = unifyPlanGroupedReferences(plan, columnarColumns);
|
|
2148
2464
|
validateGrouping(plan);
|
|
2149
2465
|
let closed = false;
|
|
2150
2466
|
let prepared;
|
|
@@ -2487,6 +2803,485 @@ function forEachNestedBlock(block, visit) {
|
|
|
2487
2803
|
}
|
|
2488
2804
|
}
|
|
2489
2805
|
}
|
|
2806
|
+
function integerTypedExpression(expression, columnInteger = () => false, tableIntegerColumns) {
|
|
2807
|
+
const typed = (candidate) => integerTypedExpression(candidate, columnInteger, tableIntegerColumns);
|
|
2808
|
+
switch (expression.kind) {
|
|
2809
|
+
case "subquery": {
|
|
2810
|
+
const item = expression.block.select[0];
|
|
2811
|
+
return expression.block.select.length === 1 && item !== void 0 && integerTypedExpression(item.expression, blockColumnIntegerResolver(expression.block, tableIntegerColumns), tableIntegerColumns);
|
|
2812
|
+
}
|
|
2813
|
+
case "literal":
|
|
2814
|
+
return typeof expression.value === "number" && expression.exactText === void 0 && expression.decimal !== true && expression.internalSqlValue !== true && Number.isSafeInteger(expression.value);
|
|
2815
|
+
case "column":
|
|
2816
|
+
return columnInteger(expression.reference);
|
|
2817
|
+
case "binary": {
|
|
2818
|
+
if (expression.operator === "||")
|
|
2819
|
+
return false;
|
|
2820
|
+
const left = typed(expression.left);
|
|
2821
|
+
const right = typed(expression.right);
|
|
2822
|
+
if (left && right)
|
|
2823
|
+
return true;
|
|
2824
|
+
const untyped = (candidate) => candidate.kind === "parameter" || candidate.kind === "literal" && typeof candidate.value === "string" && candidate.internalSqlValue !== true;
|
|
2825
|
+
return left && untyped(expression.right) || right && untyped(expression.left);
|
|
2826
|
+
}
|
|
2827
|
+
case "call": {
|
|
2828
|
+
const { name, arguments: args } = expression;
|
|
2829
|
+
if (name === "COUNT")
|
|
2830
|
+
return true;
|
|
2831
|
+
if (name === "CAST") {
|
|
2832
|
+
return args[1]?.kind === "literal" && args[1].value === "number-integer";
|
|
2833
|
+
}
|
|
2834
|
+
if (name === "SUM" || name === "MIN" || name === "MAX" || name === "ABS") {
|
|
2835
|
+
return args.length === 1 && args[0] !== void 0 && typed(args[0]);
|
|
2836
|
+
}
|
|
2837
|
+
if (name === "MOD")
|
|
2838
|
+
return args.length === 2 && args.every(typed);
|
|
2839
|
+
if (name === "COALESCE" || name === "NULLIF" || name === "GREATEST" || name === "LEAST") {
|
|
2840
|
+
return args.length > 0 && args.every(typed);
|
|
2841
|
+
}
|
|
2842
|
+
return false;
|
|
2843
|
+
}
|
|
2844
|
+
case "case":
|
|
2845
|
+
return expression.branches.every((branch) => typed(branch.then)) && (expression.otherwise === void 0 || typed(expression.otherwise));
|
|
2846
|
+
default:
|
|
2847
|
+
return false;
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
function integerQuotient(left, right) {
|
|
2851
|
+
if (right === 0)
|
|
2852
|
+
return null;
|
|
2853
|
+
if (!Number.isInteger(left) || !Number.isInteger(right))
|
|
2854
|
+
return left / right;
|
|
2855
|
+
const quotient = Math.trunc(left / right);
|
|
2856
|
+
return quotient === 0 ? 0 : quotient;
|
|
2857
|
+
}
|
|
2858
|
+
function annotateIntegerDivision(expression, columnInteger = () => false, tableIntegerColumns) {
|
|
2859
|
+
if (expression.kind === "subquery" || expression.kind === "exists") {
|
|
2860
|
+
annotatePlanIntegerDivision(expression.block, tableIntegerColumns);
|
|
2861
|
+
return;
|
|
2862
|
+
}
|
|
2863
|
+
for (const child of childExpressions(expression)) {
|
|
2864
|
+
annotateIntegerDivision(child, columnInteger, tableIntegerColumns);
|
|
2865
|
+
}
|
|
2866
|
+
if (expression.kind === "binary" && expression.operator === "/" && expression.integer !== true && integerTypedExpression(expression, columnInteger, tableIntegerColumns)) {
|
|
2867
|
+
expression.integer = true;
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
function blockIntegerOutputs(block, tableIntegerColumns, columnAliases) {
|
|
2871
|
+
const resolver = blockColumnIntegerResolver(block, tableIntegerColumns);
|
|
2872
|
+
const names = [];
|
|
2873
|
+
for (const item of block.select) {
|
|
2874
|
+
if (item.expression.kind === "wildcard") {
|
|
2875
|
+
const table = item.expression.table;
|
|
2876
|
+
for (const source of [block.base, ...block.joins]) {
|
|
2877
|
+
if (table !== void 0 && source.alias !== table)
|
|
2878
|
+
continue;
|
|
2879
|
+
const columns = sourceIntegerColumns(source, tableIntegerColumns);
|
|
2880
|
+
if (columns === true)
|
|
2881
|
+
return true;
|
|
2882
|
+
for (const name of columns)
|
|
2883
|
+
if (resolver(name))
|
|
2884
|
+
names.push(name);
|
|
2885
|
+
}
|
|
2886
|
+
continue;
|
|
2887
|
+
}
|
|
2888
|
+
if (integerTypedExpression(item.expression, resolver, tableIntegerColumns)) {
|
|
2889
|
+
names.push(item.alias);
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
if (columnAliases === void 0)
|
|
2893
|
+
return new Set(names);
|
|
2894
|
+
const aliased = /* @__PURE__ */ new Set();
|
|
2895
|
+
block.select.forEach((item, index) => {
|
|
2896
|
+
const alias = columnAliases[index];
|
|
2897
|
+
if (alias !== void 0 && item.expression.kind !== "wildcard" && names.includes(item.alias)) {
|
|
2898
|
+
aliased.add(alias);
|
|
2899
|
+
}
|
|
2900
|
+
});
|
|
2901
|
+
return aliased;
|
|
2902
|
+
}
|
|
2903
|
+
function sourceIntegerColumns(source, tableIntegerColumns) {
|
|
2904
|
+
if (source.derived !== void 0) {
|
|
2905
|
+
return blockIntegerOutputs(source.derived, tableIntegerColumns, source.columnAliases);
|
|
2906
|
+
}
|
|
2907
|
+
if (source.union !== void 0) {
|
|
2908
|
+
const first = source.union.blocks[0];
|
|
2909
|
+
return first === void 0 ? /* @__PURE__ */ new Set() : blockIntegerOutputs(first, tableIntegerColumns, source.columnAliases);
|
|
2910
|
+
}
|
|
2911
|
+
if (source.windowed !== void 0) {
|
|
2912
|
+
return blockIntegerOutputs(source.windowed.block, tableIntegerColumns, source.columnAliases);
|
|
2913
|
+
}
|
|
2914
|
+
if (source.recursive !== void 0)
|
|
2915
|
+
return /* @__PURE__ */ new Set();
|
|
2916
|
+
return tableIntegerColumns?.(source.table) ?? /* @__PURE__ */ new Set();
|
|
2917
|
+
}
|
|
2918
|
+
function blockColumnIntegerResolver(block, tableIntegerColumns) {
|
|
2919
|
+
const sources = [block.base, ...block.joins];
|
|
2920
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2921
|
+
const has = (columns, name) => columns === true || columns.has(name);
|
|
2922
|
+
const columnsOf = (source) => {
|
|
2923
|
+
let columns = cache.get(source);
|
|
2924
|
+
if (columns === void 0) {
|
|
2925
|
+
columns = sourceIntegerColumns(source, tableIntegerColumns);
|
|
2926
|
+
cache.set(source, columns);
|
|
2927
|
+
}
|
|
2928
|
+
return columns;
|
|
2929
|
+
};
|
|
2930
|
+
return (reference) => {
|
|
2931
|
+
const separator = reference.indexOf(".");
|
|
2932
|
+
if (separator !== -1) {
|
|
2933
|
+
const alias = reference.slice(0, separator);
|
|
2934
|
+
const name = reference.slice(separator + 1);
|
|
2935
|
+
const source = sources.find((candidate) => candidate.alias === alias);
|
|
2936
|
+
return source !== void 0 && has(columnsOf(source), name);
|
|
2937
|
+
}
|
|
2938
|
+
let found = 0;
|
|
2939
|
+
for (const source of sources)
|
|
2940
|
+
if (has(columnsOf(source), reference))
|
|
2941
|
+
found += 1;
|
|
2942
|
+
return found === 1;
|
|
2943
|
+
};
|
|
2944
|
+
}
|
|
2945
|
+
function annotatePlanIntegerDivision(plan, tableIntegerColumns) {
|
|
2946
|
+
forEachNestedBlock(plan, (nested) => annotatePlanIntegerDivision(nested, tableIntegerColumns));
|
|
2947
|
+
const resolver = blockColumnIntegerResolver(plan, tableIntegerColumns);
|
|
2948
|
+
forEachBlockExpression(plan, (expression) => annotateIntegerDivision(expression, resolver, tableIntegerColumns));
|
|
2949
|
+
}
|
|
2950
|
+
function planMayHaveIntegerDivision(plan) {
|
|
2951
|
+
const state = { found: false };
|
|
2952
|
+
const optimistic = () => true;
|
|
2953
|
+
const everyColumn = () => true;
|
|
2954
|
+
const probe = (expression) => {
|
|
2955
|
+
if (state.found)
|
|
2956
|
+
return;
|
|
2957
|
+
if (expression.kind === "subquery" || expression.kind === "exists") {
|
|
2958
|
+
if (planMayHaveIntegerDivision(expression.block))
|
|
2959
|
+
state.found = true;
|
|
2960
|
+
return;
|
|
2961
|
+
}
|
|
2962
|
+
if (expression.kind === "binary" && expression.operator === "/" && expression.integer !== true && integerTypedExpression(expression, optimistic, everyColumn)) {
|
|
2963
|
+
state.found = true;
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
childExpressions(expression).forEach(probe);
|
|
2967
|
+
};
|
|
2968
|
+
forEachNestedBlock(plan, (nested) => {
|
|
2969
|
+
if (!state.found && planMayHaveIntegerDivision(nested))
|
|
2970
|
+
state.found = true;
|
|
2971
|
+
});
|
|
2972
|
+
if (!state.found)
|
|
2973
|
+
forEachBlockExpression(plan, probe);
|
|
2974
|
+
return state.found;
|
|
2975
|
+
}
|
|
2976
|
+
function foldIdentifierCase(plan, tables) {
|
|
2977
|
+
const lowerTables = /* @__PURE__ */ new Map();
|
|
2978
|
+
for (const name of tables.keys()) {
|
|
2979
|
+
const lowered = name.toLowerCase();
|
|
2980
|
+
const bucket = lowerTables.get(lowered);
|
|
2981
|
+
if (bucket === void 0)
|
|
2982
|
+
lowerTables.set(lowered, [name]);
|
|
2983
|
+
else
|
|
2984
|
+
bucket.push(name);
|
|
2985
|
+
}
|
|
2986
|
+
const foldTable = (name) => {
|
|
2987
|
+
if (tables.has(name))
|
|
2988
|
+
return void 0;
|
|
2989
|
+
const lowered = name.toLowerCase();
|
|
2990
|
+
if (tables.has(lowered))
|
|
2991
|
+
return lowered;
|
|
2992
|
+
const candidates = lowerTables.get(lowered);
|
|
2993
|
+
return candidates?.length === 1 ? candidates[0] : void 0;
|
|
2994
|
+
};
|
|
2995
|
+
const foldColumn = (columns, name) => {
|
|
2996
|
+
if (columns.includes(name))
|
|
2997
|
+
return void 0;
|
|
2998
|
+
const lowered = name.toLowerCase();
|
|
2999
|
+
if (columns.includes(lowered))
|
|
3000
|
+
return lowered;
|
|
3001
|
+
const matches = columns.filter((column) => column.toLowerCase() === lowered);
|
|
3002
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
3003
|
+
};
|
|
3004
|
+
const pass = { probing: true, needed: false };
|
|
3005
|
+
const sourceColumns = (source) => {
|
|
3006
|
+
if (source.derived !== void 0) {
|
|
3007
|
+
return source.columnAliases ?? source.derived.select.map((item) => item.alias);
|
|
3008
|
+
}
|
|
3009
|
+
if (source.union !== void 0) {
|
|
3010
|
+
return source.columnAliases ?? source.union.blocks[0]?.select.map((item) => item.alias) ?? [];
|
|
3011
|
+
}
|
|
3012
|
+
if (source.windowed !== void 0) {
|
|
3013
|
+
return source.columnAliases ?? source.windowed.block.select.map((item) => item.alias);
|
|
3014
|
+
}
|
|
3015
|
+
if (source.recursive !== void 0)
|
|
3016
|
+
return source.columnAliases ?? [];
|
|
3017
|
+
return source.columnAliases ?? tables.get(source.table) ?? [];
|
|
3018
|
+
};
|
|
3019
|
+
const rewriteBlock = (block) => {
|
|
3020
|
+
if (pass.probing && pass.needed)
|
|
3021
|
+
return;
|
|
3022
|
+
forEachNestedBlock(block, rewriteBlock);
|
|
3023
|
+
const sources = [block.base, ...block.joins];
|
|
3024
|
+
for (const source of sources) {
|
|
3025
|
+
const plain = source.derived === void 0 && source.union === void 0 && source.windowed === void 0 && source.recursive === void 0;
|
|
3026
|
+
if (!plain)
|
|
3027
|
+
continue;
|
|
3028
|
+
const folded = foldTable(source.table);
|
|
3029
|
+
if (folded === void 0)
|
|
3030
|
+
continue;
|
|
3031
|
+
if (pass.probing) {
|
|
3032
|
+
pass.needed = true;
|
|
3033
|
+
return;
|
|
3034
|
+
}
|
|
3035
|
+
const aliasWasTable = source.alias === source.table;
|
|
3036
|
+
source.table = folded;
|
|
3037
|
+
if (aliasWasTable)
|
|
3038
|
+
source.alias = folded;
|
|
3039
|
+
}
|
|
3040
|
+
const foldReference = (reference) => {
|
|
3041
|
+
const separator = reference.indexOf(".");
|
|
3042
|
+
if (separator !== -1) {
|
|
3043
|
+
const qualifier = reference.slice(0, separator);
|
|
3044
|
+
const name = reference.slice(separator + 1);
|
|
3045
|
+
let source = sources.find((candidate) => candidate.alias === qualifier);
|
|
3046
|
+
let foldedQualifier;
|
|
3047
|
+
if (source === void 0) {
|
|
3048
|
+
const lowered = qualifier.toLowerCase();
|
|
3049
|
+
const matches = sources.filter((candidate) => candidate.alias.toLowerCase() === lowered);
|
|
3050
|
+
if (matches.length !== 1)
|
|
3051
|
+
return void 0;
|
|
3052
|
+
source = matches[0];
|
|
3053
|
+
foldedQualifier = source?.alias;
|
|
3054
|
+
}
|
|
3055
|
+
if (source === void 0)
|
|
3056
|
+
return void 0;
|
|
3057
|
+
const columns = sourceColumns(source);
|
|
3058
|
+
const foldedName = columns.includes(name) ? void 0 : foldColumn(columns, name);
|
|
3059
|
+
if (foldedQualifier === void 0 && foldedName === void 0)
|
|
3060
|
+
return void 0;
|
|
3061
|
+
return `${foldedQualifier ?? qualifier}.${foldedName ?? name}`;
|
|
3062
|
+
}
|
|
3063
|
+
if (sources.some((source) => sourceColumns(source).includes(reference)))
|
|
3064
|
+
return void 0;
|
|
3065
|
+
const folded = sources.map((source) => foldColumn(sourceColumns(source), reference)).filter((name) => name !== void 0);
|
|
3066
|
+
return folded.length === 1 ? folded[0] : void 0;
|
|
3067
|
+
};
|
|
3068
|
+
const aliases = new Set(block.select.map((item) => item.alias));
|
|
3069
|
+
const orderByAliases = new Set(block.orderBy.map((order) => order.expression).filter((expression) => expression.kind === "column" && aliases.has(expression.reference)));
|
|
3070
|
+
const writtenNames = block.select.map((item) => item.expression.kind === "column" ? bareColumnName(item.expression.reference) : void 0);
|
|
3071
|
+
const rewrite = (expression) => {
|
|
3072
|
+
if (expression.kind === "column") {
|
|
3073
|
+
if (orderByAliases.has(expression))
|
|
3074
|
+
return;
|
|
3075
|
+
const folded = foldReference(expression.reference);
|
|
3076
|
+
if (folded === void 0)
|
|
3077
|
+
return;
|
|
3078
|
+
if (pass.probing)
|
|
3079
|
+
pass.needed = true;
|
|
3080
|
+
else
|
|
3081
|
+
expression.reference = folded;
|
|
3082
|
+
return;
|
|
3083
|
+
}
|
|
3084
|
+
if (expression.kind === "wildcard") {
|
|
3085
|
+
if (expression.table === void 0)
|
|
3086
|
+
return;
|
|
3087
|
+
const exact = sources.some((source) => source.alias === expression.table);
|
|
3088
|
+
if (exact)
|
|
3089
|
+
return;
|
|
3090
|
+
const lowered = expression.table.toLowerCase();
|
|
3091
|
+
const matches = sources.filter((source) => source.alias.toLowerCase() === lowered);
|
|
3092
|
+
if (matches.length !== 1)
|
|
3093
|
+
return;
|
|
3094
|
+
if (pass.probing)
|
|
3095
|
+
pass.needed = true;
|
|
3096
|
+
else
|
|
3097
|
+
expression.table = matches[0]?.alias ?? expression.table;
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
3100
|
+
if (expression.kind === "subquery" || expression.kind === "exists") {
|
|
3101
|
+
rewriteBlock(expression.block);
|
|
3102
|
+
return;
|
|
3103
|
+
}
|
|
3104
|
+
childExpressions(expression).forEach(rewrite);
|
|
3105
|
+
};
|
|
3106
|
+
forEachBlockExpression(block, rewrite);
|
|
3107
|
+
if (!pass.probing) {
|
|
3108
|
+
block.select.forEach((item, index) => {
|
|
3109
|
+
if (item.expression.kind !== "column")
|
|
3110
|
+
return;
|
|
3111
|
+
const name = bareColumnName(item.expression.reference);
|
|
3112
|
+
if (item.alias !== name && item.alias === writtenNames[index])
|
|
3113
|
+
item.alias = name;
|
|
3114
|
+
});
|
|
3115
|
+
}
|
|
3116
|
+
};
|
|
3117
|
+
rewriteBlock(plan);
|
|
3118
|
+
if (!pass.needed)
|
|
3119
|
+
return plan;
|
|
3120
|
+
const cloned = clonePlanTree(plan);
|
|
3121
|
+
pass.probing = false;
|
|
3122
|
+
rewriteBlock(cloned);
|
|
3123
|
+
return cloned;
|
|
3124
|
+
}
|
|
3125
|
+
function expandRowReferences(plan, tableColumns) {
|
|
3126
|
+
const pass = { probing: true, needed: false };
|
|
3127
|
+
const sourceColumns = (source) => {
|
|
3128
|
+
if (source.columnAliases !== void 0)
|
|
3129
|
+
return source.columnAliases;
|
|
3130
|
+
if (source.derived !== void 0)
|
|
3131
|
+
return source.derived.select.map((item) => item.alias);
|
|
3132
|
+
if (source.union !== void 0) {
|
|
3133
|
+
return source.union.blocks[0]?.select.map((item) => item.alias) ?? [];
|
|
3134
|
+
}
|
|
3135
|
+
if (source.windowed !== void 0)
|
|
3136
|
+
return source.windowed.block.select.map((item) => item.alias);
|
|
3137
|
+
if (source.recursive !== void 0)
|
|
3138
|
+
return [];
|
|
3139
|
+
return tableColumns(source.table) ?? [];
|
|
3140
|
+
};
|
|
3141
|
+
const rewriteBlock = (block) => {
|
|
3142
|
+
if (pass.probing && pass.needed)
|
|
3143
|
+
return;
|
|
3144
|
+
forEachNestedBlock(block, rewriteBlock);
|
|
3145
|
+
const sources = [block.base, ...block.joins];
|
|
3146
|
+
const rowObject = (reference) => {
|
|
3147
|
+
if (reference.includes("."))
|
|
3148
|
+
return void 0;
|
|
3149
|
+
const source = sources.find((candidate) => candidate.alias === reference);
|
|
3150
|
+
if (source === void 0)
|
|
3151
|
+
return void 0;
|
|
3152
|
+
if (sources.some((candidate) => sourceColumns(candidate).includes(reference))) {
|
|
3153
|
+
return void 0;
|
|
3154
|
+
}
|
|
3155
|
+
const columns = sourceColumns(source);
|
|
3156
|
+
if (columns.length === 0 || columns.some((name) => name.startsWith("\0")))
|
|
3157
|
+
return void 0;
|
|
3158
|
+
return {
|
|
3159
|
+
kind: "call",
|
|
3160
|
+
name: "JSON_OBJECT",
|
|
3161
|
+
arguments: columns.flatMap((name) => [
|
|
3162
|
+
{ kind: "literal", value: name },
|
|
3163
|
+
{ kind: "column", reference: `${source.alias}.${name}` }
|
|
3164
|
+
])
|
|
3165
|
+
};
|
|
3166
|
+
};
|
|
3167
|
+
if (pass.probing) {
|
|
3168
|
+
const probe = (expression) => {
|
|
3169
|
+
if (pass.needed)
|
|
3170
|
+
return;
|
|
3171
|
+
if (expression.kind === "column") {
|
|
3172
|
+
if (rowObject(expression.reference) !== void 0)
|
|
3173
|
+
pass.needed = true;
|
|
3174
|
+
return;
|
|
3175
|
+
}
|
|
3176
|
+
if (expression.kind === "subquery" || expression.kind === "exists") {
|
|
3177
|
+
rewriteBlock(expression.block);
|
|
3178
|
+
return;
|
|
3179
|
+
}
|
|
3180
|
+
childExpressions(expression).forEach(probe);
|
|
3181
|
+
};
|
|
3182
|
+
forEachBlockExpression(block, probe);
|
|
3183
|
+
return;
|
|
3184
|
+
}
|
|
3185
|
+
const rewrite = (expression) => {
|
|
3186
|
+
if (expression.kind === "column") {
|
|
3187
|
+
return rowObject(expression.reference) ?? expression;
|
|
3188
|
+
}
|
|
3189
|
+
if (expression.kind === "subquery" || expression.kind === "exists") {
|
|
3190
|
+
rewriteBlock(expression.block);
|
|
3191
|
+
return expression;
|
|
3192
|
+
}
|
|
3193
|
+
return mapChildExpressions(expression, rewrite);
|
|
3194
|
+
};
|
|
3195
|
+
mapBlockExpressions(block, rewrite);
|
|
3196
|
+
};
|
|
3197
|
+
rewriteBlock(plan);
|
|
3198
|
+
if (!pass.needed)
|
|
3199
|
+
return plan;
|
|
3200
|
+
const cloned = clonePlanTree(plan);
|
|
3201
|
+
pass.probing = false;
|
|
3202
|
+
rewriteBlock(cloned);
|
|
3203
|
+
return cloned;
|
|
3204
|
+
}
|
|
3205
|
+
function extendGroupByWithKeyDependents(plan, keyColumns, tableColumns = keyColumns) {
|
|
3206
|
+
const pass = { probing: true, needed: false };
|
|
3207
|
+
const rewriteBlock = (block) => {
|
|
3208
|
+
if (pass.probing && pass.needed)
|
|
3209
|
+
return;
|
|
3210
|
+
forEachNestedBlock(block, rewriteBlock);
|
|
3211
|
+
const visitSubqueries = (expression) => {
|
|
3212
|
+
if (expression.kind === "subquery" || expression.kind === "exists") {
|
|
3213
|
+
rewriteBlock(expression.block);
|
|
3214
|
+
return;
|
|
3215
|
+
}
|
|
3216
|
+
childExpressions(expression).forEach(visitSubqueries);
|
|
3217
|
+
};
|
|
3218
|
+
forEachBlockExpression(block, visitSubqueries);
|
|
3219
|
+
if (block.groupBy.length === 0)
|
|
3220
|
+
return;
|
|
3221
|
+
const sources = [block.base, ...block.joins];
|
|
3222
|
+
const multipleSources = sources.length > 1;
|
|
3223
|
+
const groupedColumns = new Set(block.groupBy.flatMap((expression) => expression.kind === "column" ? [expression.reference] : []));
|
|
3224
|
+
const additions = [];
|
|
3225
|
+
for (const source of sources) {
|
|
3226
|
+
const plain = source.derived === void 0 && source.union === void 0 && source.windowed === void 0 && source.recursive === void 0 && source.columnAliases === void 0;
|
|
3227
|
+
if (!plain)
|
|
3228
|
+
continue;
|
|
3229
|
+
const key = keyColumns(source.table);
|
|
3230
|
+
if (key === void 0 || key.length === 0)
|
|
3231
|
+
continue;
|
|
3232
|
+
const columns = tableColumns(source.table) ?? key;
|
|
3233
|
+
const namesFor = (column) => multipleSources ? [`${source.alias}.${column}`] : [column, `${source.alias}.${column}`];
|
|
3234
|
+
const keyGrouped = key.every((column) => namesFor(column).some((name) => groupedColumns.has(name)));
|
|
3235
|
+
if (!keyGrouped)
|
|
3236
|
+
continue;
|
|
3237
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
3238
|
+
const collect = (expression, insideAggregate) => {
|
|
3239
|
+
if (expression.kind === "column") {
|
|
3240
|
+
if (insideAggregate || groupedColumns.has(expression.reference))
|
|
3241
|
+
return;
|
|
3242
|
+
const separator = expression.reference.indexOf(".");
|
|
3243
|
+
const qualifier = separator === -1 ? void 0 : expression.reference.slice(0, separator);
|
|
3244
|
+
if (qualifier === void 0 ? multipleSources : qualifier !== source.alias)
|
|
3245
|
+
return;
|
|
3246
|
+
const name = separator === -1 ? expression.reference : expression.reference.slice(separator + 1);
|
|
3247
|
+
if (!columns.includes(name))
|
|
3248
|
+
return;
|
|
3249
|
+
referenced.add(expression.reference);
|
|
3250
|
+
return;
|
|
3251
|
+
}
|
|
3252
|
+
if (expression.kind === "subquery" || expression.kind === "exists")
|
|
3253
|
+
return;
|
|
3254
|
+
const aggregate = insideAggregate || expression.kind === "call" && aggregateNames.has(expression.name);
|
|
3255
|
+
childExpressions(expression).forEach((child) => collect(child, aggregate));
|
|
3256
|
+
};
|
|
3257
|
+
for (const item of block.select)
|
|
3258
|
+
collect(item.expression, false);
|
|
3259
|
+
for (const predicate of block.having) {
|
|
3260
|
+
collect(predicate.left, false);
|
|
3261
|
+
collect(predicate.right, false);
|
|
3262
|
+
}
|
|
3263
|
+
for (const order of block.orderBy)
|
|
3264
|
+
collect(order.expression, false);
|
|
3265
|
+
for (const reference of referenced) {
|
|
3266
|
+
if (pass.probing) {
|
|
3267
|
+
pass.needed = true;
|
|
3268
|
+
return;
|
|
3269
|
+
}
|
|
3270
|
+
groupedColumns.add(reference);
|
|
3271
|
+
additions.push({ kind: "column", reference });
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
if (additions.length > 0)
|
|
3275
|
+
block.groupBy = [...block.groupBy, ...additions];
|
|
3276
|
+
};
|
|
3277
|
+
rewriteBlock(plan);
|
|
3278
|
+
if (!pass.needed)
|
|
3279
|
+
return plan;
|
|
3280
|
+
const cloned = clonePlanTree(plan);
|
|
3281
|
+
pass.probing = false;
|
|
3282
|
+
rewriteBlock(cloned);
|
|
3283
|
+
return cloned;
|
|
3284
|
+
}
|
|
2490
3285
|
function mapBlockExpressions(block, map) {
|
|
2491
3286
|
for (const item of block.select)
|
|
2492
3287
|
item.expression = map(item.expression);
|
|
@@ -2681,6 +3476,9 @@ function exactConstantFold(expression) {
|
|
|
2681
3476
|
const seeded = left.seeded || right.seeded;
|
|
2682
3477
|
if (left.value === null || right.value === null)
|
|
2683
3478
|
return { value: null, seeded };
|
|
3479
|
+
if (expression.integer === true && expression.operator === "/" && typeof left.value === "number" && typeof right.value === "number") {
|
|
3480
|
+
return { value: integerQuotient(left.value, right.value), seeded };
|
|
3481
|
+
}
|
|
2684
3482
|
const folded = exactNumericBinary(expression.operator, left.value, right.value, 0, false);
|
|
2685
3483
|
return folded === void 0 ? void 0 : { value: folded, seeded };
|
|
2686
3484
|
}
|
|
@@ -2693,11 +3491,13 @@ function resolveExactNumericConstants(expression) {
|
|
|
2693
3491
|
}
|
|
2694
3492
|
if (folded?.seeded === true) {
|
|
2695
3493
|
const value = folded.value;
|
|
2696
|
-
if (value === null
|
|
3494
|
+
if (value === null)
|
|
2697
3495
|
return { kind: "literal", value };
|
|
3496
|
+
if (typeof value === "number")
|
|
3497
|
+
return { kind: "literal", value, decimal: true };
|
|
2698
3498
|
const fits = exactNumericAsNumber(value);
|
|
2699
3499
|
if (fits !== void 0)
|
|
2700
|
-
return { kind: "literal", value: fits };
|
|
3500
|
+
return { kind: "literal", value: fits, decimal: true };
|
|
2701
3501
|
return { kind: "literal", value, internalSqlValue: true, sqlDomain: { kind: "numeric" } };
|
|
2702
3502
|
}
|
|
2703
3503
|
if (expression.kind === "subquery" || expression.kind === "exists") {
|
|
@@ -2721,8 +3521,12 @@ function resolvePlanExactNumericConstants(plan) {
|
|
|
2721
3521
|
mapBlockExpressions(plan, resolveExactNumericConstants);
|
|
2722
3522
|
forEachNestedBlock(plan, resolvePlanExactNumericConstants);
|
|
2723
3523
|
}
|
|
3524
|
+
function resolveStatementExpression(expression) {
|
|
3525
|
+
annotateIntegerDivision(expression);
|
|
3526
|
+
return resolveExactNumericConstants(expression);
|
|
3527
|
+
}
|
|
2724
3528
|
function resolveStatementExactNumericConstants(statement) {
|
|
2725
|
-
const resolve =
|
|
3529
|
+
const resolve = resolveStatementExpression;
|
|
2726
3530
|
if (statement.kind === "insert") {
|
|
2727
3531
|
const conflict = statement.onConflict;
|
|
2728
3532
|
if (conflict?.assignments !== void 0) {
|
|
@@ -2744,6 +3548,18 @@ function resolveStatementExactNumericConstants(statement) {
|
|
|
2744
3548
|
predicate.left = resolve(predicate.left);
|
|
2745
3549
|
predicate.right = resolve(predicate.right);
|
|
2746
3550
|
}
|
|
3551
|
+
if (statement.from !== void 0) {
|
|
3552
|
+
for (const predicate of statement.from.predicates) {
|
|
3553
|
+
predicate.left = resolve(predicate.left);
|
|
3554
|
+
predicate.right = resolve(predicate.right);
|
|
3555
|
+
}
|
|
3556
|
+
for (const join of statement.from.joins) {
|
|
3557
|
+
join.left = resolve(join.left);
|
|
3558
|
+
join.right = resolve(join.right);
|
|
3559
|
+
if (join.on !== void 0)
|
|
3560
|
+
join.on = resolve(join.on);
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
2747
3563
|
return;
|
|
2748
3564
|
}
|
|
2749
3565
|
if (statement.kind === "merge") {
|
|
@@ -3197,6 +4013,7 @@ function executeRowQueryInternal(plan, tables, memory) {
|
|
|
3197
4013
|
assertTailParametersBound(plan);
|
|
3198
4014
|
plan = resolveStatementDatetimes(plan);
|
|
3199
4015
|
plan = bindPendingSelectShapes(plan, wildcardRowColumns(tables));
|
|
4016
|
+
plan = unifyPlanGroupedReferences(plan, wildcardRowColumns(tables));
|
|
3200
4017
|
validateGrouping(plan);
|
|
3201
4018
|
plan = expandSourceColumnAliases(plan, wildcardRowColumns(tables));
|
|
3202
4019
|
plan = expandNaturalJoins(plan, wildcardRowColumns(tables));
|
|
@@ -3460,11 +4277,16 @@ function evaluate(expression, context, group) {
|
|
|
3460
4277
|
return null;
|
|
3461
4278
|
if (expression.operator === "||")
|
|
3462
4279
|
return concatenatedSqlValue(left, right);
|
|
3463
|
-
const
|
|
4280
|
+
const leftValue = typeof left === "string" && typeof right === "number" && !isSqlDomainValue(left) ? readUntypedText("number", left) : left;
|
|
4281
|
+
const rightValue = typeof right === "string" && typeof left === "number" && !isSqlDomainValue(right) ? readUntypedText("number", right) : right;
|
|
4282
|
+
if (expression.integer === true && expression.operator === "/" && typeof leftValue === "number" && typeof rightValue === "number") {
|
|
4283
|
+
return integerQuotient(leftValue, rightValue);
|
|
4284
|
+
}
|
|
4285
|
+
const exact = exactNumericBinary(expression.operator, leftValue, rightValue);
|
|
3464
4286
|
if (exact !== void 0)
|
|
3465
4287
|
return exact;
|
|
3466
|
-
const a = numeric(
|
|
3467
|
-
const b = numeric(
|
|
4288
|
+
const a = numeric(leftValue);
|
|
4289
|
+
const b = numeric(rightValue);
|
|
3468
4290
|
if (expression.operator === "+")
|
|
3469
4291
|
return a + b;
|
|
3470
4292
|
if (expression.operator === "-")
|
|
@@ -3904,10 +4726,12 @@ function evaluateBooleanExpression(expression, evaluateValue) {
|
|
|
3904
4726
|
const distinct = distinctFromComparison(evaluateValue(expression.left), evaluateValue(expression.right));
|
|
3905
4727
|
return operator === "IS DISTINCT FROM" ? distinct : !distinct;
|
|
3906
4728
|
}
|
|
3907
|
-
const
|
|
3908
|
-
const
|
|
3909
|
-
if (
|
|
4729
|
+
const evaluatedLeft = evaluateValue(expression.left);
|
|
4730
|
+
const evaluatedRight = evaluateValue(expression.right);
|
|
4731
|
+
if (evaluatedLeft === null || evaluatedLeft === void 0 || evaluatedRight === null || evaluatedRight === void 0) {
|
|
3910
4732
|
return null;
|
|
4733
|
+
}
|
|
4734
|
+
const [left, right] = coerceComparisonOperands(evaluatedLeft, evaluatedRight);
|
|
3911
4735
|
const a = comparable(left);
|
|
3912
4736
|
const b = comparable(right);
|
|
3913
4737
|
if (operator === "=")
|
|
@@ -4063,6 +4887,15 @@ function validateGrouping(plan) {
|
|
|
4063
4887
|
throw new TypeError(`Selected column must appear in GROUP BY: ${item.alias}`);
|
|
4064
4888
|
}
|
|
4065
4889
|
}
|
|
4890
|
+
for (const predicate of plan.having) {
|
|
4891
|
+
for (const side of [predicate.left, predicate.right]) {
|
|
4892
|
+
if (hasAggregate(side) || expressionColumns(side).length === 0)
|
|
4893
|
+
continue;
|
|
4894
|
+
if (!groupedExpression(side, groupExpressions)) {
|
|
4895
|
+
throw new TypeError("HAVING conditions must use aggregates, literals, or GROUP BY expressions");
|
|
4896
|
+
}
|
|
4897
|
+
}
|
|
4898
|
+
}
|
|
4066
4899
|
const forbiddenAggregates = [
|
|
4067
4900
|
...plan.joins.flatMap((join) => [
|
|
4068
4901
|
join.left,
|
|
@@ -4150,8 +4983,9 @@ function externalizeQueryResult(result) {
|
|
|
4150
4983
|
for (let position = 0; position < result.columns.length; position += 1) {
|
|
4151
4984
|
const name = result.columns[position] ?? "";
|
|
4152
4985
|
const value = row[name];
|
|
4153
|
-
if (value !== void 0 && !isSqlDomainValue(value))
|
|
4986
|
+
if (value !== void 0 && !isSqlDomainValue(value) && !(typeof value === "number" && result.columnDomains[position]?.kind === "numeric")) {
|
|
4154
4987
|
continue;
|
|
4988
|
+
}
|
|
4155
4989
|
const external = asQueryValue(externalSqlDomainColumnValue(value, result.columnDomains[position]));
|
|
4156
4990
|
if (external === value)
|
|
4157
4991
|
continue;
|
|
@@ -4234,6 +5068,12 @@ class Parser {
|
|
|
4234
5068
|
}
|
|
4235
5069
|
const columns = this.#cteColumnList();
|
|
4236
5070
|
this.#keyword("AS");
|
|
5071
|
+
if (this.#isKeyword("NOT")) {
|
|
5072
|
+
this.#keyword("NOT");
|
|
5073
|
+
this.#keyword("MATERIALIZED");
|
|
5074
|
+
} else if (this.#isKeyword("MATERIALIZED")) {
|
|
5075
|
+
this.#keyword("MATERIALIZED");
|
|
5076
|
+
}
|
|
4237
5077
|
this.#expectPunctuation("(");
|
|
4238
5078
|
if (recursive) {
|
|
4239
5079
|
this.#recursiveCte(name, columns);
|
|
@@ -4325,7 +5165,9 @@ class Parser {
|
|
|
4325
5165
|
return plan;
|
|
4326
5166
|
}
|
|
4327
5167
|
#compoundTail() {
|
|
4328
|
-
|
|
5168
|
+
const tail = { orderBy: this.#orderByClause(), ...this.#tailClauses() };
|
|
5169
|
+
this.#lockingClause();
|
|
5170
|
+
return tail;
|
|
4329
5171
|
}
|
|
4330
5172
|
#setTerm(sql, member) {
|
|
4331
5173
|
const first = this.#unionMember(sql, member);
|
|
@@ -4514,6 +5356,22 @@ class Parser {
|
|
|
4514
5356
|
nullable = false;
|
|
4515
5357
|
continue;
|
|
4516
5358
|
}
|
|
5359
|
+
if (this.#isKeyword("CONSTRAINT")) {
|
|
5360
|
+
this.#keyword("CONSTRAINT");
|
|
5361
|
+
const constraintName = this.#identifier();
|
|
5362
|
+
if (this.#isKeyword("CHECK")) {
|
|
5363
|
+
checks.push(this.#checkConstraint(constraintName));
|
|
5364
|
+
continue;
|
|
5365
|
+
}
|
|
5366
|
+
if (this.#isKeyword("REFERENCES")) {
|
|
5367
|
+
foreignKeys.push(this.#references(constraintName, [name]));
|
|
5368
|
+
continue;
|
|
5369
|
+
}
|
|
5370
|
+
if (this.#isKeyword("PRIMARY") || this.#isKeyword("UNIQUE") || this.#isKeyword("NOT") || this.#isKeyword("NULL")) {
|
|
5371
|
+
continue;
|
|
5372
|
+
}
|
|
5373
|
+
throw new TypeError(`Expected a constraint after CONSTRAINT ${constraintName}`);
|
|
5374
|
+
}
|
|
4517
5375
|
if (this.#isKeyword("CHECK")) {
|
|
4518
5376
|
checks.push(this.#checkConstraint(`${table}_${name}_check`));
|
|
4519
5377
|
continue;
|
|
@@ -4890,6 +5748,7 @@ class Parser {
|
|
|
4890
5748
|
return `numeric:${precision === void 0 ? "" : String(precision)}:${scale === void 0 ? "" : String(scale)}`;
|
|
4891
5749
|
}
|
|
4892
5750
|
if (["JSON", "JSONB", "UUID", "DATE", "TIME", "INTERVAL"].includes(word)) {
|
|
5751
|
+
this.#multiWordTypeTail(word);
|
|
4893
5752
|
return word.toLowerCase();
|
|
4894
5753
|
}
|
|
4895
5754
|
if (word === "DOUBLE") {
|
|
@@ -4899,14 +5758,27 @@ class Parser {
|
|
|
4899
5758
|
const mapped = createTableTypeNames.get(word);
|
|
4900
5759
|
if (mapped === void 0)
|
|
4901
5760
|
throw new TypeError(`Unsupported CAST target: ${word}`);
|
|
5761
|
+
this.#multiWordTypeTail(word);
|
|
4902
5762
|
if (this.#punctuation("(")) {
|
|
4903
5763
|
this.#typeWidth();
|
|
4904
5764
|
if (this.#punctuation(","))
|
|
4905
5765
|
this.#typeWidth();
|
|
4906
5766
|
this.#expectPunctuation(")");
|
|
4907
5767
|
}
|
|
4908
|
-
|
|
4909
|
-
|
|
5768
|
+
return integerTypeNames.has(word) ? "number-integer" : mapped;
|
|
5769
|
+
}
|
|
5770
|
+
#multiWordTypeTail(word) {
|
|
5771
|
+
if (word === "CHARACTER" && this.#isKeyword("VARYING")) {
|
|
5772
|
+
this.#keyword("VARYING");
|
|
5773
|
+
return true;
|
|
5774
|
+
}
|
|
5775
|
+
if ((word === "TIMESTAMP" || word === "TIME") && (this.#isKeyword("WITH") || this.#isKeyword("WITHOUT"))) {
|
|
5776
|
+
this.#keyword(this.#isKeyword("WITH") ? "WITH" : "WITHOUT");
|
|
5777
|
+
this.#keyword("TIME");
|
|
5778
|
+
this.#keyword("ZONE");
|
|
5779
|
+
return true;
|
|
5780
|
+
}
|
|
5781
|
+
return false;
|
|
4910
5782
|
}
|
|
4911
5783
|
#columnType() {
|
|
4912
5784
|
const declared = this.#identifier();
|
|
@@ -4932,6 +5804,7 @@ class Parser {
|
|
|
4932
5804
|
};
|
|
4933
5805
|
}
|
|
4934
5806
|
if (["JSON", "JSONB", "UUID", "DATE", "TIME", "INTERVAL"].includes(word)) {
|
|
5807
|
+
this.#multiWordTypeTail(word);
|
|
4935
5808
|
return { type: "string", sqlDomain: { kind: word.toLowerCase() } };
|
|
4936
5809
|
}
|
|
4937
5810
|
if (word === "DOUBLE") {
|
|
@@ -4942,6 +5815,7 @@ class Parser {
|
|
|
4942
5815
|
if (mapped === void 0) {
|
|
4943
5816
|
return { type: "string", sqlDomain: { kind: "enum", name: declared, values: [] } };
|
|
4944
5817
|
}
|
|
5818
|
+
const multiWord = this.#multiWordTypeTail(word);
|
|
4945
5819
|
if (this.#punctuation("(")) {
|
|
4946
5820
|
this.#typeWidth();
|
|
4947
5821
|
if (this.#punctuation(",")) {
|
|
@@ -4949,12 +5823,11 @@ class Parser {
|
|
|
4949
5823
|
}
|
|
4950
5824
|
this.#expectPunctuation(")");
|
|
4951
5825
|
}
|
|
4952
|
-
if (this.#punctuation("[")) {
|
|
5826
|
+
if (!multiWord && this.#punctuation("[")) {
|
|
4953
5827
|
this.#expectPunctuation("]");
|
|
4954
5828
|
return { type: "string", sqlDomain: { kind: "array", element: word } };
|
|
4955
5829
|
}
|
|
4956
|
-
|
|
4957
|
-
return { type: mapped, ...integer ? { integer: true } : {} };
|
|
5830
|
+
return { type: mapped, ...integerTypeNames.has(word) ? { integer: true } : {} };
|
|
4958
5831
|
}
|
|
4959
5832
|
parseMutation(keyword) {
|
|
4960
5833
|
const statement = keyword === "INSERT" ? this.#insertStatement() : keyword === "UPDATE" ? this.#updateStatement() : this.#deleteStatement();
|
|
@@ -4993,6 +5866,7 @@ class Parser {
|
|
|
4993
5866
|
}
|
|
4994
5867
|
if (this.#isKeyword("SELECT") || this.#isKeyword("WITH") || this.#peek().text === "(") {
|
|
4995
5868
|
const insertSource = this.#queryExpression("(insert select)");
|
|
5869
|
+
annotatePlanIntegerDivision(insertSource);
|
|
4996
5870
|
resolvePlanExactNumericConstants(insertSource);
|
|
4997
5871
|
const query = planHasPendingSelectShapes(insertSource) ? insertSource : optimizePlan(insertSource);
|
|
4998
5872
|
if (!planHasPendingSelectShapes(query) && !query.select.some((item) => item.expression.kind === "wildcard") && columns.length > 0 && query.select.length !== columns.length) {
|
|
@@ -5312,6 +6186,7 @@ class Parser {
|
|
|
5312
6186
|
if (new Set(assignments.map(({ column }) => column)).size !== assignments.length) {
|
|
5313
6187
|
throw new TypeError("UPDATE assignments must set each column once");
|
|
5314
6188
|
}
|
|
6189
|
+
const from = this.#mutationSources("FROM");
|
|
5315
6190
|
const predicates = this.#mutationPredicates();
|
|
5316
6191
|
return {
|
|
5317
6192
|
kind: "update",
|
|
@@ -5319,20 +6194,31 @@ class Parser {
|
|
|
5319
6194
|
...alias === void 0 ? {} : { alias },
|
|
5320
6195
|
assignments,
|
|
5321
6196
|
predicates,
|
|
6197
|
+
...from === void 0 ? {} : { from },
|
|
5322
6198
|
...this.#returningClause(table, alias)
|
|
5323
6199
|
};
|
|
5324
6200
|
}
|
|
6201
|
+
#mutationSources(keyword) {
|
|
6202
|
+
if (!this.#isKeyword(keyword))
|
|
6203
|
+
return void 0;
|
|
6204
|
+
this.#keyword(keyword);
|
|
6205
|
+
const groupConditions = [];
|
|
6206
|
+
const { base, joins } = this.#joinedSources([], groupConditions);
|
|
6207
|
+
return { base, joins, predicates: groupConditions.flatMap(splitCondition) };
|
|
6208
|
+
}
|
|
5325
6209
|
#deleteStatement() {
|
|
5326
6210
|
this.#keyword("DELETE");
|
|
5327
6211
|
this.#keyword("FROM");
|
|
5328
6212
|
const table = this.#identifier();
|
|
5329
|
-
const alias = this.#mutationAlias("WHERE", "RETURNING");
|
|
6213
|
+
const alias = this.#mutationAlias("WHERE", "USING", "RETURNING");
|
|
6214
|
+
const from = this.#mutationSources("USING");
|
|
5330
6215
|
const predicates = this.#mutationPredicates();
|
|
5331
6216
|
return {
|
|
5332
6217
|
kind: "delete",
|
|
5333
6218
|
table,
|
|
5334
6219
|
...alias === void 0 ? {} : { alias },
|
|
5335
6220
|
predicates,
|
|
6221
|
+
...from === void 0 ? {} : { from },
|
|
5336
6222
|
...this.#returningClause(table, alias)
|
|
5337
6223
|
};
|
|
5338
6224
|
}
|
|
@@ -5386,9 +6272,9 @@ class Parser {
|
|
|
5386
6272
|
}
|
|
5387
6273
|
const needsExecution = (value) => value.kind === "subquery" || value.kind === "exists" || value.kind === "call" && (statementDatetimeNames.has(value.name) || value.name === "NEXTVAL" || value.name === "CURRVAL" || volatileScalarFunctionNames.has(value.name)) || childExpressions(value).some(needsExecution);
|
|
5388
6274
|
if (needsExecution(expression)) {
|
|
5389
|
-
return { expression:
|
|
6275
|
+
return { expression: resolveStatementExpression(expression) };
|
|
5390
6276
|
}
|
|
5391
|
-
return asQueryValue(evaluate(
|
|
6277
|
+
return asQueryValue(evaluate(resolveStatementExpression(expression), {}));
|
|
5392
6278
|
}
|
|
5393
6279
|
#unionMember(sql, member) {
|
|
5394
6280
|
if (this.#punctuation("(")) {
|
|
@@ -5436,6 +6322,10 @@ class Parser {
|
|
|
5436
6322
|
#limitClause() {
|
|
5437
6323
|
if (this.#isKeyword("LIMIT")) {
|
|
5438
6324
|
this.#keyword("LIMIT");
|
|
6325
|
+
if (this.#isKeyword("ALL")) {
|
|
6326
|
+
this.#keyword("ALL");
|
|
6327
|
+
return {};
|
|
6328
|
+
}
|
|
5439
6329
|
if (this.#peek().kind === "parameter") {
|
|
5440
6330
|
const parameter = this.#parameterExpression();
|
|
5441
6331
|
return { limitParameter: parameter.index };
|
|
@@ -5488,6 +6378,37 @@ class Parser {
|
|
|
5488
6378
|
this.#keyword("ROW");
|
|
5489
6379
|
return result;
|
|
5490
6380
|
}
|
|
6381
|
+
#lockingClause() {
|
|
6382
|
+
while (this.#isKeyword("FOR")) {
|
|
6383
|
+
this.#keyword("FOR");
|
|
6384
|
+
if (this.#isKeyword("UPDATE"))
|
|
6385
|
+
this.#keyword("UPDATE");
|
|
6386
|
+
else if (this.#isKeyword("SHARE"))
|
|
6387
|
+
this.#keyword("SHARE");
|
|
6388
|
+
else if (this.#isKeyword("NO")) {
|
|
6389
|
+
this.#keyword("NO");
|
|
6390
|
+
this.#keyword("KEY");
|
|
6391
|
+
this.#keyword("UPDATE");
|
|
6392
|
+
} else if (this.#isKeyword("KEY")) {
|
|
6393
|
+
this.#keyword("KEY");
|
|
6394
|
+
this.#keyword("SHARE");
|
|
6395
|
+
} else {
|
|
6396
|
+
throw new TypeError(`Expected UPDATE or SHARE after FOR, found ${this.#peek().text}`);
|
|
6397
|
+
}
|
|
6398
|
+
if (this.#isKeyword("OF")) {
|
|
6399
|
+
this.#keyword("OF");
|
|
6400
|
+
this.#identifier();
|
|
6401
|
+
while (this.#punctuation(","))
|
|
6402
|
+
this.#identifier();
|
|
6403
|
+
}
|
|
6404
|
+
if (this.#isKeyword("NOWAIT"))
|
|
6405
|
+
this.#keyword("NOWAIT");
|
|
6406
|
+
else if (this.#isKeyword("SKIP")) {
|
|
6407
|
+
this.#keyword("SKIP");
|
|
6408
|
+
this.#keyword("LOCKED");
|
|
6409
|
+
}
|
|
6410
|
+
}
|
|
6411
|
+
}
|
|
5491
6412
|
#tailClauses() {
|
|
5492
6413
|
if (this.#isKeyword("LIMIT")) {
|
|
5493
6414
|
return { ...this.#limitClause(), ...this.#offsetClause() };
|
|
@@ -5561,33 +6482,216 @@ class Parser {
|
|
|
5561
6482
|
this.#compoundMember = false;
|
|
5562
6483
|
this.#keyword("SELECT");
|
|
5563
6484
|
let distinct = false;
|
|
6485
|
+
let distinctOn;
|
|
5564
6486
|
if (this.#isKeyword("DISTINCT")) {
|
|
5565
6487
|
this.#keyword("DISTINCT");
|
|
5566
|
-
|
|
6488
|
+
if (this.#isKeyword("ON")) {
|
|
6489
|
+
this.#keyword("ON");
|
|
6490
|
+
this.#expectPunctuation("(");
|
|
6491
|
+
distinctOn = this.#expressionList();
|
|
6492
|
+
this.#expectPunctuation(")");
|
|
6493
|
+
} else {
|
|
6494
|
+
distinct = true;
|
|
6495
|
+
}
|
|
6496
|
+
} else if (this.#isKeyword("ALL")) {
|
|
6497
|
+
this.#keyword("ALL");
|
|
5567
6498
|
}
|
|
5568
6499
|
const select = this.#selectList();
|
|
5569
6500
|
let base;
|
|
6501
|
+
let joins = [];
|
|
6502
|
+
const groupConditions = [];
|
|
5570
6503
|
if (this.#isKeyword("FROM")) {
|
|
5571
6504
|
this.#keyword("FROM");
|
|
5572
|
-
base = this.#
|
|
6505
|
+
({ base, joins } = this.#joinedSources(select, groupConditions));
|
|
5573
6506
|
} else {
|
|
5574
6507
|
if (select.some((item) => item.expression.kind === "wildcard")) {
|
|
5575
6508
|
throw new TypeError("SELECT * requires a FROM clause");
|
|
5576
6509
|
}
|
|
5577
6510
|
base = { table: DUAL_TABLE, alias: DUAL_TABLE };
|
|
5578
6511
|
}
|
|
5579
|
-
const
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
6512
|
+
const predicates = groupConditions.flatMap(splitCondition);
|
|
6513
|
+
if (this.#isKeyword("WHERE")) {
|
|
6514
|
+
this.#keyword("WHERE");
|
|
6515
|
+
predicates.push(...splitCondition(this.#expression()));
|
|
6516
|
+
}
|
|
6517
|
+
const groupBy = [];
|
|
6518
|
+
let groupingSets;
|
|
6519
|
+
if (this.#isKeyword("GROUP")) {
|
|
6520
|
+
this.#keyword("GROUP");
|
|
6521
|
+
this.#keyword("BY");
|
|
6522
|
+
if (this.#isKeyword("GROUPING") || this.#isKeyword("ROLLUP") || this.#isKeyword("CUBE")) {
|
|
6523
|
+
groupingSets = this.#groupingSets();
|
|
6524
|
+
} else {
|
|
6525
|
+
groupBy.push(...this.#expressionList());
|
|
6526
|
+
}
|
|
6527
|
+
}
|
|
6528
|
+
const having = [];
|
|
6529
|
+
if (this.#isKeyword("HAVING")) {
|
|
6530
|
+
this.#keyword("HAVING");
|
|
6531
|
+
having.push(...splitCondition(this.#expression()));
|
|
6532
|
+
}
|
|
6533
|
+
if (this.#isKeyword("WINDOW")) {
|
|
6534
|
+
this.#keyword("WINDOW");
|
|
6535
|
+
for (; ; ) {
|
|
6536
|
+
this.#identifier();
|
|
6537
|
+
this.#keyword("AS");
|
|
6538
|
+
const definition = this.#namedWindows.get(this.tokens[this.#index - 2]?.text ?? "");
|
|
6539
|
+
this.#index = definition?.end ?? this.#index;
|
|
6540
|
+
if (!this.#punctuation(","))
|
|
6541
|
+
break;
|
|
6542
|
+
}
|
|
6543
|
+
}
|
|
6544
|
+
const orderBy = compoundMember ? [] : this.#orderByClause();
|
|
6545
|
+
const tail = compoundMember ? {} : this.#tailClauses();
|
|
6546
|
+
if (!compoundMember)
|
|
6547
|
+
this.#lockingClause();
|
|
6548
|
+
if (distinctOn !== void 0) {
|
|
6549
|
+
return this.#distinctOnBlock(sql, distinctOn, { base, joins, select, predicates, groupBy, having, orderBy, groupingSets }, tail);
|
|
6550
|
+
}
|
|
6551
|
+
return assembleSelectBlock({
|
|
6552
|
+
sql,
|
|
6553
|
+
base,
|
|
6554
|
+
joins,
|
|
6555
|
+
select,
|
|
6556
|
+
distinct,
|
|
6557
|
+
predicates,
|
|
6558
|
+
groupBy,
|
|
6559
|
+
having,
|
|
6560
|
+
orderBy,
|
|
6561
|
+
...tail,
|
|
6562
|
+
...groupingSets === void 0 ? {} : { groupingSets }
|
|
6563
|
+
}, this.nextDerivedSequence);
|
|
6564
|
+
}
|
|
6565
|
+
#distinctOnBlock(sql, distinctOn, parts, tail) {
|
|
6566
|
+
const rowNumberAlias = "\0distinct_on";
|
|
6567
|
+
const innerSelect = [...parts.select];
|
|
6568
|
+
const aliased = new Map(parts.select.filter((item) => item.expression.kind !== "wildcard").map((item) => [item.alias, item.expression]));
|
|
6569
|
+
const windowExpression = (expression) => {
|
|
6570
|
+
if (expression.kind === "column" && !expression.reference.includes(".")) {
|
|
6571
|
+
const labelled = aliased.get(expression.reference);
|
|
6572
|
+
if (labelled !== void 0)
|
|
6573
|
+
return labelled;
|
|
6574
|
+
}
|
|
6575
|
+
return expression;
|
|
6576
|
+
};
|
|
6577
|
+
const outerOrderBy = [];
|
|
6578
|
+
parts.orderBy.forEach((order, index) => {
|
|
6579
|
+
const expression = order.expression;
|
|
6580
|
+
const output = expression.kind === "column" && !expression.reference.includes(".") ? parts.select.find((item) => item.alias === expression.reference) : parts.select.find((item) => item.expression.kind !== "wildcard" && JSON.stringify(item.expression) === JSON.stringify(expression));
|
|
6581
|
+
let alias = output?.alias;
|
|
6582
|
+
if (alias === void 0) {
|
|
6583
|
+
alias = `\0distinct_on_order_${String(index)}`;
|
|
6584
|
+
innerSelect.push({ expression, alias });
|
|
6585
|
+
}
|
|
6586
|
+
outerOrderBy.push({ ...order, expression: { kind: "column", reference: alias } });
|
|
6587
|
+
});
|
|
6588
|
+
innerSelect.push({
|
|
6589
|
+
expression: {
|
|
6590
|
+
kind: "window",
|
|
6591
|
+
name: "ROW_NUMBER",
|
|
6592
|
+
partitionBy: distinctOn.map(windowExpression),
|
|
6593
|
+
orderBy: parts.orderBy.map((order) => ({
|
|
6594
|
+
...order,
|
|
6595
|
+
expression: windowExpression(order.expression)
|
|
6596
|
+
}))
|
|
6597
|
+
},
|
|
6598
|
+
alias: rowNumberAlias
|
|
6599
|
+
});
|
|
6600
|
+
const inner = assembleSelectBlock({
|
|
6601
|
+
sql: "(distinct on)",
|
|
6602
|
+
base: parts.base,
|
|
6603
|
+
joins: parts.joins,
|
|
6604
|
+
select: innerSelect,
|
|
6605
|
+
distinct: false,
|
|
6606
|
+
predicates: parts.predicates,
|
|
6607
|
+
groupBy: parts.groupBy,
|
|
6608
|
+
having: parts.having,
|
|
6609
|
+
orderBy: [],
|
|
6610
|
+
...parts.groupingSets === void 0 ? {} : { groupingSets: parts.groupingSets }
|
|
6611
|
+
}, this.nextDerivedSequence);
|
|
6612
|
+
const outerAlias = "(distinct on)";
|
|
6613
|
+
const outerSelect = parts.select.map((item) => item.expression.kind === "wildcard" ? item : { expression: { kind: "column", reference: item.alias }, alias: item.alias });
|
|
6614
|
+
return assembleSelectBlock({
|
|
6615
|
+
sql,
|
|
6616
|
+
base: this.#derivedSource(inner, outerAlias),
|
|
6617
|
+
joins: [],
|
|
6618
|
+
select: outerSelect,
|
|
6619
|
+
distinct: false,
|
|
6620
|
+
predicates: [
|
|
6621
|
+
{
|
|
6622
|
+
left: { kind: "column", reference: rowNumberAlias },
|
|
6623
|
+
operator: "=",
|
|
6624
|
+
right: { kind: "literal", value: 1 }
|
|
6625
|
+
}
|
|
6626
|
+
],
|
|
6627
|
+
groupBy: [],
|
|
6628
|
+
having: [],
|
|
6629
|
+
orderBy: outerOrderBy,
|
|
6630
|
+
...tail
|
|
6631
|
+
}, this.nextDerivedSequence);
|
|
6632
|
+
}
|
|
6633
|
+
#groupingSets() {
|
|
6634
|
+
if (this.#isKeyword("GROUPING")) {
|
|
6635
|
+
this.#keyword("GROUPING");
|
|
6636
|
+
this.#keyword("SETS");
|
|
6637
|
+
this.#expectPunctuation("(");
|
|
6638
|
+
const sets2 = [];
|
|
6639
|
+
for (; ; ) {
|
|
6640
|
+
this.#expectPunctuation("(");
|
|
6641
|
+
const set = [];
|
|
6642
|
+
if (!this.#punctuation(")")) {
|
|
6643
|
+
set.push(...this.#expressionList());
|
|
6644
|
+
this.#expectPunctuation(")");
|
|
6645
|
+
}
|
|
6646
|
+
sets2.push(set);
|
|
6647
|
+
if (!this.#punctuation(","))
|
|
6648
|
+
break;
|
|
6649
|
+
}
|
|
6650
|
+
this.#expectPunctuation(")");
|
|
6651
|
+
return sets2;
|
|
6652
|
+
}
|
|
6653
|
+
const cube = this.#isKeyword("CUBE");
|
|
6654
|
+
this.#keyword(cube ? "CUBE" : "ROLLUP");
|
|
6655
|
+
this.#expectPunctuation("(");
|
|
6656
|
+
const columns = this.#expressionList();
|
|
6657
|
+
this.#expectPunctuation(")");
|
|
6658
|
+
if (columns.length === 0)
|
|
6659
|
+
throw new TypeError("ROLLUP/CUBE take at least one expression");
|
|
6660
|
+
if (cube) {
|
|
6661
|
+
if (columns.length > 5) {
|
|
6662
|
+
throw new TypeError("CUBE supports at most 5 expressions (32 grouping sets)");
|
|
6663
|
+
}
|
|
6664
|
+
const sets2 = [];
|
|
6665
|
+
for (let mask = (1 << columns.length) - 1; mask >= 0; mask -= 1) {
|
|
6666
|
+
sets2.push(columns.filter((_, index) => (mask & 1 << index) !== 0));
|
|
6667
|
+
}
|
|
6668
|
+
return sets2;
|
|
6669
|
+
}
|
|
6670
|
+
const sets = [];
|
|
6671
|
+
for (let length = columns.length; length >= 0; length -= 1) {
|
|
6672
|
+
sets.push(columns.slice(0, length));
|
|
6673
|
+
}
|
|
6674
|
+
return sets;
|
|
6675
|
+
}
|
|
6676
|
+
#joinedSources(select, groupConditions) {
|
|
6677
|
+
const first = this.#joinOperand(select, groupConditions);
|
|
6678
|
+
let base = first.base;
|
|
6679
|
+
const joins = [...first.joins];
|
|
6680
|
+
let rightJoins = first.rightJoins;
|
|
6681
|
+
while (this.#peek().text === "," || this.#isKeyword("NATURAL") || this.#isKeyword("JOIN") || this.#isKeyword("INNER") || this.#isKeyword("LEFT") || this.#isKeyword("RIGHT") || this.#isKeyword("FULL") || this.#isKeyword("CROSS")) {
|
|
6682
|
+
if (this.#punctuation(",")) {
|
|
6683
|
+
const operand2 = this.#joinOperand(select, groupConditions);
|
|
6684
|
+
joins.push(crossJoinPlan(operand2.base), ...operand2.joins);
|
|
6685
|
+
rightJoins += operand2.rightJoins;
|
|
6686
|
+
continue;
|
|
6687
|
+
}
|
|
6688
|
+
if (this.#isKeyword("CROSS")) {
|
|
6689
|
+
this.#keyword("CROSS");
|
|
6690
|
+
this.#keyword("JOIN");
|
|
6691
|
+
const operand2 = this.#joinOperand(select, groupConditions);
|
|
6692
|
+
joins.push(crossJoinPlan(operand2.base), ...operand2.joins);
|
|
6693
|
+
rightJoins += operand2.rightJoins;
|
|
6694
|
+
continue;
|
|
5591
6695
|
}
|
|
5592
6696
|
let kind = "inner";
|
|
5593
6697
|
let right = false;
|
|
@@ -5614,7 +6718,21 @@ class Parser {
|
|
|
5614
6718
|
if (this.#isKeyword("OUTER"))
|
|
5615
6719
|
this.#keyword("OUTER");
|
|
5616
6720
|
this.#keyword("JOIN");
|
|
5617
|
-
const
|
|
6721
|
+
const operand = this.#joinOperand(select, groupConditions);
|
|
6722
|
+
if (operand.group) {
|
|
6723
|
+
if (natural || right || full || kind === "left") {
|
|
6724
|
+
throw new TypeError("A parenthesized join can only follow a comma, CROSS JOIN, or INNER JOIN");
|
|
6725
|
+
}
|
|
6726
|
+
if (this.#isKeyword("USING")) {
|
|
6727
|
+
throw new TypeError("A parenthesized join takes an ON condition, not USING");
|
|
6728
|
+
}
|
|
6729
|
+
this.#keyword("ON");
|
|
6730
|
+
groupConditions.push(this.#expression());
|
|
6731
|
+
joins.push(crossJoinPlan(operand.base), ...operand.joins);
|
|
6732
|
+
rightJoins += operand.rightJoins;
|
|
6733
|
+
continue;
|
|
6734
|
+
}
|
|
6735
|
+
const source = operand.base;
|
|
5618
6736
|
if (natural) {
|
|
5619
6737
|
if (this.#isKeyword("ON") || this.#isKeyword("USING")) {
|
|
5620
6738
|
throw new TypeError("A NATURAL join takes no ON or USING clause");
|
|
@@ -5674,95 +6792,58 @@ class Parser {
|
|
|
5674
6792
|
}
|
|
5675
6793
|
}
|
|
5676
6794
|
}
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
this.#keyword("WHERE");
|
|
5680
|
-
predicates.push(...splitCondition(this.#expression()));
|
|
5681
|
-
}
|
|
5682
|
-
const groupBy = [];
|
|
5683
|
-
let groupingSets;
|
|
5684
|
-
if (this.#isKeyword("GROUP")) {
|
|
5685
|
-
this.#keyword("GROUP");
|
|
5686
|
-
this.#keyword("BY");
|
|
5687
|
-
if (this.#isKeyword("GROUPING") || this.#isKeyword("ROLLUP") || this.#isKeyword("CUBE")) {
|
|
5688
|
-
groupingSets = this.#groupingSets();
|
|
5689
|
-
} else {
|
|
5690
|
-
groupBy.push(...this.#expressionList());
|
|
5691
|
-
}
|
|
5692
|
-
}
|
|
5693
|
-
const having = [];
|
|
5694
|
-
if (this.#isKeyword("HAVING")) {
|
|
5695
|
-
this.#keyword("HAVING");
|
|
5696
|
-
having.push(...splitCondition(this.#expression()));
|
|
5697
|
-
}
|
|
5698
|
-
if (this.#isKeyword("WINDOW")) {
|
|
5699
|
-
this.#keyword("WINDOW");
|
|
5700
|
-
for (; ; ) {
|
|
5701
|
-
this.#identifier();
|
|
5702
|
-
this.#keyword("AS");
|
|
5703
|
-
const definition = this.#namedWindows.get(this.tokens[this.#index - 2]?.text ?? "");
|
|
5704
|
-
this.#index = definition?.end ?? this.#index;
|
|
5705
|
-
if (!this.#punctuation(","))
|
|
5706
|
-
break;
|
|
5707
|
-
}
|
|
6795
|
+
if (rightJoins > 0 && joins.length !== 1) {
|
|
6796
|
+
throw new TypeError("RIGHT JOIN is only supported as the sole join");
|
|
5708
6797
|
}
|
|
5709
|
-
|
|
5710
|
-
return assembleSelectBlock({
|
|
5711
|
-
sql,
|
|
5712
|
-
base,
|
|
5713
|
-
joins,
|
|
5714
|
-
select,
|
|
5715
|
-
distinct,
|
|
5716
|
-
predicates,
|
|
5717
|
-
groupBy,
|
|
5718
|
-
having,
|
|
5719
|
-
orderBy,
|
|
5720
|
-
...compoundMember ? {} : this.#tailClauses(),
|
|
5721
|
-
...groupingSets === void 0 ? {} : { groupingSets }
|
|
5722
|
-
}, this.nextDerivedSequence);
|
|
6798
|
+
return { base, joins, rightJoins };
|
|
5723
6799
|
}
|
|
5724
|
-
#
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
this.#keyword("SETS");
|
|
6800
|
+
#joinOperand(select, groupConditions) {
|
|
6801
|
+
const next = this.#peek();
|
|
6802
|
+
if (next.kind === "punctuation" && next.text === "(" && this.#parenthesizedJoinAhead()) {
|
|
5728
6803
|
this.#expectPunctuation("(");
|
|
5729
|
-
const
|
|
5730
|
-
for (; ; ) {
|
|
5731
|
-
this.#expectPunctuation("(");
|
|
5732
|
-
const set = [];
|
|
5733
|
-
if (!this.#punctuation(")")) {
|
|
5734
|
-
set.push(...this.#expressionList());
|
|
5735
|
-
this.#expectPunctuation(")");
|
|
5736
|
-
}
|
|
5737
|
-
sets2.push(set);
|
|
5738
|
-
if (!this.#punctuation(","))
|
|
5739
|
-
break;
|
|
5740
|
-
}
|
|
6804
|
+
const inner = this.#joinedSources(select, groupConditions);
|
|
5741
6805
|
this.#expectPunctuation(")");
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
const cube = this.#isKeyword("CUBE");
|
|
5745
|
-
this.#keyword(cube ? "CUBE" : "ROLLUP");
|
|
5746
|
-
this.#expectPunctuation("(");
|
|
5747
|
-
const columns = this.#expressionList();
|
|
5748
|
-
this.#expectPunctuation(")");
|
|
5749
|
-
if (columns.length === 0)
|
|
5750
|
-
throw new TypeError("ROLLUP/CUBE take at least one expression");
|
|
5751
|
-
if (cube) {
|
|
5752
|
-
if (columns.length > 5) {
|
|
5753
|
-
throw new TypeError("CUBE supports at most 5 expressions (32 grouping sets)");
|
|
6806
|
+
if (this.#sourceAlias() !== void 0) {
|
|
6807
|
+
throw new TypeError("A parenthesized join cannot take an alias");
|
|
5754
6808
|
}
|
|
5755
|
-
|
|
5756
|
-
for (let mask = (1 << columns.length) - 1; mask >= 0; mask -= 1) {
|
|
5757
|
-
sets2.push(columns.filter((_, index) => (mask & 1 << index) !== 0));
|
|
5758
|
-
}
|
|
5759
|
-
return sets2;
|
|
6809
|
+
return { ...inner, group: true };
|
|
5760
6810
|
}
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
6811
|
+
return { base: this.#source(), joins: [], rightJoins: 0, group: false };
|
|
6812
|
+
}
|
|
6813
|
+
#parenthesizedJoinAhead() {
|
|
6814
|
+
let cursor = this.#index;
|
|
6815
|
+
let first = this.tokens[cursor];
|
|
6816
|
+
while (first?.kind === "punctuation" && first.text === "(") {
|
|
6817
|
+
cursor += 1;
|
|
6818
|
+
first = this.tokens[cursor];
|
|
5764
6819
|
}
|
|
5765
|
-
|
|
6820
|
+
if (first?.kind !== "identifier")
|
|
6821
|
+
return false;
|
|
6822
|
+
const keyword = first.quoted === true ? "" : first.text.toUpperCase();
|
|
6823
|
+
if (keyword !== "SELECT" && keyword !== "VALUES" && keyword !== "WITH")
|
|
6824
|
+
return true;
|
|
6825
|
+
if (cursor === this.#index + 1)
|
|
6826
|
+
return false;
|
|
6827
|
+
let depth = 0;
|
|
6828
|
+
for (let index = this.#index; index < this.tokens.length; index += 1) {
|
|
6829
|
+
const token = this.tokens[index];
|
|
6830
|
+
if (token?.kind !== "punctuation")
|
|
6831
|
+
continue;
|
|
6832
|
+
if (token.text === "(")
|
|
6833
|
+
depth += 1;
|
|
6834
|
+
else if (token.text === ")") {
|
|
6835
|
+
depth -= 1;
|
|
6836
|
+
if (depth > 0)
|
|
6837
|
+
continue;
|
|
6838
|
+
const next = this.tokens[index + 1];
|
|
6839
|
+
if (next?.kind !== "identifier")
|
|
6840
|
+
return true;
|
|
6841
|
+
if (next.quoted === true || next.text.toUpperCase() === "AS")
|
|
6842
|
+
return false;
|
|
6843
|
+
return clauseKeywords.has(next.text.toUpperCase()) || next.text.toUpperCase() === "ON";
|
|
6844
|
+
}
|
|
6845
|
+
}
|
|
6846
|
+
return false;
|
|
5766
6847
|
}
|
|
5767
6848
|
#selectList() {
|
|
5768
6849
|
const items = [];
|
|
@@ -5775,6 +6856,9 @@ class Parser {
|
|
|
5775
6856
|
this.#keyword("AS");
|
|
5776
6857
|
alias = this.#identifier();
|
|
5777
6858
|
explicitAlias = true;
|
|
6859
|
+
} else if (this.#bareLabelAhead()) {
|
|
6860
|
+
alias = this.#identifier();
|
|
6861
|
+
explicitAlias = true;
|
|
5778
6862
|
}
|
|
5779
6863
|
items.push({ expression, alias });
|
|
5780
6864
|
explicitAliases.push(explicitAlias);
|
|
@@ -6060,6 +7144,12 @@ class Parser {
|
|
|
6060
7144
|
}
|
|
6061
7145
|
return { table, alias };
|
|
6062
7146
|
}
|
|
7147
|
+
#bareLabelAhead() {
|
|
7148
|
+
const token = this.#peek();
|
|
7149
|
+
if (token.kind !== "identifier")
|
|
7150
|
+
return false;
|
|
7151
|
+
return token.quoted === true || !bareLabelStopWords.has(token.text.toUpperCase());
|
|
7152
|
+
}
|
|
6063
7153
|
#sourceAlias() {
|
|
6064
7154
|
if (this.#isKeyword("AS")) {
|
|
6065
7155
|
this.#keyword("AS");
|
|
@@ -6324,6 +7414,14 @@ class Parser {
|
|
|
6324
7414
|
}
|
|
6325
7415
|
return { kind: "condition", operator, left, right: { kind: "list", items } };
|
|
6326
7416
|
}
|
|
7417
|
+
const likeSpelling = this.#peek();
|
|
7418
|
+
if (likeSpelling.kind === "operator" && /^!?~~\*?$/.test(likeSpelling.text)) {
|
|
7419
|
+
this.#index += 1;
|
|
7420
|
+
const negatedSpelling = likeSpelling.text.startsWith("!") !== negated;
|
|
7421
|
+
const insensitiveSpelling = likeSpelling.text.endsWith("*");
|
|
7422
|
+
const operator = insensitiveSpelling ? negatedSpelling ? "NOT ILIKE" : "ILIKE" : negatedSpelling ? "NOT LIKE" : "LIKE";
|
|
7423
|
+
return { kind: "condition", operator, left, right: this.#additive() };
|
|
7424
|
+
}
|
|
6327
7425
|
if (this.#isKeyword("LIKE") || this.#isKeyword("ILIKE")) {
|
|
6328
7426
|
const insensitive = this.#isKeyword("ILIKE");
|
|
6329
7427
|
this.#keyword(insensitive ? "ILIKE" : "LIKE");
|
|
@@ -6522,8 +7620,9 @@ class Parser {
|
|
|
6522
7620
|
throw new SqlCompileError(error instanceof Error ? error.message : `Invalid number: ${token.text}`, token.start, token.end - token.start);
|
|
6523
7621
|
}
|
|
6524
7622
|
const fits = exactNumericAsNumber(tagged);
|
|
6525
|
-
if (fits !== void 0)
|
|
6526
|
-
return { kind: "literal", value: fits, exactText: token.text };
|
|
7623
|
+
if (fits !== void 0) {
|
|
7624
|
+
return { kind: "literal", value: fits, exactText: token.text, decimal: true };
|
|
7625
|
+
}
|
|
6527
7626
|
return {
|
|
6528
7627
|
kind: "literal",
|
|
6529
7628
|
value: tagged,
|
|
@@ -6688,6 +7787,10 @@ class Parser {
|
|
|
6688
7787
|
args.push(this.#expression());
|
|
6689
7788
|
}
|
|
6690
7789
|
this.#expectPunctuation(")");
|
|
7790
|
+
const pattern = args[1];
|
|
7791
|
+
if (args.length === 2 && pattern?.kind === "literal" && typeof pattern.value === "string") {
|
|
7792
|
+
return { kind: "call", name: "REGEXP_SUBSTR", arguments: args };
|
|
7793
|
+
}
|
|
6691
7794
|
return { kind: "call", name: "SUBSTR", arguments: args };
|
|
6692
7795
|
}
|
|
6693
7796
|
this.#index = restore;
|
|
@@ -6892,8 +7995,18 @@ class Parser {
|
|
|
6892
7995
|
...fallback === null ? {} : { fallback }
|
|
6893
7996
|
};
|
|
6894
7997
|
}
|
|
6895
|
-
if (upper === "JSON_OBJECT")
|
|
7998
|
+
if (upper === "JSON_OBJECT" || upper === "JSON_BUILD_OBJECT" || upper === "JSONB_BUILD_OBJECT") {
|
|
6896
7999
|
return this.#jsonObject();
|
|
8000
|
+
}
|
|
8001
|
+
if (upper === "DATE") {
|
|
8002
|
+
const operand = this.#expression();
|
|
8003
|
+
this.#expectPunctuation(")");
|
|
8004
|
+
return {
|
|
8005
|
+
kind: "call",
|
|
8006
|
+
name: "CAST",
|
|
8007
|
+
arguments: [operand, { kind: "literal", value: "date" }]
|
|
8008
|
+
};
|
|
8009
|
+
}
|
|
6897
8010
|
if (statisticalAggregates.has(upper))
|
|
6898
8011
|
return this.#statisticalAggregate(upper);
|
|
6899
8012
|
if (upper === "EVERY" || upper === "BOOL_AND" || upper === "BOOL_OR") {
|
|
@@ -7011,6 +8124,10 @@ class Parser {
|
|
|
7011
8124
|
}
|
|
7012
8125
|
if (name === "SUBSTR" && (args.length < 2 || args.length > 3))
|
|
7013
8126
|
throw new TypeError("SUBSTR requires a string, a start, and an optional length");
|
|
8127
|
+
const substringPattern = args[1];
|
|
8128
|
+
if (upper === "SUBSTRING" && args.length === 2 && substringPattern?.kind === "literal" && typeof substringPattern.value === "string") {
|
|
8129
|
+
return { kind: "call", name: "REGEXP_SUBSTR", arguments: args };
|
|
8130
|
+
}
|
|
7014
8131
|
if (name === "DATE_TRUNC") {
|
|
7015
8132
|
if (args.length !== 2)
|
|
7016
8133
|
throw new TypeError("DATE_TRUNC requires a unit and a datetime argument");
|
|
@@ -7491,6 +8608,105 @@ function resolveGroupByReferences(groupBy, select) {
|
|
|
7491
8608
|
return structuredClone(item.expression);
|
|
7492
8609
|
});
|
|
7493
8610
|
}
|
|
8611
|
+
function unifyGroupedReferences(block, resolve) {
|
|
8612
|
+
const groupingReferences = [...block.groupBy, ...(block.groupingSets ?? []).flat()].flatMap((expression) => expression.kind === "column" ? [expression.reference] : []);
|
|
8613
|
+
if (groupingReferences.length === 0)
|
|
8614
|
+
return block;
|
|
8615
|
+
const spelled = new Set(groupingReferences);
|
|
8616
|
+
const bareNames = new Set(groupingReferences.map(bareColumnName));
|
|
8617
|
+
const candidate = (expression) => expressionColumns(expression).some((reference) => !spelled.has(reference) && bareNames.has(bareColumnName(reference)));
|
|
8618
|
+
const aliases = new Set(block.select.map((item) => item.alias));
|
|
8619
|
+
const orderCandidates = block.orderBy.filter((order) => !(order.expression.kind === "column" && aliases.has(order.expression.reference)));
|
|
8620
|
+
if (!block.select.some((item) => candidate(item.expression)) && !block.having.some((predicate) => candidate(predicate.left) || candidate(predicate.right)) && !orderCandidates.some((order) => candidate(order.expression)) && !groupingReferences.some((reference) => !spelled.has(reference))) {
|
|
8621
|
+
return block;
|
|
8622
|
+
}
|
|
8623
|
+
const spellings = /* @__PURE__ */ new Map();
|
|
8624
|
+
for (const reference of groupingReferences) {
|
|
8625
|
+
const column = resolve(reference);
|
|
8626
|
+
if (column !== void 0 && !spellings.has(column))
|
|
8627
|
+
spellings.set(column, reference);
|
|
8628
|
+
}
|
|
8629
|
+
if (spellings.size === 0)
|
|
8630
|
+
return block;
|
|
8631
|
+
let respelled = 0;
|
|
8632
|
+
const respell = (expression) => {
|
|
8633
|
+
if (expression.kind === "column") {
|
|
8634
|
+
const column = resolve(expression.reference);
|
|
8635
|
+
const spelling = column === void 0 ? void 0 : spellings.get(column);
|
|
8636
|
+
if (spelling === void 0 || spelling === expression.reference)
|
|
8637
|
+
return expression;
|
|
8638
|
+
respelled += 1;
|
|
8639
|
+
return { ...expression, reference: spelling };
|
|
8640
|
+
}
|
|
8641
|
+
if (expression.kind === "subquery" || expression.kind === "exists")
|
|
8642
|
+
return expression;
|
|
8643
|
+
if (expression.kind === "window") {
|
|
8644
|
+
return {
|
|
8645
|
+
...expression,
|
|
8646
|
+
partitionBy: expression.partitionBy.map(respell),
|
|
8647
|
+
orderBy: expression.orderBy.map((order) => ({
|
|
8648
|
+
...order,
|
|
8649
|
+
expression: respell(order.expression)
|
|
8650
|
+
})),
|
|
8651
|
+
...expression.argument === void 0 ? {} : { argument: respell(expression.argument) }
|
|
8652
|
+
};
|
|
8653
|
+
}
|
|
8654
|
+
return mapChildExpressions(expression, respell);
|
|
8655
|
+
};
|
|
8656
|
+
const unified = {
|
|
8657
|
+
...block,
|
|
8658
|
+
select: block.select.map((item) => ({ ...item, expression: respell(item.expression) })),
|
|
8659
|
+
groupBy: block.groupBy.map(respell),
|
|
8660
|
+
having: block.having.map((predicate) => ({
|
|
8661
|
+
...predicate,
|
|
8662
|
+
left: respell(predicate.left),
|
|
8663
|
+
right: respell(predicate.right)
|
|
8664
|
+
})),
|
|
8665
|
+
orderBy: block.orderBy.map((order) => order.expression.kind === "column" && aliases.has(order.expression.reference) ? order : { ...order, expression: respell(order.expression) }),
|
|
8666
|
+
...block.groupingSets === void 0 ? {} : { groupingSets: block.groupingSets.map((set) => set.map(respell)) }
|
|
8667
|
+
};
|
|
8668
|
+
return respelled > 0 ? unified : block;
|
|
8669
|
+
}
|
|
8670
|
+
function bareColumnName(reference) {
|
|
8671
|
+
const separator = reference.indexOf(".");
|
|
8672
|
+
return separator === -1 ? reference : reference.slice(separator + 1);
|
|
8673
|
+
}
|
|
8674
|
+
function unifyBlockGroupedReferences(parts) {
|
|
8675
|
+
if (parts.joins.length > 0 || parts.base.table === DUAL_TABLE)
|
|
8676
|
+
return parts;
|
|
8677
|
+
const alias = parts.base.alias;
|
|
8678
|
+
return unifyGroupedReferences(parts, (reference) => {
|
|
8679
|
+
const separator = reference.indexOf(".");
|
|
8680
|
+
if (separator === -1)
|
|
8681
|
+
return `${alias}.${reference}`;
|
|
8682
|
+
return reference.slice(0, separator) === alias ? reference : void 0;
|
|
8683
|
+
});
|
|
8684
|
+
}
|
|
8685
|
+
function unifyPlanGroupedReferences(plan, columnsOf) {
|
|
8686
|
+
if (plan.joins.length === 0 || plan.groupBy.length === 0)
|
|
8687
|
+
return plan;
|
|
8688
|
+
const sources = [plan.base, ...plan.joins];
|
|
8689
|
+
const aliases = new Set(sources.map((source) => source.alias));
|
|
8690
|
+
let columnsBySource;
|
|
8691
|
+
return unifyGroupedReferences(plan, (reference) => {
|
|
8692
|
+
const separator = reference.indexOf(".");
|
|
8693
|
+
if (separator !== -1)
|
|
8694
|
+
return aliases.has(reference.slice(0, separator)) ? reference : void 0;
|
|
8695
|
+
columnsBySource ??= new Map(sources.map((source) => [
|
|
8696
|
+
source.alias,
|
|
8697
|
+
new Set(sourceWildcardColumns(source, columnsOf) ?? [])
|
|
8698
|
+
]));
|
|
8699
|
+
let owner;
|
|
8700
|
+
for (const [alias, columns] of columnsBySource) {
|
|
8701
|
+
if (!columns.has(reference))
|
|
8702
|
+
continue;
|
|
8703
|
+
if (owner !== void 0)
|
|
8704
|
+
return void 0;
|
|
8705
|
+
owner = alias;
|
|
8706
|
+
}
|
|
8707
|
+
return owner === void 0 ? void 0 : `${owner}.${reference}`;
|
|
8708
|
+
});
|
|
8709
|
+
}
|
|
7494
8710
|
function desugarFullJoin(parts, nextSequence) {
|
|
7495
8711
|
const join = parts.joins[0];
|
|
7496
8712
|
if (parts.joins.length !== 1 || join?.full !== true) {
|
|
@@ -7591,6 +8807,7 @@ function assembleSelectBlock(parts, nextSequence) {
|
|
|
7591
8807
|
groupingSets: parts.groupingSets.map((set) => resolveGroupByReferences(set, parts.select))
|
|
7592
8808
|
}
|
|
7593
8809
|
};
|
|
8810
|
+
parts = unifyBlockGroupedReferences(parts);
|
|
7594
8811
|
if (parts.joins.some((join) => join.full === true)) {
|
|
7595
8812
|
return desugarFullJoin(parts, nextSequence);
|
|
7596
8813
|
}
|
|
@@ -7646,18 +8863,6 @@ function assembleSelectBlock(parts, nextSequence) {
|
|
|
7646
8863
|
const grouped = parts.groupBy.length > 0 || select.some((item) => hasAggregate(item.expression));
|
|
7647
8864
|
if (!grouped)
|
|
7648
8865
|
throw new TypeError("HAVING requires GROUP BY or aggregate functions");
|
|
7649
|
-
const groupExpressions = new Set(groupBy.map((expression) => JSON.stringify(expression)));
|
|
7650
|
-
for (const predicate of having) {
|
|
7651
|
-
for (const side of [predicate.left, predicate.right]) {
|
|
7652
|
-
if (hasAggregate(side))
|
|
7653
|
-
continue;
|
|
7654
|
-
if (expressionColumns(side).length === 0)
|
|
7655
|
-
continue;
|
|
7656
|
-
if (groupExpressions.has(JSON.stringify(side)))
|
|
7657
|
-
continue;
|
|
7658
|
-
throw new TypeError("HAVING conditions must use aggregates, literals, or GROUP BY expressions");
|
|
7659
|
-
}
|
|
7660
|
-
}
|
|
7661
8866
|
}
|
|
7662
8867
|
const clauseExpressions = [
|
|
7663
8868
|
...predicates.flatMap((predicate) => [predicate.left, predicate.right]),
|
|
@@ -8263,8 +9468,11 @@ function assembleOrderByExpressionBlock(parts, nextSequence) {
|
|
|
8263
9468
|
orderBy: []
|
|
8264
9469
|
};
|
|
8265
9470
|
}
|
|
9471
|
+
function blockHasRowWindow(block) {
|
|
9472
|
+
return block.limit !== void 0 || block.offset !== void 0 || block.limitParameter !== void 0 || block.offsetParameter !== void 0;
|
|
9473
|
+
}
|
|
8266
9474
|
function transparentProjectionSource(plan) {
|
|
8267
|
-
if (plan.joins.length > 0 || plan.predicates.length > 0 || plan.groupBy.length > 0 || plan.having.length > 0 || plan.orderBy.length > 0 || plan
|
|
9475
|
+
if (plan.joins.length > 0 || plan.predicates.length > 0 || plan.groupBy.length > 0 || plan.having.length > 0 || plan.orderBy.length > 0 || blockHasRowWindow(plan)) {
|
|
8268
9476
|
return void 0;
|
|
8269
9477
|
}
|
|
8270
9478
|
const inner = plan.base.derived;
|
|
@@ -8560,6 +9768,59 @@ function tokenize(sql) {
|
|
|
8560
9768
|
index += 1;
|
|
8561
9769
|
continue;
|
|
8562
9770
|
}
|
|
9771
|
+
if ((character === "E" || character === "e") && sql[index + 1] === "'") {
|
|
9772
|
+
const start = index;
|
|
9773
|
+
index += 2;
|
|
9774
|
+
let value = "";
|
|
9775
|
+
let closed = false;
|
|
9776
|
+
while (index < sql.length) {
|
|
9777
|
+
const current = sql[index] ?? "";
|
|
9778
|
+
if (current === "'" && sql[index + 1] === "'") {
|
|
9779
|
+
value += "'";
|
|
9780
|
+
index += 2;
|
|
9781
|
+
} else if (current === "'") {
|
|
9782
|
+
index += 1;
|
|
9783
|
+
closed = true;
|
|
9784
|
+
break;
|
|
9785
|
+
} else if (current === "\\") {
|
|
9786
|
+
const next = sql[index + 1] ?? "";
|
|
9787
|
+
const simple = /* @__PURE__ */ new Map([
|
|
9788
|
+
["n", "\n"],
|
|
9789
|
+
["t", " "],
|
|
9790
|
+
["r", "\r"],
|
|
9791
|
+
["b", "\b"],
|
|
9792
|
+
["f", "\f"]
|
|
9793
|
+
]);
|
|
9794
|
+
const escaped = simple.get(next);
|
|
9795
|
+
if (escaped !== void 0) {
|
|
9796
|
+
value += escaped;
|
|
9797
|
+
index += 2;
|
|
9798
|
+
} else if (next === "x" && /[0-9A-Fa-f]/.test(sql[index + 2] ?? "")) {
|
|
9799
|
+
const hex = /^[0-9A-Fa-f]{1,2}/.exec(sql.slice(index + 2, index + 4))?.[0] ?? "";
|
|
9800
|
+
value += String.fromCharCode(Number.parseInt(hex, 16));
|
|
9801
|
+
index += 2 + hex.length;
|
|
9802
|
+
} else if (next === "u" && /^[0-9A-Fa-f]{4}/.test(sql.slice(index + 2, index + 6))) {
|
|
9803
|
+
value += String.fromCharCode(Number.parseInt(sql.slice(index + 2, index + 6), 16));
|
|
9804
|
+
index += 6;
|
|
9805
|
+
} else if (/[0-7]/.test(next)) {
|
|
9806
|
+
const octal = /^[0-7]{1,3}/.exec(sql.slice(index + 1, index + 4))?.[0] ?? "";
|
|
9807
|
+
value += String.fromCharCode(Number.parseInt(octal, 8));
|
|
9808
|
+
index += 1 + octal.length;
|
|
9809
|
+
} else {
|
|
9810
|
+
value += next;
|
|
9811
|
+
index += 2;
|
|
9812
|
+
}
|
|
9813
|
+
} else {
|
|
9814
|
+
value += current;
|
|
9815
|
+
index += 1;
|
|
9816
|
+
}
|
|
9817
|
+
}
|
|
9818
|
+
if (!closed) {
|
|
9819
|
+
throw new SqlCompileError("Unterminated string literal", start, sql.length - start);
|
|
9820
|
+
}
|
|
9821
|
+
push({ kind: "string", text: value, start, end: index });
|
|
9822
|
+
continue;
|
|
9823
|
+
}
|
|
8563
9824
|
if (/[A-Za-z_]/.test(character)) {
|
|
8564
9825
|
const start = index++;
|
|
8565
9826
|
while (index < sql.length && /[A-Za-z0-9_]/.test(sql[index] ?? ""))
|
|
@@ -8567,7 +9828,7 @@ function tokenize(sql) {
|
|
|
8567
9828
|
push({ kind: "identifier", text: sql.slice(start, index), start, end: index });
|
|
8568
9829
|
continue;
|
|
8569
9830
|
}
|
|
8570
|
-
if (/\d/.test(character)) {
|
|
9831
|
+
if (/\d/.test(character) || character === "." && /\d/.test(sql[index + 1] ?? "")) {
|
|
8571
9832
|
const start = index;
|
|
8572
9833
|
const radix = { x: 16, o: 8, b: 2 }[(sql[index + 1] ?? "").toLowerCase()];
|
|
8573
9834
|
if (character === "0" && radix !== void 0) {
|
|
@@ -8593,9 +9854,10 @@ function tokenize(sql) {
|
|
|
8593
9854
|
index += 1;
|
|
8594
9855
|
}
|
|
8595
9856
|
}
|
|
8596
|
-
const
|
|
9857
|
+
const spelled = sql.slice(start, index);
|
|
9858
|
+
const text = spelled.startsWith(".") ? `0${spelled}` : /^\d+\.(?:[eE][+-]?\d+)?$/.test(spelled) ? spelled.replace(".", ".0") : spelled;
|
|
8597
9859
|
if (!validNumericLiteral(text, 10))
|
|
8598
|
-
throw new SqlCompileError(`Invalid number: ${
|
|
9860
|
+
throw new SqlCompileError(`Invalid number: ${spelled}`, start, index - start);
|
|
8599
9861
|
push({ kind: "number", text: text.replaceAll("_", ""), start, end: index });
|
|
8600
9862
|
continue;
|
|
8601
9863
|
}
|
|
@@ -8650,6 +9912,18 @@ function tokenize(sql) {
|
|
|
8650
9912
|
}
|
|
8651
9913
|
if (character === "$") {
|
|
8652
9914
|
const start = index++;
|
|
9915
|
+
const tagMatch = /^\$([A-Za-z_][A-Za-z0-9_]*)?\$/.exec(sql.slice(start));
|
|
9916
|
+
if (tagMatch !== null) {
|
|
9917
|
+
const tag = tagMatch[0];
|
|
9918
|
+
const bodyStart = start + tag.length;
|
|
9919
|
+
const close = sql.indexOf(tag, bodyStart);
|
|
9920
|
+
if (close === -1) {
|
|
9921
|
+
throw new SqlCompileError("Unterminated dollar-quoted string", start, sql.length - start);
|
|
9922
|
+
}
|
|
9923
|
+
push({ kind: "string", text: sql.slice(bodyStart, close), start, end: close + tag.length });
|
|
9924
|
+
index = close + tag.length;
|
|
9925
|
+
continue;
|
|
9926
|
+
}
|
|
8653
9927
|
while (index < sql.length && /\d/.test(sql[index] ?? ""))
|
|
8654
9928
|
index += 1;
|
|
8655
9929
|
const digits = sql.slice(start + 1, index);
|
|
@@ -8683,6 +9957,17 @@ function tokenize(sql) {
|
|
|
8683
9957
|
index += 3;
|
|
8684
9958
|
continue;
|
|
8685
9959
|
}
|
|
9960
|
+
const likeOperator = /^!?~~\*?/.exec(sql.slice(index, index + 4))?.[0];
|
|
9961
|
+
if (likeOperator !== void 0) {
|
|
9962
|
+
push({
|
|
9963
|
+
kind: "operator",
|
|
9964
|
+
text: likeOperator,
|
|
9965
|
+
start: index,
|
|
9966
|
+
end: index + likeOperator.length
|
|
9967
|
+
});
|
|
9968
|
+
index += likeOperator.length;
|
|
9969
|
+
continue;
|
|
9970
|
+
}
|
|
8686
9971
|
if ([">=", "<=", "!=", "<>", "||", "->"].includes(pair)) {
|
|
8687
9972
|
push({ kind: "operator", text: pair, start: index, end: index + 2 });
|
|
8688
9973
|
index += 2;
|
|
@@ -8732,11 +10017,14 @@ function validateSqlSource(sql) {
|
|
|
8732
10017
|
export {
|
|
8733
10018
|
DUAL_TABLE,
|
|
8734
10019
|
annotateAvgArgumentScales,
|
|
10020
|
+
annotateIntegerDivision,
|
|
10021
|
+
annotatePlanIntegerDivision,
|
|
8735
10022
|
applyWindowFunctions,
|
|
8736
10023
|
assembleSelectBlock,
|
|
8737
10024
|
bindPendingSelectShapes,
|
|
8738
10025
|
bindPlanParameters,
|
|
8739
10026
|
bindStatementParameters,
|
|
10027
|
+
blockHasRowWindow,
|
|
8740
10028
|
blockHasSubqueries,
|
|
8741
10029
|
cachedListMembership,
|
|
8742
10030
|
childExpressions,
|
|
@@ -8763,17 +10051,22 @@ export {
|
|
|
8763
10051
|
executeRowQuery,
|
|
8764
10052
|
expandFtsColumns,
|
|
8765
10053
|
expandNaturalJoins,
|
|
10054
|
+
expandRowReferences,
|
|
8766
10055
|
expandSourceColumnAliases,
|
|
8767
10056
|
expandViewSources,
|
|
8768
10057
|
expressionAliases,
|
|
8769
10058
|
expressionColumnNames,
|
|
8770
10059
|
expressionColumns,
|
|
10060
|
+
extendGroupByWithKeyDependents,
|
|
8771
10061
|
externalizeQueryResult,
|
|
10062
|
+
foldIdentifierCase,
|
|
8772
10063
|
forEachBlockExpression,
|
|
8773
10064
|
forEachNestedBlock,
|
|
8774
10065
|
hasAggregate,
|
|
8775
10066
|
inferBlockSchema,
|
|
8776
10067
|
inferResultColumnDomains,
|
|
10068
|
+
integerQuotient,
|
|
10069
|
+
integerTypedExpression,
|
|
8777
10070
|
isAggregateCall,
|
|
8778
10071
|
isDefaultInsertValue,
|
|
8779
10072
|
isDeferredInsertExpression,
|
|
@@ -8788,6 +10081,7 @@ export {
|
|
|
8788
10081
|
planHasNaturalJoins,
|
|
8789
10082
|
planHasPendingSelectShapes,
|
|
8790
10083
|
planHasSourceColumnAliases,
|
|
10084
|
+
planMayHaveIntegerDivision,
|
|
8791
10085
|
planReadsBeyondSingleScan,
|
|
8792
10086
|
planReadsTable,
|
|
8793
10087
|
planReadsViews,
|
|
@@ -8804,6 +10098,7 @@ export {
|
|
|
8804
10098
|
subqueryResolutionSteps,
|
|
8805
10099
|
topLevelFtsMatchConjuncts,
|
|
8806
10100
|
transparentProjectionSource,
|
|
10101
|
+
unifyPlanGroupedReferences,
|
|
8807
10102
|
unknownColumnDomains,
|
|
8808
10103
|
validateDefaultExpression,
|
|
8809
10104
|
validateLimit,
|