@minnowdb/core 0.7.1 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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")
@@ -1608,6 +1848,7 @@ function bindStatementParameters(statement, params) {
1608
1848
  if (clone.onConflict?.where !== void 0) {
1609
1849
  clone.onConflict.where = bindExpression(clone.onConflict.where, values);
1610
1850
  }
1851
+ bindReturningItems(clone.returningItems, values);
1611
1852
  return clone;
1612
1853
  }
1613
1854
  if (clone.kind === "drop-table" || clone.kind === "create-view" || clone.kind === "drop-view" || clone.kind === "transaction") {
@@ -1643,8 +1884,15 @@ function bindStatementParameters(statement, params) {
1643
1884
  predicate.left = bindExpression(predicate.left, values);
1644
1885
  predicate.right = bindExpression(predicate.right, values);
1645
1886
  }
1887
+ bindReturningItems(clone.returningItems, values);
1646
1888
  return clone;
1647
1889
  }
1890
+ function bindReturningItems(items, values) {
1891
+ if (items === void 0)
1892
+ return;
1893
+ for (const item of items)
1894
+ item.expression = bindExpression(item.expression, values);
1895
+ }
1648
1896
  function inferBlockSchema(plan, schemas) {
1649
1897
  const sources = [plan.base, ...plan.joins];
1650
1898
  const multipleSources = sources.filter((source) => {
@@ -1711,15 +1959,42 @@ function inferBlockSchema(plan, schemas) {
1711
1959
  return { kind: "interval" };
1712
1960
  return void 0;
1713
1961
  };
1962
+ const operandScale = (expression, domain) => {
1963
+ if (domain !== void 0)
1964
+ return domain.kind === "numeric" ? domain.scale : void 0;
1965
+ if (expression.kind === "literal") {
1966
+ return typeof expression.value === "number" && Number.isFinite(expression.value) ? decimalScaleOfNumber(expression.value) : void 0;
1967
+ }
1968
+ return expression.kind === "column" ? 0 : void 0;
1969
+ };
1970
+ const numericResultDomain = (operator, sides) => {
1971
+ const scales = sides.map(([expression, domain]) => operandScale(expression, domain));
1972
+ if (scales.some((scale) => scale === void 0))
1973
+ return { kind: "numeric" };
1974
+ const known = scales;
1975
+ return {
1976
+ kind: "numeric",
1977
+ scale: operator === "*" ? known.reduce((sum, scale) => sum + scale, 0) : Math.max(...known)
1978
+ };
1979
+ };
1714
1980
  const inferDomain = (expression) => {
1715
1981
  if (expression.kind === "literal")
1716
1982
  return expression.sqlDomain;
1717
1983
  if (expression.kind === "column")
1718
1984
  return resolveColumnDomain(expression.reference);
1719
1985
  if (expression.kind === "binary") {
1986
+ if (expression.operator === "||")
1987
+ return void 0;
1720
1988
  const left = inferDomain(expression.left);
1721
1989
  const right = inferDomain(expression.right);
1722
- 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
+ ]);
1723
1998
  }
1724
1999
  if (expression.kind === "case") {
1725
2000
  const outcomes = [
@@ -1741,11 +2016,30 @@ function inferBlockSchema(plan, schemas) {
1741
2016
  const target = expression.arguments[1];
1742
2017
  return target?.kind === "literal" && typeof target.value === "string" ? castDomain(target.value) : void 0;
1743
2018
  }
1744
- if (expression.name === "JSON_ARRAYAGG" || expression.name === "JSON_QUERY" || expression.name === "JSON_OBJECT" || expression.name === "JSON_ARRAY" || expression.name === "MINNOW_JSON_GET") {
2019
+ if (expression.name === "JSON_ARRAYAGG" || expression.name === "JSON_QUERY" || expression.name === "JSON_OBJECT" || expression.name === "JSON_ARRAY" || expression.name === "TO_JSON" || expression.name === "MINNOW_JSON_GET") {
1745
2020
  return { kind: "json" };
1746
2021
  }
1747
2022
  if (expression.name === "GEN_RANDOM_UUID")
1748
2023
  return { kind: "uuid" };
2024
+ if (expression.name === "ROUND" || expression.name === "TRUNC") {
2025
+ const input = inferDomain(expression.arguments[0] ?? { kind: "literal", value: null });
2026
+ if (input?.kind !== "numeric")
2027
+ return void 0;
2028
+ const digits = expression.arguments[1];
2029
+ return digits?.kind === "literal" && typeof digits.value === "number" && Number.isInteger(digits.value) && digits.value > 0 ? { kind: "numeric", scale: digits.value } : { kind: "numeric" };
2030
+ }
2031
+ if (expression.name === "ABS") {
2032
+ const input = inferDomain(expression.arguments[0] ?? { kind: "literal", value: null });
2033
+ return input?.kind === "numeric" ? input : void 0;
2034
+ }
2035
+ if (expression.name === "FLOOR" || expression.name === "CEIL" || expression.name === "SIGN") {
2036
+ const input = inferDomain(expression.arguments[0] ?? { kind: "literal", value: null });
2037
+ return input?.kind === "numeric" ? { kind: "numeric" } : void 0;
2038
+ }
2039
+ if (expression.name === "MOD") {
2040
+ const sides = expression.arguments.map((argument) => [argument, inferDomain(argument)]);
2041
+ return sides.some(([, domain]) => domain?.kind === "numeric") ? numericResultDomain("%", sides) : void 0;
2042
+ }
1749
2043
  if (expression.name === "DATE_ADD") {
1750
2044
  const input = inferDomain(expression.arguments[0] ?? { kind: "literal", value: null });
1751
2045
  const milliseconds = expression.arguments[2];
@@ -1756,6 +2050,7 @@ function inferBlockSchema(plan, schemas) {
1756
2050
  }
1757
2051
  return void 0;
1758
2052
  };
2053
+ const exactNumericMix = (expression, left, right) => (left === "string" && right === "number" || left === "number" && right === "string") && inferDomain(expression)?.kind === "numeric";
1759
2054
  const infer = (expression) => {
1760
2055
  if (expression.kind === "subquery" || expression.kind === "list") {
1761
2056
  throw new TypeError("Subqueries must be resolved before schema inference");
@@ -1780,6 +2075,10 @@ function inferBlockSchema(plan, schemas) {
1780
2075
  if (type === "null")
1781
2076
  continue;
1782
2077
  if (resolved !== "null" && resolved !== type) {
2078
+ if (exactNumericMix(expression, resolved, type)) {
2079
+ resolved = "string";
2080
+ continue;
2081
+ }
1783
2082
  throw new TypeError("CASE branches must produce one value type");
1784
2083
  }
1785
2084
  resolved = type;
@@ -1838,10 +2137,13 @@ function inferBlockSchema(plan, schemas) {
1838
2137
  if (expression.name === "SUM" || expression.name === "AVG") {
1839
2138
  return inferDomain(expression)?.kind === "numeric" ? "string" : "number";
1840
2139
  }
1841
- if (expression.name === "ROUND" || expression.name === "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") {
1842
2144
  return "number";
1843
2145
  }
1844
- if (expression.name === "JSON_ARRAYAGG" || expression.name === "STRING_AGG" || expression.name === "JSON_VALUE" || expression.name === "JSON_QUERY" || expression.name === "JSON_OBJECT" || expression.name === "JSON_ARRAY" || expression.name === "ARRAY" || expression.name === "MINNOW_JSON_GET" || expression.name === "MINNOW_JSON_GET_TEXT" || expression.name === "MINNOW_TUPLE_KEY" || expression.name === "MINNOW_COLLATE" || expression.name === "GEN_RANDOM_UUID") {
2146
+ if (expression.name === "JSON_ARRAYAGG" || expression.name === "STRING_AGG" || expression.name === "JSON_VALUE" || expression.name === "JSON_QUERY" || expression.name === "JSON_OBJECT" || expression.name === "JSON_ARRAY" || expression.name === "TO_JSON" || expression.name === "ARRAY" || expression.name === "MINNOW_JSON_GET" || expression.name === "MINNOW_JSON_GET_TEXT" || expression.name === "MINNOW_TUPLE_KEY" || expression.name === "MINNOW_COLLATE" || expression.name === "GEN_RANDOM_UUID") {
1845
2147
  return "string";
1846
2148
  }
1847
2149
  if (expression.name === "JSON_EXISTS" || expression.name === "IS_JSON")
@@ -1852,6 +2154,9 @@ function inferBlockSchema(plan, schemas) {
1852
2154
  const argument2 = expression.arguments[0];
1853
2155
  return argument2 === void 0 ? "null" : infer(argument2);
1854
2156
  }
2157
+ if (simple.returns === "number" && inferDomain(expression)?.kind === "numeric") {
2158
+ return "string";
2159
+ }
1855
2160
  return simple.returns === "date" || simple.returns === "interval" ? "string" : simple.returns;
1856
2161
  }
1857
2162
  if (expression.name === "DATE_TRUNC")
@@ -1872,6 +2177,10 @@ function inferBlockSchema(plan, schemas) {
1872
2177
  if (type === "null")
1873
2178
  continue;
1874
2179
  if (resolved !== "null" && resolved !== type) {
2180
+ if (exactNumericMix(expression, resolved, type)) {
2181
+ resolved = "string";
2182
+ continue;
2183
+ }
1875
2184
  throw new TypeError(`${expression.name} arguments must produce one value type`);
1876
2185
  }
1877
2186
  resolved = type;
@@ -1907,6 +2216,10 @@ function inferBlockSchema(plan, schemas) {
1907
2216
  if (type === "null")
1908
2217
  continue;
1909
2218
  if (resolved !== "null" && resolved !== type) {
2219
+ if (exactNumericMix(expression, resolved, type)) {
2220
+ resolved = "string";
2221
+ continue;
2222
+ }
1910
2223
  throw new TypeError("COALESCE arguments must produce one value type");
1911
2224
  }
1912
2225
  resolved = type;
@@ -1928,7 +2241,7 @@ function inferBlockSchema(plan, schemas) {
1928
2241
  if (type === "null") {
1929
2242
  throw new TypeError(`Cannot infer a column type for output ${item.alias}`);
1930
2243
  }
1931
- const integer = item.expression.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);
1932
2245
  const sqlDomain = inferDomain(item.expression);
1933
2246
  return [
1934
2247
  {
@@ -1980,6 +2293,7 @@ function referencedColumns(plan, schemas) {
1980
2293
  const sourceAliases = sources.map((source) => source.alias);
1981
2294
  if (new Set(sourceAliases).size !== sourceAliases.length)
1982
2295
  throw new TypeError("Table aliases must be unique");
2296
+ plan = unifyPlanGroupedReferences(plan, (tableName) => schemas.get(tableName));
1983
2297
  validateGrouping(plan);
1984
2298
  const aliases = new Map(sources.map((source) => [source.alias, source.table]));
1985
2299
  const requested = new Map(sources.map((source) => [source.table, /* @__PURE__ */ new Set()]));
@@ -2100,6 +2414,7 @@ function createPreparedQuery(plan, tables, options = {}) {
2100
2414
  assertTailParametersBound(plan);
2101
2415
  plan = resolveStatementDatetimes(plan);
2102
2416
  plan = bindPendingSelectShapes(plan, wildcardRowColumns(tables));
2417
+ plan = unifyPlanGroupedReferences(plan, wildcardRowColumns(tables));
2103
2418
  validateGrouping(plan);
2104
2419
  plan = expandSourceColumnAliases(plan, wildcardRowColumns(tables));
2105
2420
  plan = expandNaturalJoins(plan, wildcardRowColumns(tables));
@@ -2145,6 +2460,7 @@ function createPreparedColumnarQuery(plan, tables, memory = new QueryMemoryConte
2145
2460
  plan = expandNaturalJoins(plan, columnarColumns);
2146
2461
  plan = expandQualifiedWildcards(plan, columnarColumns);
2147
2462
  plan = expandDistinctWildcard(plan, columnarColumns);
2463
+ plan = unifyPlanGroupedReferences(plan, columnarColumns);
2148
2464
  validateGrouping(plan);
2149
2465
  let closed = false;
2150
2466
  let prepared;
@@ -2487,6 +2803,485 @@ function forEachNestedBlock(block, visit) {
2487
2803
  }
2488
2804
  }
2489
2805
  }
2806
+ function integerTypedExpression(expression, columnInteger = () => false, tableIntegerColumns) {
2807
+ const typed = (candidate) => integerTypedExpression(candidate, columnInteger, tableIntegerColumns);
2808
+ switch (expression.kind) {
2809
+ case "subquery": {
2810
+ const item = expression.block.select[0];
2811
+ return expression.block.select.length === 1 && item !== void 0 && integerTypedExpression(item.expression, blockColumnIntegerResolver(expression.block, tableIntegerColumns), tableIntegerColumns);
2812
+ }
2813
+ case "literal":
2814
+ return typeof expression.value === "number" && expression.exactText === void 0 && expression.decimal !== true && expression.internalSqlValue !== true && Number.isSafeInteger(expression.value);
2815
+ case "column":
2816
+ return columnInteger(expression.reference);
2817
+ case "binary": {
2818
+ if (expression.operator === "||")
2819
+ return false;
2820
+ const left = typed(expression.left);
2821
+ const right = typed(expression.right);
2822
+ if (left && right)
2823
+ return true;
2824
+ const untyped = (candidate) => candidate.kind === "parameter" || candidate.kind === "literal" && typeof candidate.value === "string" && candidate.internalSqlValue !== true;
2825
+ return left && untyped(expression.right) || right && untyped(expression.left);
2826
+ }
2827
+ case "call": {
2828
+ const { name, arguments: args } = expression;
2829
+ if (name === "COUNT")
2830
+ return true;
2831
+ if (name === "CAST") {
2832
+ return args[1]?.kind === "literal" && args[1].value === "number-integer";
2833
+ }
2834
+ if (name === "SUM" || name === "MIN" || name === "MAX" || name === "ABS") {
2835
+ return args.length === 1 && args[0] !== void 0 && typed(args[0]);
2836
+ }
2837
+ if (name === "MOD")
2838
+ return args.length === 2 && args.every(typed);
2839
+ if (name === "COALESCE" || name === "NULLIF" || name === "GREATEST" || name === "LEAST") {
2840
+ return args.length > 0 && args.every(typed);
2841
+ }
2842
+ return false;
2843
+ }
2844
+ case "case":
2845
+ return expression.branches.every((branch) => typed(branch.then)) && (expression.otherwise === void 0 || typed(expression.otherwise));
2846
+ default:
2847
+ return false;
2848
+ }
2849
+ }
2850
+ function integerQuotient(left, right) {
2851
+ if (right === 0)
2852
+ return null;
2853
+ if (!Number.isInteger(left) || !Number.isInteger(right))
2854
+ return left / right;
2855
+ const quotient = Math.trunc(left / right);
2856
+ return quotient === 0 ? 0 : quotient;
2857
+ }
2858
+ function annotateIntegerDivision(expression, columnInteger = () => false, tableIntegerColumns) {
2859
+ if (expression.kind === "subquery" || expression.kind === "exists") {
2860
+ annotatePlanIntegerDivision(expression.block, tableIntegerColumns);
2861
+ return;
2862
+ }
2863
+ for (const child of childExpressions(expression)) {
2864
+ annotateIntegerDivision(child, columnInteger, tableIntegerColumns);
2865
+ }
2866
+ if (expression.kind === "binary" && expression.operator === "/" && expression.integer !== true && integerTypedExpression(expression, columnInteger, tableIntegerColumns)) {
2867
+ expression.integer = true;
2868
+ }
2869
+ }
2870
+ function blockIntegerOutputs(block, tableIntegerColumns, columnAliases) {
2871
+ const resolver = blockColumnIntegerResolver(block, tableIntegerColumns);
2872
+ const names = [];
2873
+ for (const item of block.select) {
2874
+ if (item.expression.kind === "wildcard") {
2875
+ const table = item.expression.table;
2876
+ for (const source of [block.base, ...block.joins]) {
2877
+ if (table !== void 0 && source.alias !== table)
2878
+ continue;
2879
+ const columns = sourceIntegerColumns(source, tableIntegerColumns);
2880
+ if (columns === true)
2881
+ return true;
2882
+ for (const name of columns)
2883
+ if (resolver(name))
2884
+ names.push(name);
2885
+ }
2886
+ continue;
2887
+ }
2888
+ if (integerTypedExpression(item.expression, resolver, tableIntegerColumns)) {
2889
+ names.push(item.alias);
2890
+ }
2891
+ }
2892
+ if (columnAliases === void 0)
2893
+ return new Set(names);
2894
+ const aliased = /* @__PURE__ */ new Set();
2895
+ block.select.forEach((item, index) => {
2896
+ const alias = columnAliases[index];
2897
+ if (alias !== void 0 && item.expression.kind !== "wildcard" && names.includes(item.alias)) {
2898
+ aliased.add(alias);
2899
+ }
2900
+ });
2901
+ return aliased;
2902
+ }
2903
+ function sourceIntegerColumns(source, tableIntegerColumns) {
2904
+ if (source.derived !== void 0) {
2905
+ return blockIntegerOutputs(source.derived, tableIntegerColumns, source.columnAliases);
2906
+ }
2907
+ if (source.union !== void 0) {
2908
+ const first = source.union.blocks[0];
2909
+ return first === void 0 ? /* @__PURE__ */ new Set() : blockIntegerOutputs(first, tableIntegerColumns, source.columnAliases);
2910
+ }
2911
+ if (source.windowed !== void 0) {
2912
+ return blockIntegerOutputs(source.windowed.block, tableIntegerColumns, source.columnAliases);
2913
+ }
2914
+ if (source.recursive !== void 0)
2915
+ return /* @__PURE__ */ new Set();
2916
+ return tableIntegerColumns?.(source.table) ?? /* @__PURE__ */ new Set();
2917
+ }
2918
+ function blockColumnIntegerResolver(block, tableIntegerColumns) {
2919
+ const sources = [block.base, ...block.joins];
2920
+ const cache = /* @__PURE__ */ new Map();
2921
+ const has = (columns, name) => columns === true || columns.has(name);
2922
+ const columnsOf = (source) => {
2923
+ let columns = cache.get(source);
2924
+ if (columns === void 0) {
2925
+ columns = sourceIntegerColumns(source, tableIntegerColumns);
2926
+ cache.set(source, columns);
2927
+ }
2928
+ return columns;
2929
+ };
2930
+ return (reference) => {
2931
+ const separator = reference.indexOf(".");
2932
+ if (separator !== -1) {
2933
+ const alias = reference.slice(0, separator);
2934
+ const name = reference.slice(separator + 1);
2935
+ const source = sources.find((candidate) => candidate.alias === alias);
2936
+ return source !== void 0 && has(columnsOf(source), name);
2937
+ }
2938
+ let found = 0;
2939
+ for (const source of sources)
2940
+ if (has(columnsOf(source), reference))
2941
+ found += 1;
2942
+ return found === 1;
2943
+ };
2944
+ }
2945
+ function annotatePlanIntegerDivision(plan, tableIntegerColumns) {
2946
+ forEachNestedBlock(plan, (nested) => annotatePlanIntegerDivision(nested, tableIntegerColumns));
2947
+ const resolver = blockColumnIntegerResolver(plan, tableIntegerColumns);
2948
+ forEachBlockExpression(plan, (expression) => annotateIntegerDivision(expression, resolver, tableIntegerColumns));
2949
+ }
2950
+ function planMayHaveIntegerDivision(plan) {
2951
+ const state = { found: false };
2952
+ const optimistic = () => true;
2953
+ const everyColumn = () => true;
2954
+ const probe = (expression) => {
2955
+ if (state.found)
2956
+ return;
2957
+ if (expression.kind === "subquery" || expression.kind === "exists") {
2958
+ if (planMayHaveIntegerDivision(expression.block))
2959
+ state.found = true;
2960
+ return;
2961
+ }
2962
+ if (expression.kind === "binary" && expression.operator === "/" && expression.integer !== true && integerTypedExpression(expression, optimistic, everyColumn)) {
2963
+ state.found = true;
2964
+ return;
2965
+ }
2966
+ childExpressions(expression).forEach(probe);
2967
+ };
2968
+ forEachNestedBlock(plan, (nested) => {
2969
+ if (!state.found && planMayHaveIntegerDivision(nested))
2970
+ state.found = true;
2971
+ });
2972
+ if (!state.found)
2973
+ forEachBlockExpression(plan, probe);
2974
+ return state.found;
2975
+ }
2976
+ function foldIdentifierCase(plan, tables) {
2977
+ const lowerTables = /* @__PURE__ */ new Map();
2978
+ for (const name of tables.keys()) {
2979
+ const lowered = name.toLowerCase();
2980
+ const bucket = lowerTables.get(lowered);
2981
+ if (bucket === void 0)
2982
+ lowerTables.set(lowered, [name]);
2983
+ else
2984
+ bucket.push(name);
2985
+ }
2986
+ const foldTable = (name) => {
2987
+ if (tables.has(name))
2988
+ return void 0;
2989
+ const lowered = name.toLowerCase();
2990
+ if (tables.has(lowered))
2991
+ return lowered;
2992
+ const candidates = lowerTables.get(lowered);
2993
+ return candidates?.length === 1 ? candidates[0] : void 0;
2994
+ };
2995
+ const foldColumn = (columns, name) => {
2996
+ if (columns.includes(name))
2997
+ return void 0;
2998
+ const lowered = name.toLowerCase();
2999
+ if (columns.includes(lowered))
3000
+ return lowered;
3001
+ const matches = columns.filter((column) => column.toLowerCase() === lowered);
3002
+ return matches.length === 1 ? matches[0] : void 0;
3003
+ };
3004
+ const pass = { probing: true, needed: false };
3005
+ const sourceColumns = (source) => {
3006
+ if (source.derived !== void 0) {
3007
+ return source.columnAliases ?? source.derived.select.map((item) => item.alias);
3008
+ }
3009
+ if (source.union !== void 0) {
3010
+ return source.columnAliases ?? source.union.blocks[0]?.select.map((item) => item.alias) ?? [];
3011
+ }
3012
+ if (source.windowed !== void 0) {
3013
+ return source.columnAliases ?? source.windowed.block.select.map((item) => item.alias);
3014
+ }
3015
+ if (source.recursive !== void 0)
3016
+ return source.columnAliases ?? [];
3017
+ return source.columnAliases ?? tables.get(source.table) ?? [];
3018
+ };
3019
+ const rewriteBlock = (block) => {
3020
+ if (pass.probing && pass.needed)
3021
+ return;
3022
+ forEachNestedBlock(block, rewriteBlock);
3023
+ const sources = [block.base, ...block.joins];
3024
+ for (const source of sources) {
3025
+ const plain = source.derived === void 0 && source.union === void 0 && source.windowed === void 0 && source.recursive === void 0;
3026
+ if (!plain)
3027
+ continue;
3028
+ const folded = foldTable(source.table);
3029
+ if (folded === void 0)
3030
+ continue;
3031
+ if (pass.probing) {
3032
+ pass.needed = true;
3033
+ return;
3034
+ }
3035
+ const aliasWasTable = source.alias === source.table;
3036
+ source.table = folded;
3037
+ if (aliasWasTable)
3038
+ source.alias = folded;
3039
+ }
3040
+ const foldReference = (reference) => {
3041
+ const separator = reference.indexOf(".");
3042
+ if (separator !== -1) {
3043
+ const qualifier = reference.slice(0, separator);
3044
+ const name = reference.slice(separator + 1);
3045
+ let source = sources.find((candidate) => candidate.alias === qualifier);
3046
+ let foldedQualifier;
3047
+ if (source === void 0) {
3048
+ const lowered = qualifier.toLowerCase();
3049
+ const matches = sources.filter((candidate) => candidate.alias.toLowerCase() === lowered);
3050
+ if (matches.length !== 1)
3051
+ return void 0;
3052
+ source = matches[0];
3053
+ foldedQualifier = source?.alias;
3054
+ }
3055
+ if (source === void 0)
3056
+ return void 0;
3057
+ const columns = sourceColumns(source);
3058
+ const foldedName = columns.includes(name) ? void 0 : foldColumn(columns, name);
3059
+ if (foldedQualifier === void 0 && foldedName === void 0)
3060
+ return void 0;
3061
+ return `${foldedQualifier ?? qualifier}.${foldedName ?? name}`;
3062
+ }
3063
+ if (sources.some((source) => sourceColumns(source).includes(reference)))
3064
+ return void 0;
3065
+ const folded = sources.map((source) => foldColumn(sourceColumns(source), reference)).filter((name) => name !== void 0);
3066
+ return folded.length === 1 ? folded[0] : void 0;
3067
+ };
3068
+ const aliases = new Set(block.select.map((item) => item.alias));
3069
+ const orderByAliases = new Set(block.orderBy.map((order) => order.expression).filter((expression) => expression.kind === "column" && aliases.has(expression.reference)));
3070
+ const writtenNames = block.select.map((item) => item.expression.kind === "column" ? bareColumnName(item.expression.reference) : void 0);
3071
+ const rewrite = (expression) => {
3072
+ if (expression.kind === "column") {
3073
+ if (orderByAliases.has(expression))
3074
+ return;
3075
+ const folded = foldReference(expression.reference);
3076
+ if (folded === void 0)
3077
+ return;
3078
+ if (pass.probing)
3079
+ pass.needed = true;
3080
+ else
3081
+ expression.reference = folded;
3082
+ return;
3083
+ }
3084
+ if (expression.kind === "wildcard") {
3085
+ if (expression.table === void 0)
3086
+ return;
3087
+ const exact = sources.some((source) => source.alias === expression.table);
3088
+ if (exact)
3089
+ return;
3090
+ const lowered = expression.table.toLowerCase();
3091
+ const matches = sources.filter((source) => source.alias.toLowerCase() === lowered);
3092
+ if (matches.length !== 1)
3093
+ return;
3094
+ if (pass.probing)
3095
+ pass.needed = true;
3096
+ else
3097
+ expression.table = matches[0]?.alias ?? expression.table;
3098
+ return;
3099
+ }
3100
+ if (expression.kind === "subquery" || expression.kind === "exists") {
3101
+ rewriteBlock(expression.block);
3102
+ return;
3103
+ }
3104
+ childExpressions(expression).forEach(rewrite);
3105
+ };
3106
+ forEachBlockExpression(block, rewrite);
3107
+ if (!pass.probing) {
3108
+ block.select.forEach((item, index) => {
3109
+ if (item.expression.kind !== "column")
3110
+ return;
3111
+ const name = bareColumnName(item.expression.reference);
3112
+ if (item.alias !== name && item.alias === writtenNames[index])
3113
+ item.alias = name;
3114
+ });
3115
+ }
3116
+ };
3117
+ rewriteBlock(plan);
3118
+ if (!pass.needed)
3119
+ return plan;
3120
+ const cloned = clonePlanTree(plan);
3121
+ pass.probing = false;
3122
+ rewriteBlock(cloned);
3123
+ return cloned;
3124
+ }
3125
+ function expandRowReferences(plan, tableColumns) {
3126
+ const pass = { probing: true, needed: false };
3127
+ const sourceColumns = (source) => {
3128
+ if (source.columnAliases !== void 0)
3129
+ return source.columnAliases;
3130
+ if (source.derived !== void 0)
3131
+ return source.derived.select.map((item) => item.alias);
3132
+ if (source.union !== void 0) {
3133
+ return source.union.blocks[0]?.select.map((item) => item.alias) ?? [];
3134
+ }
3135
+ if (source.windowed !== void 0)
3136
+ return source.windowed.block.select.map((item) => item.alias);
3137
+ if (source.recursive !== void 0)
3138
+ return [];
3139
+ return tableColumns(source.table) ?? [];
3140
+ };
3141
+ const rewriteBlock = (block) => {
3142
+ if (pass.probing && pass.needed)
3143
+ return;
3144
+ forEachNestedBlock(block, rewriteBlock);
3145
+ const sources = [block.base, ...block.joins];
3146
+ const rowObject = (reference) => {
3147
+ if (reference.includes("."))
3148
+ return void 0;
3149
+ const source = sources.find((candidate) => candidate.alias === reference);
3150
+ if (source === void 0)
3151
+ return void 0;
3152
+ if (sources.some((candidate) => sourceColumns(candidate).includes(reference))) {
3153
+ return void 0;
3154
+ }
3155
+ const columns = sourceColumns(source);
3156
+ if (columns.length === 0 || columns.some((name) => name.startsWith("\0")))
3157
+ return void 0;
3158
+ return {
3159
+ kind: "call",
3160
+ name: "JSON_OBJECT",
3161
+ arguments: columns.flatMap((name) => [
3162
+ { kind: "literal", value: name },
3163
+ { kind: "column", reference: `${source.alias}.${name}` }
3164
+ ])
3165
+ };
3166
+ };
3167
+ if (pass.probing) {
3168
+ const probe = (expression) => {
3169
+ if (pass.needed)
3170
+ return;
3171
+ if (expression.kind === "column") {
3172
+ if (rowObject(expression.reference) !== void 0)
3173
+ pass.needed = true;
3174
+ return;
3175
+ }
3176
+ if (expression.kind === "subquery" || expression.kind === "exists") {
3177
+ rewriteBlock(expression.block);
3178
+ return;
3179
+ }
3180
+ childExpressions(expression).forEach(probe);
3181
+ };
3182
+ forEachBlockExpression(block, probe);
3183
+ return;
3184
+ }
3185
+ const rewrite = (expression) => {
3186
+ if (expression.kind === "column") {
3187
+ return rowObject(expression.reference) ?? expression;
3188
+ }
3189
+ if (expression.kind === "subquery" || expression.kind === "exists") {
3190
+ rewriteBlock(expression.block);
3191
+ return expression;
3192
+ }
3193
+ return mapChildExpressions(expression, rewrite);
3194
+ };
3195
+ mapBlockExpressions(block, rewrite);
3196
+ };
3197
+ rewriteBlock(plan);
3198
+ if (!pass.needed)
3199
+ return plan;
3200
+ const cloned = clonePlanTree(plan);
3201
+ pass.probing = false;
3202
+ rewriteBlock(cloned);
3203
+ return cloned;
3204
+ }
3205
+ function extendGroupByWithKeyDependents(plan, keyColumns, tableColumns = keyColumns) {
3206
+ const pass = { probing: true, needed: false };
3207
+ const rewriteBlock = (block) => {
3208
+ if (pass.probing && pass.needed)
3209
+ return;
3210
+ forEachNestedBlock(block, rewriteBlock);
3211
+ const visitSubqueries = (expression) => {
3212
+ if (expression.kind === "subquery" || expression.kind === "exists") {
3213
+ rewriteBlock(expression.block);
3214
+ return;
3215
+ }
3216
+ childExpressions(expression).forEach(visitSubqueries);
3217
+ };
3218
+ forEachBlockExpression(block, visitSubqueries);
3219
+ if (block.groupBy.length === 0)
3220
+ return;
3221
+ const sources = [block.base, ...block.joins];
3222
+ const multipleSources = sources.length > 1;
3223
+ const groupedColumns = new Set(block.groupBy.flatMap((expression) => expression.kind === "column" ? [expression.reference] : []));
3224
+ const additions = [];
3225
+ for (const source of sources) {
3226
+ const plain = source.derived === void 0 && source.union === void 0 && source.windowed === void 0 && source.recursive === void 0 && source.columnAliases === void 0;
3227
+ if (!plain)
3228
+ continue;
3229
+ const key = keyColumns(source.table);
3230
+ if (key === void 0 || key.length === 0)
3231
+ continue;
3232
+ const columns = tableColumns(source.table) ?? key;
3233
+ const namesFor = (column) => multipleSources ? [`${source.alias}.${column}`] : [column, `${source.alias}.${column}`];
3234
+ const keyGrouped = key.every((column) => namesFor(column).some((name) => groupedColumns.has(name)));
3235
+ if (!keyGrouped)
3236
+ continue;
3237
+ const referenced = /* @__PURE__ */ new Set();
3238
+ const collect = (expression, insideAggregate) => {
3239
+ if (expression.kind === "column") {
3240
+ if (insideAggregate || groupedColumns.has(expression.reference))
3241
+ return;
3242
+ const separator = expression.reference.indexOf(".");
3243
+ const qualifier = separator === -1 ? void 0 : expression.reference.slice(0, separator);
3244
+ if (qualifier === void 0 ? multipleSources : qualifier !== source.alias)
3245
+ return;
3246
+ const name = separator === -1 ? expression.reference : expression.reference.slice(separator + 1);
3247
+ if (!columns.includes(name))
3248
+ return;
3249
+ referenced.add(expression.reference);
3250
+ return;
3251
+ }
3252
+ if (expression.kind === "subquery" || expression.kind === "exists")
3253
+ return;
3254
+ const aggregate = insideAggregate || expression.kind === "call" && aggregateNames.has(expression.name);
3255
+ childExpressions(expression).forEach((child) => collect(child, aggregate));
3256
+ };
3257
+ for (const item of block.select)
3258
+ collect(item.expression, false);
3259
+ for (const predicate of block.having) {
3260
+ collect(predicate.left, false);
3261
+ collect(predicate.right, false);
3262
+ }
3263
+ for (const order of block.orderBy)
3264
+ collect(order.expression, false);
3265
+ for (const reference of referenced) {
3266
+ if (pass.probing) {
3267
+ pass.needed = true;
3268
+ return;
3269
+ }
3270
+ groupedColumns.add(reference);
3271
+ additions.push({ kind: "column", reference });
3272
+ }
3273
+ }
3274
+ if (additions.length > 0)
3275
+ block.groupBy = [...block.groupBy, ...additions];
3276
+ };
3277
+ rewriteBlock(plan);
3278
+ if (!pass.needed)
3279
+ return plan;
3280
+ const cloned = clonePlanTree(plan);
3281
+ pass.probing = false;
3282
+ rewriteBlock(cloned);
3283
+ return cloned;
3284
+ }
2490
3285
  function mapBlockExpressions(block, map) {
2491
3286
  for (const item of block.select)
2492
3287
  item.expression = map(item.expression);
@@ -2681,6 +3476,9 @@ function exactConstantFold(expression) {
2681
3476
  const seeded = left.seeded || right.seeded;
2682
3477
  if (left.value === null || right.value === null)
2683
3478
  return { value: null, seeded };
3479
+ if (expression.integer === true && expression.operator === "/" && typeof left.value === "number" && typeof right.value === "number") {
3480
+ return { value: integerQuotient(left.value, right.value), seeded };
3481
+ }
2684
3482
  const folded = exactNumericBinary(expression.operator, left.value, right.value, 0, false);
2685
3483
  return folded === void 0 ? void 0 : { value: folded, seeded };
2686
3484
  }
@@ -2693,11 +3491,13 @@ function resolveExactNumericConstants(expression) {
2693
3491
  }
2694
3492
  if (folded?.seeded === true) {
2695
3493
  const value = folded.value;
2696
- if (value === null || typeof value === "number")
3494
+ if (value === null)
2697
3495
  return { kind: "literal", value };
3496
+ if (typeof value === "number")
3497
+ return { kind: "literal", value, decimal: true };
2698
3498
  const fits = exactNumericAsNumber(value);
2699
3499
  if (fits !== void 0)
2700
- return { kind: "literal", value: fits };
3500
+ return { kind: "literal", value: fits, decimal: true };
2701
3501
  return { kind: "literal", value, internalSqlValue: true, sqlDomain: { kind: "numeric" } };
2702
3502
  }
2703
3503
  if (expression.kind === "subquery" || expression.kind === "exists") {
@@ -2721,8 +3521,12 @@ function resolvePlanExactNumericConstants(plan) {
2721
3521
  mapBlockExpressions(plan, resolveExactNumericConstants);
2722
3522
  forEachNestedBlock(plan, resolvePlanExactNumericConstants);
2723
3523
  }
3524
+ function resolveStatementExpression(expression) {
3525
+ annotateIntegerDivision(expression);
3526
+ return resolveExactNumericConstants(expression);
3527
+ }
2724
3528
  function resolveStatementExactNumericConstants(statement) {
2725
- const resolve = resolveExactNumericConstants;
3529
+ const resolve = resolveStatementExpression;
2726
3530
  if (statement.kind === "insert") {
2727
3531
  const conflict = statement.onConflict;
2728
3532
  if (conflict?.assignments !== void 0) {
@@ -2744,6 +3548,18 @@ function resolveStatementExactNumericConstants(statement) {
2744
3548
  predicate.left = resolve(predicate.left);
2745
3549
  predicate.right = resolve(predicate.right);
2746
3550
  }
3551
+ if (statement.from !== void 0) {
3552
+ for (const predicate of statement.from.predicates) {
3553
+ predicate.left = resolve(predicate.left);
3554
+ predicate.right = resolve(predicate.right);
3555
+ }
3556
+ for (const join of statement.from.joins) {
3557
+ join.left = resolve(join.left);
3558
+ join.right = resolve(join.right);
3559
+ if (join.on !== void 0)
3560
+ join.on = resolve(join.on);
3561
+ }
3562
+ }
2747
3563
  return;
2748
3564
  }
2749
3565
  if (statement.kind === "merge") {
@@ -3197,6 +4013,7 @@ function executeRowQueryInternal(plan, tables, memory) {
3197
4013
  assertTailParametersBound(plan);
3198
4014
  plan = resolveStatementDatetimes(plan);
3199
4015
  plan = bindPendingSelectShapes(plan, wildcardRowColumns(tables));
4016
+ plan = unifyPlanGroupedReferences(plan, wildcardRowColumns(tables));
3200
4017
  validateGrouping(plan);
3201
4018
  plan = expandSourceColumnAliases(plan, wildcardRowColumns(tables));
3202
4019
  plan = expandNaturalJoins(plan, wildcardRowColumns(tables));
@@ -3460,11 +4277,16 @@ function evaluate(expression, context, group) {
3460
4277
  return null;
3461
4278
  if (expression.operator === "||")
3462
4279
  return concatenatedSqlValue(left, right);
3463
- const 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);
3464
4286
  if (exact !== void 0)
3465
4287
  return exact;
3466
- const a = numeric(left);
3467
- const b = numeric(right);
4288
+ const a = numeric(leftValue);
4289
+ const b = numeric(rightValue);
3468
4290
  if (expression.operator === "+")
3469
4291
  return a + b;
3470
4292
  if (expression.operator === "-")
@@ -3904,10 +4726,12 @@ function evaluateBooleanExpression(expression, evaluateValue) {
3904
4726
  const distinct = distinctFromComparison(evaluateValue(expression.left), evaluateValue(expression.right));
3905
4727
  return operator === "IS DISTINCT FROM" ? distinct : !distinct;
3906
4728
  }
3907
- const left = evaluateValue(expression.left);
3908
- const right = evaluateValue(expression.right);
3909
- 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) {
3910
4732
  return null;
4733
+ }
4734
+ const [left, right] = coerceComparisonOperands(evaluatedLeft, evaluatedRight);
3911
4735
  const a = comparable(left);
3912
4736
  const b = comparable(right);
3913
4737
  if (operator === "=")
@@ -4063,6 +4887,15 @@ function validateGrouping(plan) {
4063
4887
  throw new TypeError(`Selected column must appear in GROUP BY: ${item.alias}`);
4064
4888
  }
4065
4889
  }
4890
+ for (const predicate of plan.having) {
4891
+ for (const side of [predicate.left, predicate.right]) {
4892
+ if (hasAggregate(side) || expressionColumns(side).length === 0)
4893
+ continue;
4894
+ if (!groupedExpression(side, groupExpressions)) {
4895
+ throw new TypeError("HAVING conditions must use aggregates, literals, or GROUP BY expressions");
4896
+ }
4897
+ }
4898
+ }
4066
4899
  const forbiddenAggregates = [
4067
4900
  ...plan.joins.flatMap((join) => [
4068
4901
  join.left,
@@ -4150,8 +4983,9 @@ function externalizeQueryResult(result) {
4150
4983
  for (let position = 0; position < result.columns.length; position += 1) {
4151
4984
  const name = result.columns[position] ?? "";
4152
4985
  const value = row[name];
4153
- if (value !== void 0 && !isSqlDomainValue(value))
4986
+ if (value !== void 0 && !isSqlDomainValue(value) && !(typeof value === "number" && result.columnDomains[position]?.kind === "numeric")) {
4154
4987
  continue;
4988
+ }
4155
4989
  const external = asQueryValue(externalSqlDomainColumnValue(value, result.columnDomains[position]));
4156
4990
  if (external === value)
4157
4991
  continue;
@@ -4234,6 +5068,12 @@ class Parser {
4234
5068
  }
4235
5069
  const columns = this.#cteColumnList();
4236
5070
  this.#keyword("AS");
5071
+ if (this.#isKeyword("NOT")) {
5072
+ this.#keyword("NOT");
5073
+ this.#keyword("MATERIALIZED");
5074
+ } else if (this.#isKeyword("MATERIALIZED")) {
5075
+ this.#keyword("MATERIALIZED");
5076
+ }
4237
5077
  this.#expectPunctuation("(");
4238
5078
  if (recursive) {
4239
5079
  this.#recursiveCte(name, columns);
@@ -4325,7 +5165,9 @@ class Parser {
4325
5165
  return plan;
4326
5166
  }
4327
5167
  #compoundTail() {
4328
- return { orderBy: this.#orderByClause(), ...this.#tailClauses() };
5168
+ const tail = { orderBy: this.#orderByClause(), ...this.#tailClauses() };
5169
+ this.#lockingClause();
5170
+ return tail;
4329
5171
  }
4330
5172
  #setTerm(sql, member) {
4331
5173
  const first = this.#unionMember(sql, member);
@@ -4514,6 +5356,22 @@ class Parser {
4514
5356
  nullable = false;
4515
5357
  continue;
4516
5358
  }
5359
+ if (this.#isKeyword("CONSTRAINT")) {
5360
+ this.#keyword("CONSTRAINT");
5361
+ const constraintName = this.#identifier();
5362
+ if (this.#isKeyword("CHECK")) {
5363
+ checks.push(this.#checkConstraint(constraintName));
5364
+ continue;
5365
+ }
5366
+ if (this.#isKeyword("REFERENCES")) {
5367
+ foreignKeys.push(this.#references(constraintName, [name]));
5368
+ continue;
5369
+ }
5370
+ if (this.#isKeyword("PRIMARY") || this.#isKeyword("UNIQUE") || this.#isKeyword("NOT") || this.#isKeyword("NULL")) {
5371
+ continue;
5372
+ }
5373
+ throw new TypeError(`Expected a constraint after CONSTRAINT ${constraintName}`);
5374
+ }
4517
5375
  if (this.#isKeyword("CHECK")) {
4518
5376
  checks.push(this.#checkConstraint(`${table}_${name}_check`));
4519
5377
  continue;
@@ -4890,6 +5748,7 @@ class Parser {
4890
5748
  return `numeric:${precision === void 0 ? "" : String(precision)}:${scale === void 0 ? "" : String(scale)}`;
4891
5749
  }
4892
5750
  if (["JSON", "JSONB", "UUID", "DATE", "TIME", "INTERVAL"].includes(word)) {
5751
+ this.#multiWordTypeTail(word);
4893
5752
  return word.toLowerCase();
4894
5753
  }
4895
5754
  if (word === "DOUBLE") {
@@ -4899,14 +5758,27 @@ class Parser {
4899
5758
  const mapped = createTableTypeNames.get(word);
4900
5759
  if (mapped === void 0)
4901
5760
  throw new TypeError(`Unsupported CAST target: ${word}`);
5761
+ this.#multiWordTypeTail(word);
4902
5762
  if (this.#punctuation("(")) {
4903
5763
  this.#typeWidth();
4904
5764
  if (this.#punctuation(","))
4905
5765
  this.#typeWidth();
4906
5766
  this.#expectPunctuation(")");
4907
5767
  }
4908
- const integer = word === "INTEGER" || word === "INT" || word === "BIGINT" || word === "SMALLINT";
4909
- 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;
4910
5782
  }
4911
5783
  #columnType() {
4912
5784
  const declared = this.#identifier();
@@ -4932,6 +5804,7 @@ class Parser {
4932
5804
  };
4933
5805
  }
4934
5806
  if (["JSON", "JSONB", "UUID", "DATE", "TIME", "INTERVAL"].includes(word)) {
5807
+ this.#multiWordTypeTail(word);
4935
5808
  return { type: "string", sqlDomain: { kind: word.toLowerCase() } };
4936
5809
  }
4937
5810
  if (word === "DOUBLE") {
@@ -4942,6 +5815,7 @@ class Parser {
4942
5815
  if (mapped === void 0) {
4943
5816
  return { type: "string", sqlDomain: { kind: "enum", name: declared, values: [] } };
4944
5817
  }
5818
+ const multiWord = this.#multiWordTypeTail(word);
4945
5819
  if (this.#punctuation("(")) {
4946
5820
  this.#typeWidth();
4947
5821
  if (this.#punctuation(",")) {
@@ -4949,12 +5823,11 @@ class Parser {
4949
5823
  }
4950
5824
  this.#expectPunctuation(")");
4951
5825
  }
4952
- if (this.#punctuation("[")) {
5826
+ if (!multiWord && this.#punctuation("[")) {
4953
5827
  this.#expectPunctuation("]");
4954
5828
  return { type: "string", sqlDomain: { kind: "array", element: word } };
4955
5829
  }
4956
- const integer = word === "INTEGER" || word === "INT" || word === "BIGINT" || word === "SMALLINT";
4957
- return { type: mapped, ...integer ? { integer: true } : {} };
5830
+ return { type: mapped, ...integerTypeNames.has(word) ? { integer: true } : {} };
4958
5831
  }
4959
5832
  parseMutation(keyword) {
4960
5833
  const statement = keyword === "INSERT" ? this.#insertStatement() : keyword === "UPDATE" ? this.#updateStatement() : this.#deleteStatement();
@@ -4993,6 +5866,7 @@ class Parser {
4993
5866
  }
4994
5867
  if (this.#isKeyword("SELECT") || this.#isKeyword("WITH") || this.#peek().text === "(") {
4995
5868
  const insertSource = this.#queryExpression("(insert select)");
5869
+ annotatePlanIntegerDivision(insertSource);
4996
5870
  resolvePlanExactNumericConstants(insertSource);
4997
5871
  const query = planHasPendingSelectShapes(insertSource) ? insertSource : optimizePlan(insertSource);
4998
5872
  if (!planHasPendingSelectShapes(query) && !query.select.some((item) => item.expression.kind === "wildcard") && columns.length > 0 && query.select.length !== columns.length) {
@@ -5312,6 +6186,7 @@ class Parser {
5312
6186
  if (new Set(assignments.map(({ column }) => column)).size !== assignments.length) {
5313
6187
  throw new TypeError("UPDATE assignments must set each column once");
5314
6188
  }
6189
+ const from = this.#mutationSources("FROM");
5315
6190
  const predicates = this.#mutationPredicates();
5316
6191
  return {
5317
6192
  kind: "update",
@@ -5319,20 +6194,31 @@ class Parser {
5319
6194
  ...alias === void 0 ? {} : { alias },
5320
6195
  assignments,
5321
6196
  predicates,
6197
+ ...from === void 0 ? {} : { from },
5322
6198
  ...this.#returningClause(table, alias)
5323
6199
  };
5324
6200
  }
6201
+ #mutationSources(keyword) {
6202
+ if (!this.#isKeyword(keyword))
6203
+ return void 0;
6204
+ this.#keyword(keyword);
6205
+ const groupConditions = [];
6206
+ const { base, joins } = this.#joinedSources([], groupConditions);
6207
+ return { base, joins, predicates: groupConditions.flatMap(splitCondition) };
6208
+ }
5325
6209
  #deleteStatement() {
5326
6210
  this.#keyword("DELETE");
5327
6211
  this.#keyword("FROM");
5328
6212
  const table = this.#identifier();
5329
- const alias = this.#mutationAlias("WHERE", "RETURNING");
6213
+ const alias = this.#mutationAlias("WHERE", "USING", "RETURNING");
6214
+ const from = this.#mutationSources("USING");
5330
6215
  const predicates = this.#mutationPredicates();
5331
6216
  return {
5332
6217
  kind: "delete",
5333
6218
  table,
5334
6219
  ...alias === void 0 ? {} : { alias },
5335
6220
  predicates,
6221
+ ...from === void 0 ? {} : { from },
5336
6222
  ...this.#returningClause(table, alias)
5337
6223
  };
5338
6224
  }
@@ -5386,9 +6272,9 @@ class Parser {
5386
6272
  }
5387
6273
  const needsExecution = (value) => value.kind === "subquery" || value.kind === "exists" || value.kind === "call" && (statementDatetimeNames.has(value.name) || value.name === "NEXTVAL" || value.name === "CURRVAL" || volatileScalarFunctionNames.has(value.name)) || childExpressions(value).some(needsExecution);
5388
6274
  if (needsExecution(expression)) {
5389
- return { expression: resolveExactNumericConstants(expression) };
6275
+ return { expression: resolveStatementExpression(expression) };
5390
6276
  }
5391
- return asQueryValue(evaluate(resolveExactNumericConstants(expression), {}));
6277
+ return asQueryValue(evaluate(resolveStatementExpression(expression), {}));
5392
6278
  }
5393
6279
  #unionMember(sql, member) {
5394
6280
  if (this.#punctuation("(")) {
@@ -5436,6 +6322,10 @@ class Parser {
5436
6322
  #limitClause() {
5437
6323
  if (this.#isKeyword("LIMIT")) {
5438
6324
  this.#keyword("LIMIT");
6325
+ if (this.#isKeyword("ALL")) {
6326
+ this.#keyword("ALL");
6327
+ return {};
6328
+ }
5439
6329
  if (this.#peek().kind === "parameter") {
5440
6330
  const parameter = this.#parameterExpression();
5441
6331
  return { limitParameter: parameter.index };
@@ -5488,6 +6378,37 @@ class Parser {
5488
6378
  this.#keyword("ROW");
5489
6379
  return result;
5490
6380
  }
6381
+ #lockingClause() {
6382
+ while (this.#isKeyword("FOR")) {
6383
+ this.#keyword("FOR");
6384
+ if (this.#isKeyword("UPDATE"))
6385
+ this.#keyword("UPDATE");
6386
+ else if (this.#isKeyword("SHARE"))
6387
+ this.#keyword("SHARE");
6388
+ else if (this.#isKeyword("NO")) {
6389
+ this.#keyword("NO");
6390
+ this.#keyword("KEY");
6391
+ this.#keyword("UPDATE");
6392
+ } else if (this.#isKeyword("KEY")) {
6393
+ this.#keyword("KEY");
6394
+ this.#keyword("SHARE");
6395
+ } else {
6396
+ throw new TypeError(`Expected UPDATE or SHARE after FOR, found ${this.#peek().text}`);
6397
+ }
6398
+ if (this.#isKeyword("OF")) {
6399
+ this.#keyword("OF");
6400
+ this.#identifier();
6401
+ while (this.#punctuation(","))
6402
+ this.#identifier();
6403
+ }
6404
+ if (this.#isKeyword("NOWAIT"))
6405
+ this.#keyword("NOWAIT");
6406
+ else if (this.#isKeyword("SKIP")) {
6407
+ this.#keyword("SKIP");
6408
+ this.#keyword("LOCKED");
6409
+ }
6410
+ }
6411
+ }
5491
6412
  #tailClauses() {
5492
6413
  if (this.#isKeyword("LIMIT")) {
5493
6414
  return { ...this.#limitClause(), ...this.#offsetClause() };
@@ -5561,33 +6482,216 @@ class Parser {
5561
6482
  this.#compoundMember = false;
5562
6483
  this.#keyword("SELECT");
5563
6484
  let distinct = false;
6485
+ let distinctOn;
5564
6486
  if (this.#isKeyword("DISTINCT")) {
5565
6487
  this.#keyword("DISTINCT");
5566
- 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");
5567
6498
  }
5568
6499
  const select = this.#selectList();
5569
6500
  let base;
6501
+ let joins = [];
6502
+ const groupConditions = [];
5570
6503
  if (this.#isKeyword("FROM")) {
5571
6504
  this.#keyword("FROM");
5572
- base = this.#source();
6505
+ ({ base, joins } = this.#joinedSources(select, groupConditions));
5573
6506
  } else {
5574
6507
  if (select.some((item) => item.expression.kind === "wildcard")) {
5575
6508
  throw new TypeError("SELECT * requires a FROM clause");
5576
6509
  }
5577
6510
  base = { table: DUAL_TABLE, alias: DUAL_TABLE };
5578
6511
  }
5579
- const joins = [];
5580
- let rightJoins = 0;
5581
- 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")) {
5582
- if (this.#punctuation(",")) {
5583
- joins.push(crossJoinPlan(this.#source()));
5584
- continue;
5585
- }
5586
- if (this.#isKeyword("CROSS")) {
5587
- this.#keyword("CROSS");
5588
- this.#keyword("JOIN");
5589
- joins.push(crossJoinPlan(this.#source()));
5590
- continue;
6512
+ const predicates = groupConditions.flatMap(splitCondition);
6513
+ if (this.#isKeyword("WHERE")) {
6514
+ this.#keyword("WHERE");
6515
+ predicates.push(...splitCondition(this.#expression()));
6516
+ }
6517
+ const groupBy = [];
6518
+ let groupingSets;
6519
+ if (this.#isKeyword("GROUP")) {
6520
+ this.#keyword("GROUP");
6521
+ this.#keyword("BY");
6522
+ if (this.#isKeyword("GROUPING") || this.#isKeyword("ROLLUP") || this.#isKeyword("CUBE")) {
6523
+ groupingSets = this.#groupingSets();
6524
+ } else {
6525
+ groupBy.push(...this.#expressionList());
6526
+ }
6527
+ }
6528
+ const having = [];
6529
+ if (this.#isKeyword("HAVING")) {
6530
+ this.#keyword("HAVING");
6531
+ having.push(...splitCondition(this.#expression()));
6532
+ }
6533
+ if (this.#isKeyword("WINDOW")) {
6534
+ this.#keyword("WINDOW");
6535
+ for (; ; ) {
6536
+ this.#identifier();
6537
+ this.#keyword("AS");
6538
+ const definition = this.#namedWindows.get(this.tokens[this.#index - 2]?.text ?? "");
6539
+ this.#index = definition?.end ?? this.#index;
6540
+ if (!this.#punctuation(","))
6541
+ break;
6542
+ }
6543
+ }
6544
+ const orderBy = compoundMember ? [] : this.#orderByClause();
6545
+ const tail = compoundMember ? {} : this.#tailClauses();
6546
+ if (!compoundMember)
6547
+ this.#lockingClause();
6548
+ if (distinctOn !== void 0) {
6549
+ return this.#distinctOnBlock(sql, distinctOn, { base, joins, select, predicates, groupBy, having, orderBy, groupingSets }, tail);
6550
+ }
6551
+ return assembleSelectBlock({
6552
+ sql,
6553
+ base,
6554
+ joins,
6555
+ select,
6556
+ distinct,
6557
+ predicates,
6558
+ groupBy,
6559
+ having,
6560
+ orderBy,
6561
+ ...tail,
6562
+ ...groupingSets === void 0 ? {} : { groupingSets }
6563
+ }, this.nextDerivedSequence);
6564
+ }
6565
+ #distinctOnBlock(sql, distinctOn, parts, tail) {
6566
+ const rowNumberAlias = "\0distinct_on";
6567
+ const innerSelect = [...parts.select];
6568
+ const aliased = new Map(parts.select.filter((item) => item.expression.kind !== "wildcard").map((item) => [item.alias, item.expression]));
6569
+ const windowExpression = (expression) => {
6570
+ if (expression.kind === "column" && !expression.reference.includes(".")) {
6571
+ const labelled = aliased.get(expression.reference);
6572
+ if (labelled !== void 0)
6573
+ return labelled;
6574
+ }
6575
+ return expression;
6576
+ };
6577
+ const outerOrderBy = [];
6578
+ parts.orderBy.forEach((order, index) => {
6579
+ const expression = order.expression;
6580
+ const output = expression.kind === "column" && !expression.reference.includes(".") ? parts.select.find((item) => item.alias === expression.reference) : parts.select.find((item) => item.expression.kind !== "wildcard" && JSON.stringify(item.expression) === JSON.stringify(expression));
6581
+ let alias = output?.alias;
6582
+ if (alias === void 0) {
6583
+ alias = `\0distinct_on_order_${String(index)}`;
6584
+ innerSelect.push({ expression, alias });
6585
+ }
6586
+ outerOrderBy.push({ ...order, expression: { kind: "column", reference: alias } });
6587
+ });
6588
+ innerSelect.push({
6589
+ expression: {
6590
+ kind: "window",
6591
+ name: "ROW_NUMBER",
6592
+ partitionBy: distinctOn.map(windowExpression),
6593
+ orderBy: parts.orderBy.map((order) => ({
6594
+ ...order,
6595
+ expression: windowExpression(order.expression)
6596
+ }))
6597
+ },
6598
+ alias: rowNumberAlias
6599
+ });
6600
+ const inner = assembleSelectBlock({
6601
+ sql: "(distinct on)",
6602
+ base: parts.base,
6603
+ joins: parts.joins,
6604
+ select: innerSelect,
6605
+ distinct: false,
6606
+ predicates: parts.predicates,
6607
+ groupBy: parts.groupBy,
6608
+ having: parts.having,
6609
+ orderBy: [],
6610
+ ...parts.groupingSets === void 0 ? {} : { groupingSets: parts.groupingSets }
6611
+ }, this.nextDerivedSequence);
6612
+ const outerAlias = "(distinct on)";
6613
+ const outerSelect = parts.select.map((item) => item.expression.kind === "wildcard" ? item : { expression: { kind: "column", reference: item.alias }, alias: item.alias });
6614
+ return assembleSelectBlock({
6615
+ sql,
6616
+ base: this.#derivedSource(inner, outerAlias),
6617
+ joins: [],
6618
+ select: outerSelect,
6619
+ distinct: false,
6620
+ predicates: [
6621
+ {
6622
+ left: { kind: "column", reference: rowNumberAlias },
6623
+ operator: "=",
6624
+ right: { kind: "literal", value: 1 }
6625
+ }
6626
+ ],
6627
+ groupBy: [],
6628
+ having: [],
6629
+ orderBy: outerOrderBy,
6630
+ ...tail
6631
+ }, this.nextDerivedSequence);
6632
+ }
6633
+ #groupingSets() {
6634
+ if (this.#isKeyword("GROUPING")) {
6635
+ this.#keyword("GROUPING");
6636
+ this.#keyword("SETS");
6637
+ this.#expectPunctuation("(");
6638
+ const sets2 = [];
6639
+ for (; ; ) {
6640
+ this.#expectPunctuation("(");
6641
+ const set = [];
6642
+ if (!this.#punctuation(")")) {
6643
+ set.push(...this.#expressionList());
6644
+ this.#expectPunctuation(")");
6645
+ }
6646
+ sets2.push(set);
6647
+ if (!this.#punctuation(","))
6648
+ break;
6649
+ }
6650
+ this.#expectPunctuation(")");
6651
+ return sets2;
6652
+ }
6653
+ const cube = this.#isKeyword("CUBE");
6654
+ this.#keyword(cube ? "CUBE" : "ROLLUP");
6655
+ this.#expectPunctuation("(");
6656
+ const columns = this.#expressionList();
6657
+ this.#expectPunctuation(")");
6658
+ if (columns.length === 0)
6659
+ throw new TypeError("ROLLUP/CUBE take at least one expression");
6660
+ if (cube) {
6661
+ if (columns.length > 5) {
6662
+ throw new TypeError("CUBE supports at most 5 expressions (32 grouping sets)");
6663
+ }
6664
+ const sets2 = [];
6665
+ for (let mask = (1 << columns.length) - 1; mask >= 0; mask -= 1) {
6666
+ sets2.push(columns.filter((_, index) => (mask & 1 << index) !== 0));
6667
+ }
6668
+ return sets2;
6669
+ }
6670
+ const sets = [];
6671
+ for (let length = columns.length; length >= 0; length -= 1) {
6672
+ sets.push(columns.slice(0, length));
6673
+ }
6674
+ return sets;
6675
+ }
6676
+ #joinedSources(select, groupConditions) {
6677
+ const first = this.#joinOperand(select, groupConditions);
6678
+ let base = first.base;
6679
+ const joins = [...first.joins];
6680
+ let rightJoins = first.rightJoins;
6681
+ while (this.#peek().text === "," || this.#isKeyword("NATURAL") || this.#isKeyword("JOIN") || this.#isKeyword("INNER") || this.#isKeyword("LEFT") || this.#isKeyword("RIGHT") || this.#isKeyword("FULL") || this.#isKeyword("CROSS")) {
6682
+ if (this.#punctuation(",")) {
6683
+ const operand2 = this.#joinOperand(select, groupConditions);
6684
+ joins.push(crossJoinPlan(operand2.base), ...operand2.joins);
6685
+ rightJoins += operand2.rightJoins;
6686
+ continue;
6687
+ }
6688
+ if (this.#isKeyword("CROSS")) {
6689
+ this.#keyword("CROSS");
6690
+ this.#keyword("JOIN");
6691
+ const operand2 = this.#joinOperand(select, groupConditions);
6692
+ joins.push(crossJoinPlan(operand2.base), ...operand2.joins);
6693
+ rightJoins += operand2.rightJoins;
6694
+ continue;
5591
6695
  }
5592
6696
  let kind = "inner";
5593
6697
  let right = false;
@@ -5614,7 +6718,21 @@ class Parser {
5614
6718
  if (this.#isKeyword("OUTER"))
5615
6719
  this.#keyword("OUTER");
5616
6720
  this.#keyword("JOIN");
5617
- const 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;
5618
6736
  if (natural) {
5619
6737
  if (this.#isKeyword("ON") || this.#isKeyword("USING")) {
5620
6738
  throw new TypeError("A NATURAL join takes no ON or USING clause");
@@ -5674,95 +6792,58 @@ class Parser {
5674
6792
  }
5675
6793
  }
5676
6794
  }
5677
- const predicates = [];
5678
- if (this.#isKeyword("WHERE")) {
5679
- this.#keyword("WHERE");
5680
- predicates.push(...splitCondition(this.#expression()));
5681
- }
5682
- const groupBy = [];
5683
- let groupingSets;
5684
- if (this.#isKeyword("GROUP")) {
5685
- this.#keyword("GROUP");
5686
- this.#keyword("BY");
5687
- if (this.#isKeyword("GROUPING") || this.#isKeyword("ROLLUP") || this.#isKeyword("CUBE")) {
5688
- groupingSets = this.#groupingSets();
5689
- } else {
5690
- groupBy.push(...this.#expressionList());
5691
- }
5692
- }
5693
- const having = [];
5694
- if (this.#isKeyword("HAVING")) {
5695
- this.#keyword("HAVING");
5696
- having.push(...splitCondition(this.#expression()));
5697
- }
5698
- if (this.#isKeyword("WINDOW")) {
5699
- this.#keyword("WINDOW");
5700
- for (; ; ) {
5701
- this.#identifier();
5702
- this.#keyword("AS");
5703
- const definition = this.#namedWindows.get(this.tokens[this.#index - 2]?.text ?? "");
5704
- this.#index = definition?.end ?? this.#index;
5705
- if (!this.#punctuation(","))
5706
- break;
5707
- }
6795
+ if (rightJoins > 0 && joins.length !== 1) {
6796
+ throw new TypeError("RIGHT JOIN is only supported as the sole join");
5708
6797
  }
5709
- const orderBy = compoundMember ? [] : this.#orderByClause();
5710
- return assembleSelectBlock({
5711
- sql,
5712
- base,
5713
- joins,
5714
- select,
5715
- distinct,
5716
- predicates,
5717
- groupBy,
5718
- having,
5719
- orderBy,
5720
- ...compoundMember ? {} : this.#tailClauses(),
5721
- ...groupingSets === void 0 ? {} : { groupingSets }
5722
- }, this.nextDerivedSequence);
6798
+ return { base, joins, rightJoins };
5723
6799
  }
5724
- #groupingSets() {
5725
- if (this.#isKeyword("GROUPING")) {
5726
- this.#keyword("GROUPING");
5727
- this.#keyword("SETS");
6800
+ #joinOperand(select, groupConditions) {
6801
+ const next = this.#peek();
6802
+ if (next.kind === "punctuation" && next.text === "(" && this.#parenthesizedJoinAhead()) {
5728
6803
  this.#expectPunctuation("(");
5729
- const sets2 = [];
5730
- for (; ; ) {
5731
- this.#expectPunctuation("(");
5732
- const set = [];
5733
- if (!this.#punctuation(")")) {
5734
- set.push(...this.#expressionList());
5735
- this.#expectPunctuation(")");
5736
- }
5737
- sets2.push(set);
5738
- if (!this.#punctuation(","))
5739
- break;
5740
- }
6804
+ const inner = this.#joinedSources(select, groupConditions);
5741
6805
  this.#expectPunctuation(")");
5742
- return sets2;
5743
- }
5744
- const cube = this.#isKeyword("CUBE");
5745
- this.#keyword(cube ? "CUBE" : "ROLLUP");
5746
- this.#expectPunctuation("(");
5747
- const columns = this.#expressionList();
5748
- this.#expectPunctuation(")");
5749
- if (columns.length === 0)
5750
- throw new TypeError("ROLLUP/CUBE take at least one expression");
5751
- if (cube) {
5752
- if (columns.length > 5) {
5753
- throw new TypeError("CUBE supports at most 5 expressions (32 grouping sets)");
6806
+ if (this.#sourceAlias() !== void 0) {
6807
+ throw new TypeError("A parenthesized join cannot take an alias");
5754
6808
  }
5755
- const sets2 = [];
5756
- for (let mask = (1 << columns.length) - 1; mask >= 0; mask -= 1) {
5757
- sets2.push(columns.filter((_, index) => (mask & 1 << index) !== 0));
5758
- }
5759
- return sets2;
6809
+ return { ...inner, group: true };
5760
6810
  }
5761
- const sets = [];
5762
- for (let length = columns.length; length >= 0; length -= 1) {
5763
- 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];
5764
6819
  }
5765
- 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;
5766
6847
  }
5767
6848
  #selectList() {
5768
6849
  const items = [];
@@ -5775,6 +6856,9 @@ class Parser {
5775
6856
  this.#keyword("AS");
5776
6857
  alias = this.#identifier();
5777
6858
  explicitAlias = true;
6859
+ } else if (this.#bareLabelAhead()) {
6860
+ alias = this.#identifier();
6861
+ explicitAlias = true;
5778
6862
  }
5779
6863
  items.push({ expression, alias });
5780
6864
  explicitAliases.push(explicitAlias);
@@ -6060,6 +7144,12 @@ class Parser {
6060
7144
  }
6061
7145
  return { table, alias };
6062
7146
  }
7147
+ #bareLabelAhead() {
7148
+ const token = this.#peek();
7149
+ if (token.kind !== "identifier")
7150
+ return false;
7151
+ return token.quoted === true || !bareLabelStopWords.has(token.text.toUpperCase());
7152
+ }
6063
7153
  #sourceAlias() {
6064
7154
  if (this.#isKeyword("AS")) {
6065
7155
  this.#keyword("AS");
@@ -6324,6 +7414,14 @@ class Parser {
6324
7414
  }
6325
7415
  return { kind: "condition", operator, left, right: { kind: "list", items } };
6326
7416
  }
7417
+ const likeSpelling = this.#peek();
7418
+ if (likeSpelling.kind === "operator" && /^!?~~\*?$/.test(likeSpelling.text)) {
7419
+ this.#index += 1;
7420
+ const negatedSpelling = likeSpelling.text.startsWith("!") !== negated;
7421
+ const insensitiveSpelling = likeSpelling.text.endsWith("*");
7422
+ const operator = insensitiveSpelling ? negatedSpelling ? "NOT ILIKE" : "ILIKE" : negatedSpelling ? "NOT LIKE" : "LIKE";
7423
+ return { kind: "condition", operator, left, right: this.#additive() };
7424
+ }
6327
7425
  if (this.#isKeyword("LIKE") || this.#isKeyword("ILIKE")) {
6328
7426
  const insensitive = this.#isKeyword("ILIKE");
6329
7427
  this.#keyword(insensitive ? "ILIKE" : "LIKE");
@@ -6522,8 +7620,9 @@ class Parser {
6522
7620
  throw new SqlCompileError(error instanceof Error ? error.message : `Invalid number: ${token.text}`, token.start, token.end - token.start);
6523
7621
  }
6524
7622
  const fits = exactNumericAsNumber(tagged);
6525
- if (fits !== void 0)
6526
- return { kind: "literal", value: fits, exactText: token.text };
7623
+ if (fits !== void 0) {
7624
+ return { kind: "literal", value: fits, exactText: token.text, decimal: true };
7625
+ }
6527
7626
  return {
6528
7627
  kind: "literal",
6529
7628
  value: tagged,
@@ -6688,6 +7787,10 @@ class Parser {
6688
7787
  args.push(this.#expression());
6689
7788
  }
6690
7789
  this.#expectPunctuation(")");
7790
+ const pattern = args[1];
7791
+ if (args.length === 2 && pattern?.kind === "literal" && typeof pattern.value === "string") {
7792
+ return { kind: "call", name: "REGEXP_SUBSTR", arguments: args };
7793
+ }
6691
7794
  return { kind: "call", name: "SUBSTR", arguments: args };
6692
7795
  }
6693
7796
  this.#index = restore;
@@ -6892,8 +7995,18 @@ class Parser {
6892
7995
  ...fallback === null ? {} : { fallback }
6893
7996
  };
6894
7997
  }
6895
- if (upper === "JSON_OBJECT")
7998
+ if (upper === "JSON_OBJECT" || upper === "JSON_BUILD_OBJECT" || upper === "JSONB_BUILD_OBJECT") {
6896
7999
  return this.#jsonObject();
8000
+ }
8001
+ if (upper === "DATE") {
8002
+ const operand = this.#expression();
8003
+ this.#expectPunctuation(")");
8004
+ return {
8005
+ kind: "call",
8006
+ name: "CAST",
8007
+ arguments: [operand, { kind: "literal", value: "date" }]
8008
+ };
8009
+ }
6897
8010
  if (statisticalAggregates.has(upper))
6898
8011
  return this.#statisticalAggregate(upper);
6899
8012
  if (upper === "EVERY" || upper === "BOOL_AND" || upper === "BOOL_OR") {
@@ -7011,6 +8124,10 @@ class Parser {
7011
8124
  }
7012
8125
  if (name === "SUBSTR" && (args.length < 2 || args.length > 3))
7013
8126
  throw new TypeError("SUBSTR requires a string, a start, and an optional length");
8127
+ const substringPattern = args[1];
8128
+ if (upper === "SUBSTRING" && args.length === 2 && substringPattern?.kind === "literal" && typeof substringPattern.value === "string") {
8129
+ return { kind: "call", name: "REGEXP_SUBSTR", arguments: args };
8130
+ }
7014
8131
  if (name === "DATE_TRUNC") {
7015
8132
  if (args.length !== 2)
7016
8133
  throw new TypeError("DATE_TRUNC requires a unit and a datetime argument");
@@ -7491,6 +8608,105 @@ function resolveGroupByReferences(groupBy, select) {
7491
8608
  return structuredClone(item.expression);
7492
8609
  });
7493
8610
  }
8611
+ function unifyGroupedReferences(block, resolve) {
8612
+ const groupingReferences = [...block.groupBy, ...(block.groupingSets ?? []).flat()].flatMap((expression) => expression.kind === "column" ? [expression.reference] : []);
8613
+ if (groupingReferences.length === 0)
8614
+ return block;
8615
+ const spelled = new Set(groupingReferences);
8616
+ const bareNames = new Set(groupingReferences.map(bareColumnName));
8617
+ const candidate = (expression) => expressionColumns(expression).some((reference) => !spelled.has(reference) && bareNames.has(bareColumnName(reference)));
8618
+ const aliases = new Set(block.select.map((item) => item.alias));
8619
+ const orderCandidates = block.orderBy.filter((order) => !(order.expression.kind === "column" && aliases.has(order.expression.reference)));
8620
+ if (!block.select.some((item) => candidate(item.expression)) && !block.having.some((predicate) => candidate(predicate.left) || candidate(predicate.right)) && !orderCandidates.some((order) => candidate(order.expression)) && !groupingReferences.some((reference) => !spelled.has(reference))) {
8621
+ return block;
8622
+ }
8623
+ const spellings = /* @__PURE__ */ new Map();
8624
+ for (const reference of groupingReferences) {
8625
+ const column = resolve(reference);
8626
+ if (column !== void 0 && !spellings.has(column))
8627
+ spellings.set(column, reference);
8628
+ }
8629
+ if (spellings.size === 0)
8630
+ return block;
8631
+ let respelled = 0;
8632
+ const respell = (expression) => {
8633
+ if (expression.kind === "column") {
8634
+ const column = resolve(expression.reference);
8635
+ const spelling = column === void 0 ? void 0 : spellings.get(column);
8636
+ if (spelling === void 0 || spelling === expression.reference)
8637
+ return expression;
8638
+ respelled += 1;
8639
+ return { ...expression, reference: spelling };
8640
+ }
8641
+ if (expression.kind === "subquery" || expression.kind === "exists")
8642
+ return expression;
8643
+ if (expression.kind === "window") {
8644
+ return {
8645
+ ...expression,
8646
+ partitionBy: expression.partitionBy.map(respell),
8647
+ orderBy: expression.orderBy.map((order) => ({
8648
+ ...order,
8649
+ expression: respell(order.expression)
8650
+ })),
8651
+ ...expression.argument === void 0 ? {} : { argument: respell(expression.argument) }
8652
+ };
8653
+ }
8654
+ return mapChildExpressions(expression, respell);
8655
+ };
8656
+ const unified = {
8657
+ ...block,
8658
+ select: block.select.map((item) => ({ ...item, expression: respell(item.expression) })),
8659
+ groupBy: block.groupBy.map(respell),
8660
+ having: block.having.map((predicate) => ({
8661
+ ...predicate,
8662
+ left: respell(predicate.left),
8663
+ right: respell(predicate.right)
8664
+ })),
8665
+ orderBy: block.orderBy.map((order) => order.expression.kind === "column" && aliases.has(order.expression.reference) ? order : { ...order, expression: respell(order.expression) }),
8666
+ ...block.groupingSets === void 0 ? {} : { groupingSets: block.groupingSets.map((set) => set.map(respell)) }
8667
+ };
8668
+ return respelled > 0 ? unified : block;
8669
+ }
8670
+ function bareColumnName(reference) {
8671
+ const separator = reference.indexOf(".");
8672
+ return separator === -1 ? reference : reference.slice(separator + 1);
8673
+ }
8674
+ function unifyBlockGroupedReferences(parts) {
8675
+ if (parts.joins.length > 0 || parts.base.table === DUAL_TABLE)
8676
+ return parts;
8677
+ const alias = parts.base.alias;
8678
+ return unifyGroupedReferences(parts, (reference) => {
8679
+ const separator = reference.indexOf(".");
8680
+ if (separator === -1)
8681
+ return `${alias}.${reference}`;
8682
+ return reference.slice(0, separator) === alias ? reference : void 0;
8683
+ });
8684
+ }
8685
+ function unifyPlanGroupedReferences(plan, columnsOf) {
8686
+ if (plan.joins.length === 0 || plan.groupBy.length === 0)
8687
+ return plan;
8688
+ const sources = [plan.base, ...plan.joins];
8689
+ const aliases = new Set(sources.map((source) => source.alias));
8690
+ let columnsBySource;
8691
+ return unifyGroupedReferences(plan, (reference) => {
8692
+ const separator = reference.indexOf(".");
8693
+ if (separator !== -1)
8694
+ return aliases.has(reference.slice(0, separator)) ? reference : void 0;
8695
+ columnsBySource ??= new Map(sources.map((source) => [
8696
+ source.alias,
8697
+ new Set(sourceWildcardColumns(source, columnsOf) ?? [])
8698
+ ]));
8699
+ let owner;
8700
+ for (const [alias, columns] of columnsBySource) {
8701
+ if (!columns.has(reference))
8702
+ continue;
8703
+ if (owner !== void 0)
8704
+ return void 0;
8705
+ owner = alias;
8706
+ }
8707
+ return owner === void 0 ? void 0 : `${owner}.${reference}`;
8708
+ });
8709
+ }
7494
8710
  function desugarFullJoin(parts, nextSequence) {
7495
8711
  const join = parts.joins[0];
7496
8712
  if (parts.joins.length !== 1 || join?.full !== true) {
@@ -7591,6 +8807,7 @@ function assembleSelectBlock(parts, nextSequence) {
7591
8807
  groupingSets: parts.groupingSets.map((set) => resolveGroupByReferences(set, parts.select))
7592
8808
  }
7593
8809
  };
8810
+ parts = unifyBlockGroupedReferences(parts);
7594
8811
  if (parts.joins.some((join) => join.full === true)) {
7595
8812
  return desugarFullJoin(parts, nextSequence);
7596
8813
  }
@@ -7646,18 +8863,6 @@ function assembleSelectBlock(parts, nextSequence) {
7646
8863
  const grouped = parts.groupBy.length > 0 || select.some((item) => hasAggregate(item.expression));
7647
8864
  if (!grouped)
7648
8865
  throw new TypeError("HAVING requires GROUP BY or aggregate functions");
7649
- const groupExpressions = new Set(groupBy.map((expression) => JSON.stringify(expression)));
7650
- for (const predicate of having) {
7651
- for (const side of [predicate.left, predicate.right]) {
7652
- if (hasAggregate(side))
7653
- continue;
7654
- if (expressionColumns(side).length === 0)
7655
- continue;
7656
- if (groupExpressions.has(JSON.stringify(side)))
7657
- continue;
7658
- throw new TypeError("HAVING conditions must use aggregates, literals, or GROUP BY expressions");
7659
- }
7660
- }
7661
8866
  }
7662
8867
  const clauseExpressions = [
7663
8868
  ...predicates.flatMap((predicate) => [predicate.left, predicate.right]),
@@ -8263,8 +9468,11 @@ function assembleOrderByExpressionBlock(parts, nextSequence) {
8263
9468
  orderBy: []
8264
9469
  };
8265
9470
  }
9471
+ function blockHasRowWindow(block) {
9472
+ return block.limit !== void 0 || block.offset !== void 0 || block.limitParameter !== void 0 || block.offsetParameter !== void 0;
9473
+ }
8266
9474
  function transparentProjectionSource(plan) {
8267
- if (plan.joins.length > 0 || plan.predicates.length > 0 || plan.groupBy.length > 0 || plan.having.length > 0 || plan.orderBy.length > 0 || plan.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)) {
8268
9476
  return void 0;
8269
9477
  }
8270
9478
  const inner = plan.base.derived;
@@ -8560,6 +9768,59 @@ function tokenize(sql) {
8560
9768
  index += 1;
8561
9769
  continue;
8562
9770
  }
9771
+ if ((character === "E" || character === "e") && sql[index + 1] === "'") {
9772
+ const start = index;
9773
+ index += 2;
9774
+ let value = "";
9775
+ let closed = false;
9776
+ while (index < sql.length) {
9777
+ const current = sql[index] ?? "";
9778
+ if (current === "'" && sql[index + 1] === "'") {
9779
+ value += "'";
9780
+ index += 2;
9781
+ } else if (current === "'") {
9782
+ index += 1;
9783
+ closed = true;
9784
+ break;
9785
+ } else if (current === "\\") {
9786
+ const next = sql[index + 1] ?? "";
9787
+ const simple = /* @__PURE__ */ new Map([
9788
+ ["n", "\n"],
9789
+ ["t", " "],
9790
+ ["r", "\r"],
9791
+ ["b", "\b"],
9792
+ ["f", "\f"]
9793
+ ]);
9794
+ const escaped = simple.get(next);
9795
+ if (escaped !== void 0) {
9796
+ value += escaped;
9797
+ index += 2;
9798
+ } else if (next === "x" && /[0-9A-Fa-f]/.test(sql[index + 2] ?? "")) {
9799
+ const hex = /^[0-9A-Fa-f]{1,2}/.exec(sql.slice(index + 2, index + 4))?.[0] ?? "";
9800
+ value += String.fromCharCode(Number.parseInt(hex, 16));
9801
+ index += 2 + hex.length;
9802
+ } else if (next === "u" && /^[0-9A-Fa-f]{4}/.test(sql.slice(index + 2, index + 6))) {
9803
+ value += String.fromCharCode(Number.parseInt(sql.slice(index + 2, index + 6), 16));
9804
+ index += 6;
9805
+ } else if (/[0-7]/.test(next)) {
9806
+ const octal = /^[0-7]{1,3}/.exec(sql.slice(index + 1, index + 4))?.[0] ?? "";
9807
+ value += String.fromCharCode(Number.parseInt(octal, 8));
9808
+ index += 1 + octal.length;
9809
+ } else {
9810
+ value += next;
9811
+ index += 2;
9812
+ }
9813
+ } else {
9814
+ value += current;
9815
+ index += 1;
9816
+ }
9817
+ }
9818
+ if (!closed) {
9819
+ throw new SqlCompileError("Unterminated string literal", start, sql.length - start);
9820
+ }
9821
+ push({ kind: "string", text: value, start, end: index });
9822
+ continue;
9823
+ }
8563
9824
  if (/[A-Za-z_]/.test(character)) {
8564
9825
  const start = index++;
8565
9826
  while (index < sql.length && /[A-Za-z0-9_]/.test(sql[index] ?? ""))
@@ -8567,7 +9828,7 @@ function tokenize(sql) {
8567
9828
  push({ kind: "identifier", text: sql.slice(start, index), start, end: index });
8568
9829
  continue;
8569
9830
  }
8570
- if (/\d/.test(character)) {
9831
+ if (/\d/.test(character) || character === "." && /\d/.test(sql[index + 1] ?? "")) {
8571
9832
  const start = index;
8572
9833
  const radix = { x: 16, o: 8, b: 2 }[(sql[index + 1] ?? "").toLowerCase()];
8573
9834
  if (character === "0" && radix !== void 0) {
@@ -8593,9 +9854,10 @@ function tokenize(sql) {
8593
9854
  index += 1;
8594
9855
  }
8595
9856
  }
8596
- const 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;
8597
9859
  if (!validNumericLiteral(text, 10))
8598
- throw new SqlCompileError(`Invalid number: ${text}`, start, index - start);
9860
+ throw new SqlCompileError(`Invalid number: ${spelled}`, start, index - start);
8599
9861
  push({ kind: "number", text: text.replaceAll("_", ""), start, end: index });
8600
9862
  continue;
8601
9863
  }
@@ -8650,6 +9912,18 @@ function tokenize(sql) {
8650
9912
  }
8651
9913
  if (character === "$") {
8652
9914
  const start = index++;
9915
+ const tagMatch = /^\$([A-Za-z_][A-Za-z0-9_]*)?\$/.exec(sql.slice(start));
9916
+ if (tagMatch !== null) {
9917
+ const tag = tagMatch[0];
9918
+ const bodyStart = start + tag.length;
9919
+ const close = sql.indexOf(tag, bodyStart);
9920
+ if (close === -1) {
9921
+ throw new SqlCompileError("Unterminated dollar-quoted string", start, sql.length - start);
9922
+ }
9923
+ push({ kind: "string", text: sql.slice(bodyStart, close), start, end: close + tag.length });
9924
+ index = close + tag.length;
9925
+ continue;
9926
+ }
8653
9927
  while (index < sql.length && /\d/.test(sql[index] ?? ""))
8654
9928
  index += 1;
8655
9929
  const digits = sql.slice(start + 1, index);
@@ -8683,6 +9957,17 @@ function tokenize(sql) {
8683
9957
  index += 3;
8684
9958
  continue;
8685
9959
  }
9960
+ const likeOperator = /^!?~~\*?/.exec(sql.slice(index, index + 4))?.[0];
9961
+ if (likeOperator !== void 0) {
9962
+ push({
9963
+ kind: "operator",
9964
+ text: likeOperator,
9965
+ start: index,
9966
+ end: index + likeOperator.length
9967
+ });
9968
+ index += likeOperator.length;
9969
+ continue;
9970
+ }
8686
9971
  if ([">=", "<=", "!=", "<>", "||", "->"].includes(pair)) {
8687
9972
  push({ kind: "operator", text: pair, start: index, end: index + 2 });
8688
9973
  index += 2;
@@ -8732,11 +10017,14 @@ function validateSqlSource(sql) {
8732
10017
  export {
8733
10018
  DUAL_TABLE,
8734
10019
  annotateAvgArgumentScales,
10020
+ annotateIntegerDivision,
10021
+ annotatePlanIntegerDivision,
8735
10022
  applyWindowFunctions,
8736
10023
  assembleSelectBlock,
8737
10024
  bindPendingSelectShapes,
8738
10025
  bindPlanParameters,
8739
10026
  bindStatementParameters,
10027
+ blockHasRowWindow,
8740
10028
  blockHasSubqueries,
8741
10029
  cachedListMembership,
8742
10030
  childExpressions,
@@ -8763,17 +10051,22 @@ export {
8763
10051
  executeRowQuery,
8764
10052
  expandFtsColumns,
8765
10053
  expandNaturalJoins,
10054
+ expandRowReferences,
8766
10055
  expandSourceColumnAliases,
8767
10056
  expandViewSources,
8768
10057
  expressionAliases,
8769
10058
  expressionColumnNames,
8770
10059
  expressionColumns,
10060
+ extendGroupByWithKeyDependents,
8771
10061
  externalizeQueryResult,
10062
+ foldIdentifierCase,
8772
10063
  forEachBlockExpression,
8773
10064
  forEachNestedBlock,
8774
10065
  hasAggregate,
8775
10066
  inferBlockSchema,
8776
10067
  inferResultColumnDomains,
10068
+ integerQuotient,
10069
+ integerTypedExpression,
8777
10070
  isAggregateCall,
8778
10071
  isDefaultInsertValue,
8779
10072
  isDeferredInsertExpression,
@@ -8788,6 +10081,7 @@ export {
8788
10081
  planHasNaturalJoins,
8789
10082
  planHasPendingSelectShapes,
8790
10083
  planHasSourceColumnAliases,
10084
+ planMayHaveIntegerDivision,
8791
10085
  planReadsBeyondSingleScan,
8792
10086
  planReadsTable,
8793
10087
  planReadsViews,
@@ -8804,6 +10098,7 @@ export {
8804
10098
  subqueryResolutionSteps,
8805
10099
  topLevelFtsMatchConjuncts,
8806
10100
  transparentProjectionSource,
10101
+ unifyPlanGroupedReferences,
8807
10102
  unknownColumnDomains,
8808
10103
  validateDefaultExpression,
8809
10104
  validateLimit,