@minnowdb/core 0.7.10 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/engine/artifact-cache.js +3 -2
  2. package/dist/engine/buffered-writer.js +12 -2
  3. package/dist/engine/client.d.ts +9 -0
  4. package/dist/engine/client.js +112 -20
  5. package/dist/engine/database.d.ts +5 -3
  6. package/dist/engine/database.js +2199 -1675
  7. package/dist/engine/errors.d.ts +21 -2
  8. package/dist/engine/errors.js +30 -1
  9. package/dist/engine/index.d.ts +1 -0
  10. package/dist/engine/index.js +4 -0
  11. package/dist/engine/live-accept.js +13 -0
  12. package/dist/engine/live-aggregate.js +264 -0
  13. package/dist/engine/live-patch.d.ts +21 -0
  14. package/dist/engine/live-patch.js +31 -0
  15. package/dist/engine/live.d.ts +14 -0
  16. package/dist/engine/live.js +146 -142
  17. package/dist/engine/optimizer.js +11 -6
  18. package/dist/engine/query-cache.js +19 -15
  19. package/dist/engine/query-generations.js +61 -0
  20. package/dist/engine/query-identity.js +41 -0
  21. package/dist/engine/query.d.ts +7 -13
  22. package/dist/engine/query.js +298 -435
  23. package/dist/engine/result-state.d.ts +7 -0
  24. package/dist/engine/result-state.js +15 -0
  25. package/dist/engine/sql-domains.js +121 -0
  26. package/dist/engine/sql-semantics.js +7 -4
  27. package/dist/engine/typed-live.js +28 -20
  28. package/dist/engine/vector.d.ts +4 -0
  29. package/dist/engine/vector.js +14 -14
  30. package/dist/engine/windows.d.ts +12 -0
  31. package/dist/engine/windows.js +387 -0
  32. package/dist/engine/worker-server.d.ts +1 -1
  33. package/dist/engine/worker-server.js +28 -15
  34. package/dist/engine/write-coordinator.js +54 -0
  35. package/dist/plan/model.d.ts +1 -1
  36. package/dist/storage/indexeddb.js +134 -89
  37. package/dist/storage/opfs/index.d.ts +1 -1
  38. package/dist/storage/opfs/index.js +3 -1
  39. package/dist/storage/opfs/leader.js +20 -9
  40. package/dist/storage/opfs/rpc.js +3 -1
  41. package/dist/storage/opfs/store.d.ts +2 -0
  42. package/dist/storage/opfs/store.js +119 -43
  43. package/dist/storage/toolkit/record-core.js +2 -1
  44. package/dist/storage/types.d.ts +14 -0
  45. package/dist/storage/types.js +21 -0
  46. package/dist/transactions/index.d.ts +3 -0
  47. package/dist/transactions/index.js +4 -1
  48. package/dist/worker-protocol/index.d.ts +1 -1
  49. package/dist/worker-protocol/index.js +1 -1
  50. package/package.json +2 -2
  51. package/postgres-feature-profile.json +6 -1
  52. package/sql-feature-matrix.json +20 -27
  53. package/dist/date-value.d.ts +0 -20
  54. package/dist/engine/artifact-cache.d.ts +0 -29
  55. package/dist/engine/byte-estimates.d.ts +0 -11
  56. package/dist/engine/cancellation.d.ts +0 -2
  57. package/dist/engine/defaults.d.ts +0 -29
  58. package/dist/engine/group-index.d.ts +0 -33
  59. package/dist/engine/join-index.d.ts +0 -10
  60. package/dist/engine/live-equal.d.ts +0 -7
  61. package/dist/engine/point-read.d.ts +0 -59
  62. package/dist/engine/query-cache.d.ts +0 -21
  63. package/dist/engine/result-wire.d.ts +0 -70
  64. package/dist/engine/sort-keys.d.ts +0 -73
  65. package/dist/engine/sql-domains.d.ts +0 -93
  66. package/dist/engine/sql-functions.d.ts +0 -11
  67. package/dist/engine/sql-json.d.ts +0 -40
  68. package/dist/engine/sql-semantics.d.ts +0 -66
  69. package/dist/engine/worker-store-indexeddb.d.ts +0 -2
  70. package/dist/engine/worker-store-memory.d.ts +0 -2
  71. package/dist/engine/worker-store-opfs.d.ts +0 -2
  72. package/dist/engine/write-block-planner.d.ts +0 -19
  73. package/dist/storage/opfs/leader.d.ts +0 -460
  74. package/dist/storage/opfs/rpc.d.ts +0 -82
  75. package/dist/storage/opfs/snapshot-ledger.d.ts +0 -41
@@ -1,3 +1,6 @@
1
+ import { encodeQueryIdentity } from "./query-identity.js";
2
+ import { alreadyExternalResults, copyQueryResultExternalization } from "./result-state.js";
3
+ import { copyQueryResultExternalization as copyQueryResultExternalization2, markQueryResultExternal } from "./result-state.js";
1
4
  import { civilFromDays, copyDate, dateIsoString, dateMilliseconds, daysFromCivil, epochDays, dateUtcDate, dateUtcFullYear, dateUtcMonth, setDateUtcDate, setDateUtcMonth } from "../date-value.js";
2
5
  import { crossJoinPlan } from "../plan/model.js";
3
6
  import { assertWellFormedString, wellFormedUtf8ByteLength } from "../block-format/unicode.js";
@@ -5,13 +8,14 @@ import { MAX_SQL_NESTING_DEPTH, MAX_SQL_PARAMETERS, MAX_SQL_SCALAR_RESULT_CHARAC
5
8
  import { SqlCompileError } from "./errors.js";
6
9
  import { cachedQueryTerms, ftsBm25Row, FtsStatsAccumulator, ftsMatchTruth, renderDocumentValue, tokenize as ftsTokenize, validateFtsQuery } from "./fts.js";
7
10
  import { QueryMemoryContext } from "./memory.js";
8
- import { buildSortKeyColumn, sortKeyIndexes } from "./sort-keys.js";
11
+ import { applyWindowFunctions } from "./windows.js";
12
+ import { applyWindowFunctions as applyWindowFunctions2 } from "./windows.js";
9
13
  import { coerceComparisonOperands, coercedComparable, parseSqlTimestampText, stringArgument, readUntypedText } from "./sql-semantics.js";
10
14
  import { simpleScalarFunctions } from "./sql-functions.js";
11
15
  import { jsonArrowStep, jsonAtPath, jsonConstructor, jsonIsValid, parseJsonPath, jsonDocumentOf } from "./sql-json.js";
12
16
  import { optimizePlan, rewriteBoundCalendarEqualities } from "./optimizer.js";
13
17
  import { compareSqlValues as compareValues, compileLikePattern, compileSimilarPattern, encodeSqlEqualityValue, roundSqlNumber } from "./sql-semantics.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";
18
+ import { arrayDomainValue, boundedJsonText, collatedDomainValue, concatenatedSqlValue, dateDomainValue, exactNumericAsNumber, exactNumericBinary, exactNumericLiteral, decimalScaleOfNumber, exactNumericRounded, exactNumericUnary, exactNumericValue, externalSqlDomainColumnValue, externalSqlDomainValue, intervalDomainValue, temporalDomainPart, isDateDomainValue, isExactNumeric, isSqlDomainValue, jsonDomainValue, normalizeSqlDomainValue, preservedJsonDomainValue, protectedSqlTextValue, timeDomainValue, uuidDomainValue } from "./sql-domains.js";
15
19
  import { columnarTableFromRows, prepareVectorQuery } from "./vector.js";
16
20
  function unknownColumnDomains(columns) {
17
21
  return columns.map(() => null);
@@ -64,6 +68,9 @@ const scalarFunctionNames = /* @__PURE__ */ new Set([
64
68
  "TO_JSON",
65
69
  "IS_JSON",
66
70
  "ARRAY",
71
+ "MINNOW_ARRAY_AT",
72
+ "MINNOW_ARRAY_FROM_JSON",
73
+ "MINNOW_ARRAY_ELEMENT",
67
74
  "MINNOW_JSON_GET",
68
75
  "MINNOW_JSON_GET_TEXT",
69
76
  "MINNOW_TUPLE_KEY",
@@ -90,6 +97,7 @@ const functionSpellings = /* @__PURE__ */ new Map([
90
97
  ["CHAR_LENGTH", "LENGTH"],
91
98
  ["CHARACTER_LENGTH", "LENGTH"],
92
99
  ["POW", "POWER"],
100
+ ["ARRAY_AGG", "JSON_ARRAYAGG"],
93
101
  ["JSON_AGG", "JSON_ARRAYAGG"],
94
102
  ["JSONB_AGG", "JSON_ARRAYAGG"],
95
103
  ["JSON_BUILD_ARRAY", "JSON_ARRAY"],
@@ -159,6 +167,12 @@ function castValue(value, target) {
159
167
  if (value instanceof Date)
160
168
  return protectedSqlTextValue(dateIsoString(value));
161
169
  }
170
+ if (target === "number-integer" && isExactNumeric(value)) {
171
+ const integer = Number(externalSqlDomainValue(exactNumericRounded(value, 0, "round")));
172
+ if (!Number.isSafeInteger(integer))
173
+ throw new RangeError("Integer cast is outside the exact safe range");
174
+ return integer;
175
+ }
162
176
  if (target === "number" || target === "number-integer") {
163
177
  const external = externalSqlDomainValue(value);
164
178
  let parsed;
@@ -168,6 +182,9 @@ function castValue(value, target) {
168
182
  parsed = external ? 1 : 0;
169
183
  else if (typeof external === "string") {
170
184
  const text = external.trim();
185
+ if (target === "number-integer" && !/^[+-]?\d+$/.test(text)) {
186
+ throw new TypeError(`Cannot cast this string to a number: ${text} (expected integer text)`);
187
+ }
171
188
  const candidate = text === "" ? Number.NaN : Number(text);
172
189
  if (!Number.isFinite(candidate)) {
173
190
  throw new TypeError(`Cannot cast this string to a number: ${text}`);
@@ -197,10 +214,9 @@ function castValue(value, target) {
197
214
  if (typeof value === "string") {
198
215
  const external = externalSqlDomainValue(value);
199
216
  const text = typeof external === "string" ? external.trim().toLowerCase() : "";
200
- if (text === "true" || text === "t" || text === "1")
201
- return true;
202
- if (text === "false" || text === "f" || text === "0")
203
- return false;
217
+ const parsed = readUntypedText("boolean", text);
218
+ if (typeof parsed === "boolean")
219
+ return parsed;
204
220
  throw new TypeError(`Cannot cast this string to a boolean: ${typeof external === "string" ? external : value}`);
205
221
  }
206
222
  }
@@ -344,6 +360,32 @@ function scalarFunctionValueGeneric(name, values) {
344
360
  }
345
361
  if (name === "ARRAY")
346
362
  return arrayDomainValue(values);
363
+ if (name === "MINNOW_ARRAY_ELEMENT") {
364
+ const value = externalSqlDomainValue(values[0]);
365
+ return typeof value === "string" ? protectedSqlTextValue(value) : value;
366
+ }
367
+ if (name === "MINNOW_ARRAY_FROM_JSON") {
368
+ if (values[0] === null)
369
+ return null;
370
+ return normalizeSqlDomainValue({ kind: "array", element: "TEXT" }, externalSqlDomainValue(values[0]));
371
+ }
372
+ if (name === "MINNOW_ARRAY_AT") {
373
+ if (values[0] === null || values[1] === null)
374
+ return null;
375
+ const source = externalSqlDomainValue(values[0]);
376
+ const index = values[1];
377
+ if (typeof source !== "string" || typeof index !== "number" || !Number.isSafeInteger(index))
378
+ throw new TypeError("Array subscripts require an array and an integer position");
379
+ const array = JSON.parse(source);
380
+ if (!Array.isArray(array))
381
+ throw new TypeError("Array subscripts require an array");
382
+ const selected = array[index - 1] ?? null;
383
+ if (typeof selected === "string")
384
+ return protectedSqlTextValue(selected);
385
+ if (selected === null || typeof selected === "number" || typeof selected === "boolean")
386
+ return selected;
387
+ throw new TypeError("Array subscripts currently require one-dimensional scalar arrays");
388
+ }
347
389
  if (name === "MINNOW_COLLATE")
348
390
  return collatedDomainValue(values[0], values[1]);
349
391
  if (name === "NEXTVAL" || name === "CURRVAL") {
@@ -720,7 +762,7 @@ function dateAddValue(value, months, milliseconds) {
720
762
  setDateUtcDate(shifted, Math.min(day, dateUtcDate(lastDay)));
721
763
  }
722
764
  const result = new Date(dateMilliseconds(shifted) + millisecondCount);
723
- return calendarDate && millisecondCount % 864e5 === 0 ? dateDomainValue(dateIsoString(result).slice(0, 10)) : result;
765
+ return result;
724
766
  }
725
767
  function dateTruncValue(unit, value) {
726
768
  if (typeof unit !== "string" || !dateTruncUnits.has(unit.toLowerCase())) {
@@ -1902,6 +1944,7 @@ function inferBlockSchema(plan, schemas) {
1902
1944
  const wildcardSchema = (source) => (schemas.get(source.table) ?? []).filter((column) => !column.name.startsWith("\0")).map((column) => ({
1903
1945
  name: multipleSources ? `${source.alias}.${column.name}` : column.name,
1904
1946
  type: column.type,
1947
+ ...column.unknown === true ? { unknown: true } : {},
1905
1948
  ...column.integer === true ? { integer: true } : {},
1906
1949
  ...column.sqlDomain === void 0 ? {} : { sqlDomain: column.sqlDomain }
1907
1950
  }));
@@ -1916,9 +1959,9 @@ function inferBlockSchema(plan, schemas) {
1916
1959
  const column = (schemas.get(source.table) ?? []).find(({ name }) => name === parts[1]);
1917
1960
  if (column === void 0)
1918
1961
  throw new TypeError(`Unknown column: ${reference}`);
1919
- return column.type;
1962
+ return column.unknown === true ? "null" : column.type;
1920
1963
  }
1921
- const matches = sources.flatMap((source) => (schemas.get(source.table) ?? []).filter(({ name }) => name === parts[0]).map((column) => column.type));
1964
+ const matches = sources.flatMap((source) => (schemas.get(source.table) ?? []).filter(({ name }) => name === parts[0]).map((column) => column.unknown === true ? "null" : column.type));
1922
1965
  if (matches.length !== 1)
1923
1966
  throw new TypeError(`Ambiguous or missing column: ${reference}`);
1924
1967
  return matches[0] ?? "string";
@@ -2007,6 +2050,16 @@ function inferBlockSchema(plan, schemas) {
2007
2050
  return void 0;
2008
2051
  if (expression.name === "CURRENT_DATE")
2009
2052
  return { kind: "date" };
2053
+ if (expression.name === "ARRAY" || expression.name === "MINNOW_ARRAY_FROM_JSON") {
2054
+ let argument = expression.name === "ARRAY" ? expression.arguments[0] : expression.arguments[0]?.kind === "call" ? expression.arguments[0].arguments[0] : void 0;
2055
+ if (argument?.kind === "call" && argument.name === "MINNOW_ARRAY_ELEMENT")
2056
+ argument = argument.arguments[0];
2057
+ const type = argument === void 0 ? "string" : infer(argument);
2058
+ return {
2059
+ kind: "array",
2060
+ element: type === "number" ? "DOUBLE" : type === "boolean" ? "BOOLEAN" : type === "datetime" ? "TIMESTAMP" : "TEXT"
2061
+ };
2062
+ }
2010
2063
  const simple = simpleScalarFunctions.get(expression.name);
2011
2064
  if (simple?.returns === "date")
2012
2065
  return { kind: "date" };
@@ -2040,11 +2093,8 @@ function inferBlockSchema(plan, schemas) {
2040
2093
  const sides = expression.arguments.map((argument) => [argument, inferDomain(argument)]);
2041
2094
  return sides.some(([, domain]) => domain?.kind === "numeric") ? numericResultDomain("%", sides) : void 0;
2042
2095
  }
2043
- if (expression.name === "DATE_ADD") {
2044
- const input = inferDomain(expression.arguments[0] ?? { kind: "literal", value: null });
2045
- const milliseconds = expression.arguments[2];
2046
- return input?.kind === "date" && milliseconds?.kind === "literal" && typeof milliseconds.value === "number" && milliseconds.value % 864e5 === 0 ? input : void 0;
2047
- }
2096
+ if (expression.name === "DATE_ADD")
2097
+ return void 0;
2048
2098
  if (expression.name === "SUM" || expression.name === "AVG" || expression.name === "MIN" || expression.name === "MAX" || expression.name === "MINNOW_SINGLE_VALUE" || expression.name === "COALESCE" || expression.name === "NULLIF" || expression.name === "GREATEST" || expression.name === "LEAST") {
2049
2099
  return expression.arguments.map(inferDomain).find((domain) => domain !== void 0);
2050
2100
  }
@@ -2143,7 +2193,12 @@ function inferBlockSchema(plan, schemas) {
2143
2193
  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") {
2144
2194
  return "number";
2145
2195
  }
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") {
2196
+ if (expression.name === "MINNOW_ARRAY_AT") {
2197
+ const argument2 = expression.arguments[0];
2198
+ const domain = argument2 === void 0 ? void 0 : inferDomain(argument2);
2199
+ return domain?.kind === "array" ? createTableTypeNames.get(domain.element.toUpperCase()) ?? "string" : "string";
2200
+ }
2201
+ if (expression.name === "MINNOW_ARRAY_FROM_JSON" || 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") {
2147
2202
  return "string";
2148
2203
  }
2149
2204
  if (expression.name === "JSON_EXISTS" || expression.name === "IS_JSON")
@@ -2161,9 +2216,8 @@ function inferBlockSchema(plan, schemas) {
2161
2216
  }
2162
2217
  if (expression.name === "DATE_TRUNC")
2163
2218
  return "datetime";
2164
- if (expression.name === "DATE_ADD") {
2165
- return inferDomain(expression)?.kind === "date" ? "string" : "datetime";
2166
- }
2219
+ if (expression.name === "DATE_ADD")
2220
+ return "datetime";
2167
2221
  if (expression.name === "CURRENT_DATE")
2168
2222
  return "string";
2169
2223
  if (expression.name === "CURRENT_TIMESTAMP")
@@ -2238,9 +2292,8 @@ function inferBlockSchema(plan, schemas) {
2238
2292
  return wildcardSchema(source);
2239
2293
  }
2240
2294
  const type = infer(item.expression);
2241
- if (type === "null") {
2242
- throw new TypeError(`Cannot infer a column type for output ${item.alias}`);
2243
- }
2295
+ if (type === "null")
2296
+ return [{ name: item.alias, type: "string", unknown: true }];
2244
2297
  const integer = integerTypedExpression(item.expression, resolveColumnInteger);
2245
2298
  const sqlDomain = inferDomain(item.expression);
2246
2299
  return [
@@ -2974,22 +3027,29 @@ function planMayHaveIntegerDivision(plan) {
2974
3027
  return state.found;
2975
3028
  }
2976
3029
  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
- }
3030
+ let lowerTables;
3031
+ const caseInsensitiveTables = () => {
3032
+ if (lowerTables !== void 0)
3033
+ return lowerTables;
3034
+ const indexed = /* @__PURE__ */ new Map();
3035
+ for (const name of tables.keys()) {
3036
+ const lowered = name.toLowerCase();
3037
+ const bucket = indexed.get(lowered);
3038
+ if (bucket === void 0)
3039
+ indexed.set(lowered, [name]);
3040
+ else
3041
+ bucket.push(name);
3042
+ }
3043
+ lowerTables = indexed;
3044
+ return indexed;
3045
+ };
2986
3046
  const foldTable = (name) => {
2987
3047
  if (tables.has(name))
2988
3048
  return void 0;
2989
3049
  const lowered = name.toLowerCase();
2990
3050
  if (tables.has(lowered))
2991
3051
  return lowered;
2992
- const candidates = lowerTables.get(lowered);
3052
+ const candidates = caseInsensitiveTables().get(lowered);
2993
3053
  return candidates?.length === 1 ? candidates[0] : void 0;
2994
3054
  };
2995
3055
  const foldColumn = (columns, name) => {
@@ -3127,16 +3187,7 @@ function expandRowReferences(plan, tableColumns) {
3127
3187
  const sourceColumns = (source) => {
3128
3188
  if (source.columnAliases !== void 0)
3129
3189
  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) ?? [];
3190
+ return sourceWildcardColumns(source, tableColumns) ?? [];
3140
3191
  };
3141
3192
  const rewriteBlock = (block) => {
3142
3193
  if (pass.probing && pass.needed)
@@ -3483,6 +3534,26 @@ function exactConstantFold(expression) {
3483
3534
  return folded === void 0 ? void 0 : { value: folded, seeded };
3484
3535
  }
3485
3536
  function resolveExactNumericConstants(expression) {
3537
+ if (expression.kind === "call" && expression.name === "CAST") {
3538
+ const [source, target] = expression.arguments;
3539
+ if (source !== void 0 && target?.kind === "literal" && target.value === "number-integer") {
3540
+ const exact = exactConstantFold(source);
3541
+ if (exact?.seeded === true && exact.value !== null) {
3542
+ return {
3543
+ ...expression,
3544
+ arguments: [
3545
+ {
3546
+ kind: "literal",
3547
+ value: exactNumericValue(exact.value),
3548
+ internalSqlValue: true,
3549
+ sqlDomain: { kind: "numeric" }
3550
+ },
3551
+ target
3552
+ ]
3553
+ };
3554
+ }
3555
+ }
3556
+ }
3486
3557
  let folded;
3487
3558
  try {
3488
3559
  folded = exactConstantFold(expression);
@@ -3578,296 +3649,6 @@ function resolveStatementExactNumericConstants(statement) {
3578
3649
  }
3579
3650
  }
3580
3651
  }
3581
- function aggregateWindowMembers(window, values, members, avgScale) {
3582
- const present = members.filter((member) => {
3583
- const value = values[member];
3584
- return value !== null && value !== void 0;
3585
- });
3586
- if (window.name === "COUNT") {
3587
- return window.argumentAlias === void 0 ? members.length : present.length;
3588
- }
3589
- if (window.name === "FIRST_VALUE")
3590
- return values[members[0] ?? -1] ?? null;
3591
- if (window.name === "LAST_VALUE")
3592
- return values[members[members.length - 1] ?? -1] ?? null;
3593
- if (window.name === "NTH_VALUE")
3594
- return values[members[(window.offset ?? 1) - 1] ?? -1] ?? null;
3595
- if (present.length === 0)
3596
- return null;
3597
- if (window.name === "SUM" || window.name === "AVG") {
3598
- const exact = present.some((member) => isExactNumeric(values[member]));
3599
- if (exact) {
3600
- let total2 = exactNumericValue(0);
3601
- for (const member of present) {
3602
- const value = values[member];
3603
- if (!isExactNumeric(value)) {
3604
- throw new TypeError("Exact NUMERIC window input mixed with an approximate number");
3605
- }
3606
- const next = exactNumericBinary("+", total2, value);
3607
- if (next === null || next === void 0)
3608
- throw new Error("Exact NUMERIC sum disappeared");
3609
- total2 = next;
3610
- }
3611
- return window.name === "SUM" ? total2 : exactNumericBinary("/", total2, present.length, avgScale);
3612
- }
3613
- const total = present.reduce((sum, member) => sum + numeric(values[member]), 0);
3614
- return window.name === "SUM" ? total : total / present.length;
3615
- }
3616
- let best;
3617
- for (const member of present) {
3618
- const candidate = values[member];
3619
- if (best === void 0 || (window.name === "MIN" ? compareValues(candidate, best) < 0 : compareValues(candidate, best) > 0)) {
3620
- best = candidate;
3621
- }
3622
- }
3623
- return best ?? null;
3624
- }
3625
- function applyAggregateWindow(rows, indexes, window, samePartition, sameOrderKeys, avgScale) {
3626
- const frame = window.frame ?? {
3627
- unit: "range",
3628
- start: { kind: "unbounded-preceding" },
3629
- end: window.orderAliases.length === 0 ? { kind: "unbounded-following" } : { kind: "current-row" }
3630
- };
3631
- let start = 0;
3632
- while (start < indexes.length) {
3633
- let end = start + 1;
3634
- while (end < indexes.length && samePartition(indexes[start] ?? 0, indexes[end] ?? 0)) {
3635
- end += 1;
3636
- }
3637
- applyAggregateWindowPartition(rows, indexes, window, frame, sameOrderKeys, start, end, avgScale);
3638
- start = end;
3639
- }
3640
- }
3641
- function applyAggregateWindowPartition(rows, indexes, window, frame, sameOrderKeys, start, end, avgScale) {
3642
- const size = end - start;
3643
- const peerStart = new Array(size).fill(0);
3644
- const peerEnd = new Array(size).fill(size);
3645
- if (window.orderAliases.length > 0) {
3646
- let groupBegin = 0;
3647
- for (let position = 1; position <= size; position += 1) {
3648
- if (position === size || !sameOrderKeys(indexes[start + groupBegin] ?? 0, indexes[start + position] ?? 0)) {
3649
- for (let member = groupBegin; member < position; member += 1) {
3650
- peerStart[member] = groupBegin;
3651
- peerEnd[member] = position;
3652
- }
3653
- groupBegin = position;
3654
- }
3655
- }
3656
- }
3657
- const values = [];
3658
- const sums = window.name === "SUM" || window.name === "AVG";
3659
- for (let position = 0; position < size; position += 1) {
3660
- const value = window.argumentAlias === void 0 ? void 0 : rows[indexes[start + position] ?? -1]?.[window.argumentAlias] ?? null;
3661
- values.push(value);
3662
- }
3663
- const exactSums = sums && values.some((value) => isExactNumeric(value));
3664
- const prefixNonNull = new Float64Array(size + 1);
3665
- const prefixSum = exactSums ? void 0 : new Float64Array(size + 1);
3666
- const prefixExact = exactSums ? new Array(size + 1) : void 0;
3667
- const exactZero = exactNumericValue(0);
3668
- if (exactZero === null)
3669
- throw new Error("Exact NUMERIC zero disappeared");
3670
- if (prefixExact !== void 0)
3671
- prefixExact[0] = exactZero;
3672
- for (let position = 0; position < size; position += 1) {
3673
- const value = values[position];
3674
- const nonNull = window.argumentAlias !== void 0 && value !== null && value !== void 0;
3675
- prefixNonNull[position + 1] = (prefixNonNull[position] ?? 0) + (nonNull ? 1 : 0);
3676
- if (prefixExact !== void 0) {
3677
- if (nonNull && !isExactNumeric(value)) {
3678
- throw new TypeError("Exact NUMERIC window input mixed with an approximate number");
3679
- }
3680
- const previous = prefixExact[position] ?? exactZero;
3681
- const next = nonNull ? exactNumericBinary("+", previous, value) : previous;
3682
- if (next === null || next === void 0)
3683
- throw new Error("Exact NUMERIC sum disappeared");
3684
- prefixExact[position + 1] = next;
3685
- } else if (prefixSum !== void 0) {
3686
- prefixSum[position + 1] = (prefixSum[position] ?? 0) + (sums && nonNull ? numeric(value) : 0);
3687
- }
3688
- }
3689
- const groupOrdinal = new Array(size).fill(0);
3690
- const groupStarts = [];
3691
- if (frame.unit === "groups") {
3692
- for (let position = 0; position < size; position += 1) {
3693
- if (position === 0 || peerStart[position] !== peerStart[position - 1]) {
3694
- groupStarts.push(peerStart[position] ?? position);
3695
- }
3696
- groupOrdinal[position] = groupStarts.length - 1;
3697
- }
3698
- }
3699
- const groupEdge = (ordinal, isStart) => {
3700
- if (ordinal < 0)
3701
- return isStart ? 0 : 0;
3702
- if (ordinal >= groupStarts.length)
3703
- return size;
3704
- return isStart ? groupStarts[ordinal] ?? 0 : groupStarts[ordinal + 1] ?? size;
3705
- };
3706
- const bound = (edge, position, isStart) => {
3707
- switch (edge.kind) {
3708
- case "unbounded-preceding":
3709
- return 0;
3710
- case "unbounded-following":
3711
- return size;
3712
- case "preceding":
3713
- if (frame.unit === "groups") {
3714
- return groupEdge((groupOrdinal[position] ?? 0) - (edge.offset ?? 0), isStart);
3715
- }
3716
- return position - (edge.offset ?? 0) + (isStart ? 0 : 1);
3717
- case "following":
3718
- if (frame.unit === "groups") {
3719
- return groupEdge((groupOrdinal[position] ?? 0) + (edge.offset ?? 0), isStart);
3720
- }
3721
- return position + (edge.offset ?? 0) + (isStart ? 0 : 1);
3722
- case "current-row":
3723
- if (frame.unit === "range" || frame.unit === "groups") {
3724
- return (isStart ? peerStart[position] : peerEnd[position]) ?? position;
3725
- }
3726
- return position + (isStart ? 0 : 1);
3727
- }
3728
- };
3729
- const excluded = (position) => {
3730
- switch (frame.exclude) {
3731
- case "current-row":
3732
- return { from: position, to: position + 1, keepCurrent: false };
3733
- case "group":
3734
- return {
3735
- from: peerStart[position] ?? position,
3736
- to: peerEnd[position] ?? position + 1,
3737
- keepCurrent: false
3738
- };
3739
- case "ties":
3740
- return {
3741
- from: peerStart[position] ?? position,
3742
- to: peerEnd[position] ?? position + 1,
3743
- keepCurrent: true
3744
- };
3745
- default:
3746
- return { from: 0, to: 0, keepCurrent: true };
3747
- }
3748
- };
3749
- for (let position = 0; position < size; position += 1) {
3750
- const low = Math.max(0, Math.min(size, bound(frame.start, position, true)));
3751
- const high = Math.max(0, Math.min(size, bound(frame.end, position, false)));
3752
- let value;
3753
- if (frame.exclude !== void 0) {
3754
- const skip = excluded(position);
3755
- const members = [];
3756
- for (let member = low; member < high; member += 1) {
3757
- const dropped = member >= skip.from && member < skip.to && !(skip.keepCurrent && member === position);
3758
- if (!dropped)
3759
- members.push(member);
3760
- }
3761
- value = aggregateWindowMembers(window, values, members, avgScale);
3762
- const row2 = rows[indexes[start + position] ?? -1];
3763
- if (row2 !== void 0)
3764
- row2[window.alias] = asQueryValue(value);
3765
- continue;
3766
- }
3767
- if (high <= low) {
3768
- value = window.name === "COUNT" ? 0 : null;
3769
- } else if (window.name === "FIRST_VALUE") {
3770
- value = values[low] ?? null;
3771
- } else if (window.name === "NTH_VALUE") {
3772
- const position2 = low + (window.offset ?? 1) - 1;
3773
- value = position2 < high ? values[position2] ?? null : null;
3774
- } else if (window.name === "LAST_VALUE") {
3775
- value = values[high - 1] ?? null;
3776
- } else if (window.name === "COUNT") {
3777
- value = window.argumentAlias === void 0 ? high - low : (prefixNonNull[high] ?? 0) - (prefixNonNull[low] ?? 0);
3778
- } else if (sums) {
3779
- const nonNull = (prefixNonNull[high] ?? 0) - (prefixNonNull[low] ?? 0);
3780
- if (nonNull === 0) {
3781
- value = null;
3782
- } else if (prefixExact !== void 0) {
3783
- const total = exactNumericBinary("-", prefixExact[high] ?? exactZero, prefixExact[low] ?? exactZero);
3784
- value = window.name === "SUM" ? total : exactNumericBinary("/", total, nonNull, avgScale);
3785
- } else {
3786
- const total = (prefixSum?.[high] ?? 0) - (prefixSum?.[low] ?? 0);
3787
- value = window.name === "SUM" ? total : total / nonNull;
3788
- }
3789
- } else {
3790
- let best;
3791
- for (let member = low; member < high; member += 1) {
3792
- const candidate = values[member];
3793
- if (candidate === null || candidate === void 0)
3794
- continue;
3795
- if (best === void 0 || (window.name === "MIN" ? compareValues(candidate, best) < 0 : compareValues(candidate, best) > 0)) {
3796
- best = candidate;
3797
- }
3798
- }
3799
- value = best ?? null;
3800
- }
3801
- const row = rows[indexes[start + position] ?? -1];
3802
- if (row !== void 0)
3803
- row[window.alias] = asQueryValue(value);
3804
- }
3805
- }
3806
- function applyDistributionWindow(rows, indexes, window, samePartition, sameOrderKeys) {
3807
- let start = 0;
3808
- while (start < indexes.length) {
3809
- let end = start + 1;
3810
- while (end < indexes.length && samePartition(indexes[start] ?? 0, indexes[end] ?? 0)) {
3811
- end += 1;
3812
- }
3813
- const size = end - start;
3814
- let groupBegin = 0;
3815
- const assign = (position, value) => {
3816
- const row = rows[indexes[start + position] ?? -1];
3817
- if (row !== void 0)
3818
- row[window.alias] = value;
3819
- };
3820
- for (let position = 1; position <= size; position += 1) {
3821
- if (position === size || !sameOrderKeys(indexes[start + groupBegin] ?? 0, indexes[start + position] ?? 0)) {
3822
- for (let member = groupBegin; member < position; member += 1) {
3823
- if (window.name === "PERCENT_RANK") {
3824
- assign(member, size === 1 ? 0 : groupBegin / (size - 1));
3825
- } else if (window.name === "CUME_DIST") {
3826
- assign(member, position / size);
3827
- }
3828
- }
3829
- groupBegin = position;
3830
- }
3831
- }
3832
- if (window.name === "NTILE") {
3833
- const buckets = window.offset ?? 1;
3834
- const bucketSize = Math.floor(size / buckets);
3835
- const remainder = size % buckets;
3836
- let position = 0;
3837
- for (let bucket = 1; bucket <= buckets && position < size; bucket += 1) {
3838
- const width = bucketSize + (bucket <= remainder ? 1 : 0);
3839
- for (let member = 0; member < width && position < size; member += 1, position += 1) {
3840
- assign(position, bucket);
3841
- }
3842
- }
3843
- }
3844
- start = end;
3845
- }
3846
- }
3847
- function applyOffsetWindow(rows, indexes, window, samePartition) {
3848
- const offset = window.offset ?? 1;
3849
- const fallback = window.fallback ?? null;
3850
- let start = 0;
3851
- while (start < indexes.length) {
3852
- let end = start + 1;
3853
- while (end < indexes.length && samePartition(indexes[start] ?? 0, indexes[end] ?? 0)) {
3854
- end += 1;
3855
- }
3856
- for (let position = start; position < end; position += 1) {
3857
- const source = window.name === "LAG" ? position - offset : position + offset;
3858
- const row = rows[indexes[position] ?? -1];
3859
- if (row === void 0)
3860
- continue;
3861
- if (source < start || source >= end) {
3862
- row[window.alias] = fallback;
3863
- continue;
3864
- }
3865
- const sourceRow = rows[indexes[source] ?? -1];
3866
- row[window.alias] = window.argumentAlias === void 0 ? fallback : sourceRow?.[window.argumentAlias] ?? null;
3867
- }
3868
- start = end;
3869
- }
3870
- }
3871
3652
  function containsDistinctCount(expression) {
3872
3653
  if (expression.kind === "call" && expression.distinct === true)
3873
3654
  return true;
@@ -3888,75 +3669,6 @@ function containsFtsExpression(expression) {
3888
3669
  return true;
3889
3670
  return childExpressions(expression).some(containsFtsExpression);
3890
3671
  }
3891
- function applyWindowFunctions(result, windows, options = {}) {
3892
- const rows = options.copyRows === false ? result.rows : result.rows.map((row) => ({ ...row }));
3893
- for (const window of windows) {
3894
- const partitionKeys = window.partitionAliases.map((alias) => rows.map((row) => comparable(row[alias] ?? null)));
3895
- const orderKeys = window.orderAliases.map(({ alias }) => rows.map((row) => comparable(row[alias] ?? null)));
3896
- const partitionColumns = partitionKeys.map((keys) => buildSortKeyColumn(keys.length, (index) => keys[index]));
3897
- const orderColumns = orderKeys.map((keys) => buildSortKeyColumn(keys.length, (index) => keys[index]));
3898
- const indexes = sortKeyIndexes(rows.length, [
3899
- ...partitionColumns.map((column) => ({ column, descending: false, nulls: void 0 })),
3900
- ...orderColumns.map((column, index) => {
3901
- const term = window.orderAliases[index];
3902
- return { column, descending: term?.direction === "desc", nulls: term?.nulls };
3903
- })
3904
- ]);
3905
- const samePartition = (left, right) => {
3906
- for (const column of partitionColumns) {
3907
- if (column.compare(left, right) !== 0)
3908
- return false;
3909
- }
3910
- return true;
3911
- };
3912
- const sameOrderKeys = (left, right) => {
3913
- for (const column of orderColumns) {
3914
- if (column.compare(left, right) !== 0)
3915
- return false;
3916
- }
3917
- return true;
3918
- };
3919
- if (window.name === "LAG" || window.name === "LEAD") {
3920
- applyOffsetWindow(rows, indexes, window, samePartition);
3921
- continue;
3922
- }
3923
- if (window.name === "NTILE" || window.name === "PERCENT_RANK" || window.name === "CUME_DIST") {
3924
- applyDistributionWindow(rows, indexes, window, samePartition, sameOrderKeys);
3925
- continue;
3926
- }
3927
- if (window.name !== "ROW_NUMBER" && window.name !== "RANK" && window.name !== "DENSE_RANK") {
3928
- const argumentDomain = window.name === "AVG" && window.argumentAlias !== void 0 ? result.columnDomains[result.columns.indexOf(window.argumentAlias)] : void 0;
3929
- applyAggregateWindow(rows, indexes, window, samePartition, sameOrderKeys, argumentDomain?.kind === "numeric" ? argumentDomain.scale : void 0);
3930
- continue;
3931
- }
3932
- let rowNumber = 0;
3933
- let rank = 0;
3934
- let denseRank = 0;
3935
- for (const [position, index] of indexes.entries()) {
3936
- const previous = position > 0 ? indexes[position - 1] : void 0;
3937
- if (previous === void 0 || !samePartition(previous, index)) {
3938
- rowNumber = 1;
3939
- rank = 1;
3940
- denseRank = 1;
3941
- } else {
3942
- rowNumber += 1;
3943
- if (!sameOrderKeys(previous, index)) {
3944
- rank = rowNumber;
3945
- denseRank += 1;
3946
- }
3947
- }
3948
- const row = rows[index];
3949
- if (row === void 0)
3950
- continue;
3951
- row[window.alias] = window.name === "ROW_NUMBER" ? rowNumber : window.name === "RANK" ? rank : denseRank;
3952
- }
3953
- }
3954
- return {
3955
- columns: [...result.columns, ...windows.map((window) => window.alias)],
3956
- columnDomains: [...result.columnDomains, ...windows.map(() => null)],
3957
- rows
3958
- };
3959
- }
3960
3672
  function resolveDerivedRowTables(plan, tables, memory) {
3961
3673
  const sources = [plan.base, ...plan.joins];
3962
3674
  const needsDual = sources.some((source) => source.derived === void 0 && source.table === DUAL_TABLE);
@@ -4541,6 +4253,9 @@ function extractDatePart(field, value) {
4541
4253
  }
4542
4254
  if (value === null || value === void 0)
4543
4255
  return null;
4256
+ const domainPart = temporalDomainPart(normalized, value);
4257
+ if (domainPart !== void 0)
4258
+ return domainPart;
4544
4259
  let milliseconds;
4545
4260
  if (value instanceof Date)
4546
4261
  milliseconds = dateMilliseconds(value);
@@ -4563,7 +4278,7 @@ function extractDatePart(field, value) {
4563
4278
  case "minute":
4564
4279
  return Math.floor(timeOfDay / 6e4) % 60;
4565
4280
  case "second":
4566
- return Math.floor(timeOfDay / 1e3) % 60;
4281
+ return timeOfDay % 6e4 / 1e3;
4567
4282
  case "milliseconds":
4568
4283
  return timeOfDay % 6e4;
4569
4284
  case "microseconds":
@@ -4863,7 +4578,7 @@ function isAggregateCall(expression) {
4863
4578
  return expression.kind === "call" && aggregateNames.has(expression.name);
4864
4579
  }
4865
4580
  function groupedExpression(expression, grouped) {
4866
- if (grouped.has(JSON.stringify(expression)))
4581
+ if (grouped.has(encodeQueryIdentity(expression)))
4867
4582
  return true;
4868
4583
  if (expression.kind === "column" || containsFtsExpression(expression))
4869
4584
  return false;
@@ -4876,7 +4591,7 @@ function validateGrouping(plan) {
4876
4591
  const grouped = plan.groupBy.length > 0 || plan.select.some((item) => hasAggregate(item.expression));
4877
4592
  if (!grouped)
4878
4593
  return;
4879
- const groupExpressions = new Set(plan.groupBy.map((expression) => JSON.stringify(expression)));
4594
+ const groupExpressions = new Set(plan.groupBy.map((expression) => encodeQueryIdentity(expression)));
4880
4595
  for (const item of plan.select) {
4881
4596
  if (hasAggregate(item.expression))
4882
4597
  continue;
@@ -4961,17 +4676,11 @@ function asQueryValue(value) {
4961
4676
  return null;
4962
4677
  throw new TypeError("Query produced an unsupported value");
4963
4678
  }
4964
- const alreadyExternalResults = /* @__PURE__ */ new WeakSet();
4965
4679
  function markExternalizationState(result, outputNeedsExternalization) {
4966
4680
  if (outputNeedsExternalization === false)
4967
4681
  alreadyExternalResults.add(result);
4968
4682
  return result;
4969
4683
  }
4970
- function copyQueryResultExternalization(source, copy) {
4971
- if (alreadyExternalResults.has(source))
4972
- alreadyExternalResults.add(copy);
4973
- return copy;
4974
- }
4975
4684
  function externalizeQueryResult(result) {
4976
4685
  if (alreadyExternalResults.has(result))
4977
4686
  return result;
@@ -6577,7 +6286,7 @@ class Parser {
6577
6286
  const outerOrderBy = [];
6578
6287
  parts.orderBy.forEach((order, index) => {
6579
6288
  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));
6289
+ const output = expression.kind === "column" && !expression.reference.includes(".") ? parts.select.find((item) => item.alias === expression.reference) : parts.select.find((item) => item.expression.kind !== "wildcard" && encodeQueryIdentity(item.expression) === encodeQueryIdentity(expression));
6581
6290
  let alias = output?.alias;
6582
6291
  if (alias === void 0) {
6583
6292
  alias = `\0distinct_on_order_${String(index)}`;
@@ -7021,6 +6730,47 @@ class Parser {
7021
6730
  }
7022
6731
  if (this.#peek().text === "(") {
7023
6732
  const upper = table.toUpperCase();
6733
+ if (upper === "UNNEST") {
6734
+ this.#expectPunctuation("(");
6735
+ const array = this.#expression();
6736
+ this.#expectPunctuation(")");
6737
+ if (array.kind !== "call" || array.name !== "ARRAY")
6738
+ throw new TypeError("UNNEST currently requires an ARRAY constructor");
6739
+ if (array.arguments.some((argument) => expressionColumns(argument).length > 0 || hasAggregate(argument) || containsWindow(argument)))
6740
+ throw new TypeError("UNNEST ARRAY members cannot refer to table rows");
6741
+ let ordinality = false;
6742
+ if (this.#isKeyword("WITH")) {
6743
+ this.#keyword("WITH");
6744
+ this.#keyword("ORDINALITY");
6745
+ ordinality = true;
6746
+ }
6747
+ const alias2 = this.#sourceAlias() ?? "unnest";
6748
+ const blocks = (array.arguments.length === 0 ? [{ kind: "literal", value: null }] : array.arguments).map((expression, index) => ({
6749
+ sql: "(unnest row)",
6750
+ base: { table: DUAL_TABLE, alias: DUAL_TABLE },
6751
+ joins: [],
6752
+ select: [
6753
+ { expression, alias: alias2 },
6754
+ ...ordinality ? [
6755
+ {
6756
+ expression: { kind: "literal", value: index + 1 },
6757
+ alias: "ordinality"
6758
+ }
6759
+ ] : []
6760
+ ],
6761
+ predicates: [],
6762
+ groupBy: [],
6763
+ having: [],
6764
+ orderBy: [],
6765
+ ...array.arguments.length === 0 ? { limit: 0 } : {}
6766
+ }));
6767
+ const derived = blocks.length === 1 ? blocks[0] : compoundSelectBlock("(unnest)", blocks, blocks.slice(1).map(() => "union all"), { orderBy: [] }, this.nextDerivedSequence);
6768
+ if (derived === void 0)
6769
+ throw new TypeError("UNNEST requires an ARRAY constructor");
6770
+ if (this.#punctuation("("))
6771
+ this.#applyColumnAliases(derived);
6772
+ return this.#derivedSource(derived, alias2);
6773
+ }
7024
6774
  if (upper === "JSON_TABLE") {
7025
6775
  this.#expectPunctuation("(");
7026
6776
  const document = this.#expression();
@@ -7431,8 +7181,8 @@ class Parser {
7431
7181
  if (this.#isKeyword("ESCAPE")) {
7432
7182
  this.#keyword("ESCAPE");
7433
7183
  const token2 = this.#take("string");
7434
- if (Array.from(token2.text).length !== 1) {
7435
- throw new TypeError("LIKE ESCAPE takes a single character");
7184
+ if (Array.from(token2.text).length > 1) {
7185
+ throw new TypeError("LIKE ESCAPE takes at most one character");
7436
7186
  }
7437
7187
  escape = token2.text;
7438
7188
  }
@@ -7508,7 +7258,11 @@ class Parser {
7508
7258
  }
7509
7259
  const operator = this.#peek().text;
7510
7260
  if (operator === "[") {
7511
- throw new TypeError("Array subscripts are not supported");
7261
+ this.#index += 1;
7262
+ const index = this.#expression();
7263
+ this.#expectPunctuation("]");
7264
+ left = { kind: "call", name: "MINNOW_ARRAY_AT", arguments: [left, index] };
7265
+ continue;
7512
7266
  }
7513
7267
  const regexOperator = operator === "~" || operator === "~*" || operator === "!~" || operator === "!~*";
7514
7268
  const precedence = operator === "::" ? 30 : operator === "^" ? 25 : operator === "*" || operator === "/" || operator === "%" ? 20 : operator === "+" || operator === "-" ? 10 : operator === "||" || operator === "->" || operator === "->>" || regexOperator ? 5 : -1;
@@ -8013,7 +7767,7 @@ class Parser {
8013
7767
  return this.#booleanAggregate(upper);
8014
7768
  }
8015
7769
  const name = upper === "ANY_VALUE" ? "MIN" : functionSpellings.get(upper) ?? upper;
8016
- if (name === "MINNOW_TUPLE_KEY" || name === "MINNOW_COLLATE" || name === "MINNOW_SINGLE_VALUE" || name === "MINNOW_JSON_GET" || name === "MINNOW_JSON_GET_TEXT" || name === "MINNOW_REGEX_MATCH") {
7770
+ if (name === "MINNOW_TUPLE_KEY" || name === "MINNOW_COLLATE" || name === "MINNOW_ARRAY_AT" || name === "MINNOW_ARRAY_FROM_JSON" || name === "MINNOW_ARRAY_ELEMENT" || name === "MINNOW_SINGLE_VALUE" || name === "MINNOW_JSON_GET" || name === "MINNOW_JSON_GET_TEXT" || name === "MINNOW_REGEX_MATCH") {
8017
7771
  throw new TypeError(`Unsupported function: ${identifier}`);
8018
7772
  }
8019
7773
  if (!aggregateNames.has(name) && !scalarFunctionNames.has(name))
@@ -8176,13 +7930,17 @@ class Parser {
8176
7930
  ...frame === void 0 ? {} : { frame }
8177
7931
  };
8178
7932
  }
8179
- return {
7933
+ if (upper === "ARRAY_AGG" && args[0] !== void 0) {
7934
+ args[0] = { kind: "call", name: "MINNOW_ARRAY_ELEMENT", arguments: [args[0]] };
7935
+ }
7936
+ const call = {
8180
7937
  kind: "call",
8181
7938
  name,
8182
7939
  arguments: args,
8183
7940
  ...distinct ? { distinct: true } : {},
8184
7941
  ...aggregateOrderBy === void 0 ? {} : { aggregateOrderBy }
8185
7942
  };
7943
+ return upper === "ARRAY_AGG" ? { kind: "call", name: "MINNOW_ARRAY_FROM_JSON", arguments: [call] } : call;
8186
7944
  }
8187
7945
  let reference = identifier;
8188
7946
  if (this.#punctuation(".")) {
@@ -8346,6 +8104,8 @@ class Parser {
8346
8104
  if (unit === "groups" && orderBy.length === 0) {
8347
8105
  throw new TypeError("GROUPS frames require ORDER BY inside OVER (...)");
8348
8106
  }
8107
+ if (unit === "range" && (start.offset !== void 0 || end.offset !== void 0) && orderBy.length !== 1)
8108
+ throw new TypeError("Offset RANGE frames require exactly one ORDER BY expression");
8349
8109
  let exclude;
8350
8110
  if (this.#isKeyword("EXCLUDE")) {
8351
8111
  this.#keyword("EXCLUDE");
@@ -8391,11 +8151,8 @@ class Parser {
8391
8151
  return { kind: "current-row" };
8392
8152
  }
8393
8153
  const offset = Number(this.#take("number").text);
8394
- if (!Number.isInteger(offset) || offset < 0) {
8395
- throw new TypeError("Window frame offsets must be non-negative integers");
8396
- }
8397
- if (unit === "range") {
8398
- throw new TypeError("RANGE frames take only UNBOUNDED and CURRENT ROW bounds; use ROWS");
8154
+ if (!Number.isFinite(offset) || offset < 0 || unit !== "range" && !Number.isInteger(offset)) {
8155
+ throw new TypeError("Window frame offsets must be non-negative integers, or numeric RANGE offsets");
8399
8156
  }
8400
8157
  if (this.#isKeyword("PRECEDING")) {
8401
8158
  this.#keyword("PRECEDING");
@@ -8468,7 +8225,7 @@ function desugarGroupingSets(parts, nextSequence) {
8468
8225
  }
8469
8226
  }
8470
8227
  }
8471
- const signatureOf = (expression) => JSON.stringify(expression);
8228
+ const signatureOf = (expression) => encodeQueryIdentity(expression);
8472
8229
  const universe = new Set(sets.flat().map(signatureOf));
8473
8230
  const members = sets.map((set) => {
8474
8231
  const setSignatures = new Set(set.map(signatureOf));
@@ -8707,6 +8464,108 @@ function unifyPlanGroupedReferences(plan, columnsOf) {
8707
8464
  return owner === void 0 ? void 0 : `${owner}.${reference}`;
8708
8465
  });
8709
8466
  }
8467
+ function desugarComposedFullJoin(parts, join, nextSequence) {
8468
+ const marker = `(full marker ${String(nextSequence())})`;
8469
+ const plain = {
8470
+ sql: parts.sql,
8471
+ distinct: false,
8472
+ predicates: [],
8473
+ groupBy: [],
8474
+ having: [],
8475
+ orderBy: []
8476
+ };
8477
+ const marked = derivedTableSource(assembleSelectBlock({
8478
+ ...plain,
8479
+ base: parts.base,
8480
+ joins: [],
8481
+ select: [
8482
+ { expression: { kind: "wildcard", table: parts.base.alias }, alias: "*" },
8483
+ { expression: { kind: "literal", value: 1 }, alias: marker }
8484
+ ]
8485
+ }, nextSequence), parts.base.alias, nextSequence);
8486
+ const columns = /* @__PURE__ */ new Map();
8487
+ const gather = (expression) => {
8488
+ if (expression.kind === "column" && !columns.has(expression.reference))
8489
+ columns.set(expression.reference, `(full column ${String(columns.size)})`);
8490
+ for (const child of childExpressions(expression))
8491
+ gather(child);
8492
+ };
8493
+ for (const item of parts.select)
8494
+ gather(item.expression);
8495
+ for (const expression of parts.groupBy)
8496
+ gather(expression);
8497
+ for (const set of parts.groupingSets ?? [])
8498
+ for (const expression of set)
8499
+ gather(expression);
8500
+ for (const predicate of [...parts.predicates, ...parts.having]) {
8501
+ gather(predicate.left);
8502
+ gather(predicate.right);
8503
+ }
8504
+ const outputs = new Set(parts.select.map((item) => item.alias));
8505
+ for (const order of parts.orderBy)
8506
+ if (order.expression.kind !== "column" || !outputs.has(order.expression.reference))
8507
+ gather(order.expression);
8508
+ const select = [...columns].map(([reference, alias]) => ({
8509
+ expression: { kind: "column", reference },
8510
+ alias
8511
+ }));
8512
+ if (select.length === 0)
8513
+ select.push({ expression: { kind: "literal", value: 1 }, alias: "(full row)" });
8514
+ const { full, kind, left, right, on, ...rightSource } = join;
8515
+ void full;
8516
+ void kind;
8517
+ const matched = assembleSelectBlock({
8518
+ ...plain,
8519
+ base: marked,
8520
+ joins: [{ ...rightSource, kind: "left", left, right, ...on === void 0 ? {} : { on } }],
8521
+ select
8522
+ }, nextSequence);
8523
+ const unmatched = assembleSelectBlock({
8524
+ ...plain,
8525
+ base: rightSource,
8526
+ joins: [{ ...marked, kind: "left", left, right, ...on === void 0 ? {} : { on } }],
8527
+ select,
8528
+ predicates: [
8529
+ {
8530
+ left: { kind: "column", reference: `${marked.alias}.${marker}` },
8531
+ operator: "IS NULL",
8532
+ right: { kind: "literal", value: null }
8533
+ }
8534
+ ]
8535
+ }, nextSequence);
8536
+ const combined = compoundSelectBlock(parts.sql, [matched, unmatched], ["union all"], { orderBy: [] }, nextSequence);
8537
+ const source = derivedTableSource(combined, `(full result ${String(nextSequence())})`, nextSequence);
8538
+ const rewrite = (expression) => {
8539
+ if (expression.kind === "column") {
8540
+ const column = columns.get(expression.reference);
8541
+ if (column !== void 0)
8542
+ return { kind: "column", reference: `${source.alias}.${column}` };
8543
+ }
8544
+ return mapChildExpressions(expression, rewrite);
8545
+ };
8546
+ return assembleSelectBlock({
8547
+ ...parts,
8548
+ base: source,
8549
+ joins: [],
8550
+ select: parts.select.map((item) => ({ ...item, expression: rewrite(item.expression) })),
8551
+ predicates: parts.predicates.map((predicate) => ({
8552
+ ...predicate,
8553
+ left: rewrite(predicate.left),
8554
+ right: rewrite(predicate.right)
8555
+ })),
8556
+ groupBy: parts.groupBy.map(rewrite),
8557
+ having: parts.having.map((predicate) => ({
8558
+ ...predicate,
8559
+ left: rewrite(predicate.left),
8560
+ right: rewrite(predicate.right)
8561
+ })),
8562
+ orderBy: parts.orderBy.map((order) => ({
8563
+ ...order,
8564
+ expression: order.expression.kind === "column" && outputs.has(order.expression.reference) ? order.expression : rewrite(order.expression)
8565
+ })),
8566
+ ...parts.groupingSets === void 0 ? {} : { groupingSets: parts.groupingSets.map((set) => set.map(rewrite)) }
8567
+ }, nextSequence);
8568
+ }
8710
8569
  function desugarFullJoin(parts, nextSequence) {
8711
8570
  const join = parts.joins[0];
8712
8571
  if (parts.joins.length !== 1 || join?.full !== true) {
@@ -8716,10 +8575,10 @@ function desugarFullJoin(parts, nextSequence) {
8716
8575
  throw new TypeError("FULL JOIN cannot be combined with SELECT *");
8717
8576
  }
8718
8577
  if (parts.groupBy.length > 0 || parts.having.length > 0 || parts.distinct || parts.select.some((item) => hasAggregate(item.expression) || containsWindow(item.expression))) {
8719
- throw new TypeError("FULL JOIN cannot be combined with grouping, DISTINCT, or window functions yet");
8578
+ return desugarComposedFullJoin(parts, join, nextSequence);
8720
8579
  }
8721
8580
  if (join.on !== void 0) {
8722
- throw new TypeError("FULL JOIN requires a single equality ON condition");
8581
+ return desugarComposedFullJoin(parts, join, nextSequence);
8723
8582
  }
8724
8583
  const outputAlias = (reference) => {
8725
8584
  const exact = parts.select.find((item) => item.alias === reference || item.expression.kind === "column" && item.expression.reference === reference);
@@ -8775,9 +8634,6 @@ function desugarFullJoin(parts, nextSequence) {
8775
8634
  }
8776
8635
  function assembleSelectBlock(parts, nextSequence) {
8777
8636
  if (parts.select.some((item) => item.expression.kind === "wildcard")) {
8778
- if (parts.joins.some((join) => join.full === true)) {
8779
- throw new TypeError("FULL JOIN cannot be combined with SELECT *");
8780
- }
8781
8637
  return {
8782
8638
  sql: parts.sql,
8783
8639
  base: parts.base,
@@ -9408,19 +9264,16 @@ function assembleOrderByExpressionBlock(parts, nextSequence) {
9408
9264
  for (const order of parts.orderBy) {
9409
9265
  if (!orderNeedsHiddenColumn(order.expression, parts))
9410
9266
  continue;
9411
- if (containsWindow(order.expression)) {
9412
- throw new TypeError("Window functions are only allowed in the select list");
9413
- }
9414
9267
  if (order.expression.kind === "literal") {
9415
9268
  throw new TypeError("ORDER BY position is outside the select list");
9416
9269
  }
9417
9270
  }
9418
- const selectSignatures = new Map(parts.select.map((item) => [JSON.stringify(item.expression), item.alias]));
9271
+ const selectSignatures = new Map(parts.select.map((item) => [encodeQueryIdentity(item.expression), item.alias]));
9419
9272
  const hiddenItems = [];
9420
9273
  const rewrittenOrder = parts.orderBy.map((order) => {
9421
9274
  if (!orderNeedsHiddenColumn(order.expression, parts))
9422
9275
  return order;
9423
- const existingAlias = selectSignatures.get(JSON.stringify(order.expression));
9276
+ const existingAlias = selectSignatures.get(encodeQueryIdentity(order.expression));
9424
9277
  if (existingAlias !== void 0) {
9425
9278
  return {
9426
9279
  ...order,
@@ -9596,16 +9449,24 @@ function validateOffset(offset) {
9596
9449
  }
9597
9450
  function desugarWindows(sql, base, joins, select, predicates, groupBy, having, tail, nextSequence) {
9598
9451
  const grouped = groupBy.length > 0 || select.some((item) => hasAggregate(item.expression));
9599
- const groupExpressions = new Set(groupBy.map((expression) => JSON.stringify(expression)));
9600
- const readableWhenGrouped = (expression) => hasAggregate(expression) || expressionColumns(expression).length === 0 || groupExpressions.has(JSON.stringify(expression));
9452
+ const groupExpressions = new Set(groupBy.map((expression) => encodeQueryIdentity(expression)));
9453
+ const readableWhenGrouped = (expression) => hasAggregate(expression) || expressionColumns(expression).length === 0 || groupExpressions.has(encodeQueryIdentity(expression));
9601
9454
  const internalAliases = select.map((item, index) => item.alias.includes(".") ? `(window visible ${String(index + 1)})` : item.alias);
9602
9455
  const innerSelect = select.flatMap((item, index) => containsWindow(item.expression) ? [] : [{ ...item, alias: internalAliases[index] ?? item.alias }]);
9603
9456
  const windows = [];
9604
9457
  let hidden = 0;
9458
+ const hiddenExpressions = /* @__PURE__ */ new Map();
9459
+ const repeatable = (expression) => !(expression.kind === "call" && volatileScalarFunctionNames.has(expression.name)) && childExpressions(expression).every(repeatable);
9605
9460
  const hide = (expression) => {
9461
+ const key = repeatable(expression) ? encodeQueryIdentity(expression) : void 0;
9462
+ const existing = key === void 0 ? void 0 : hiddenExpressions.get(key);
9463
+ if (existing !== void 0)
9464
+ return existing;
9606
9465
  hidden += 1;
9607
9466
  const alias = `(window ${String(hidden)})`;
9608
9467
  innerSelect.push({ expression, alias });
9468
+ if (key !== void 0)
9469
+ hiddenExpressions.set(key, alias);
9609
9470
  return alias;
9610
9471
  };
9611
9472
  const registerWindow = (expression, alias) => {
@@ -10019,7 +9880,7 @@ export {
10019
9880
  annotateAvgArgumentScales,
10020
9881
  annotateIntegerDivision,
10021
9882
  annotatePlanIntegerDivision,
10022
- applyWindowFunctions,
9883
+ applyWindowFunctions2 as applyWindowFunctions,
10023
9884
  assembleSelectBlock,
10024
9885
  bindPendingSelectShapes,
10025
9886
  bindPlanParameters,
@@ -10035,7 +9896,7 @@ export {
10035
9896
  compileQuery,
10036
9897
  compileStatement,
10037
9898
  compoundSelectBlock,
10038
- copyQueryResultExternalization,
9899
+ copyQueryResultExternalization2 as copyQueryResultExternalization,
10039
9900
  createPreparedColumnarQuery,
10040
9901
  createPreparedQuery,
10041
9902
  createRecursiveCteState,
@@ -10049,6 +9910,7 @@ export {
10049
9910
  evaluateRowExpression,
10050
9911
  executeQuery,
10051
9912
  executeRowQuery,
9913
+ executeRowQueryInternal,
10052
9914
  expandFtsColumns,
10053
9915
  expandNaturalJoins,
10054
9916
  expandRowReferences,
@@ -10074,6 +9936,7 @@ export {
10074
9936
  likeMatches,
10075
9937
  mapBlockExpressions,
10076
9938
  mapChildExpressions,
9939
+ markQueryResultExternal,
10077
9940
  nullOrder,
10078
9941
  orderOutputName,
10079
9942
  parseQuantified,