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/README.md +18 -5
- package/dist/bin.js +816 -23
- package/dist/{chunk-5K5JMJ2M.js → chunk-2XZJK7EF.js} +3 -2
- package/dist/{chunk-TFD5RFKB.js → chunk-4VJQ56HF.js} +424 -4
- package/dist/{chunk-MXGEAVHL.js → chunk-6XXXYVXT.js} +3843 -3483
- package/dist/chunk-ZVHJD6R3.js +1175 -0
- package/dist/cloudflare.d.ts +1 -1
- package/dist/cloudflare.js +5 -4
- package/dist/{geo-backend-TSQJWAAB.js → geo-backend-MY6MET3L.js} +1 -1
- package/dist/index.d.ts +420 -220
- package/dist/index.js +4 -3
- package/dist/node.d.ts +1 -1
- package/dist/node.js +48 -9
- package/dist/window-backend-DIMZN7EZ.js +905 -0
- package/package.json +9 -9
package/dist/bin.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createParquetLake, readParquetMetadata, memoryStore, writePartitionedParquet, broadcastJoin,
|
|
3
|
-
import {
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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 `${
|
|
8888
|
+
if (args.format === "json") return `${jsonStringify(rows)}
|
|
8113
8889
|
`;
|
|
8114
8890
|
if (args.format === "csv") return rowsToCsv(rows);
|
|
8115
|
-
return rows.map((row) =>
|
|
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 `${
|
|
8895
|
+
if (args.format === "json") return `${jsonStringify(rows)}
|
|
8120
8896
|
`;
|
|
8121
8897
|
if (args.format === "csv") return rowsToCsv(rows);
|
|
8122
|
-
return rows.map((row) =>
|
|
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 `${
|
|
8903
|
+
if (args.format === "json") return `${jsonStringify(rows)}
|
|
8128
8904
|
`;
|
|
8129
8905
|
if (args.format === "csv") return rowsToCsv(rows);
|
|
8130
|
-
return rows.map((row) =>
|
|
8906
|
+
return rows.map((row) => jsonStringify(row)).join("\n") + (rows.length > 0 ? "\n" : "");
|
|
8131
8907
|
}
|
|
8132
|
-
if (args.format === "json") return `${
|
|
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 += `${
|
|
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 `${
|
|
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 `${
|
|
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 `${
|
|
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 `${
|
|
9555
|
+
if (format === "json") return `${jsonStringify(rows)}
|
|
8766
9556
|
`;
|
|
8767
9557
|
if (format === "csv") return rowsToCsv(rows);
|
|
8768
|
-
return rows.map((row) =>
|
|
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);
|