@minnowdb/core 0.7.2 → 0.7.7

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.
@@ -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) && digits >= 0) {
260
- return exactNumericValue(first, void 0, digits);
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) => values[0] === null || values[0] === void 0 ? null : Math.abs(numeric(values[0]));
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) && digits >= 0) {
416
- return exactNumericValue(first, void 0, digits);
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 } = normalizeSql(sql);
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
- const allowed = /* @__PURE__ */ new Set(["BEGIN", "START", "COMMIT", "ROLLBACK", "WORK", "TRANSACTION"]);
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 } = normalizeSql(sql);
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
- const second = tokens[1];
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")
@@ -1719,15 +1959,42 @@ function inferBlockSchema(plan, schemas) {
1719
1959
  return { kind: "interval" };
1720
1960
  return void 0;
1721
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
+ };
1722
1980
  const inferDomain = (expression) => {
1723
1981
  if (expression.kind === "literal")
1724
1982
  return expression.sqlDomain;
1725
1983
  if (expression.kind === "column")
1726
1984
  return resolveColumnDomain(expression.reference);
1727
1985
  if (expression.kind === "binary") {
1986
+ if (expression.operator === "||")
1987
+ return void 0;
1728
1988
  const left = inferDomain(expression.left);
1729
1989
  const right = inferDomain(expression.right);
1730
- return left?.kind === "numeric" || right?.kind === "numeric" ? { kind: "numeric" } : void 0;
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
+ ]);
1731
1998
  }
1732
1999
  if (expression.kind === "case") {
1733
2000
  const outcomes = [
@@ -1749,11 +2016,30 @@ function inferBlockSchema(plan, schemas) {
1749
2016
  const target = expression.arguments[1];
1750
2017
  return target?.kind === "literal" && typeof target.value === "string" ? castDomain(target.value) : void 0;
1751
2018
  }
1752
- 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") {
1753
2020
  return { kind: "json" };
1754
2021
  }
1755
2022
  if (expression.name === "GEN_RANDOM_UUID")
1756
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
+ }
1757
2043
  if (expression.name === "DATE_ADD") {
1758
2044
  const input = inferDomain(expression.arguments[0] ?? { kind: "literal", value: null });
1759
2045
  const milliseconds = expression.arguments[2];
@@ -1764,6 +2050,7 @@ function inferBlockSchema(plan, schemas) {
1764
2050
  }
1765
2051
  return void 0;
1766
2052
  };
2053
+ const exactNumericMix = (expression, left, right) => (left === "string" && right === "number" || left === "number" && right === "string") && inferDomain(expression)?.kind === "numeric";
1767
2054
  const infer = (expression) => {
1768
2055
  if (expression.kind === "subquery" || expression.kind === "list") {
1769
2056
  throw new TypeError("Subqueries must be resolved before schema inference");
@@ -1788,6 +2075,10 @@ function inferBlockSchema(plan, schemas) {
1788
2075
  if (type === "null")
1789
2076
  continue;
1790
2077
  if (resolved !== "null" && resolved !== type) {
2078
+ if (exactNumericMix(expression, resolved, type)) {
2079
+ resolved = "string";
2080
+ continue;
2081
+ }
1791
2082
  throw new TypeError("CASE branches must produce one value type");
1792
2083
  }
1793
2084
  resolved = type;
@@ -1846,10 +2137,13 @@ function inferBlockSchema(plan, schemas) {
1846
2137
  if (expression.name === "SUM" || expression.name === "AVG") {
1847
2138
  return inferDomain(expression)?.kind === "numeric" ? "string" : "number";
1848
2139
  }
1849
- if (expression.name === "ROUND" || expression.name === "LENGTH" || expression.name === "ABS" || expression.name === "FLOOR" || expression.name === "CEIL" || expression.name === "MOD" || 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") {
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") {
1850
2144
  return "number";
1851
2145
  }
1852
- 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") {
1853
2147
  return "string";
1854
2148
  }
1855
2149
  if (expression.name === "JSON_EXISTS" || expression.name === "IS_JSON")
@@ -1860,6 +2154,9 @@ function inferBlockSchema(plan, schemas) {
1860
2154
  const argument2 = expression.arguments[0];
1861
2155
  return argument2 === void 0 ? "null" : infer(argument2);
1862
2156
  }
2157
+ if (simple.returns === "number" && inferDomain(expression)?.kind === "numeric") {
2158
+ return "string";
2159
+ }
1863
2160
  return simple.returns === "date" || simple.returns === "interval" ? "string" : simple.returns;
1864
2161
  }
1865
2162
  if (expression.name === "DATE_TRUNC")
@@ -1880,6 +2177,10 @@ function inferBlockSchema(plan, schemas) {
1880
2177
  if (type === "null")
1881
2178
  continue;
1882
2179
  if (resolved !== "null" && resolved !== type) {
2180
+ if (exactNumericMix(expression, resolved, type)) {
2181
+ resolved = "string";
2182
+ continue;
2183
+ }
1883
2184
  throw new TypeError(`${expression.name} arguments must produce one value type`);
1884
2185
  }
1885
2186
  resolved = type;
@@ -1915,6 +2216,10 @@ function inferBlockSchema(plan, schemas) {
1915
2216
  if (type === "null")
1916
2217
  continue;
1917
2218
  if (resolved !== "null" && resolved !== type) {
2219
+ if (exactNumericMix(expression, resolved, type)) {
2220
+ resolved = "string";
2221
+ continue;
2222
+ }
1918
2223
  throw new TypeError("COALESCE arguments must produce one value type");
1919
2224
  }
1920
2225
  resolved = type;
@@ -1936,7 +2241,7 @@ function inferBlockSchema(plan, schemas) {
1936
2241
  if (type === "null") {
1937
2242
  throw new TypeError(`Cannot infer a column type for output ${item.alias}`);
1938
2243
  }
1939
- const integer = item.expression.kind === "column" && resolveColumnInteger(item.expression.reference) || item.expression.kind === "call" && item.expression.name === "CAST" && item.expression.arguments[1]?.kind === "literal" && item.expression.arguments[1].value === "number-integer" || item.expression.kind === "call" && item.expression.name === "COUNT";
2244
+ const integer = integerTypedExpression(item.expression, resolveColumnInteger);
1940
2245
  const sqlDomain = inferDomain(item.expression);
1941
2246
  return [
1942
2247
  {
@@ -1988,6 +2293,7 @@ function referencedColumns(plan, schemas) {
1988
2293
  const sourceAliases = sources.map((source) => source.alias);
1989
2294
  if (new Set(sourceAliases).size !== sourceAliases.length)
1990
2295
  throw new TypeError("Table aliases must be unique");
2296
+ plan = unifyPlanGroupedReferences(plan, (tableName) => schemas.get(tableName));
1991
2297
  validateGrouping(plan);
1992
2298
  const aliases = new Map(sources.map((source) => [source.alias, source.table]));
1993
2299
  const requested = new Map(sources.map((source) => [source.table, /* @__PURE__ */ new Set()]));
@@ -2108,6 +2414,7 @@ function createPreparedQuery(plan, tables, options = {}) {
2108
2414
  assertTailParametersBound(plan);
2109
2415
  plan = resolveStatementDatetimes(plan);
2110
2416
  plan = bindPendingSelectShapes(plan, wildcardRowColumns(tables));
2417
+ plan = unifyPlanGroupedReferences(plan, wildcardRowColumns(tables));
2111
2418
  validateGrouping(plan);
2112
2419
  plan = expandSourceColumnAliases(plan, wildcardRowColumns(tables));
2113
2420
  plan = expandNaturalJoins(plan, wildcardRowColumns(tables));
@@ -2153,6 +2460,7 @@ function createPreparedColumnarQuery(plan, tables, memory = new QueryMemoryConte
2153
2460
  plan = expandNaturalJoins(plan, columnarColumns);
2154
2461
  plan = expandQualifiedWildcards(plan, columnarColumns);
2155
2462
  plan = expandDistinctWildcard(plan, columnarColumns);
2463
+ plan = unifyPlanGroupedReferences(plan, columnarColumns);
2156
2464
  validateGrouping(plan);
2157
2465
  let closed = false;
2158
2466
  let prepared;
@@ -2495,6 +2803,485 @@ function forEachNestedBlock(block, visit) {
2495
2803
  }
2496
2804
  }
2497
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
+ }
2498
3285
  function mapBlockExpressions(block, map) {
2499
3286
  for (const item of block.select)
2500
3287
  item.expression = map(item.expression);
@@ -2689,6 +3476,9 @@ function exactConstantFold(expression) {
2689
3476
  const seeded = left.seeded || right.seeded;
2690
3477
  if (left.value === null || right.value === null)
2691
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
+ }
2692
3482
  const folded = exactNumericBinary(expression.operator, left.value, right.value, 0, false);
2693
3483
  return folded === void 0 ? void 0 : { value: folded, seeded };
2694
3484
  }
@@ -2701,11 +3491,13 @@ function resolveExactNumericConstants(expression) {
2701
3491
  }
2702
3492
  if (folded?.seeded === true) {
2703
3493
  const value = folded.value;
2704
- if (value === null || typeof value === "number")
3494
+ if (value === null)
2705
3495
  return { kind: "literal", value };
3496
+ if (typeof value === "number")
3497
+ return { kind: "literal", value, decimal: true };
2706
3498
  const fits = exactNumericAsNumber(value);
2707
3499
  if (fits !== void 0)
2708
- return { kind: "literal", value: fits };
3500
+ return { kind: "literal", value: fits, decimal: true };
2709
3501
  return { kind: "literal", value, internalSqlValue: true, sqlDomain: { kind: "numeric" } };
2710
3502
  }
2711
3503
  if (expression.kind === "subquery" || expression.kind === "exists") {
@@ -2729,8 +3521,12 @@ function resolvePlanExactNumericConstants(plan) {
2729
3521
  mapBlockExpressions(plan, resolveExactNumericConstants);
2730
3522
  forEachNestedBlock(plan, resolvePlanExactNumericConstants);
2731
3523
  }
3524
+ function resolveStatementExpression(expression) {
3525
+ annotateIntegerDivision(expression);
3526
+ return resolveExactNumericConstants(expression);
3527
+ }
2732
3528
  function resolveStatementExactNumericConstants(statement) {
2733
- const resolve = resolveExactNumericConstants;
3529
+ const resolve = resolveStatementExpression;
2734
3530
  if (statement.kind === "insert") {
2735
3531
  const conflict = statement.onConflict;
2736
3532
  if (conflict?.assignments !== void 0) {
@@ -2752,6 +3548,18 @@ function resolveStatementExactNumericConstants(statement) {
2752
3548
  predicate.left = resolve(predicate.left);
2753
3549
  predicate.right = resolve(predicate.right);
2754
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
+ }
2755
3563
  return;
2756
3564
  }
2757
3565
  if (statement.kind === "merge") {
@@ -3205,6 +4013,7 @@ function executeRowQueryInternal(plan, tables, memory) {
3205
4013
  assertTailParametersBound(plan);
3206
4014
  plan = resolveStatementDatetimes(plan);
3207
4015
  plan = bindPendingSelectShapes(plan, wildcardRowColumns(tables));
4016
+ plan = unifyPlanGroupedReferences(plan, wildcardRowColumns(tables));
3208
4017
  validateGrouping(plan);
3209
4018
  plan = expandSourceColumnAliases(plan, wildcardRowColumns(tables));
3210
4019
  plan = expandNaturalJoins(plan, wildcardRowColumns(tables));
@@ -3468,11 +4277,16 @@ function evaluate(expression, context, group) {
3468
4277
  return null;
3469
4278
  if (expression.operator === "||")
3470
4279
  return concatenatedSqlValue(left, right);
3471
- const exact = exactNumericBinary(expression.operator, left, right);
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);
3472
4286
  if (exact !== void 0)
3473
4287
  return exact;
3474
- const a = numeric(left);
3475
- const b = numeric(right);
4288
+ const a = numeric(leftValue);
4289
+ const b = numeric(rightValue);
3476
4290
  if (expression.operator === "+")
3477
4291
  return a + b;
3478
4292
  if (expression.operator === "-")
@@ -3912,10 +4726,12 @@ function evaluateBooleanExpression(expression, evaluateValue) {
3912
4726
  const distinct = distinctFromComparison(evaluateValue(expression.left), evaluateValue(expression.right));
3913
4727
  return operator === "IS DISTINCT FROM" ? distinct : !distinct;
3914
4728
  }
3915
- const left = evaluateValue(expression.left);
3916
- const right = evaluateValue(expression.right);
3917
- if (left === null || left === void 0 || right === null || right === void 0)
4729
+ const evaluatedLeft = evaluateValue(expression.left);
4730
+ const evaluatedRight = evaluateValue(expression.right);
4731
+ if (evaluatedLeft === null || evaluatedLeft === void 0 || evaluatedRight === null || evaluatedRight === void 0) {
3918
4732
  return null;
4733
+ }
4734
+ const [left, right] = coerceComparisonOperands(evaluatedLeft, evaluatedRight);
3919
4735
  const a = comparable(left);
3920
4736
  const b = comparable(right);
3921
4737
  if (operator === "=")
@@ -4071,6 +4887,15 @@ function validateGrouping(plan) {
4071
4887
  throw new TypeError(`Selected column must appear in GROUP BY: ${item.alias}`);
4072
4888
  }
4073
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
+ }
4074
4899
  const forbiddenAggregates = [
4075
4900
  ...plan.joins.flatMap((join) => [
4076
4901
  join.left,
@@ -4158,8 +4983,9 @@ function externalizeQueryResult(result) {
4158
4983
  for (let position = 0; position < result.columns.length; position += 1) {
4159
4984
  const name = result.columns[position] ?? "";
4160
4985
  const value = row[name];
4161
- if (value !== void 0 && !isSqlDomainValue(value))
4986
+ if (value !== void 0 && !isSqlDomainValue(value) && !(typeof value === "number" && result.columnDomains[position]?.kind === "numeric")) {
4162
4987
  continue;
4988
+ }
4163
4989
  const external = asQueryValue(externalSqlDomainColumnValue(value, result.columnDomains[position]));
4164
4990
  if (external === value)
4165
4991
  continue;
@@ -4242,6 +5068,12 @@ class Parser {
4242
5068
  }
4243
5069
  const columns = this.#cteColumnList();
4244
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
+ }
4245
5077
  this.#expectPunctuation("(");
4246
5078
  if (recursive) {
4247
5079
  this.#recursiveCte(name, columns);
@@ -4333,7 +5165,9 @@ class Parser {
4333
5165
  return plan;
4334
5166
  }
4335
5167
  #compoundTail() {
4336
- return { orderBy: this.#orderByClause(), ...this.#tailClauses() };
5168
+ const tail = { orderBy: this.#orderByClause(), ...this.#tailClauses() };
5169
+ this.#lockingClause();
5170
+ return tail;
4337
5171
  }
4338
5172
  #setTerm(sql, member) {
4339
5173
  const first = this.#unionMember(sql, member);
@@ -4522,6 +5356,22 @@ class Parser {
4522
5356
  nullable = false;
4523
5357
  continue;
4524
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
+ }
4525
5375
  if (this.#isKeyword("CHECK")) {
4526
5376
  checks.push(this.#checkConstraint(`${table}_${name}_check`));
4527
5377
  continue;
@@ -4898,6 +5748,7 @@ class Parser {
4898
5748
  return `numeric:${precision === void 0 ? "" : String(precision)}:${scale === void 0 ? "" : String(scale)}`;
4899
5749
  }
4900
5750
  if (["JSON", "JSONB", "UUID", "DATE", "TIME", "INTERVAL"].includes(word)) {
5751
+ this.#multiWordTypeTail(word);
4901
5752
  return word.toLowerCase();
4902
5753
  }
4903
5754
  if (word === "DOUBLE") {
@@ -4907,14 +5758,27 @@ class Parser {
4907
5758
  const mapped = createTableTypeNames.get(word);
4908
5759
  if (mapped === void 0)
4909
5760
  throw new TypeError(`Unsupported CAST target: ${word}`);
5761
+ this.#multiWordTypeTail(word);
4910
5762
  if (this.#punctuation("(")) {
4911
5763
  this.#typeWidth();
4912
5764
  if (this.#punctuation(","))
4913
5765
  this.#typeWidth();
4914
5766
  this.#expectPunctuation(")");
4915
5767
  }
4916
- const integer = word === "INTEGER" || word === "INT" || word === "BIGINT" || word === "SMALLINT";
4917
- return integer ? "number-integer" : mapped;
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;
4918
5782
  }
4919
5783
  #columnType() {
4920
5784
  const declared = this.#identifier();
@@ -4940,6 +5804,7 @@ class Parser {
4940
5804
  };
4941
5805
  }
4942
5806
  if (["JSON", "JSONB", "UUID", "DATE", "TIME", "INTERVAL"].includes(word)) {
5807
+ this.#multiWordTypeTail(word);
4943
5808
  return { type: "string", sqlDomain: { kind: word.toLowerCase() } };
4944
5809
  }
4945
5810
  if (word === "DOUBLE") {
@@ -4950,6 +5815,7 @@ class Parser {
4950
5815
  if (mapped === void 0) {
4951
5816
  return { type: "string", sqlDomain: { kind: "enum", name: declared, values: [] } };
4952
5817
  }
5818
+ const multiWord = this.#multiWordTypeTail(word);
4953
5819
  if (this.#punctuation("(")) {
4954
5820
  this.#typeWidth();
4955
5821
  if (this.#punctuation(",")) {
@@ -4957,12 +5823,11 @@ class Parser {
4957
5823
  }
4958
5824
  this.#expectPunctuation(")");
4959
5825
  }
4960
- if (this.#punctuation("[")) {
5826
+ if (!multiWord && this.#punctuation("[")) {
4961
5827
  this.#expectPunctuation("]");
4962
5828
  return { type: "string", sqlDomain: { kind: "array", element: word } };
4963
5829
  }
4964
- const integer = word === "INTEGER" || word === "INT" || word === "BIGINT" || word === "SMALLINT";
4965
- return { type: mapped, ...integer ? { integer: true } : {} };
5830
+ return { type: mapped, ...integerTypeNames.has(word) ? { integer: true } : {} };
4966
5831
  }
4967
5832
  parseMutation(keyword) {
4968
5833
  const statement = keyword === "INSERT" ? this.#insertStatement() : keyword === "UPDATE" ? this.#updateStatement() : this.#deleteStatement();
@@ -5001,6 +5866,7 @@ class Parser {
5001
5866
  }
5002
5867
  if (this.#isKeyword("SELECT") || this.#isKeyword("WITH") || this.#peek().text === "(") {
5003
5868
  const insertSource = this.#queryExpression("(insert select)");
5869
+ annotatePlanIntegerDivision(insertSource);
5004
5870
  resolvePlanExactNumericConstants(insertSource);
5005
5871
  const query = planHasPendingSelectShapes(insertSource) ? insertSource : optimizePlan(insertSource);
5006
5872
  if (!planHasPendingSelectShapes(query) && !query.select.some((item) => item.expression.kind === "wildcard") && columns.length > 0 && query.select.length !== columns.length) {
@@ -5320,6 +6186,7 @@ class Parser {
5320
6186
  if (new Set(assignments.map(({ column }) => column)).size !== assignments.length) {
5321
6187
  throw new TypeError("UPDATE assignments must set each column once");
5322
6188
  }
6189
+ const from = this.#mutationSources("FROM");
5323
6190
  const predicates = this.#mutationPredicates();
5324
6191
  return {
5325
6192
  kind: "update",
@@ -5327,20 +6194,31 @@ class Parser {
5327
6194
  ...alias === void 0 ? {} : { alias },
5328
6195
  assignments,
5329
6196
  predicates,
6197
+ ...from === void 0 ? {} : { from },
5330
6198
  ...this.#returningClause(table, alias)
5331
6199
  };
5332
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
+ }
5333
6209
  #deleteStatement() {
5334
6210
  this.#keyword("DELETE");
5335
6211
  this.#keyword("FROM");
5336
6212
  const table = this.#identifier();
5337
- const alias = this.#mutationAlias("WHERE", "RETURNING");
6213
+ const alias = this.#mutationAlias("WHERE", "USING", "RETURNING");
6214
+ const from = this.#mutationSources("USING");
5338
6215
  const predicates = this.#mutationPredicates();
5339
6216
  return {
5340
6217
  kind: "delete",
5341
6218
  table,
5342
6219
  ...alias === void 0 ? {} : { alias },
5343
6220
  predicates,
6221
+ ...from === void 0 ? {} : { from },
5344
6222
  ...this.#returningClause(table, alias)
5345
6223
  };
5346
6224
  }
@@ -5394,9 +6272,9 @@ class Parser {
5394
6272
  }
5395
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);
5396
6274
  if (needsExecution(expression)) {
5397
- return { expression: resolveExactNumericConstants(expression) };
6275
+ return { expression: resolveStatementExpression(expression) };
5398
6276
  }
5399
- return asQueryValue(evaluate(resolveExactNumericConstants(expression), {}));
6277
+ return asQueryValue(evaluate(resolveStatementExpression(expression), {}));
5400
6278
  }
5401
6279
  #unionMember(sql, member) {
5402
6280
  if (this.#punctuation("(")) {
@@ -5444,6 +6322,10 @@ class Parser {
5444
6322
  #limitClause() {
5445
6323
  if (this.#isKeyword("LIMIT")) {
5446
6324
  this.#keyword("LIMIT");
6325
+ if (this.#isKeyword("ALL")) {
6326
+ this.#keyword("ALL");
6327
+ return {};
6328
+ }
5447
6329
  if (this.#peek().kind === "parameter") {
5448
6330
  const parameter = this.#parameterExpression();
5449
6331
  return { limitParameter: parameter.index };
@@ -5496,6 +6378,37 @@ class Parser {
5496
6378
  this.#keyword("ROW");
5497
6379
  return result;
5498
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
+ }
5499
6412
  #tailClauses() {
5500
6413
  if (this.#isKeyword("LIMIT")) {
5501
6414
  return { ...this.#limitClause(), ...this.#offsetClause() };
@@ -5569,36 +6482,219 @@ class Parser {
5569
6482
  this.#compoundMember = false;
5570
6483
  this.#keyword("SELECT");
5571
6484
  let distinct = false;
6485
+ let distinctOn;
5572
6486
  if (this.#isKeyword("DISTINCT")) {
5573
6487
  this.#keyword("DISTINCT");
5574
- distinct = true;
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");
5575
6498
  }
5576
6499
  const select = this.#selectList();
5577
6500
  let base;
6501
+ let joins = [];
6502
+ const groupConditions = [];
5578
6503
  if (this.#isKeyword("FROM")) {
5579
6504
  this.#keyword("FROM");
5580
- base = this.#source();
6505
+ ({ base, joins } = this.#joinedSources(select, groupConditions));
5581
6506
  } else {
5582
6507
  if (select.some((item) => item.expression.kind === "wildcard")) {
5583
6508
  throw new TypeError("SELECT * requires a FROM clause");
5584
6509
  }
5585
6510
  base = { table: DUAL_TABLE, alias: DUAL_TABLE };
5586
6511
  }
5587
- const joins = [];
5588
- let rightJoins = 0;
5589
- 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")) {
5590
- if (this.#punctuation(",")) {
5591
- joins.push(crossJoinPlan(this.#source()));
5592
- continue;
5593
- }
5594
- if (this.#isKeyword("CROSS")) {
5595
- this.#keyword("CROSS");
5596
- this.#keyword("JOIN");
5597
- joins.push(crossJoinPlan(this.#source()));
5598
- continue;
5599
- }
5600
- let kind = "inner";
5601
- let right = false;
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;
6695
+ }
6696
+ let kind = "inner";
6697
+ let right = false;
5602
6698
  let full = false;
5603
6699
  let natural = false;
5604
6700
  if (this.#isKeyword("NATURAL")) {
@@ -5622,7 +6718,21 @@ class Parser {
5622
6718
  if (this.#isKeyword("OUTER"))
5623
6719
  this.#keyword("OUTER");
5624
6720
  this.#keyword("JOIN");
5625
- const source = this.#source();
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;
5626
6736
  if (natural) {
5627
6737
  if (this.#isKeyword("ON") || this.#isKeyword("USING")) {
5628
6738
  throw new TypeError("A NATURAL join takes no ON or USING clause");
@@ -5682,95 +6792,58 @@ class Parser {
5682
6792
  }
5683
6793
  }
5684
6794
  }
5685
- const predicates = [];
5686
- if (this.#isKeyword("WHERE")) {
5687
- this.#keyword("WHERE");
5688
- predicates.push(...splitCondition(this.#expression()));
5689
- }
5690
- const groupBy = [];
5691
- let groupingSets;
5692
- if (this.#isKeyword("GROUP")) {
5693
- this.#keyword("GROUP");
5694
- this.#keyword("BY");
5695
- if (this.#isKeyword("GROUPING") || this.#isKeyword("ROLLUP") || this.#isKeyword("CUBE")) {
5696
- groupingSets = this.#groupingSets();
5697
- } else {
5698
- groupBy.push(...this.#expressionList());
5699
- }
5700
- }
5701
- const having = [];
5702
- if (this.#isKeyword("HAVING")) {
5703
- this.#keyword("HAVING");
5704
- having.push(...splitCondition(this.#expression()));
6795
+ if (rightJoins > 0 && joins.length !== 1) {
6796
+ throw new TypeError("RIGHT JOIN is only supported as the sole join");
5705
6797
  }
5706
- if (this.#isKeyword("WINDOW")) {
5707
- this.#keyword("WINDOW");
5708
- for (; ; ) {
5709
- this.#identifier();
5710
- this.#keyword("AS");
5711
- const definition = this.#namedWindows.get(this.tokens[this.#index - 2]?.text ?? "");
5712
- this.#index = definition?.end ?? this.#index;
5713
- if (!this.#punctuation(","))
5714
- break;
5715
- }
5716
- }
5717
- const orderBy = compoundMember ? [] : this.#orderByClause();
5718
- return assembleSelectBlock({
5719
- sql,
5720
- base,
5721
- joins,
5722
- select,
5723
- distinct,
5724
- predicates,
5725
- groupBy,
5726
- having,
5727
- orderBy,
5728
- ...compoundMember ? {} : this.#tailClauses(),
5729
- ...groupingSets === void 0 ? {} : { groupingSets }
5730
- }, this.nextDerivedSequence);
6798
+ return { base, joins, rightJoins };
5731
6799
  }
5732
- #groupingSets() {
5733
- if (this.#isKeyword("GROUPING")) {
5734
- this.#keyword("GROUPING");
5735
- this.#keyword("SETS");
6800
+ #joinOperand(select, groupConditions) {
6801
+ const next = this.#peek();
6802
+ if (next.kind === "punctuation" && next.text === "(" && this.#parenthesizedJoinAhead()) {
5736
6803
  this.#expectPunctuation("(");
5737
- const sets2 = [];
5738
- for (; ; ) {
5739
- this.#expectPunctuation("(");
5740
- const set = [];
5741
- if (!this.#punctuation(")")) {
5742
- set.push(...this.#expressionList());
5743
- this.#expectPunctuation(")");
5744
- }
5745
- sets2.push(set);
5746
- if (!this.#punctuation(","))
5747
- break;
5748
- }
6804
+ const inner = this.#joinedSources(select, groupConditions);
5749
6805
  this.#expectPunctuation(")");
5750
- return sets2;
5751
- }
5752
- const cube = this.#isKeyword("CUBE");
5753
- this.#keyword(cube ? "CUBE" : "ROLLUP");
5754
- this.#expectPunctuation("(");
5755
- const columns = this.#expressionList();
5756
- this.#expectPunctuation(")");
5757
- if (columns.length === 0)
5758
- throw new TypeError("ROLLUP/CUBE take at least one expression");
5759
- if (cube) {
5760
- if (columns.length > 5) {
5761
- 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");
5762
6808
  }
5763
- const sets2 = [];
5764
- for (let mask = (1 << columns.length) - 1; mask >= 0; mask -= 1) {
5765
- sets2.push(columns.filter((_, index) => (mask & 1 << index) !== 0));
5766
- }
5767
- return sets2;
6809
+ return { ...inner, group: true };
5768
6810
  }
5769
- const sets = [];
5770
- for (let length = columns.length; length >= 0; length -= 1) {
5771
- sets.push(columns.slice(0, length));
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];
5772
6819
  }
5773
- return sets;
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;
5774
6847
  }
5775
6848
  #selectList() {
5776
6849
  const items = [];
@@ -5783,6 +6856,9 @@ class Parser {
5783
6856
  this.#keyword("AS");
5784
6857
  alias = this.#identifier();
5785
6858
  explicitAlias = true;
6859
+ } else if (this.#bareLabelAhead()) {
6860
+ alias = this.#identifier();
6861
+ explicitAlias = true;
5786
6862
  }
5787
6863
  items.push({ expression, alias });
5788
6864
  explicitAliases.push(explicitAlias);
@@ -6068,6 +7144,12 @@ class Parser {
6068
7144
  }
6069
7145
  return { table, alias };
6070
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
+ }
6071
7153
  #sourceAlias() {
6072
7154
  if (this.#isKeyword("AS")) {
6073
7155
  this.#keyword("AS");
@@ -6332,6 +7414,14 @@ class Parser {
6332
7414
  }
6333
7415
  return { kind: "condition", operator, left, right: { kind: "list", items } };
6334
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
+ }
6335
7425
  if (this.#isKeyword("LIKE") || this.#isKeyword("ILIKE")) {
6336
7426
  const insensitive = this.#isKeyword("ILIKE");
6337
7427
  this.#keyword(insensitive ? "ILIKE" : "LIKE");
@@ -6530,8 +7620,9 @@ class Parser {
6530
7620
  throw new SqlCompileError(error instanceof Error ? error.message : `Invalid number: ${token.text}`, token.start, token.end - token.start);
6531
7621
  }
6532
7622
  const fits = exactNumericAsNumber(tagged);
6533
- if (fits !== void 0)
6534
- 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
+ }
6535
7626
  return {
6536
7627
  kind: "literal",
6537
7628
  value: tagged,
@@ -6696,6 +7787,10 @@ class Parser {
6696
7787
  args.push(this.#expression());
6697
7788
  }
6698
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
+ }
6699
7794
  return { kind: "call", name: "SUBSTR", arguments: args };
6700
7795
  }
6701
7796
  this.#index = restore;
@@ -6900,8 +7995,18 @@ class Parser {
6900
7995
  ...fallback === null ? {} : { fallback }
6901
7996
  };
6902
7997
  }
6903
- if (upper === "JSON_OBJECT")
7998
+ if (upper === "JSON_OBJECT" || upper === "JSON_BUILD_OBJECT" || upper === "JSONB_BUILD_OBJECT") {
6904
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
+ }
6905
8010
  if (statisticalAggregates.has(upper))
6906
8011
  return this.#statisticalAggregate(upper);
6907
8012
  if (upper === "EVERY" || upper === "BOOL_AND" || upper === "BOOL_OR") {
@@ -7019,6 +8124,10 @@ class Parser {
7019
8124
  }
7020
8125
  if (name === "SUBSTR" && (args.length < 2 || args.length > 3))
7021
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
+ }
7022
8131
  if (name === "DATE_TRUNC") {
7023
8132
  if (args.length !== 2)
7024
8133
  throw new TypeError("DATE_TRUNC requires a unit and a datetime argument");
@@ -7499,6 +8608,105 @@ function resolveGroupByReferences(groupBy, select) {
7499
8608
  return structuredClone(item.expression);
7500
8609
  });
7501
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
+ }
7502
8710
  function desugarFullJoin(parts, nextSequence) {
7503
8711
  const join = parts.joins[0];
7504
8712
  if (parts.joins.length !== 1 || join?.full !== true) {
@@ -7599,6 +8807,7 @@ function assembleSelectBlock(parts, nextSequence) {
7599
8807
  groupingSets: parts.groupingSets.map((set) => resolveGroupByReferences(set, parts.select))
7600
8808
  }
7601
8809
  };
8810
+ parts = unifyBlockGroupedReferences(parts);
7602
8811
  if (parts.joins.some((join) => join.full === true)) {
7603
8812
  return desugarFullJoin(parts, nextSequence);
7604
8813
  }
@@ -7654,18 +8863,6 @@ function assembleSelectBlock(parts, nextSequence) {
7654
8863
  const grouped = parts.groupBy.length > 0 || select.some((item) => hasAggregate(item.expression));
7655
8864
  if (!grouped)
7656
8865
  throw new TypeError("HAVING requires GROUP BY or aggregate functions");
7657
- const groupExpressions = new Set(groupBy.map((expression) => JSON.stringify(expression)));
7658
- for (const predicate of having) {
7659
- for (const side of [predicate.left, predicate.right]) {
7660
- if (hasAggregate(side))
7661
- continue;
7662
- if (expressionColumns(side).length === 0)
7663
- continue;
7664
- if (groupExpressions.has(JSON.stringify(side)))
7665
- continue;
7666
- throw new TypeError("HAVING conditions must use aggregates, literals, or GROUP BY expressions");
7667
- }
7668
- }
7669
8866
  }
7670
8867
  const clauseExpressions = [
7671
8868
  ...predicates.flatMap((predicate) => [predicate.left, predicate.right]),
@@ -8271,8 +9468,11 @@ function assembleOrderByExpressionBlock(parts, nextSequence) {
8271
9468
  orderBy: []
8272
9469
  };
8273
9470
  }
9471
+ function blockHasRowWindow(block) {
9472
+ return block.limit !== void 0 || block.offset !== void 0 || block.limitParameter !== void 0 || block.offsetParameter !== void 0;
9473
+ }
8274
9474
  function transparentProjectionSource(plan) {
8275
- if (plan.joins.length > 0 || plan.predicates.length > 0 || plan.groupBy.length > 0 || plan.having.length > 0 || plan.orderBy.length > 0 || plan.limit !== void 0 || plan.offset !== void 0) {
9475
+ if (plan.joins.length > 0 || plan.predicates.length > 0 || plan.groupBy.length > 0 || plan.having.length > 0 || plan.orderBy.length > 0 || blockHasRowWindow(plan)) {
8276
9476
  return void 0;
8277
9477
  }
8278
9478
  const inner = plan.base.derived;
@@ -8568,6 +9768,59 @@ function tokenize(sql) {
8568
9768
  index += 1;
8569
9769
  continue;
8570
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
+ }
8571
9824
  if (/[A-Za-z_]/.test(character)) {
8572
9825
  const start = index++;
8573
9826
  while (index < sql.length && /[A-Za-z0-9_]/.test(sql[index] ?? ""))
@@ -8575,7 +9828,7 @@ function tokenize(sql) {
8575
9828
  push({ kind: "identifier", text: sql.slice(start, index), start, end: index });
8576
9829
  continue;
8577
9830
  }
8578
- if (/\d/.test(character)) {
9831
+ if (/\d/.test(character) || character === "." && /\d/.test(sql[index + 1] ?? "")) {
8579
9832
  const start = index;
8580
9833
  const radix = { x: 16, o: 8, b: 2 }[(sql[index + 1] ?? "").toLowerCase()];
8581
9834
  if (character === "0" && radix !== void 0) {
@@ -8601,9 +9854,10 @@ function tokenize(sql) {
8601
9854
  index += 1;
8602
9855
  }
8603
9856
  }
8604
- const text = sql.slice(start, index);
9857
+ const spelled = sql.slice(start, index);
9858
+ const text = spelled.startsWith(".") ? `0${spelled}` : /^\d+\.(?:[eE][+-]?\d+)?$/.test(spelled) ? spelled.replace(".", ".0") : spelled;
8605
9859
  if (!validNumericLiteral(text, 10))
8606
- throw new SqlCompileError(`Invalid number: ${text}`, start, index - start);
9860
+ throw new SqlCompileError(`Invalid number: ${spelled}`, start, index - start);
8607
9861
  push({ kind: "number", text: text.replaceAll("_", ""), start, end: index });
8608
9862
  continue;
8609
9863
  }
@@ -8658,6 +9912,18 @@ function tokenize(sql) {
8658
9912
  }
8659
9913
  if (character === "$") {
8660
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
+ }
8661
9927
  while (index < sql.length && /\d/.test(sql[index] ?? ""))
8662
9928
  index += 1;
8663
9929
  const digits = sql.slice(start + 1, index);
@@ -8691,6 +9957,17 @@ function tokenize(sql) {
8691
9957
  index += 3;
8692
9958
  continue;
8693
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
+ }
8694
9971
  if ([">=", "<=", "!=", "<>", "||", "->"].includes(pair)) {
8695
9972
  push({ kind: "operator", text: pair, start: index, end: index + 2 });
8696
9973
  index += 2;
@@ -8740,11 +10017,14 @@ function validateSqlSource(sql) {
8740
10017
  export {
8741
10018
  DUAL_TABLE,
8742
10019
  annotateAvgArgumentScales,
10020
+ annotateIntegerDivision,
10021
+ annotatePlanIntegerDivision,
8743
10022
  applyWindowFunctions,
8744
10023
  assembleSelectBlock,
8745
10024
  bindPendingSelectShapes,
8746
10025
  bindPlanParameters,
8747
10026
  bindStatementParameters,
10027
+ blockHasRowWindow,
8748
10028
  blockHasSubqueries,
8749
10029
  cachedListMembership,
8750
10030
  childExpressions,
@@ -8771,17 +10051,22 @@ export {
8771
10051
  executeRowQuery,
8772
10052
  expandFtsColumns,
8773
10053
  expandNaturalJoins,
10054
+ expandRowReferences,
8774
10055
  expandSourceColumnAliases,
8775
10056
  expandViewSources,
8776
10057
  expressionAliases,
8777
10058
  expressionColumnNames,
8778
10059
  expressionColumns,
10060
+ extendGroupByWithKeyDependents,
8779
10061
  externalizeQueryResult,
10062
+ foldIdentifierCase,
8780
10063
  forEachBlockExpression,
8781
10064
  forEachNestedBlock,
8782
10065
  hasAggregate,
8783
10066
  inferBlockSchema,
8784
10067
  inferResultColumnDomains,
10068
+ integerQuotient,
10069
+ integerTypedExpression,
8785
10070
  isAggregateCall,
8786
10071
  isDefaultInsertValue,
8787
10072
  isDeferredInsertExpression,
@@ -8796,6 +10081,7 @@ export {
8796
10081
  planHasNaturalJoins,
8797
10082
  planHasPendingSelectShapes,
8798
10083
  planHasSourceColumnAliases,
10084
+ planMayHaveIntegerDivision,
8799
10085
  planReadsBeyondSingleScan,
8800
10086
  planReadsTable,
8801
10087
  planReadsViews,
@@ -8812,6 +10098,7 @@ export {
8812
10098
  subqueryResolutionSteps,
8813
10099
  topLevelFtsMatchConjuncts,
8814
10100
  transparentProjectionSource,
10101
+ unifyPlanGroupedReferences,
8815
10102
  unknownColumnDomains,
8816
10103
  validateDefaultExpression,
8817
10104
  validateLimit,