@minnowdb/core 0.7.10 → 0.8.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.
@@ -1,3 +1,4 @@
1
+ import { encodeQueryIdentity } from "./query-identity.js";
1
2
  import { civilFromDays, copyDate, dateIsoString, dateMilliseconds, daysFromCivil, epochDays, dateUtcDate, dateUtcFullYear, dateUtcMonth, setDateUtcDate, setDateUtcMonth } from "../date-value.js";
2
3
  import { crossJoinPlan } from "../plan/model.js";
3
4
  import { assertWellFormedString, wellFormedUtf8ByteLength } from "../block-format/unicode.js";
@@ -5,13 +6,14 @@ import { MAX_SQL_NESTING_DEPTH, MAX_SQL_PARAMETERS, MAX_SQL_SCALAR_RESULT_CHARAC
5
6
  import { SqlCompileError } from "./errors.js";
6
7
  import { cachedQueryTerms, ftsBm25Row, FtsStatsAccumulator, ftsMatchTruth, renderDocumentValue, tokenize as ftsTokenize, validateFtsQuery } from "./fts.js";
7
8
  import { QueryMemoryContext } from "./memory.js";
8
- import { buildSortKeyColumn, sortKeyIndexes } from "./sort-keys.js";
9
+ import { applyWindowFunctions } from "./windows.js";
10
+ import { applyWindowFunctions as applyWindowFunctions2 } from "./windows.js";
9
11
  import { coerceComparisonOperands, coercedComparable, parseSqlTimestampText, stringArgument, readUntypedText } from "./sql-semantics.js";
10
12
  import { simpleScalarFunctions } from "./sql-functions.js";
11
13
  import { jsonArrowStep, jsonAtPath, jsonConstructor, jsonIsValid, parseJsonPath, jsonDocumentOf } from "./sql-json.js";
12
14
  import { optimizePlan, rewriteBoundCalendarEqualities } from "./optimizer.js";
13
15
  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";
16
+ 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
17
  import { columnarTableFromRows, prepareVectorQuery } from "./vector.js";
16
18
  function unknownColumnDomains(columns) {
17
19
  return columns.map(() => null);
@@ -64,6 +66,9 @@ const scalarFunctionNames = /* @__PURE__ */ new Set([
64
66
  "TO_JSON",
65
67
  "IS_JSON",
66
68
  "ARRAY",
69
+ "MINNOW_ARRAY_AT",
70
+ "MINNOW_ARRAY_FROM_JSON",
71
+ "MINNOW_ARRAY_ELEMENT",
67
72
  "MINNOW_JSON_GET",
68
73
  "MINNOW_JSON_GET_TEXT",
69
74
  "MINNOW_TUPLE_KEY",
@@ -90,6 +95,7 @@ const functionSpellings = /* @__PURE__ */ new Map([
90
95
  ["CHAR_LENGTH", "LENGTH"],
91
96
  ["CHARACTER_LENGTH", "LENGTH"],
92
97
  ["POW", "POWER"],
98
+ ["ARRAY_AGG", "JSON_ARRAYAGG"],
93
99
  ["JSON_AGG", "JSON_ARRAYAGG"],
94
100
  ["JSONB_AGG", "JSON_ARRAYAGG"],
95
101
  ["JSON_BUILD_ARRAY", "JSON_ARRAY"],
@@ -159,6 +165,12 @@ function castValue(value, target) {
159
165
  if (value instanceof Date)
160
166
  return protectedSqlTextValue(dateIsoString(value));
161
167
  }
168
+ if (target === "number-integer" && isExactNumeric(value)) {
169
+ const integer = Number(externalSqlDomainValue(exactNumericRounded(value, 0, "round")));
170
+ if (!Number.isSafeInteger(integer))
171
+ throw new RangeError("Integer cast is outside the exact safe range");
172
+ return integer;
173
+ }
162
174
  if (target === "number" || target === "number-integer") {
163
175
  const external = externalSqlDomainValue(value);
164
176
  let parsed;
@@ -168,6 +180,9 @@ function castValue(value, target) {
168
180
  parsed = external ? 1 : 0;
169
181
  else if (typeof external === "string") {
170
182
  const text = external.trim();
183
+ if (target === "number-integer" && !/^[+-]?\d+$/.test(text)) {
184
+ throw new TypeError(`Cannot cast this string to a number: ${text} (expected integer text)`);
185
+ }
171
186
  const candidate = text === "" ? Number.NaN : Number(text);
172
187
  if (!Number.isFinite(candidate)) {
173
188
  throw new TypeError(`Cannot cast this string to a number: ${text}`);
@@ -197,10 +212,9 @@ function castValue(value, target) {
197
212
  if (typeof value === "string") {
198
213
  const external = externalSqlDomainValue(value);
199
214
  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;
215
+ const parsed = readUntypedText("boolean", text);
216
+ if (typeof parsed === "boolean")
217
+ return parsed;
204
218
  throw new TypeError(`Cannot cast this string to a boolean: ${typeof external === "string" ? external : value}`);
205
219
  }
206
220
  }
@@ -344,6 +358,32 @@ function scalarFunctionValueGeneric(name, values) {
344
358
  }
345
359
  if (name === "ARRAY")
346
360
  return arrayDomainValue(values);
361
+ if (name === "MINNOW_ARRAY_ELEMENT") {
362
+ const value = externalSqlDomainValue(values[0]);
363
+ return typeof value === "string" ? protectedSqlTextValue(value) : value;
364
+ }
365
+ if (name === "MINNOW_ARRAY_FROM_JSON") {
366
+ if (values[0] === null)
367
+ return null;
368
+ return normalizeSqlDomainValue({ kind: "array", element: "TEXT" }, externalSqlDomainValue(values[0]));
369
+ }
370
+ if (name === "MINNOW_ARRAY_AT") {
371
+ if (values[0] === null || values[1] === null)
372
+ return null;
373
+ const source = externalSqlDomainValue(values[0]);
374
+ const index = values[1];
375
+ if (typeof source !== "string" || typeof index !== "number" || !Number.isSafeInteger(index))
376
+ throw new TypeError("Array subscripts require an array and an integer position");
377
+ const array = JSON.parse(source);
378
+ if (!Array.isArray(array))
379
+ throw new TypeError("Array subscripts require an array");
380
+ const selected = array[index - 1] ?? null;
381
+ if (typeof selected === "string")
382
+ return protectedSqlTextValue(selected);
383
+ if (selected === null || typeof selected === "number" || typeof selected === "boolean")
384
+ return selected;
385
+ throw new TypeError("Array subscripts currently require one-dimensional scalar arrays");
386
+ }
347
387
  if (name === "MINNOW_COLLATE")
348
388
  return collatedDomainValue(values[0], values[1]);
349
389
  if (name === "NEXTVAL" || name === "CURRVAL") {
@@ -720,7 +760,7 @@ function dateAddValue(value, months, milliseconds) {
720
760
  setDateUtcDate(shifted, Math.min(day, dateUtcDate(lastDay)));
721
761
  }
722
762
  const result = new Date(dateMilliseconds(shifted) + millisecondCount);
723
- return calendarDate && millisecondCount % 864e5 === 0 ? dateDomainValue(dateIsoString(result).slice(0, 10)) : result;
763
+ return result;
724
764
  }
725
765
  function dateTruncValue(unit, value) {
726
766
  if (typeof unit !== "string" || !dateTruncUnits.has(unit.toLowerCase())) {
@@ -1902,6 +1942,7 @@ function inferBlockSchema(plan, schemas) {
1902
1942
  const wildcardSchema = (source) => (schemas.get(source.table) ?? []).filter((column) => !column.name.startsWith("\0")).map((column) => ({
1903
1943
  name: multipleSources ? `${source.alias}.${column.name}` : column.name,
1904
1944
  type: column.type,
1945
+ ...column.unknown === true ? { unknown: true } : {},
1905
1946
  ...column.integer === true ? { integer: true } : {},
1906
1947
  ...column.sqlDomain === void 0 ? {} : { sqlDomain: column.sqlDomain }
1907
1948
  }));
@@ -1916,9 +1957,9 @@ function inferBlockSchema(plan, schemas) {
1916
1957
  const column = (schemas.get(source.table) ?? []).find(({ name }) => name === parts[1]);
1917
1958
  if (column === void 0)
1918
1959
  throw new TypeError(`Unknown column: ${reference}`);
1919
- return column.type;
1960
+ return column.unknown === true ? "null" : column.type;
1920
1961
  }
1921
- const matches = sources.flatMap((source) => (schemas.get(source.table) ?? []).filter(({ name }) => name === parts[0]).map((column) => column.type));
1962
+ const matches = sources.flatMap((source) => (schemas.get(source.table) ?? []).filter(({ name }) => name === parts[0]).map((column) => column.unknown === true ? "null" : column.type));
1922
1963
  if (matches.length !== 1)
1923
1964
  throw new TypeError(`Ambiguous or missing column: ${reference}`);
1924
1965
  return matches[0] ?? "string";
@@ -2007,6 +2048,16 @@ function inferBlockSchema(plan, schemas) {
2007
2048
  return void 0;
2008
2049
  if (expression.name === "CURRENT_DATE")
2009
2050
  return { kind: "date" };
2051
+ if (expression.name === "ARRAY" || expression.name === "MINNOW_ARRAY_FROM_JSON") {
2052
+ let argument = expression.name === "ARRAY" ? expression.arguments[0] : expression.arguments[0]?.kind === "call" ? expression.arguments[0].arguments[0] : void 0;
2053
+ if (argument?.kind === "call" && argument.name === "MINNOW_ARRAY_ELEMENT")
2054
+ argument = argument.arguments[0];
2055
+ const type = argument === void 0 ? "string" : infer(argument);
2056
+ return {
2057
+ kind: "array",
2058
+ element: type === "number" ? "DOUBLE" : type === "boolean" ? "BOOLEAN" : type === "datetime" ? "TIMESTAMP" : "TEXT"
2059
+ };
2060
+ }
2010
2061
  const simple = simpleScalarFunctions.get(expression.name);
2011
2062
  if (simple?.returns === "date")
2012
2063
  return { kind: "date" };
@@ -2040,11 +2091,8 @@ function inferBlockSchema(plan, schemas) {
2040
2091
  const sides = expression.arguments.map((argument) => [argument, inferDomain(argument)]);
2041
2092
  return sides.some(([, domain]) => domain?.kind === "numeric") ? numericResultDomain("%", sides) : void 0;
2042
2093
  }
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
- }
2094
+ if (expression.name === "DATE_ADD")
2095
+ return void 0;
2048
2096
  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
2097
  return expression.arguments.map(inferDomain).find((domain) => domain !== void 0);
2050
2098
  }
@@ -2143,7 +2191,12 @@ function inferBlockSchema(plan, schemas) {
2143
2191
  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
2192
  return "number";
2145
2193
  }
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") {
2194
+ if (expression.name === "MINNOW_ARRAY_AT") {
2195
+ const argument2 = expression.arguments[0];
2196
+ const domain = argument2 === void 0 ? void 0 : inferDomain(argument2);
2197
+ return domain?.kind === "array" ? createTableTypeNames.get(domain.element.toUpperCase()) ?? "string" : "string";
2198
+ }
2199
+ 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
2200
  return "string";
2148
2201
  }
2149
2202
  if (expression.name === "JSON_EXISTS" || expression.name === "IS_JSON")
@@ -2161,9 +2214,8 @@ function inferBlockSchema(plan, schemas) {
2161
2214
  }
2162
2215
  if (expression.name === "DATE_TRUNC")
2163
2216
  return "datetime";
2164
- if (expression.name === "DATE_ADD") {
2165
- return inferDomain(expression)?.kind === "date" ? "string" : "datetime";
2166
- }
2217
+ if (expression.name === "DATE_ADD")
2218
+ return "datetime";
2167
2219
  if (expression.name === "CURRENT_DATE")
2168
2220
  return "string";
2169
2221
  if (expression.name === "CURRENT_TIMESTAMP")
@@ -2238,9 +2290,8 @@ function inferBlockSchema(plan, schemas) {
2238
2290
  return wildcardSchema(source);
2239
2291
  }
2240
2292
  const type = infer(item.expression);
2241
- if (type === "null") {
2242
- throw new TypeError(`Cannot infer a column type for output ${item.alias}`);
2243
- }
2293
+ if (type === "null")
2294
+ return [{ name: item.alias, type: "string", unknown: true }];
2244
2295
  const integer = integerTypedExpression(item.expression, resolveColumnInteger);
2245
2296
  const sqlDomain = inferDomain(item.expression);
2246
2297
  return [
@@ -2974,22 +3025,29 @@ function planMayHaveIntegerDivision(plan) {
2974
3025
  return state.found;
2975
3026
  }
2976
3027
  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
- }
3028
+ let lowerTables;
3029
+ const caseInsensitiveTables = () => {
3030
+ if (lowerTables !== void 0)
3031
+ return lowerTables;
3032
+ const indexed = /* @__PURE__ */ new Map();
3033
+ for (const name of tables.keys()) {
3034
+ const lowered = name.toLowerCase();
3035
+ const bucket = indexed.get(lowered);
3036
+ if (bucket === void 0)
3037
+ indexed.set(lowered, [name]);
3038
+ else
3039
+ bucket.push(name);
3040
+ }
3041
+ lowerTables = indexed;
3042
+ return indexed;
3043
+ };
2986
3044
  const foldTable = (name) => {
2987
3045
  if (tables.has(name))
2988
3046
  return void 0;
2989
3047
  const lowered = name.toLowerCase();
2990
3048
  if (tables.has(lowered))
2991
3049
  return lowered;
2992
- const candidates = lowerTables.get(lowered);
3050
+ const candidates = caseInsensitiveTables().get(lowered);
2993
3051
  return candidates?.length === 1 ? candidates[0] : void 0;
2994
3052
  };
2995
3053
  const foldColumn = (columns, name) => {
@@ -3127,16 +3185,7 @@ function expandRowReferences(plan, tableColumns) {
3127
3185
  const sourceColumns = (source) => {
3128
3186
  if (source.columnAliases !== void 0)
3129
3187
  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) ?? [];
3188
+ return sourceWildcardColumns(source, tableColumns) ?? [];
3140
3189
  };
3141
3190
  const rewriteBlock = (block) => {
3142
3191
  if (pass.probing && pass.needed)
@@ -3483,6 +3532,26 @@ function exactConstantFold(expression) {
3483
3532
  return folded === void 0 ? void 0 : { value: folded, seeded };
3484
3533
  }
3485
3534
  function resolveExactNumericConstants(expression) {
3535
+ if (expression.kind === "call" && expression.name === "CAST") {
3536
+ const [source, target] = expression.arguments;
3537
+ if (source !== void 0 && target?.kind === "literal" && target.value === "number-integer") {
3538
+ const exact = exactConstantFold(source);
3539
+ if (exact?.seeded === true && exact.value !== null) {
3540
+ return {
3541
+ ...expression,
3542
+ arguments: [
3543
+ {
3544
+ kind: "literal",
3545
+ value: exactNumericValue(exact.value),
3546
+ internalSqlValue: true,
3547
+ sqlDomain: { kind: "numeric" }
3548
+ },
3549
+ target
3550
+ ]
3551
+ };
3552
+ }
3553
+ }
3554
+ }
3486
3555
  let folded;
3487
3556
  try {
3488
3557
  folded = exactConstantFold(expression);
@@ -3578,296 +3647,6 @@ function resolveStatementExactNumericConstants(statement) {
3578
3647
  }
3579
3648
  }
3580
3649
  }
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
3650
  function containsDistinctCount(expression) {
3872
3651
  if (expression.kind === "call" && expression.distinct === true)
3873
3652
  return true;
@@ -3888,75 +3667,6 @@ function containsFtsExpression(expression) {
3888
3667
  return true;
3889
3668
  return childExpressions(expression).some(containsFtsExpression);
3890
3669
  }
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
3670
  function resolveDerivedRowTables(plan, tables, memory) {
3961
3671
  const sources = [plan.base, ...plan.joins];
3962
3672
  const needsDual = sources.some((source) => source.derived === void 0 && source.table === DUAL_TABLE);
@@ -4541,6 +4251,9 @@ function extractDatePart(field, value) {
4541
4251
  }
4542
4252
  if (value === null || value === void 0)
4543
4253
  return null;
4254
+ const domainPart = temporalDomainPart(normalized, value);
4255
+ if (domainPart !== void 0)
4256
+ return domainPart;
4544
4257
  let milliseconds;
4545
4258
  if (value instanceof Date)
4546
4259
  milliseconds = dateMilliseconds(value);
@@ -4563,7 +4276,7 @@ function extractDatePart(field, value) {
4563
4276
  case "minute":
4564
4277
  return Math.floor(timeOfDay / 6e4) % 60;
4565
4278
  case "second":
4566
- return Math.floor(timeOfDay / 1e3) % 60;
4279
+ return timeOfDay % 6e4 / 1e3;
4567
4280
  case "milliseconds":
4568
4281
  return timeOfDay % 6e4;
4569
4282
  case "microseconds":
@@ -4863,7 +4576,7 @@ function isAggregateCall(expression) {
4863
4576
  return expression.kind === "call" && aggregateNames.has(expression.name);
4864
4577
  }
4865
4578
  function groupedExpression(expression, grouped) {
4866
- if (grouped.has(JSON.stringify(expression)))
4579
+ if (grouped.has(encodeQueryIdentity(expression)))
4867
4580
  return true;
4868
4581
  if (expression.kind === "column" || containsFtsExpression(expression))
4869
4582
  return false;
@@ -4876,7 +4589,7 @@ function validateGrouping(plan) {
4876
4589
  const grouped = plan.groupBy.length > 0 || plan.select.some((item) => hasAggregate(item.expression));
4877
4590
  if (!grouped)
4878
4591
  return;
4879
- const groupExpressions = new Set(plan.groupBy.map((expression) => JSON.stringify(expression)));
4592
+ const groupExpressions = new Set(plan.groupBy.map((expression) => encodeQueryIdentity(expression)));
4880
4593
  for (const item of plan.select) {
4881
4594
  if (hasAggregate(item.expression))
4882
4595
  continue;
@@ -6577,7 +6290,7 @@ class Parser {
6577
6290
  const outerOrderBy = [];
6578
6291
  parts.orderBy.forEach((order, index) => {
6579
6292
  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));
6293
+ 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
6294
  let alias = output?.alias;
6582
6295
  if (alias === void 0) {
6583
6296
  alias = `\0distinct_on_order_${String(index)}`;
@@ -7021,6 +6734,47 @@ class Parser {
7021
6734
  }
7022
6735
  if (this.#peek().text === "(") {
7023
6736
  const upper = table.toUpperCase();
6737
+ if (upper === "UNNEST") {
6738
+ this.#expectPunctuation("(");
6739
+ const array = this.#expression();
6740
+ this.#expectPunctuation(")");
6741
+ if (array.kind !== "call" || array.name !== "ARRAY")
6742
+ throw new TypeError("UNNEST currently requires an ARRAY constructor");
6743
+ if (array.arguments.some((argument) => expressionColumns(argument).length > 0 || hasAggregate(argument) || containsWindow(argument)))
6744
+ throw new TypeError("UNNEST ARRAY members cannot refer to table rows");
6745
+ let ordinality = false;
6746
+ if (this.#isKeyword("WITH")) {
6747
+ this.#keyword("WITH");
6748
+ this.#keyword("ORDINALITY");
6749
+ ordinality = true;
6750
+ }
6751
+ const alias2 = this.#sourceAlias() ?? "unnest";
6752
+ const blocks = (array.arguments.length === 0 ? [{ kind: "literal", value: null }] : array.arguments).map((expression, index) => ({
6753
+ sql: "(unnest row)",
6754
+ base: { table: DUAL_TABLE, alias: DUAL_TABLE },
6755
+ joins: [],
6756
+ select: [
6757
+ { expression, alias: alias2 },
6758
+ ...ordinality ? [
6759
+ {
6760
+ expression: { kind: "literal", value: index + 1 },
6761
+ alias: "ordinality"
6762
+ }
6763
+ ] : []
6764
+ ],
6765
+ predicates: [],
6766
+ groupBy: [],
6767
+ having: [],
6768
+ orderBy: [],
6769
+ ...array.arguments.length === 0 ? { limit: 0 } : {}
6770
+ }));
6771
+ const derived = blocks.length === 1 ? blocks[0] : compoundSelectBlock("(unnest)", blocks, blocks.slice(1).map(() => "union all"), { orderBy: [] }, this.nextDerivedSequence);
6772
+ if (derived === void 0)
6773
+ throw new TypeError("UNNEST requires an ARRAY constructor");
6774
+ if (this.#punctuation("("))
6775
+ this.#applyColumnAliases(derived);
6776
+ return this.#derivedSource(derived, alias2);
6777
+ }
7024
6778
  if (upper === "JSON_TABLE") {
7025
6779
  this.#expectPunctuation("(");
7026
6780
  const document = this.#expression();
@@ -7431,8 +7185,8 @@ class Parser {
7431
7185
  if (this.#isKeyword("ESCAPE")) {
7432
7186
  this.#keyword("ESCAPE");
7433
7187
  const token2 = this.#take("string");
7434
- if (Array.from(token2.text).length !== 1) {
7435
- throw new TypeError("LIKE ESCAPE takes a single character");
7188
+ if (Array.from(token2.text).length > 1) {
7189
+ throw new TypeError("LIKE ESCAPE takes at most one character");
7436
7190
  }
7437
7191
  escape = token2.text;
7438
7192
  }
@@ -7508,7 +7262,11 @@ class Parser {
7508
7262
  }
7509
7263
  const operator = this.#peek().text;
7510
7264
  if (operator === "[") {
7511
- throw new TypeError("Array subscripts are not supported");
7265
+ this.#index += 1;
7266
+ const index = this.#expression();
7267
+ this.#expectPunctuation("]");
7268
+ left = { kind: "call", name: "MINNOW_ARRAY_AT", arguments: [left, index] };
7269
+ continue;
7512
7270
  }
7513
7271
  const regexOperator = operator === "~" || operator === "~*" || operator === "!~" || operator === "!~*";
7514
7272
  const precedence = operator === "::" ? 30 : operator === "^" ? 25 : operator === "*" || operator === "/" || operator === "%" ? 20 : operator === "+" || operator === "-" ? 10 : operator === "||" || operator === "->" || operator === "->>" || regexOperator ? 5 : -1;
@@ -8013,7 +7771,7 @@ class Parser {
8013
7771
  return this.#booleanAggregate(upper);
8014
7772
  }
8015
7773
  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") {
7774
+ 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
7775
  throw new TypeError(`Unsupported function: ${identifier}`);
8018
7776
  }
8019
7777
  if (!aggregateNames.has(name) && !scalarFunctionNames.has(name))
@@ -8176,13 +7934,17 @@ class Parser {
8176
7934
  ...frame === void 0 ? {} : { frame }
8177
7935
  };
8178
7936
  }
8179
- return {
7937
+ if (upper === "ARRAY_AGG" && args[0] !== void 0) {
7938
+ args[0] = { kind: "call", name: "MINNOW_ARRAY_ELEMENT", arguments: [args[0]] };
7939
+ }
7940
+ const call = {
8180
7941
  kind: "call",
8181
7942
  name,
8182
7943
  arguments: args,
8183
7944
  ...distinct ? { distinct: true } : {},
8184
7945
  ...aggregateOrderBy === void 0 ? {} : { aggregateOrderBy }
8185
7946
  };
7947
+ return upper === "ARRAY_AGG" ? { kind: "call", name: "MINNOW_ARRAY_FROM_JSON", arguments: [call] } : call;
8186
7948
  }
8187
7949
  let reference = identifier;
8188
7950
  if (this.#punctuation(".")) {
@@ -8346,6 +8108,8 @@ class Parser {
8346
8108
  if (unit === "groups" && orderBy.length === 0) {
8347
8109
  throw new TypeError("GROUPS frames require ORDER BY inside OVER (...)");
8348
8110
  }
8111
+ if (unit === "range" && (start.offset !== void 0 || end.offset !== void 0) && orderBy.length !== 1)
8112
+ throw new TypeError("Offset RANGE frames require exactly one ORDER BY expression");
8349
8113
  let exclude;
8350
8114
  if (this.#isKeyword("EXCLUDE")) {
8351
8115
  this.#keyword("EXCLUDE");
@@ -8391,11 +8155,8 @@ class Parser {
8391
8155
  return { kind: "current-row" };
8392
8156
  }
8393
8157
  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");
8158
+ if (!Number.isFinite(offset) || offset < 0 || unit !== "range" && !Number.isInteger(offset)) {
8159
+ throw new TypeError("Window frame offsets must be non-negative integers, or numeric RANGE offsets");
8399
8160
  }
8400
8161
  if (this.#isKeyword("PRECEDING")) {
8401
8162
  this.#keyword("PRECEDING");
@@ -8468,7 +8229,7 @@ function desugarGroupingSets(parts, nextSequence) {
8468
8229
  }
8469
8230
  }
8470
8231
  }
8471
- const signatureOf = (expression) => JSON.stringify(expression);
8232
+ const signatureOf = (expression) => encodeQueryIdentity(expression);
8472
8233
  const universe = new Set(sets.flat().map(signatureOf));
8473
8234
  const members = sets.map((set) => {
8474
8235
  const setSignatures = new Set(set.map(signatureOf));
@@ -8707,6 +8468,108 @@ function unifyPlanGroupedReferences(plan, columnsOf) {
8707
8468
  return owner === void 0 ? void 0 : `${owner}.${reference}`;
8708
8469
  });
8709
8470
  }
8471
+ function desugarComposedFullJoin(parts, join, nextSequence) {
8472
+ const marker = `(full marker ${String(nextSequence())})`;
8473
+ const plain = {
8474
+ sql: parts.sql,
8475
+ distinct: false,
8476
+ predicates: [],
8477
+ groupBy: [],
8478
+ having: [],
8479
+ orderBy: []
8480
+ };
8481
+ const marked = derivedTableSource(assembleSelectBlock({
8482
+ ...plain,
8483
+ base: parts.base,
8484
+ joins: [],
8485
+ select: [
8486
+ { expression: { kind: "wildcard", table: parts.base.alias }, alias: "*" },
8487
+ { expression: { kind: "literal", value: 1 }, alias: marker }
8488
+ ]
8489
+ }, nextSequence), parts.base.alias, nextSequence);
8490
+ const columns = /* @__PURE__ */ new Map();
8491
+ const gather = (expression) => {
8492
+ if (expression.kind === "column" && !columns.has(expression.reference))
8493
+ columns.set(expression.reference, `(full column ${String(columns.size)})`);
8494
+ for (const child of childExpressions(expression))
8495
+ gather(child);
8496
+ };
8497
+ for (const item of parts.select)
8498
+ gather(item.expression);
8499
+ for (const expression of parts.groupBy)
8500
+ gather(expression);
8501
+ for (const set of parts.groupingSets ?? [])
8502
+ for (const expression of set)
8503
+ gather(expression);
8504
+ for (const predicate of [...parts.predicates, ...parts.having]) {
8505
+ gather(predicate.left);
8506
+ gather(predicate.right);
8507
+ }
8508
+ const outputs = new Set(parts.select.map((item) => item.alias));
8509
+ for (const order of parts.orderBy)
8510
+ if (order.expression.kind !== "column" || !outputs.has(order.expression.reference))
8511
+ gather(order.expression);
8512
+ const select = [...columns].map(([reference, alias]) => ({
8513
+ expression: { kind: "column", reference },
8514
+ alias
8515
+ }));
8516
+ if (select.length === 0)
8517
+ select.push({ expression: { kind: "literal", value: 1 }, alias: "(full row)" });
8518
+ const { full, kind, left, right, on, ...rightSource } = join;
8519
+ void full;
8520
+ void kind;
8521
+ const matched = assembleSelectBlock({
8522
+ ...plain,
8523
+ base: marked,
8524
+ joins: [{ ...rightSource, kind: "left", left, right, ...on === void 0 ? {} : { on } }],
8525
+ select
8526
+ }, nextSequence);
8527
+ const unmatched = assembleSelectBlock({
8528
+ ...plain,
8529
+ base: rightSource,
8530
+ joins: [{ ...marked, kind: "left", left, right, ...on === void 0 ? {} : { on } }],
8531
+ select,
8532
+ predicates: [
8533
+ {
8534
+ left: { kind: "column", reference: `${marked.alias}.${marker}` },
8535
+ operator: "IS NULL",
8536
+ right: { kind: "literal", value: null }
8537
+ }
8538
+ ]
8539
+ }, nextSequence);
8540
+ const combined = compoundSelectBlock(parts.sql, [matched, unmatched], ["union all"], { orderBy: [] }, nextSequence);
8541
+ const source = derivedTableSource(combined, `(full result ${String(nextSequence())})`, nextSequence);
8542
+ const rewrite = (expression) => {
8543
+ if (expression.kind === "column") {
8544
+ const column = columns.get(expression.reference);
8545
+ if (column !== void 0)
8546
+ return { kind: "column", reference: `${source.alias}.${column}` };
8547
+ }
8548
+ return mapChildExpressions(expression, rewrite);
8549
+ };
8550
+ return assembleSelectBlock({
8551
+ ...parts,
8552
+ base: source,
8553
+ joins: [],
8554
+ select: parts.select.map((item) => ({ ...item, expression: rewrite(item.expression) })),
8555
+ predicates: parts.predicates.map((predicate) => ({
8556
+ ...predicate,
8557
+ left: rewrite(predicate.left),
8558
+ right: rewrite(predicate.right)
8559
+ })),
8560
+ groupBy: parts.groupBy.map(rewrite),
8561
+ having: parts.having.map((predicate) => ({
8562
+ ...predicate,
8563
+ left: rewrite(predicate.left),
8564
+ right: rewrite(predicate.right)
8565
+ })),
8566
+ orderBy: parts.orderBy.map((order) => ({
8567
+ ...order,
8568
+ expression: order.expression.kind === "column" && outputs.has(order.expression.reference) ? order.expression : rewrite(order.expression)
8569
+ })),
8570
+ ...parts.groupingSets === void 0 ? {} : { groupingSets: parts.groupingSets.map((set) => set.map(rewrite)) }
8571
+ }, nextSequence);
8572
+ }
8710
8573
  function desugarFullJoin(parts, nextSequence) {
8711
8574
  const join = parts.joins[0];
8712
8575
  if (parts.joins.length !== 1 || join?.full !== true) {
@@ -8716,10 +8579,10 @@ function desugarFullJoin(parts, nextSequence) {
8716
8579
  throw new TypeError("FULL JOIN cannot be combined with SELECT *");
8717
8580
  }
8718
8581
  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");
8582
+ return desugarComposedFullJoin(parts, join, nextSequence);
8720
8583
  }
8721
8584
  if (join.on !== void 0) {
8722
- throw new TypeError("FULL JOIN requires a single equality ON condition");
8585
+ return desugarComposedFullJoin(parts, join, nextSequence);
8723
8586
  }
8724
8587
  const outputAlias = (reference) => {
8725
8588
  const exact = parts.select.find((item) => item.alias === reference || item.expression.kind === "column" && item.expression.reference === reference);
@@ -8775,9 +8638,6 @@ function desugarFullJoin(parts, nextSequence) {
8775
8638
  }
8776
8639
  function assembleSelectBlock(parts, nextSequence) {
8777
8640
  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
8641
  return {
8782
8642
  sql: parts.sql,
8783
8643
  base: parts.base,
@@ -9408,19 +9268,16 @@ function assembleOrderByExpressionBlock(parts, nextSequence) {
9408
9268
  for (const order of parts.orderBy) {
9409
9269
  if (!orderNeedsHiddenColumn(order.expression, parts))
9410
9270
  continue;
9411
- if (containsWindow(order.expression)) {
9412
- throw new TypeError("Window functions are only allowed in the select list");
9413
- }
9414
9271
  if (order.expression.kind === "literal") {
9415
9272
  throw new TypeError("ORDER BY position is outside the select list");
9416
9273
  }
9417
9274
  }
9418
- const selectSignatures = new Map(parts.select.map((item) => [JSON.stringify(item.expression), item.alias]));
9275
+ const selectSignatures = new Map(parts.select.map((item) => [encodeQueryIdentity(item.expression), item.alias]));
9419
9276
  const hiddenItems = [];
9420
9277
  const rewrittenOrder = parts.orderBy.map((order) => {
9421
9278
  if (!orderNeedsHiddenColumn(order.expression, parts))
9422
9279
  return order;
9423
- const existingAlias = selectSignatures.get(JSON.stringify(order.expression));
9280
+ const existingAlias = selectSignatures.get(encodeQueryIdentity(order.expression));
9424
9281
  if (existingAlias !== void 0) {
9425
9282
  return {
9426
9283
  ...order,
@@ -9596,16 +9453,24 @@ function validateOffset(offset) {
9596
9453
  }
9597
9454
  function desugarWindows(sql, base, joins, select, predicates, groupBy, having, tail, nextSequence) {
9598
9455
  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));
9456
+ const groupExpressions = new Set(groupBy.map((expression) => encodeQueryIdentity(expression)));
9457
+ const readableWhenGrouped = (expression) => hasAggregate(expression) || expressionColumns(expression).length === 0 || groupExpressions.has(encodeQueryIdentity(expression));
9601
9458
  const internalAliases = select.map((item, index) => item.alias.includes(".") ? `(window visible ${String(index + 1)})` : item.alias);
9602
9459
  const innerSelect = select.flatMap((item, index) => containsWindow(item.expression) ? [] : [{ ...item, alias: internalAliases[index] ?? item.alias }]);
9603
9460
  const windows = [];
9604
9461
  let hidden = 0;
9462
+ const hiddenExpressions = /* @__PURE__ */ new Map();
9463
+ const repeatable = (expression) => !(expression.kind === "call" && volatileScalarFunctionNames.has(expression.name)) && childExpressions(expression).every(repeatable);
9605
9464
  const hide = (expression) => {
9465
+ const key = repeatable(expression) ? encodeQueryIdentity(expression) : void 0;
9466
+ const existing = key === void 0 ? void 0 : hiddenExpressions.get(key);
9467
+ if (existing !== void 0)
9468
+ return existing;
9606
9469
  hidden += 1;
9607
9470
  const alias = `(window ${String(hidden)})`;
9608
9471
  innerSelect.push({ expression, alias });
9472
+ if (key !== void 0)
9473
+ hiddenExpressions.set(key, alias);
9609
9474
  return alias;
9610
9475
  };
9611
9476
  const registerWindow = (expression, alias) => {
@@ -10019,7 +9884,7 @@ export {
10019
9884
  annotateAvgArgumentScales,
10020
9885
  annotateIntegerDivision,
10021
9886
  annotatePlanIntegerDivision,
10022
- applyWindowFunctions,
9887
+ applyWindowFunctions2 as applyWindowFunctions,
10023
9888
  assembleSelectBlock,
10024
9889
  bindPendingSelectShapes,
10025
9890
  bindPlanParameters,
@@ -10049,6 +9914,7 @@ export {
10049
9914
  evaluateRowExpression,
10050
9915
  executeQuery,
10051
9916
  executeRowQuery,
9917
+ executeRowQueryInternal,
10052
9918
  expandFtsColumns,
10053
9919
  expandNaturalJoins,
10054
9920
  expandRowReferences,