lakeql 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { createParquetLake, readParquetMetadata, memoryStore, writePartitionedParquet, broadcastJoin, createOutputManifest, partitionedParquetOutputEntries, fingerprint, writeOutputManifest } from './chunk-MXGEAVHL.js';
3
- import { __commonJS, __toESM, LakeqlError, matches, evaluate, isTimestampValue } from './chunk-TFD5RFKB.js';
2
+ import { createParquetLake, readParquetMetadata, memoryStore, writePartitionedParquet, broadcastJoin, partitionedParquetOutputEntries } from './chunk-6XXXYVXT.js';
3
+ import { createOutputManifest, fingerprint, writeOutputManifest } from './chunk-ZVHJD6R3.js';
4
+ import { __commonJS, __toESM, LakeqlError, matches, evaluate, intervalValue, isTimestampValue, isIntervalValue } from './chunk-4VJQ56HF.js';
4
5
  import { readFile, mkdir, writeFile } from 'fs/promises';
5
6
  import { dirname } from 'path';
6
7
 
@@ -2598,7 +2599,7 @@ var require_pgsql_ast_parser = __commonJS({
2598
2599
  return ret;
2599
2600
  }
2600
2601
  exports2.normalizeInterval = normalizeInterval;
2601
- function intervalToString(value2) {
2602
+ function intervalToString2(value2) {
2602
2603
  var _a, _b, _c;
2603
2604
  value2 = normalizeInterval(value2);
2604
2605
  const ret = [];
@@ -2623,7 +2624,7 @@ var require_pgsql_ast_parser = __commonJS({
2623
2624
  }
2624
2625
  return ret.join(" ");
2625
2626
  }
2626
- exports2.intervalToString = intervalToString;
2627
+ exports2.intervalToString = intervalToString2;
2627
2628
  function num(v) {
2628
2629
  v = Math.abs(v);
2629
2630
  return v < 10 ? "0" + v : v.toString();
@@ -7291,15 +7292,510 @@ var require_pgsql_ast_parser = __commonJS({
7291
7292
 
7292
7293
  // ../sql/dist/index.js
7293
7294
  var import_pgsql_ast_parser = __toESM(require_pgsql_ast_parser(), 1);
7295
+
7296
+ // ../sql/dist/sql-scan.js
7297
+ function findTopLevelKeyword(sql, keyword, start) {
7298
+ let depth = 0;
7299
+ let quote;
7300
+ for (let index = start; index < sql.length; index += 1) {
7301
+ const char = sql[index];
7302
+ const next = sql[index + 1];
7303
+ if (quote !== void 0) {
7304
+ if (char === quote) {
7305
+ if (next === quote) {
7306
+ index += 1;
7307
+ continue;
7308
+ }
7309
+ quote = void 0;
7310
+ }
7311
+ continue;
7312
+ }
7313
+ if (char === "-" && next === "-") {
7314
+ index = skipLineComment(sql, index + 2);
7315
+ continue;
7316
+ }
7317
+ if (char === "/" && next === "*") {
7318
+ index = skipBlockComment(sql, index + 2);
7319
+ continue;
7320
+ }
7321
+ if (char === "'" || char === '"' || char === "`") {
7322
+ quote = char;
7323
+ continue;
7324
+ }
7325
+ if (char === "(")
7326
+ depth += 1;
7327
+ else if (char === ")")
7328
+ depth = Math.max(0, depth - 1);
7329
+ if (depth === 0 && keywordAt(sql, index, keyword))
7330
+ return index;
7331
+ }
7332
+ return -1;
7333
+ }
7334
+ function keywordAt(sql, index, keyword) {
7335
+ if (sql.slice(index, index + keyword.length).toLowerCase() !== keyword)
7336
+ return false;
7337
+ return isBoundary(sql[index - 1]) && isBoundary(sql[index + keyword.length]);
7338
+ }
7339
+ function isBoundary(char) {
7340
+ return char === void 0 || !/[A-Za-z0-9_]/u.test(char);
7341
+ }
7342
+ function skipLineComment(sql, index) {
7343
+ const newline = sql.indexOf("\n", index);
7344
+ return newline === -1 ? sql.length : newline;
7345
+ }
7346
+ function skipBlockComment(sql, index) {
7347
+ const end = sql.indexOf("*/", index);
7348
+ return end === -1 ? sql.length : end + 1;
7349
+ }
7350
+
7351
+ // ../sql/dist/qualify-prepass.js
7352
+ var CLAUSE_KEYWORDS = ["order by", "limit", "offset"];
7353
+ function extractTopLevelQualify(sql) {
7354
+ const qualify = findTopLevelKeyword(sql, "qualify", 0);
7355
+ if (qualify === -1)
7356
+ return { sql };
7357
+ const bodyStart = qualify + "qualify".length;
7358
+ const bodyEnd = nextClauseStart(sql, bodyStart);
7359
+ const predicate = sql.slice(bodyStart, bodyEnd).trim();
7360
+ if (predicate.length === 0)
7361
+ return { sql };
7362
+ const rewritten = `${sql.slice(0, qualify)} ${sql.slice(bodyEnd)}`.trim();
7363
+ return { sql: rewritten, qualify: predicate };
7364
+ }
7365
+ function nextClauseStart(sql, start) {
7366
+ let best = sql.length;
7367
+ for (const keyword of CLAUSE_KEYWORDS) {
7368
+ const index = findTopLevelKeyword(sql, keyword, start);
7369
+ if (index !== -1 && index < best)
7370
+ best = index;
7371
+ }
7372
+ return best;
7373
+ }
7374
+
7375
+ // ../sql/dist/window-prepass.js
7376
+ var FRAME_KEYWORDS = ["rows", "range", "groups"];
7377
+ function extractWindowFrames(sql) {
7378
+ const named = inlineNamedWindows(sql);
7379
+ let out = "";
7380
+ const frames = [];
7381
+ let index = 0;
7382
+ while (index < named.length) {
7383
+ const over = findKeyword(named, "over", index);
7384
+ if (over === -1) {
7385
+ out += named.slice(index);
7386
+ break;
7387
+ }
7388
+ const beforeOver = stripWindowNullTreatment(named.slice(index, over));
7389
+ out += beforeOver.sql;
7390
+ const afterOver = skipWhitespace(named, over + "over".length);
7391
+ if (named[afterOver] !== "(") {
7392
+ out += named.slice(over, afterOver);
7393
+ index = afterOver;
7394
+ frames.push(beforeOver.ignoreNulls === void 0 ? void 0 : { ignoreNulls: beforeOver.ignoreNulls });
7395
+ continue;
7396
+ }
7397
+ const close = matchingParen(named, afterOver);
7398
+ if (close === -1) {
7399
+ out += named.slice(over);
7400
+ break;
7401
+ }
7402
+ const content = named.slice(afterOver + 1, close);
7403
+ const stripped = stripFrame(content);
7404
+ frames.push(stripped.frame === void 0 && beforeOver.ignoreNulls === void 0 ? void 0 : {
7405
+ ...stripped.frame === void 0 ? {} : { text: stripped.frame },
7406
+ ...beforeOver.ignoreNulls === void 0 ? {} : { ignoreNulls: beforeOver.ignoreNulls }
7407
+ });
7408
+ out += `${named.slice(over, afterOver + 1)}${stripped.content})`;
7409
+ index = close + 1;
7410
+ }
7411
+ return { sql: out, frames };
7412
+ }
7413
+ function inlineNamedWindows(sql) {
7414
+ const fromKeyword = findTopLevelKeyword(sql, "from", 0);
7415
+ const windowKeyword = findTopLevelKeyword(sql, "window", fromKeyword === -1 ? 0 : fromKeyword);
7416
+ if (windowKeyword === -1)
7417
+ return rewriteNamedWindowRefs(sql, /* @__PURE__ */ new Map());
7418
+ const definitionsStart = skipWhitespace(sql, windowKeyword + "window".length);
7419
+ const definitionsEnd = findNamedWindowClauseEnd(sql, definitionsStart);
7420
+ const definitions = parseNamedWindowDefinitions(sql.slice(definitionsStart, definitionsEnd));
7421
+ const withoutClause = `${sql.slice(0, windowKeyword)} ${sql.slice(definitionsEnd)}`;
7422
+ return rewriteNamedWindowRefs(withoutClause, resolveNamedWindows(definitions));
7423
+ }
7424
+ function parseNamedWindowDefinitions(sql) {
7425
+ const definitions = /* @__PURE__ */ new Map();
7426
+ let index = 0;
7427
+ while (index < sql.length) {
7428
+ index = skipWhitespace(sql, index);
7429
+ if (index >= sql.length)
7430
+ break;
7431
+ const name = readIdentifier(sql, index);
7432
+ if (name === void 0)
7433
+ break;
7434
+ index = skipWhitespace(sql, name.end);
7435
+ if (!keywordAt(sql, index, "as"))
7436
+ break;
7437
+ index = skipWhitespace(sql, index + "as".length);
7438
+ if (sql[index] !== "(")
7439
+ break;
7440
+ const close = matchingParen(sql, index);
7441
+ if (close === -1)
7442
+ break;
7443
+ definitions.set(name.name, sql.slice(index + 1, close).trim());
7444
+ index = skipWhitespace(sql, close + 1);
7445
+ if (sql[index] !== ",")
7446
+ break;
7447
+ index += 1;
7448
+ }
7449
+ return definitions;
7450
+ }
7451
+ function resolveNamedWindows(definitions) {
7452
+ const resolved = /* @__PURE__ */ new Map();
7453
+ const resolve = (name, stack) => {
7454
+ const cached = resolved.get(name);
7455
+ if (cached !== void 0)
7456
+ return cached;
7457
+ const definition = definitions.get(name);
7458
+ if (definition === void 0)
7459
+ return void 0;
7460
+ if (stack.has(name))
7461
+ return definition;
7462
+ const base = readIdentifier(definition, skipWhitespace(definition, 0));
7463
+ if (base !== void 0) {
7464
+ const parent = resolve(base.name, /* @__PURE__ */ new Set([...stack, name]));
7465
+ if (parent !== void 0) {
7466
+ const merged = `${parent} ${definition.slice(base.end).trim()}`.trim();
7467
+ resolved.set(name, merged);
7468
+ return merged;
7469
+ }
7470
+ }
7471
+ resolved.set(name, definition);
7472
+ return definition;
7473
+ };
7474
+ for (const name of definitions.keys())
7475
+ resolve(name, /* @__PURE__ */ new Set());
7476
+ return resolved;
7477
+ }
7478
+ function rewriteNamedWindowRefs(sql, definitions) {
7479
+ if (definitions.size === 0)
7480
+ return sql;
7481
+ let out = "";
7482
+ let index = 0;
7483
+ while (index < sql.length) {
7484
+ const over = findKeyword(sql, "over", index);
7485
+ if (over === -1) {
7486
+ out += sql.slice(index);
7487
+ break;
7488
+ }
7489
+ out += sql.slice(index, over);
7490
+ const afterOver = skipWhitespace(sql, over + "over".length);
7491
+ if (sql[afterOver] === "(") {
7492
+ const close = matchingParen(sql, afterOver);
7493
+ if (close === -1) {
7494
+ out += sql.slice(over);
7495
+ break;
7496
+ }
7497
+ const content = sql.slice(afterOver + 1, close).trim();
7498
+ const name2 = readIdentifier(content, 0);
7499
+ if (name2 !== void 0 && definitions.has(name2.name)) {
7500
+ const rest = content.slice(name2.end).trim();
7501
+ out += `over (${definitions.get(name2.name)}${rest.length === 0 ? "" : ` ${rest}`})`;
7502
+ } else {
7503
+ out += sql.slice(over, close + 1);
7504
+ }
7505
+ index = close + 1;
7506
+ continue;
7507
+ }
7508
+ const name = readIdentifier(sql, afterOver);
7509
+ if (name !== void 0 && definitions.has(name.name)) {
7510
+ out += `over (${definitions.get(name.name)})`;
7511
+ index = name.end;
7512
+ continue;
7513
+ }
7514
+ out += sql.slice(over, afterOver);
7515
+ index = afterOver;
7516
+ }
7517
+ return out;
7518
+ }
7519
+ function stripWindowNullTreatment(sql) {
7520
+ const suffix = /(\s+)(ignore|respect)\s+nulls\s*$/iu.exec(sql);
7521
+ if (suffix !== null && suffix[2] !== void 0) {
7522
+ return {
7523
+ sql: sql.slice(0, suffix.index) + (suffix[1] ?? ""),
7524
+ ignoreNulls: suffix[2].toLowerCase() === "ignore"
7525
+ };
7526
+ }
7527
+ const close = previousNonWhitespace(sql, sql.length - 1);
7528
+ if (close === -1 || sql[close] !== ")")
7529
+ return { sql };
7530
+ const open = matchingOpenParen(sql, close);
7531
+ if (open === -1)
7532
+ return { sql };
7533
+ const content = sql.slice(open + 1, close);
7534
+ const stripped = stripTopLevelNullTreatment(content);
7535
+ if (stripped.ignoreNulls === void 0)
7536
+ return { sql };
7537
+ return {
7538
+ sql: `${sql.slice(0, open + 1)}${stripped.sql}${sql.slice(close)}`,
7539
+ ignoreNulls: stripped.ignoreNulls
7540
+ };
7541
+ }
7542
+ function stripTopLevelNullTreatment(sql) {
7543
+ let depth = 0;
7544
+ let quote;
7545
+ for (let index = 0; index < sql.length; index += 1) {
7546
+ const char = sql[index];
7547
+ const next = sql[index + 1];
7548
+ if (quote !== void 0) {
7549
+ if (char === quote) {
7550
+ if (next === quote) {
7551
+ index += 1;
7552
+ continue;
7553
+ }
7554
+ quote = void 0;
7555
+ }
7556
+ continue;
7557
+ }
7558
+ if (char === "'" || char === '"' || char === "`") {
7559
+ quote = char;
7560
+ continue;
7561
+ }
7562
+ if (char === "(")
7563
+ depth += 1;
7564
+ else if (char === ")")
7565
+ depth = Math.max(0, depth - 1);
7566
+ if (depth !== 0)
7567
+ continue;
7568
+ const match = /^(ignore|respect)\s+nulls\b/iu.exec(sql.slice(index));
7569
+ if (match === null || match[1] === void 0 || !isBoundary(sql[index - 1]))
7570
+ continue;
7571
+ const start = trimLeftWhitespace(sql, index);
7572
+ const end = trimRightWhitespace(sql, index + match[0].length);
7573
+ return {
7574
+ sql: `${sql.slice(0, start)}${sql.slice(end)}`.replace(/\s+,/u, ","),
7575
+ ignoreNulls: match[1].toLowerCase() === "ignore"
7576
+ };
7577
+ }
7578
+ return { sql };
7579
+ }
7580
+ function stripFrame(content) {
7581
+ const start = findFrameStart(content);
7582
+ if (start === -1)
7583
+ return { content };
7584
+ const frame = content.slice(start).trim();
7585
+ return { content: trimTrailingHorizontalWhitespace(content.slice(0, start)), frame };
7586
+ }
7587
+ function trimTrailingHorizontalWhitespace(value2) {
7588
+ return value2.replace(/[ \t\f\v]+$/u, "");
7589
+ }
7590
+ function findFrameStart(content) {
7591
+ let depth = 0;
7592
+ let quote;
7593
+ for (let index = 0; index < content.length; index += 1) {
7594
+ const char = content[index];
7595
+ const next = content[index + 1];
7596
+ if (quote !== void 0) {
7597
+ if (char === quote) {
7598
+ if (next === quote) {
7599
+ index += 1;
7600
+ continue;
7601
+ }
7602
+ quote = void 0;
7603
+ }
7604
+ continue;
7605
+ }
7606
+ if (char === "-" && next === "-") {
7607
+ index = skipLineComment(content, index + 2);
7608
+ continue;
7609
+ }
7610
+ if (char === "/" && next === "*") {
7611
+ index = skipBlockComment(content, index + 2);
7612
+ continue;
7613
+ }
7614
+ if (char === "'" || char === '"' || char === "`") {
7615
+ quote = char;
7616
+ continue;
7617
+ }
7618
+ if (char === "(")
7619
+ depth += 1;
7620
+ else if (char === ")")
7621
+ depth = Math.max(0, depth - 1);
7622
+ if (depth === 0 && FRAME_KEYWORDS.some((keyword) => keywordAt(content, index, keyword))) {
7623
+ return index;
7624
+ }
7625
+ }
7626
+ return -1;
7627
+ }
7628
+ function findKeyword(sql, keyword, start) {
7629
+ let quote;
7630
+ for (let index = start; index < sql.length; index += 1) {
7631
+ const char = sql[index];
7632
+ const next = sql[index + 1];
7633
+ if (quote !== void 0) {
7634
+ if (char === quote) {
7635
+ if (next === quote) {
7636
+ index += 1;
7637
+ continue;
7638
+ }
7639
+ quote = void 0;
7640
+ }
7641
+ continue;
7642
+ }
7643
+ if (char === "-" && next === "-") {
7644
+ index = skipLineComment(sql, index + 2);
7645
+ continue;
7646
+ }
7647
+ if (char === "/" && next === "*") {
7648
+ index = skipBlockComment(sql, index + 2);
7649
+ continue;
7650
+ }
7651
+ if (char === "'" || char === '"' || char === "`") {
7652
+ quote = char;
7653
+ continue;
7654
+ }
7655
+ if (keywordAt(sql, index, keyword))
7656
+ return index;
7657
+ }
7658
+ return -1;
7659
+ }
7660
+ function findNamedWindowClauseEnd(sql, start) {
7661
+ const candidates = ["order", "limit", "offset", "fetch", "union", "qualify"];
7662
+ let end = sql.length;
7663
+ for (const keyword of candidates) {
7664
+ const index = findTopLevelKeyword(sql, keyword, start);
7665
+ if (index !== -1 && index < end)
7666
+ end = index;
7667
+ }
7668
+ return end;
7669
+ }
7670
+ function matchingParen(sql, open) {
7671
+ let depth = 0;
7672
+ let quote;
7673
+ for (let index = open; index < sql.length; index += 1) {
7674
+ const char = sql[index];
7675
+ const next = sql[index + 1];
7676
+ if (quote !== void 0) {
7677
+ if (char === quote) {
7678
+ if (next === quote) {
7679
+ index += 1;
7680
+ continue;
7681
+ }
7682
+ quote = void 0;
7683
+ }
7684
+ continue;
7685
+ }
7686
+ if (char === "-" && next === "-") {
7687
+ index = skipLineComment(sql, index + 2);
7688
+ continue;
7689
+ }
7690
+ if (char === "/" && next === "*") {
7691
+ index = skipBlockComment(sql, index + 2);
7692
+ continue;
7693
+ }
7694
+ if (char === "'" || char === '"' || char === "`") {
7695
+ quote = char;
7696
+ continue;
7697
+ }
7698
+ if (char === "(")
7699
+ depth += 1;
7700
+ else if (char === ")") {
7701
+ depth -= 1;
7702
+ if (depth === 0)
7703
+ return index;
7704
+ }
7705
+ }
7706
+ return -1;
7707
+ }
7708
+ function matchingOpenParen(sql, close) {
7709
+ let depth = 0;
7710
+ let quote;
7711
+ for (let index = close; index >= 0; index -= 1) {
7712
+ const char = sql[index];
7713
+ const previous = sql[index - 1];
7714
+ if (quote !== void 0) {
7715
+ if (char === quote)
7716
+ quote = void 0;
7717
+ continue;
7718
+ }
7719
+ if (char === "'" || char === '"' || char === "`") {
7720
+ if (previous === char) {
7721
+ index -= 1;
7722
+ continue;
7723
+ }
7724
+ quote = char;
7725
+ continue;
7726
+ }
7727
+ if (char === ")")
7728
+ depth += 1;
7729
+ else if (char === "(") {
7730
+ depth -= 1;
7731
+ if (depth === 0)
7732
+ return index;
7733
+ }
7734
+ }
7735
+ return -1;
7736
+ }
7737
+ function skipWhitespace(sql, index) {
7738
+ let cursor = index;
7739
+ while (/\s/u.test(sql[cursor] ?? ""))
7740
+ cursor += 1;
7741
+ return cursor;
7742
+ }
7743
+ function readIdentifier(sql, index) {
7744
+ const char = sql[index];
7745
+ if (char === '"') {
7746
+ let cursor = index + 1;
7747
+ let name = "";
7748
+ while (cursor < sql.length) {
7749
+ const current = sql[cursor];
7750
+ if (current === '"' && sql[cursor + 1] === '"') {
7751
+ name += '"';
7752
+ cursor += 2;
7753
+ continue;
7754
+ }
7755
+ if (current === '"')
7756
+ return { name, end: cursor + 1 };
7757
+ name += current ?? "";
7758
+ cursor += 1;
7759
+ }
7760
+ return void 0;
7761
+ }
7762
+ const match = /^[A-Za-z_][A-Za-z0-9_]*/u.exec(sql.slice(index));
7763
+ if (match === null)
7764
+ return void 0;
7765
+ return { name: match[0], end: index + match[0].length };
7766
+ }
7767
+ function previousNonWhitespace(sql, index) {
7768
+ let cursor = index;
7769
+ while (cursor >= 0 && /\s/u.test(sql[cursor] ?? ""))
7770
+ cursor -= 1;
7771
+ return cursor;
7772
+ }
7773
+ function trimLeftWhitespace(sql, index) {
7774
+ let cursor = index;
7775
+ while (cursor > 0 && /\s/u.test(sql[cursor - 1] ?? ""))
7776
+ cursor -= 1;
7777
+ return cursor;
7778
+ }
7779
+ function trimRightWhitespace(sql, index) {
7780
+ let cursor = index;
7781
+ while (cursor < sql.length && /\s/u.test(sql[cursor] ?? ""))
7782
+ cursor += 1;
7783
+ return cursor;
7784
+ }
7785
+
7786
+ // ../sql/dist/index.js
7294
7787
  var MAX_SQL_LENGTH = 128e3;
7295
7788
  var MAX_AST_DEPTH = 128;
7789
+ var PROTOTYPE_MUTATION_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
7296
7790
  function parseSql(sql, options = {}) {
7297
7791
  if (sql.length > MAX_SQL_LENGTH) {
7298
7792
  throwParse(`SQL input length exceeds ${MAX_SQL_LENGTH}`);
7299
7793
  }
7794
+ const windowPrepass = extractWindowFrames(sql);
7795
+ const prepass = extractTopLevelQualify(windowPrepass.sql);
7300
7796
  let statements;
7301
7797
  try {
7302
- statements = (0, import_pgsql_ast_parser.parse)(sql);
7798
+ statements = (0, import_pgsql_ast_parser.parse)(prepass.sql);
7303
7799
  } catch (error) {
7304
7800
  if (error instanceof Error)
7305
7801
  throwParse(error.message);
@@ -7310,11 +7806,14 @@ function parseSql(sql, options = {}) {
7310
7806
  const statement = asNode(statements[0], "statement");
7311
7807
  assertAstDepth(statement);
7312
7808
  const context = newSqlParseContext(options);
7809
+ context.windowFrames = windowPrepass.frames;
7810
+ if (prepass.qualify !== void 0)
7811
+ context.qualifySql = prepass.qualify;
7313
7812
  if (statement.type === "with")
7314
7813
  return withStatementToAst(statement, context);
7315
7814
  if (statement.type !== "select")
7316
7815
  throwUnsupported("Only SELECT statements are supported");
7317
- return selectStatementToAst(statement, context);
7816
+ return selectStatementToAst(statement, context, context.qualifySql);
7318
7817
  }
7319
7818
  function parseSqlStatement(sql, options = {}) {
7320
7819
  const describe = parseDescribeStatement(sql);
@@ -7353,7 +7852,7 @@ function withStatementToAst(statement, context) {
7353
7852
  const outer = asNode(statement.in, "CTE outer query");
7354
7853
  if (outer.type !== "select")
7355
7854
  throwUnsupported("Only SELECT after WITH is supported");
7356
- const ast = selectStatementToAst(outer, context);
7855
+ const ast = selectStatementToAst(outer, context, context.qualifySql);
7357
7856
  ast.cte = { name, query: cteQuery };
7358
7857
  return ast;
7359
7858
  }
@@ -7362,7 +7861,26 @@ function validateCteQuery(ast) {
7362
7861
  throwUnsupported("Nested CTE joins and subqueries are not supported");
7363
7862
  }
7364
7863
  }
7365
- function selectStatementToAst(statement, context = newSqlParseContext()) {
7864
+ function selectStatementToAst(statement, context = newSqlParseContext(), qualifySql) {
7865
+ const previousWindows = context.windows;
7866
+ const previousNextWindowId = context.nextWindowId;
7867
+ const previousQualifySql = context.qualifySql;
7868
+ context.windows = {};
7869
+ context.nextWindowId = 0;
7870
+ if (qualifySql === void 0)
7871
+ delete context.qualifySql;
7872
+ else
7873
+ context.qualifySql = qualifySql;
7874
+ const ast = selectStatementToAstInScope(statement, context);
7875
+ context.windows = previousWindows;
7876
+ context.nextWindowId = previousNextWindowId;
7877
+ if (previousQualifySql === void 0)
7878
+ delete context.qualifySql;
7879
+ else
7880
+ context.qualifySql = previousQualifySql;
7881
+ return ast;
7882
+ }
7883
+ function selectStatementToAstInScope(statement, context) {
7366
7884
  rejectPresent(statement, "with", "CTEs are not supported");
7367
7885
  rejectPresent(statement, "windows", "Window functions are not supported");
7368
7886
  const from = optionalArray(statement.from);
@@ -7389,6 +7907,7 @@ function selectStatementToAst(statement, context = newSqlParseContext()) {
7389
7907
  const select = [];
7390
7908
  const projections = {};
7391
7909
  const aggregates = {};
7910
+ const outputAliases = /* @__PURE__ */ new Set();
7392
7911
  for (const column of columns) {
7393
7912
  const item = asNode(column, "select item");
7394
7913
  const expr = asNode(item.expr, "select expression");
@@ -7398,14 +7917,24 @@ function selectStatementToAst(statement, context = newSqlParseContext()) {
7398
7917
  select.push("*");
7399
7918
  continue;
7400
7919
  }
7920
+ if (expr.type === "call" && hasWindowOver(expr)) {
7921
+ const alias2 = aliasName(item.alias) ?? syntheticWindowAlias(context);
7922
+ ensureUniqueOutputAlias(alias2, outputAliases);
7923
+ context.windows[alias2] = windowCallToExpr(expr, scope, context);
7924
+ select.push(alias2);
7925
+ continue;
7926
+ }
7401
7927
  if (expr.type === "call" && isAggregateCall(expr)) {
7402
7928
  const alias2 = aliasName(item.alias) ?? functionName(expr.function);
7929
+ ensureUniqueOutputAlias(alias2, outputAliases);
7403
7930
  aggregates[alias2] = aggregateCallToSpec(expr, scope, context);
7404
7931
  continue;
7405
7932
  }
7406
7933
  if (expr.type === "ref") {
7407
7934
  const name = scope.refName(expr);
7408
7935
  const alias2 = aliasName(item.alias);
7936
+ const output = alias2 === void 0 ? name : alias2;
7937
+ ensureUniqueOutputAlias(output, outputAliases);
7409
7938
  if (alias2 === void 0 || alias2 === name)
7410
7939
  select.push(name);
7411
7940
  else
@@ -7416,6 +7945,7 @@ function selectStatementToAst(statement, context = newSqlParseContext()) {
7416
7945
  if (alias === void 0) {
7417
7946
  throwUnsupported("Computed projections require an explicit alias");
7418
7947
  }
7948
+ ensureUniqueOutputAlias(alias, outputAliases);
7419
7949
  projections[alias] = exprToLakeql(expr, scope, context);
7420
7950
  }
7421
7951
  if (select.length > 0)
@@ -7437,6 +7967,11 @@ function selectStatementToAst(statement, context = newSqlParseContext()) {
7437
7967
  if (statement.having !== void 0) {
7438
7968
  ast.having = exprToLakeql(asNode(statement.having, "HAVING"), scope, context);
7439
7969
  }
7970
+ if (context.qualifySql !== void 0) {
7971
+ ast.qualify = qualifyToExpr(context.qualifySql, scope, context);
7972
+ }
7973
+ if (Object.keys(context.windows).length > 0)
7974
+ ast.windows = context.windows;
7440
7975
  if (statement.orderBy !== void 0) {
7441
7976
  ast.orderBy = optionalArray(statement.orderBy).map((term) => orderByToTerm(term, scope));
7442
7977
  }
@@ -7453,10 +7988,27 @@ function selectStatementToAst(statement, context = newSqlParseContext()) {
7453
7988
  }
7454
7989
  return ast;
7455
7990
  }
7991
+ function qualifyToExpr(sql, scope, context) {
7992
+ let statements;
7993
+ try {
7994
+ statements = (0, import_pgsql_ast_parser.parse)(`select * from __lakeql_qualify where ${sql}`);
7995
+ } catch (error) {
7996
+ if (error instanceof Error)
7997
+ throwParse(error.message);
7998
+ throwParse("Invalid QUALIFY clause");
7999
+ }
8000
+ const statement = asNode(statements[0], "QUALIFY wrapper");
8001
+ const where = asNode(statement.where, "QUALIFY predicate");
8002
+ return exprToLakeql(where, scope, context);
8003
+ }
7456
8004
  function newSqlParseContext(options = {}) {
7457
8005
  return {
7458
8006
  scalarSubqueries: {},
7459
8007
  nextScalarSubqueryId: 0,
8008
+ windows: {},
8009
+ nextWindowId: 0,
8010
+ windowFrames: [],
8011
+ nextWindowFrameId: 0,
7460
8012
  ...options.parameters === void 0 ? {} : { parameters: options.parameters }
7461
8013
  };
7462
8014
  }
@@ -7483,9 +8035,11 @@ function exprToLakeql(expr, scope = SqlScope.empty(), context = newSqlParseConte
7483
8035
  return caseToExpr(expr, scope, context);
7484
8036
  case "select":
7485
8037
  return scalarSubqueryToExpr(expr, context);
8038
+ case "cast":
8039
+ return castToExpr(expr, scope, context);
7486
8040
  case "call":
7487
8041
  if ("over" in expr && expr.over !== void 0) {
7488
- throwUnsupported("Window functions are not supported");
8042
+ return { kind: "column", name: registerSyntheticWindow(expr, scope, context) };
7489
8043
  }
7490
8044
  return {
7491
8045
  kind: "call",
@@ -7496,6 +8050,17 @@ function exprToLakeql(expr, scope = SqlScope.empty(), context = newSqlParseConte
7496
8050
  throwUnsupported(`Unsupported SQL expression ${String(expr.type)}`);
7497
8051
  }
7498
8052
  }
8053
+ function castToExpr(expr, scope = SqlScope.empty(), context = newSqlParseContext()) {
8054
+ const target = String(asNode(expr.to, "cast target").name ?? "").toLowerCase();
8055
+ if (target === "interval") {
8056
+ const operand = exprToLakeql(asNode(expr.operand, "interval literal"), scope, context);
8057
+ if (operand.kind !== "literal" || typeof operand.value !== "string") {
8058
+ throwType("INTERVAL requires a string literal", { target });
8059
+ }
8060
+ return literal(intervalValue(operand.value));
8061
+ }
8062
+ throwUnsupported(`Unsupported SQL cast to ${target}`);
8063
+ }
7499
8064
  function binaryToExpr(expr, scope = SqlScope.empty(), context = newSqlParseContext()) {
7500
8065
  const op = String(expr.op).toUpperCase();
7501
8066
  const left = exprToLakeql(asNode(expr.left, "left expression"), scope, context);
@@ -7713,6 +8278,215 @@ function aggregateCallToSpec(expr, scope = SqlScope.empty(), context = newSqlPar
7713
8278
  return { op, column: scope.refName(arg) };
7714
8279
  return { op, expr: exprToLakeql(arg, scope, context) };
7715
8280
  }
8281
+ function hasWindowOver(expr) {
8282
+ return "over" in expr && expr.over !== void 0;
8283
+ }
8284
+ function registerSyntheticWindow(expr, scope, context) {
8285
+ const alias = syntheticWindowAlias(context);
8286
+ context.windows[alias] = windowCallToExpr(expr, scope, context);
8287
+ return alias;
8288
+ }
8289
+ function syntheticWindowAlias(context) {
8290
+ const alias = `__lakeql_window_${context.nextWindowId}`;
8291
+ context.nextWindowId += 1;
8292
+ return alias;
8293
+ }
8294
+ function ensureUniqueOutputAlias(alias, aliases) {
8295
+ if (PROTOTYPE_MUTATION_KEYS.has(alias))
8296
+ throwUnsupported(`Unsafe SELECT output alias ${alias}`);
8297
+ if (aliases.has(alias))
8298
+ throwUnsupported(`Duplicate SELECT output alias ${alias}`);
8299
+ aliases.add(alias);
8300
+ }
8301
+ function windowCallToExpr(expr, scope, context) {
8302
+ if (!hasWindowOver(expr))
8303
+ throwUnsupported("Window call requires OVER");
8304
+ const args = optionalArray(expr.args);
8305
+ const fnName = functionName(expr.function);
8306
+ let aggregate = aggregateOpOrUndefined(fnName);
8307
+ if (expr.distinct !== void 0) {
8308
+ if (expr.distinct !== "distinct") {
8309
+ throwUnsupported("Only DISTINCT window arguments are supported");
8310
+ }
8311
+ if (aggregate !== "count")
8312
+ throwUnsupported("Only COUNT(DISTINCT x) window arguments are supported");
8313
+ aggregate = "count_distinct";
8314
+ }
8315
+ const windowFn = aggregate === void 0 ? windowFunctionName(fnName) : { aggregate };
8316
+ const over = callOverToWindowSpec(asNode(expr.over, "OVER clause"), scope, context);
8317
+ const out = {
8318
+ fn: windowFn,
8319
+ args: args.filter((arg) => !isWildcard(asNode(arg, "window argument"))).map((arg) => exprToLakeql(asNode(arg, "window argument"), scope, context)),
8320
+ over: over.spec
8321
+ };
8322
+ if (over.ignoreNulls !== void 0)
8323
+ out.ignoreNulls = over.ignoreNulls;
8324
+ if (expr.filter !== void 0) {
8325
+ out.filter = exprToLakeql(asNode(expr.filter, "window FILTER predicate"), scope, context);
8326
+ }
8327
+ return out;
8328
+ }
8329
+ function windowFunctionName(name) {
8330
+ switch (name.toLowerCase()) {
8331
+ case "row_number":
8332
+ case "rank":
8333
+ case "dense_rank":
8334
+ case "percent_rank":
8335
+ case "cume_dist":
8336
+ case "ntile":
8337
+ case "lag":
8338
+ case "lead":
8339
+ case "first_value":
8340
+ case "last_value":
8341
+ case "nth_value":
8342
+ return name.toLowerCase();
8343
+ default:
8344
+ throwUnsupported(`Unsupported window function ${name}`);
8345
+ }
8346
+ }
8347
+ function callOverToWindowSpec(over, scope, context) {
8348
+ rejectPresent(over, "name", "Named windows require the window pre-pass");
8349
+ const prepass = nextWindowFrame(context);
8350
+ const spec = {
8351
+ partitionBy: optionalArray(over.partitionBy).map((expr) => exprToLakeql(asNode(expr, "window PARTITION BY expression"), scope, context)),
8352
+ orderBy: optionalArray(over.orderBy).map((term) => windowOrderByToTerm(term, scope, context))
8353
+ };
8354
+ if (prepass?.text !== void 0)
8355
+ spec.frame = frameTextToSpec(prepass.text, scope, context);
8356
+ return prepass?.ignoreNulls === void 0 ? { spec } : { spec, ignoreNulls: prepass.ignoreNulls };
8357
+ }
8358
+ function nextWindowFrame(context) {
8359
+ const frame = context.windowFrames[context.nextWindowFrameId];
8360
+ context.nextWindowFrameId += 1;
8361
+ return frame;
8362
+ }
8363
+ function frameTextToSpec(text, scope, context) {
8364
+ const { body, exclude } = splitFrameExclude(text);
8365
+ const modeMatch = /^(rows|range|groups)\b/iu.exec(body.trim());
8366
+ if (modeMatch === null || modeMatch[1] === void 0) {
8367
+ throwParse("Window frame must start with ROWS, RANGE, or GROUPS");
8368
+ }
8369
+ const mode = modeMatch[1].toLowerCase();
8370
+ const rest = body.trim().slice(modeMatch[0].length).trim();
8371
+ const bounds = frameBounds(rest, scope, context);
8372
+ return { mode, ...bounds, exclude };
8373
+ }
8374
+ function splitFrameExclude(text) {
8375
+ const match = /\bexclude\s+(no\s+others|current\s+row|group|ties)\s*$/iu.exec(text);
8376
+ if (match === null)
8377
+ return { body: text, exclude: "no-others" };
8378
+ const value2 = String(match[1]).toLowerCase().replace(/\s+/gu, "-");
8379
+ const exclude = value2 === "no-others" || value2 === "current-row" || value2 === "group" || value2 === "ties" ? value2 : "no-others";
8380
+ return { body: text.slice(0, match.index).trim(), exclude };
8381
+ }
8382
+ function frameBounds(text, scope, context) {
8383
+ const trimmed = text.trim();
8384
+ if (trimmed.length === 0) {
8385
+ return { start: { kind: "unbounded-preceding" }, end: { kind: "current-row" } };
8386
+ }
8387
+ if (/^between\b/iu.test(trimmed)) {
8388
+ const between = trimmed.replace(/^between\b/iu, "").trim();
8389
+ const andIndex = findTopLevelAnd(between);
8390
+ if (andIndex === -1)
8391
+ throwParse("Window frame BETWEEN requires AND");
8392
+ return {
8393
+ start: frameBound(between.slice(0, andIndex).trim(), scope, context),
8394
+ end: frameBound(between.slice(andIndex + "and".length).trim(), scope, context)
8395
+ };
8396
+ }
8397
+ return {
8398
+ start: frameBound(trimmed, scope, context),
8399
+ end: { kind: "current-row" }
8400
+ };
8401
+ }
8402
+ function frameBound(text, scope, context) {
8403
+ if (/^unbounded\s+preceding$/iu.test(text))
8404
+ return { kind: "unbounded-preceding" };
8405
+ if (/^unbounded\s+following$/iu.test(text))
8406
+ return { kind: "unbounded-following" };
8407
+ if (/^current\s+row$/iu.test(text))
8408
+ return { kind: "current-row" };
8409
+ const preceding = /\bpreceding$/iu.exec(text);
8410
+ if (preceding !== null) {
8411
+ return {
8412
+ kind: "preceding",
8413
+ offset: parseFrameOffset(text.slice(0, preceding.index).trim(), scope, context)
8414
+ };
8415
+ }
8416
+ const following = /\bfollowing$/iu.exec(text);
8417
+ if (following !== null) {
8418
+ return {
8419
+ kind: "following",
8420
+ offset: parseFrameOffset(text.slice(0, following.index).trim(), scope, context)
8421
+ };
8422
+ }
8423
+ throwParse(`Unsupported window frame bound ${text}`);
8424
+ }
8425
+ function parseFrameOffset(text, scope, context) {
8426
+ if (text.length === 0)
8427
+ throwParse("Window frame offset is required");
8428
+ let statements;
8429
+ try {
8430
+ statements = (0, import_pgsql_ast_parser.parse)(`select ${text} as frame_offset from __lakeql_frame`);
8431
+ } catch (error) {
8432
+ if (error instanceof Error)
8433
+ throwParse(error.message);
8434
+ throwParse("Invalid window frame offset");
8435
+ }
8436
+ const statement = asNode(statements[0], "frame offset wrapper");
8437
+ const column = asNode(optionalArray(statement.columns)[0], "frame offset column");
8438
+ return exprToLakeql(asNode(column.expr, "frame offset expression"), scope, context);
8439
+ }
8440
+ function findTopLevelAnd(text) {
8441
+ let depth = 0;
8442
+ let quote;
8443
+ for (let index = 0; index < text.length; index += 1) {
8444
+ const char = text[index];
8445
+ const next = text[index + 1];
8446
+ if (quote !== void 0) {
8447
+ if (char === quote) {
8448
+ if (next === quote) {
8449
+ index += 1;
8450
+ continue;
8451
+ }
8452
+ quote = void 0;
8453
+ }
8454
+ continue;
8455
+ }
8456
+ if (char === "'" || char === '"' || char === "`") {
8457
+ quote = char;
8458
+ continue;
8459
+ }
8460
+ if (char === "(")
8461
+ depth += 1;
8462
+ else if (char === ")")
8463
+ depth = Math.max(0, depth - 1);
8464
+ if (depth === 0 && text.slice(index, index + "and".length).toLowerCase() === "and" && /(?:^|[^A-Za-z0-9_])$/u.test(text.slice(0, index)) && !/[A-Za-z0-9_]/u.test(text[index + "and".length] ?? "")) {
8465
+ return index;
8466
+ }
8467
+ }
8468
+ return -1;
8469
+ }
8470
+ function windowOrderByToTerm(value2, scope, context) {
8471
+ const node = asNode(value2, "window ORDER BY term");
8472
+ const term = {
8473
+ expr: exprToLakeql(asNode(node.by, "window ORDER BY expression"), scope, context)
8474
+ };
8475
+ if (node.order !== void 0) {
8476
+ const order = String(node.order).toLowerCase();
8477
+ if (order !== "asc" && order !== "desc")
8478
+ throwUnsupported(`Unsupported ORDER BY ${order}`);
8479
+ term.direction = order;
8480
+ }
8481
+ if (node.nulls !== void 0) {
8482
+ const nulls = String(node.nulls).toLowerCase();
8483
+ if (nulls !== "first" && nulls !== "last") {
8484
+ throwUnsupported(`Unsupported ORDER BY NULLS ${nulls}`);
8485
+ }
8486
+ term.nulls = nulls;
8487
+ }
8488
+ return term;
8489
+ }
7716
8490
  function quantileAggregateCallToSpec(args, scope, context) {
7717
8491
  if (args.length !== 2)
7718
8492
  throwUnsupported("quantile_cont requires value and percentile arguments");
@@ -7734,6 +8508,8 @@ function requiredQuantilePercentile(expr) {
7734
8508
  function isAggregateCall(expr) {
7735
8509
  if (expr.type !== "call")
7736
8510
  return false;
8511
+ if (hasWindowOver(expr))
8512
+ return false;
7737
8513
  return aggregateOpOrUndefined(functionName(expr.function)) !== void 0;
7738
8514
  }
7739
8515
  function isAggregateOp(op) {
@@ -8005,7 +8781,7 @@ function parameterIndex(name) {
8005
8781
  return Number(name.slice(1));
8006
8782
  }
8007
8783
  function isScalar(value2) {
8008
- return value2 === null || typeof value2 === "string" || typeof value2 === "boolean" || typeof value2 === "bigint" || isTimestampValue(value2) || typeof value2 === "number" && Number.isFinite(value2);
8784
+ return value2 === null || typeof value2 === "string" || typeof value2 === "boolean" || typeof value2 === "bigint" || isTimestampValue(value2) || isIntervalValue(value2) || typeof value2 === "number" && Number.isFinite(value2);
8009
8785
  }
8010
8786
  function assertAstDepth(value2, depth = 0) {
8011
8787
  if (depth > MAX_AST_DEPTH)
@@ -8109,31 +8885,31 @@ async function query(args) {
8109
8885
  const resolvedAst = await resolveScalarSubqueries(executableLake, executableAst);
8110
8886
  if (resolvedAst.subqueryJoin !== void 0) {
8111
8887
  const rows = await subqueryJoinRowsFromAst(executableLake, resolvedAst, args);
8112
- if (args.format === "json") return `${JSON.stringify(rows)}
8888
+ if (args.format === "json") return `${jsonStringify(rows)}
8113
8889
  `;
8114
8890
  if (args.format === "csv") return rowsToCsv(rows);
8115
- return rows.map((row) => JSON.stringify(row)).join("\n") + (rows.length > 0 ? "\n" : "");
8891
+ return rows.map((row) => jsonStringify(row)).join("\n") + (rows.length > 0 ? "\n" : "");
8116
8892
  }
8117
8893
  if (resolvedAst.join !== void 0) {
8118
8894
  const rows = await joinRowsFromAst(executableLake, resolvedAst, args);
8119
- if (args.format === "json") return `${JSON.stringify(rows)}
8895
+ if (args.format === "json") return `${jsonStringify(rows)}
8120
8896
  `;
8121
8897
  if (args.format === "csv") return rowsToCsv(rows);
8122
- return rows.map((row) => JSON.stringify(row)).join("\n") + (rows.length > 0 ? "\n" : "");
8898
+ return rows.map((row) => jsonStringify(row)).join("\n") + (rows.length > 0 ? "\n" : "");
8123
8899
  }
8124
8900
  const result2 = builderFromAst(executableLake.path(resolvedAst.source), resolvedAst);
8125
8901
  if (hasAggregation(resolvedAst)) {
8126
8902
  const rows = await aggregateRowsFromAst(executableLake.path(resolvedAst.source), resolvedAst);
8127
- if (args.format === "json") return `${JSON.stringify(rows)}
8903
+ if (args.format === "json") return `${jsonStringify(rows)}
8128
8904
  `;
8129
8905
  if (args.format === "csv") return rowsToCsv(rows);
8130
- return rows.map((row) => JSON.stringify(row)).join("\n") + (rows.length > 0 ? "\n" : "");
8906
+ return rows.map((row) => jsonStringify(row)).join("\n") + (rows.length > 0 ? "\n" : "");
8131
8907
  }
8132
- if (args.format === "json") return `${JSON.stringify(await result2.toArray())}
8908
+ if (args.format === "json") return `${jsonStringify(await result2.toArray())}
8133
8909
  `;
8134
8910
  if (args.format === "csv") return new Response(result2.streamCsv()).text();
8135
8911
  let out = "";
8136
- for await (const row of result2.rows()) out += `${JSON.stringify(row)}
8912
+ for await (const row of result2.rows()) out += `${jsonStringify(row)}
8137
8913
  `;
8138
8914
  return out;
8139
8915
  }
@@ -8160,14 +8936,14 @@ async function compact(args) {
8160
8936
  async function schema(args) {
8161
8937
  const path = requireOption(args.path, "--path");
8162
8938
  const { store, key } = await localStore(path);
8163
- return `${JSON.stringify(await schemaSummary(store, key, path))}
8939
+ return `${jsonStringify(await schemaSummary(store, key, path))}
8164
8940
  `;
8165
8941
  }
8166
8942
  async function inspect(args) {
8167
8943
  const path = requireOption(args.path, "--path");
8168
8944
  const { store, key } = await localStore(path);
8169
8945
  const metadata = await readParquetMetadata(store, key);
8170
- return `${JSON.stringify({
8946
+ return `${jsonStringify({
8171
8947
  path,
8172
8948
  rows: Number(totalRows(metadata.row_groups)),
8173
8949
  rowGroups: metadata.row_groups.length,
@@ -8233,14 +9009,16 @@ async function writeRows(outputPrefix, rows, args) {
8233
9009
  await writeFile(args.manifest, bytes);
8234
9010
  body.manifest = args.manifest;
8235
9011
  }
8236
- return `${JSON.stringify(body)}
9012
+ return `${jsonStringify(body)}
8237
9013
  `;
8238
9014
  }
8239
9015
  function builderFromAst(builder, ast) {
8240
9016
  let next = builder;
9017
+ for (const [alias, expr] of Object.entries(ast.windows ?? {})) next = next.window(alias, expr);
8241
9018
  if (ast.select) next = next.select(ast.select);
8242
9019
  if (ast.projections) next = next.project(ast.projections);
8243
9020
  if (ast.where) next = next.where(ast.where);
9021
+ if (ast.qualify) next = next.qualify(ast.qualify);
8244
9022
  if (ast.distinct === true) next = next.distinct();
8245
9023
  if (ast.orderBy) next = next.orderBy(ast.orderBy);
8246
9024
  if (ast.offset !== void 0) next = next.offset(ast.offset);
@@ -8250,6 +9028,9 @@ function builderFromAst(builder, ast) {
8250
9028
  function hasAggregation(ast) {
8251
9029
  return ast.aggregates !== void 0 || ast.groupBy !== void 0 && ast.groupBy.length > 0 || ast.having !== void 0;
8252
9030
  }
9031
+ function hasWindowSemantics(ast) {
9032
+ return ast.windows !== void 0 && Object.keys(ast.windows).length > 0 || ast.qualify !== void 0;
9033
+ }
8253
9034
  async function materializeCteIfNeeded(store, lake, ast) {
8254
9035
  if (ast.cte === void 0) return ast;
8255
9036
  if (ast.source !== ast.cte.name) {
@@ -8361,6 +9142,9 @@ function replaceScalarSubqueryExpr(expr, values) {
8361
9142
  async function joinRowsFromAst(lake, ast, args) {
8362
9143
  if (ast.join === void 0) throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Missing SQL JOIN");
8363
9144
  const join = ast.join;
9145
+ if (hasWindowSemantics(ast)) {
9146
+ throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Window SQL over JOIN is not supported");
9147
+ }
8364
9148
  if (hasAggregation(ast)) {
8365
9149
  throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Aggregate SQL over JOIN is not supported yet");
8366
9150
  }
@@ -8606,6 +9390,9 @@ async function subqueryJoinRowsFromAst(lake, ast, args) {
8606
9390
  if (ast.subqueryJoin === void 0) {
8607
9391
  throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Missing SQL IN subquery");
8608
9392
  }
9393
+ if (hasWindowSemantics(ast)) {
9394
+ throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Window SQL over IN subquery is not supported");
9395
+ }
8609
9396
  if (hasAggregation(ast)) {
8610
9397
  throw new LakeqlError(
8611
9398
  "LAKEQL_SQL_UNSUPPORTED",
@@ -8659,6 +9446,9 @@ function projectRows(rows, ast) {
8659
9446
  });
8660
9447
  }
8661
9448
  async function aggregateRowsFromAst(builder, ast) {
9449
+ if (hasWindowSemantics(ast)) {
9450
+ throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Window SQL over aggregates is not supported");
9451
+ }
8662
9452
  validateAggregateProjection(ast);
8663
9453
  let next = builder;
8664
9454
  if (ast.where) next = next.where(ast.where);
@@ -8762,10 +9552,13 @@ function rowsToCsv(rows) {
8762
9552
  `;
8763
9553
  }
8764
9554
  function formatRows(rows, format) {
8765
- if (format === "json") return `${JSON.stringify(rows)}
9555
+ if (format === "json") return `${jsonStringify(rows)}
8766
9556
  `;
8767
9557
  if (format === "csv") return rowsToCsv(rows);
8768
- return rows.map((row) => JSON.stringify(row)).join("\n") + (rows.length > 0 ? "\n" : "");
9558
+ return rows.map((row) => jsonStringify(row)).join("\n") + (rows.length > 0 ? "\n" : "");
9559
+ }
9560
+ function jsonStringify(value2) {
9561
+ return JSON.stringify(value2, (_key, item) => typeof item === "bigint" ? item.toString() : item);
8769
9562
  }
8770
9563
  async function schemaSummary(store, key, displayPath = key) {
8771
9564
  const metadata = await readParquetMetadata(store, key);