lakeql 0.1.2 → 0.1.8
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 +205 -81
- package/dist/{chunk-MUTXXFOW.js → chunk-5K5JMJ2M.js} +772 -131
- package/dist/chunk-MXGEAVHL.js +11381 -0
- package/dist/chunk-TFD5RFKB.js +1152 -0
- package/dist/cloudflare.d.ts +21 -5
- package/dist/cloudflare.js +105 -5
- package/dist/geo-backend-TSQJWAAB.js +18 -0
- package/dist/index.d.ts +842 -232
- package/dist/index.js +3 -2
- package/dist/node.d.ts +29 -7
- package/dist/node.js +195 -35
- package/package.json +13 -10
- package/dist/chunk-D3A4VN3U.js +0 -5677
package/dist/bin.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
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';
|
|
3
4
|
import { readFile, mkdir, writeFile } from 'fs/promises';
|
|
4
5
|
import { dirname } from 'path';
|
|
5
6
|
|
|
@@ -7292,7 +7293,7 @@ var require_pgsql_ast_parser = __commonJS({
|
|
|
7292
7293
|
var import_pgsql_ast_parser = __toESM(require_pgsql_ast_parser(), 1);
|
|
7293
7294
|
var MAX_SQL_LENGTH = 128e3;
|
|
7294
7295
|
var MAX_AST_DEPTH = 128;
|
|
7295
|
-
function parseSql(sql) {
|
|
7296
|
+
function parseSql(sql, options = {}) {
|
|
7296
7297
|
if (sql.length > MAX_SQL_LENGTH) {
|
|
7297
7298
|
throwParse(`SQL input length exceeds ${MAX_SQL_LENGTH}`);
|
|
7298
7299
|
}
|
|
@@ -7308,13 +7309,37 @@ function parseSql(sql) {
|
|
|
7308
7309
|
throwUnsupported("Only one SELECT statement is supported");
|
|
7309
7310
|
const statement = asNode(statements[0], "statement");
|
|
7310
7311
|
assertAstDepth(statement);
|
|
7312
|
+
const context = newSqlParseContext(options);
|
|
7311
7313
|
if (statement.type === "with")
|
|
7312
|
-
return withStatementToAst(statement);
|
|
7314
|
+
return withStatementToAst(statement, context);
|
|
7313
7315
|
if (statement.type !== "select")
|
|
7314
7316
|
throwUnsupported("Only SELECT statements are supported");
|
|
7315
|
-
return selectStatementToAst(statement);
|
|
7317
|
+
return selectStatementToAst(statement, context);
|
|
7316
7318
|
}
|
|
7317
|
-
function
|
|
7319
|
+
function parseSqlStatement(sql, options = {}) {
|
|
7320
|
+
const describe = parseDescribeStatement(sql);
|
|
7321
|
+
if (describe !== void 0)
|
|
7322
|
+
return describe;
|
|
7323
|
+
return parseSql(sql, options);
|
|
7324
|
+
}
|
|
7325
|
+
function parseDescribeStatement(sql) {
|
|
7326
|
+
if (sql.length > MAX_SQL_LENGTH) {
|
|
7327
|
+
throwParse(`SQL input length exceeds ${MAX_SQL_LENGTH}`);
|
|
7328
|
+
}
|
|
7329
|
+
const match = /^\s*describe\s+([A-Za-z_][A-Za-z0-9_]*|"(?:""|[^"])+")\s*;?\s*$/iu.exec(sql);
|
|
7330
|
+
if (match === null)
|
|
7331
|
+
return void 0;
|
|
7332
|
+
const source = match[1];
|
|
7333
|
+
if (source === void 0)
|
|
7334
|
+
throwParse("DESCRIBE requires a table name");
|
|
7335
|
+
return { type: "describe", source: unquoteIdentifier(source) };
|
|
7336
|
+
}
|
|
7337
|
+
function unquoteIdentifier(value2) {
|
|
7338
|
+
if (!value2.startsWith('"'))
|
|
7339
|
+
return value2;
|
|
7340
|
+
return value2.slice(1, -1).replaceAll('""', '"');
|
|
7341
|
+
}
|
|
7342
|
+
function withStatementToAst(statement, context) {
|
|
7318
7343
|
const bindings = optionalArray(statement.bind);
|
|
7319
7344
|
if (bindings.length !== 1)
|
|
7320
7345
|
throwUnsupported("Only one CTE is supported");
|
|
@@ -7323,12 +7348,12 @@ function withStatementToAst(statement) {
|
|
|
7323
7348
|
const inner = asNode(binding.statement, "CTE statement");
|
|
7324
7349
|
if (inner.type !== "select")
|
|
7325
7350
|
throwUnsupported("Only SELECT CTEs are supported");
|
|
7326
|
-
const cteQuery = selectStatementToAst(inner);
|
|
7351
|
+
const cteQuery = selectStatementToAst(inner, context);
|
|
7327
7352
|
validateCteQuery(cteQuery);
|
|
7328
7353
|
const outer = asNode(statement.in, "CTE outer query");
|
|
7329
7354
|
if (outer.type !== "select")
|
|
7330
7355
|
throwUnsupported("Only SELECT after WITH is supported");
|
|
7331
|
-
const ast = selectStatementToAst(outer);
|
|
7356
|
+
const ast = selectStatementToAst(outer, context);
|
|
7332
7357
|
ast.cte = { name, query: cteQuery };
|
|
7333
7358
|
return ast;
|
|
7334
7359
|
}
|
|
@@ -7337,7 +7362,7 @@ function validateCteQuery(ast) {
|
|
|
7337
7362
|
throwUnsupported("Nested CTE joins and subqueries are not supported");
|
|
7338
7363
|
}
|
|
7339
7364
|
}
|
|
7340
|
-
function selectStatementToAst(statement) {
|
|
7365
|
+
function selectStatementToAst(statement, context = newSqlParseContext()) {
|
|
7341
7366
|
rejectPresent(statement, "with", "CTEs are not supported");
|
|
7342
7367
|
rejectPresent(statement, "windows", "Window functions are not supported");
|
|
7343
7368
|
const from = optionalArray(statement.from);
|
|
@@ -7347,7 +7372,6 @@ function selectStatementToAst(statement) {
|
|
|
7347
7372
|
const leftSource = sourceTable(from[0]);
|
|
7348
7373
|
const ast = { source: leftSource.source };
|
|
7349
7374
|
const scope = new SqlScope(leftSource);
|
|
7350
|
-
const context = newSqlParseContext();
|
|
7351
7375
|
if (from.length === 2) {
|
|
7352
7376
|
const join = joinToAst(from[1], leftSource);
|
|
7353
7377
|
ast.join = join;
|
|
@@ -7376,7 +7400,7 @@ function selectStatementToAst(statement) {
|
|
|
7376
7400
|
}
|
|
7377
7401
|
if (expr.type === "call" && isAggregateCall(expr)) {
|
|
7378
7402
|
const alias2 = aliasName(item.alias) ?? functionName(expr.function);
|
|
7379
|
-
aggregates[alias2] = aggregateCallToSpec(expr, scope);
|
|
7403
|
+
aggregates[alias2] = aggregateCallToSpec(expr, scope, context);
|
|
7380
7404
|
continue;
|
|
7381
7405
|
}
|
|
7382
7406
|
if (expr.type === "ref") {
|
|
@@ -7392,7 +7416,7 @@ function selectStatementToAst(statement) {
|
|
|
7392
7416
|
if (alias === void 0) {
|
|
7393
7417
|
throwUnsupported("Computed projections require an explicit alias");
|
|
7394
7418
|
}
|
|
7395
|
-
projections[alias] =
|
|
7419
|
+
projections[alias] = exprToLakeql(expr, scope, context);
|
|
7396
7420
|
}
|
|
7397
7421
|
if (select.length > 0)
|
|
7398
7422
|
ast.select = select;
|
|
@@ -7411,7 +7435,7 @@ function selectStatementToAst(statement) {
|
|
|
7411
7435
|
ast.groupBy = optionalArray(statement.groupBy).map((expr) => scope.refName(asNode(expr, "GROUP BY expression")));
|
|
7412
7436
|
}
|
|
7413
7437
|
if (statement.having !== void 0) {
|
|
7414
|
-
ast.having =
|
|
7438
|
+
ast.having = exprToLakeql(asNode(statement.having, "HAVING"), scope, context);
|
|
7415
7439
|
}
|
|
7416
7440
|
if (statement.orderBy !== void 0) {
|
|
7417
7441
|
ast.orderBy = optionalArray(statement.orderBy).map((term) => orderByToTerm(term, scope));
|
|
@@ -7420,19 +7444,23 @@ function selectStatementToAst(statement) {
|
|
|
7420
7444
|
if (limit !== void 0) {
|
|
7421
7445
|
const node = asNode(limit, "LIMIT");
|
|
7422
7446
|
if (node.limit !== void 0)
|
|
7423
|
-
ast.limit = nonNegativeInteger(node.limit, "LIMIT");
|
|
7447
|
+
ast.limit = nonNegativeInteger(node.limit, "LIMIT", context);
|
|
7424
7448
|
if (node.offset !== void 0)
|
|
7425
|
-
ast.offset = nonNegativeInteger(node.offset, "OFFSET");
|
|
7449
|
+
ast.offset = nonNegativeInteger(node.offset, "OFFSET", context);
|
|
7426
7450
|
}
|
|
7427
7451
|
if (Object.keys(context.scalarSubqueries).length > 0) {
|
|
7428
7452
|
ast.scalarSubqueries = context.scalarSubqueries;
|
|
7429
7453
|
}
|
|
7430
7454
|
return ast;
|
|
7431
7455
|
}
|
|
7432
|
-
function newSqlParseContext() {
|
|
7433
|
-
return {
|
|
7456
|
+
function newSqlParseContext(options = {}) {
|
|
7457
|
+
return {
|
|
7458
|
+
scalarSubqueries: {},
|
|
7459
|
+
nextScalarSubqueryId: 0,
|
|
7460
|
+
...options.parameters === void 0 ? {} : { parameters: options.parameters }
|
|
7461
|
+
};
|
|
7434
7462
|
}
|
|
7435
|
-
function
|
|
7463
|
+
function exprToLakeql(expr, scope = SqlScope.empty(), context = newSqlParseContext()) {
|
|
7436
7464
|
switch (expr.type) {
|
|
7437
7465
|
case "ref":
|
|
7438
7466
|
return { kind: "column", name: scope.refName(expr) };
|
|
@@ -7443,6 +7471,8 @@ function exprToLaql(expr, scope = SqlScope.empty(), context = newSqlParseContext
|
|
|
7443
7471
|
return literal(expr.value);
|
|
7444
7472
|
case "null":
|
|
7445
7473
|
return literal(null);
|
|
7474
|
+
case "parameter":
|
|
7475
|
+
return literal(parameterValue(expr, context));
|
|
7446
7476
|
case "unary":
|
|
7447
7477
|
return unaryToExpr(expr, scope, context);
|
|
7448
7478
|
case "binary":
|
|
@@ -7460,7 +7490,7 @@ function exprToLaql(expr, scope = SqlScope.empty(), context = newSqlParseContext
|
|
|
7460
7490
|
return {
|
|
7461
7491
|
kind: "call",
|
|
7462
7492
|
fn: functionName(expr.function),
|
|
7463
|
-
args: optionalArray(expr.args).map((arg) =>
|
|
7493
|
+
args: optionalArray(expr.args).map((arg) => exprToLakeql(asNode(arg, "function argument"), scope, context))
|
|
7464
7494
|
};
|
|
7465
7495
|
default:
|
|
7466
7496
|
throwUnsupported(`Unsupported SQL expression ${String(expr.type)}`);
|
|
@@ -7468,10 +7498,10 @@ function exprToLaql(expr, scope = SqlScope.empty(), context = newSqlParseContext
|
|
|
7468
7498
|
}
|
|
7469
7499
|
function binaryToExpr(expr, scope = SqlScope.empty(), context = newSqlParseContext()) {
|
|
7470
7500
|
const op = String(expr.op).toUpperCase();
|
|
7471
|
-
const left =
|
|
7501
|
+
const left = exprToLakeql(asNode(expr.left, "left expression"), scope, context);
|
|
7472
7502
|
const right = asNode(expr.right, "right expression");
|
|
7473
7503
|
if (op === "AND" || op === "OR") {
|
|
7474
|
-
const operands = flattenLogical(op.toLowerCase(), left,
|
|
7504
|
+
const operands = flattenLogical(op.toLowerCase(), left, exprToLakeql(right, scope, context));
|
|
7475
7505
|
return { kind: "logical", op: op.toLowerCase(), operands };
|
|
7476
7506
|
}
|
|
7477
7507
|
if (op === "IN" || op === "NOT IN") {
|
|
@@ -7481,11 +7511,11 @@ function binaryToExpr(expr, scope = SqlScope.empty(), context = newSqlParseConte
|
|
|
7481
7511
|
kind: "in",
|
|
7482
7512
|
negated: op === "NOT IN",
|
|
7483
7513
|
target: left,
|
|
7484
|
-
values: optionalArray(right.expressions).map((value2) =>
|
|
7514
|
+
values: optionalArray(right.expressions).map((value2) => exprToLakeql(asNode(value2, "IN value"), scope, context))
|
|
7485
7515
|
};
|
|
7486
7516
|
}
|
|
7487
7517
|
if (op === "LIKE" || op === "NOT LIKE" || op === "ILIKE" || op === "NOT ILIKE") {
|
|
7488
|
-
const pattern =
|
|
7518
|
+
const pattern = exprToLakeql(right, scope, context);
|
|
7489
7519
|
if (pattern.kind !== "literal" || typeof pattern.value !== "string") {
|
|
7490
7520
|
throwUnsupported("LIKE pattern must be a string literal");
|
|
7491
7521
|
}
|
|
@@ -7502,10 +7532,10 @@ function binaryToExpr(expr, scope = SqlScope.empty(), context = newSqlParseConte
|
|
|
7502
7532
|
kind: "arithmetic",
|
|
7503
7533
|
op: arithmeticOp(op),
|
|
7504
7534
|
left,
|
|
7505
|
-
right:
|
|
7535
|
+
right: exprToLakeql(right, scope, context)
|
|
7506
7536
|
};
|
|
7507
7537
|
}
|
|
7508
|
-
return { kind: "compare", op: compareOp(op), left, right:
|
|
7538
|
+
return { kind: "compare", op: compareOp(op), left, right: exprToLakeql(right, scope, context) };
|
|
7509
7539
|
}
|
|
7510
7540
|
function whereToAst(expr, scope, context) {
|
|
7511
7541
|
const predicates = flattenWhereConjuncts(expr);
|
|
@@ -7523,12 +7553,12 @@ function whereToAst(expr, scope, context) {
|
|
|
7523
7553
|
}
|
|
7524
7554
|
const out = {};
|
|
7525
7555
|
if (residual.length === 1)
|
|
7526
|
-
out.where =
|
|
7556
|
+
out.where = exprToLakeql(residual[0], scope, context);
|
|
7527
7557
|
else if (residual.length > 1) {
|
|
7528
7558
|
out.where = {
|
|
7529
7559
|
kind: "logical",
|
|
7530
7560
|
op: "and",
|
|
7531
|
-
operands: residual.map((predicate) =>
|
|
7561
|
+
operands: residual.map((predicate) => exprToLakeql(predicate, scope, context))
|
|
7532
7562
|
};
|
|
7533
7563
|
}
|
|
7534
7564
|
if (subqueryJoin !== void 0)
|
|
@@ -7571,7 +7601,7 @@ function subqueryJoinToAst(left, subquery, outerScope, negated) {
|
|
|
7571
7601
|
rightKey
|
|
7572
7602
|
};
|
|
7573
7603
|
if (subquery.where !== void 0) {
|
|
7574
|
-
out.where =
|
|
7604
|
+
out.where = exprToLakeql(asNode(subquery.where, "IN subquery WHERE"), subqueryScope);
|
|
7575
7605
|
}
|
|
7576
7606
|
return out;
|
|
7577
7607
|
}
|
|
@@ -7591,7 +7621,7 @@ function flattenWhereConjuncts(expr) {
|
|
|
7591
7621
|
return [expr];
|
|
7592
7622
|
}
|
|
7593
7623
|
function scalarSubqueryToExpr(subquery, context) {
|
|
7594
|
-
const query2 = selectStatementToAst(subquery);
|
|
7624
|
+
const query2 = selectStatementToAst(subquery, context);
|
|
7595
7625
|
const outputColumns = scalarSubqueryOutputColumns(query2);
|
|
7596
7626
|
if (outputColumns.length !== 1) {
|
|
7597
7627
|
throwUnsupported("Scalar subqueries must return exactly one column");
|
|
@@ -7602,7 +7632,7 @@ function scalarSubqueryToExpr(subquery, context) {
|
|
|
7602
7632
|
const id = `scalar_${context.nextScalarSubqueryId}`;
|
|
7603
7633
|
context.nextScalarSubqueryId += 1;
|
|
7604
7634
|
context.scalarSubqueries[id] = { query: query2, column: outputColumns[0] };
|
|
7605
|
-
return { kind: "call", fn: "
|
|
7635
|
+
return { kind: "call", fn: "__lakeql_scalar_subquery", args: [{ kind: "literal", value: id }] };
|
|
7606
7636
|
}
|
|
7607
7637
|
function scalarSubqueryOutputColumns(query2) {
|
|
7608
7638
|
return [
|
|
@@ -7613,7 +7643,7 @@ function scalarSubqueryOutputColumns(query2) {
|
|
|
7613
7643
|
}
|
|
7614
7644
|
function unaryToExpr(expr, scope = SqlScope.empty(), context = newSqlParseContext()) {
|
|
7615
7645
|
const op = String(expr.op).toUpperCase();
|
|
7616
|
-
const operand =
|
|
7646
|
+
const operand = exprToLakeql(asNode(expr.operand, "unary operand"), scope, context);
|
|
7617
7647
|
if (op === "NOT")
|
|
7618
7648
|
return { kind: "not", operand };
|
|
7619
7649
|
if (op === "IS NULL")
|
|
@@ -7632,13 +7662,13 @@ function caseToExpr(expr, scope = SqlScope.empty(), context = newSqlParseContext
|
|
|
7632
7662
|
const whens = optionalArray(expr.whens).map((branch) => {
|
|
7633
7663
|
const node = asNode(branch, "CASE branch");
|
|
7634
7664
|
return {
|
|
7635
|
-
when:
|
|
7636
|
-
value:
|
|
7665
|
+
when: exprToLakeql(asNode(node.when, "CASE WHEN expression"), scope, context),
|
|
7666
|
+
value: exprToLakeql(asNode(node.value, "CASE THEN expression"), scope, context)
|
|
7637
7667
|
};
|
|
7638
7668
|
});
|
|
7639
7669
|
const out = { kind: "case", whens };
|
|
7640
7670
|
if (expr.else !== void 0) {
|
|
7641
|
-
out.else =
|
|
7671
|
+
out.else = exprToLakeql(asNode(expr.else, "CASE ELSE expression"), scope, context);
|
|
7642
7672
|
}
|
|
7643
7673
|
return out;
|
|
7644
7674
|
}
|
|
@@ -7649,13 +7679,13 @@ function ternaryToExpr(expr, scope = SqlScope.empty(), context = newSqlParseCont
|
|
|
7649
7679
|
}
|
|
7650
7680
|
const between = {
|
|
7651
7681
|
kind: "between",
|
|
7652
|
-
target:
|
|
7653
|
-
low:
|
|
7654
|
-
high:
|
|
7682
|
+
target: exprToLakeql(asNode(expr.value, "BETWEEN target"), scope, context),
|
|
7683
|
+
low: exprToLakeql(asNode(expr.lo, "BETWEEN low value"), scope, context),
|
|
7684
|
+
high: exprToLakeql(asNode(expr.hi, "BETWEEN high value"), scope, context)
|
|
7655
7685
|
};
|
|
7656
7686
|
return op === "NOT BETWEEN" ? { kind: "not", operand: between } : between;
|
|
7657
7687
|
}
|
|
7658
|
-
function aggregateCallToSpec(expr, scope = SqlScope.empty()) {
|
|
7688
|
+
function aggregateCallToSpec(expr, scope = SqlScope.empty(), context = newSqlParseContext()) {
|
|
7659
7689
|
if ("over" in expr && expr.over !== void 0) {
|
|
7660
7690
|
throwUnsupported("Window functions are not supported");
|
|
7661
7691
|
}
|
|
@@ -7671,6 +7701,8 @@ function aggregateCallToSpec(expr, scope = SqlScope.empty()) {
|
|
|
7671
7701
|
}
|
|
7672
7702
|
op = "count_distinct";
|
|
7673
7703
|
}
|
|
7704
|
+
if (op === "quantile")
|
|
7705
|
+
return quantileAggregateCallToSpec(args, scope, context);
|
|
7674
7706
|
if (op === "count" && (args.length === 0 || args.length === 1 && isWildcard(asNode(args[0], "COUNT argument")))) {
|
|
7675
7707
|
return { op };
|
|
7676
7708
|
}
|
|
@@ -7679,22 +7711,46 @@ function aggregateCallToSpec(expr, scope = SqlScope.empty()) {
|
|
|
7679
7711
|
const arg = asNode(args[0], "aggregate argument");
|
|
7680
7712
|
if (arg.type === "ref")
|
|
7681
7713
|
return { op, column: scope.refName(arg) };
|
|
7682
|
-
return { op, expr:
|
|
7714
|
+
return { op, expr: exprToLakeql(arg, scope, context) };
|
|
7715
|
+
}
|
|
7716
|
+
function quantileAggregateCallToSpec(args, scope, context) {
|
|
7717
|
+
if (args.length !== 2)
|
|
7718
|
+
throwUnsupported("quantile_cont requires value and percentile arguments");
|
|
7719
|
+
const value2 = asNode(args[0], "quantile_cont value argument");
|
|
7720
|
+
const percentile = requiredQuantilePercentile(exprToLakeql(asNode(args[1], "quantile_cont percentile argument"), scope, context));
|
|
7721
|
+
if (value2.type === "ref") {
|
|
7722
|
+
return { op: "quantile", column: scope.refName(value2), quantile: percentile };
|
|
7723
|
+
}
|
|
7724
|
+
return { op: "quantile", expr: exprToLakeql(value2, scope, context), quantile: percentile };
|
|
7725
|
+
}
|
|
7726
|
+
function requiredQuantilePercentile(expr) {
|
|
7727
|
+
if (expr.kind !== "literal" || typeof expr.value !== "number" || !Number.isFinite(expr.value) || expr.value < 0 || expr.value > 1) {
|
|
7728
|
+
throwType("quantile_cont percentile must be a numeric literal between 0 and 1", {
|
|
7729
|
+
function: "quantile_cont"
|
|
7730
|
+
});
|
|
7731
|
+
}
|
|
7732
|
+
return expr.value;
|
|
7683
7733
|
}
|
|
7684
7734
|
function isAggregateCall(expr) {
|
|
7685
7735
|
if (expr.type !== "call")
|
|
7686
7736
|
return false;
|
|
7687
|
-
return
|
|
7737
|
+
return aggregateOpOrUndefined(functionName(expr.function)) !== void 0;
|
|
7688
7738
|
}
|
|
7689
7739
|
function isAggregateOp(op) {
|
|
7690
7740
|
return [
|
|
7691
7741
|
"count",
|
|
7692
7742
|
"sum",
|
|
7693
7743
|
"avg",
|
|
7744
|
+
"var_samp",
|
|
7745
|
+
"var_pop",
|
|
7746
|
+
"stddev_samp",
|
|
7747
|
+
"stddev_pop",
|
|
7748
|
+
"median",
|
|
7694
7749
|
"min",
|
|
7695
7750
|
"max",
|
|
7696
7751
|
"count_distinct",
|
|
7697
7752
|
"approx_count_distinct",
|
|
7753
|
+
"mode",
|
|
7698
7754
|
"first",
|
|
7699
7755
|
"last",
|
|
7700
7756
|
"any"
|
|
@@ -7863,9 +7919,9 @@ function optionalArray(value2) {
|
|
|
7863
7919
|
throwUnsupported("Expected SQL AST array");
|
|
7864
7920
|
return value2;
|
|
7865
7921
|
}
|
|
7866
|
-
function nonNegativeInteger(value2, label) {
|
|
7922
|
+
function nonNegativeInteger(value2, label, context = newSqlParseContext()) {
|
|
7867
7923
|
const node = asNode(value2, label);
|
|
7868
|
-
const parsed = node.value;
|
|
7924
|
+
const parsed = node.type === "parameter" ? parameterValue(node, context) : node.value;
|
|
7869
7925
|
if (typeof parsed !== "number" || !Number.isInteger(parsed) || parsed < 0) {
|
|
7870
7926
|
throwUnsupported(`${label} must be a non-negative integer`);
|
|
7871
7927
|
}
|
|
@@ -7913,13 +7969,44 @@ function arithmeticOp(op) {
|
|
|
7913
7969
|
}
|
|
7914
7970
|
}
|
|
7915
7971
|
function aggregateOp(op) {
|
|
7972
|
+
const aggregate = aggregateOpOrUndefined(op);
|
|
7973
|
+
if (aggregate !== void 0)
|
|
7974
|
+
return aggregate;
|
|
7975
|
+
throwUnsupported(`Unsupported aggregate ${op}`);
|
|
7976
|
+
}
|
|
7977
|
+
function aggregateOpOrUndefined(op) {
|
|
7916
7978
|
if (isAggregateOp(op))
|
|
7917
7979
|
return op;
|
|
7918
|
-
|
|
7980
|
+
if (op === "var" || op === "variance")
|
|
7981
|
+
return "var_samp";
|
|
7982
|
+
if (op === "stddev")
|
|
7983
|
+
return "stddev_samp";
|
|
7984
|
+
if (op === "quantile_cont")
|
|
7985
|
+
return "quantile";
|
|
7986
|
+
return void 0;
|
|
7919
7987
|
}
|
|
7920
7988
|
function literal(value2) {
|
|
7921
7989
|
return { kind: "literal", value: value2 };
|
|
7922
7990
|
}
|
|
7991
|
+
function parameterValue(expr, context) {
|
|
7992
|
+
const index = parameterIndex(expr.name);
|
|
7993
|
+
const value2 = context.parameters?.[index - 1];
|
|
7994
|
+
if (value2 === void 0) {
|
|
7995
|
+
throwUnsupported(`SQL parameter $${index} has no bound value`);
|
|
7996
|
+
}
|
|
7997
|
+
if (isScalar(value2))
|
|
7998
|
+
return value2;
|
|
7999
|
+
throwType(`SQL parameter $${index} must be a scalar value`, { parameter: index });
|
|
8000
|
+
}
|
|
8001
|
+
function parameterIndex(name) {
|
|
8002
|
+
if (typeof name !== "string" || !/^\$[1-9][0-9]*$/u.test(name)) {
|
|
8003
|
+
throwUnsupported(`Unsupported SQL parameter ${String(name)}`);
|
|
8004
|
+
}
|
|
8005
|
+
return Number(name.slice(1));
|
|
8006
|
+
}
|
|
8007
|
+
function isScalar(value2) {
|
|
8008
|
+
return value2 === null || typeof value2 === "string" || typeof value2 === "boolean" || typeof value2 === "bigint" || isTimestampValue(value2) || typeof value2 === "number" && Number.isFinite(value2);
|
|
8009
|
+
}
|
|
7923
8010
|
function assertAstDepth(value2, depth = 0) {
|
|
7924
8011
|
if (depth > MAX_AST_DEPTH)
|
|
7925
8012
|
throwParse(`SQL AST nesting exceeds ${MAX_AST_DEPTH}`);
|
|
@@ -7945,10 +8032,13 @@ function asNode(value2, label) {
|
|
|
7945
8032
|
return value2;
|
|
7946
8033
|
}
|
|
7947
8034
|
function throwParse(message) {
|
|
7948
|
-
throw new
|
|
8035
|
+
throw new LakeqlError("LAKEQL_PARSE_ERROR", message);
|
|
7949
8036
|
}
|
|
7950
8037
|
function throwUnsupported(message) {
|
|
7951
|
-
throw new
|
|
8038
|
+
throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", message);
|
|
8039
|
+
}
|
|
8040
|
+
function throwType(message, details) {
|
|
8041
|
+
throw new LakeqlError("LAKEQL_TYPE_ERROR", message, details);
|
|
7952
8042
|
}
|
|
7953
8043
|
|
|
7954
8044
|
// ../cli/src/index.ts
|
|
@@ -7977,7 +8067,7 @@ ${usage()}
|
|
|
7977
8067
|
`, 2);
|
|
7978
8068
|
}
|
|
7979
8069
|
} catch (error) {
|
|
7980
|
-
if (error instanceof
|
|
8070
|
+
if (error instanceof LakeqlError) return fail(`${error.code}: ${error.message}
|
|
7981
8071
|
`, 1);
|
|
7982
8072
|
if (error instanceof Error) return fail(`${error.message}
|
|
7983
8073
|
`, 1);
|
|
@@ -8008,7 +8098,11 @@ function usage() {
|
|
|
8008
8098
|
async function query(args) {
|
|
8009
8099
|
const sql = requireOption(args.sql, "--sql");
|
|
8010
8100
|
const { store, key, preserveSqlSource } = await queryStore(args);
|
|
8011
|
-
const
|
|
8101
|
+
const statement = parseCliSqlStatement(sql, key, preserveSqlSource);
|
|
8102
|
+
if (isDescribeStatement(statement)) {
|
|
8103
|
+
return formatRows([await schemaSummary(store, statement.source)], args.format);
|
|
8104
|
+
}
|
|
8105
|
+
const ast = statement;
|
|
8012
8106
|
const lake = createParquetLake({ store });
|
|
8013
8107
|
const executableAst = await materializeCteIfNeeded(store, lake, ast);
|
|
8014
8108
|
const executableLake = createParquetLake({ store });
|
|
@@ -8050,7 +8144,7 @@ async function explain(args) {
|
|
|
8050
8144
|
const lake = createParquetLake({ store });
|
|
8051
8145
|
const ast = parseCliSql(sql, key);
|
|
8052
8146
|
if (hasAggregation(ast)) {
|
|
8053
|
-
throw new
|
|
8147
|
+
throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "EXPLAIN for aggregate SQL is not supported");
|
|
8054
8148
|
}
|
|
8055
8149
|
return `${(await builderFromAst(lake.path(key), ast).explain()).text}
|
|
8056
8150
|
`;
|
|
@@ -8066,9 +8160,7 @@ async function compact(args) {
|
|
|
8066
8160
|
async function schema(args) {
|
|
8067
8161
|
const path = requireOption(args.path, "--path");
|
|
8068
8162
|
const { store, key } = await localStore(path);
|
|
8069
|
-
|
|
8070
|
-
const columns = metadata.schema.filter((field) => field.name !== "root").map((field) => ({ name: field.name, type: field.type ?? field.converted_type ?? "group" }));
|
|
8071
|
-
return `${JSON.stringify({ path, rows: Number(totalRows(metadata.row_groups)), columns })}
|
|
8163
|
+
return `${JSON.stringify(await schemaSummary(store, key, path))}
|
|
8072
8164
|
`;
|
|
8073
8165
|
}
|
|
8074
8166
|
async function inspect(args) {
|
|
@@ -8106,7 +8198,7 @@ async function writeRows(outputPrefix, rows, args) {
|
|
|
8106
8198
|
for (const file of result2.files) {
|
|
8107
8199
|
const bytes = await outStore.get(file.path);
|
|
8108
8200
|
if (bytes === null) {
|
|
8109
|
-
throw new
|
|
8201
|
+
throw new LakeqlError("LAKEQL_OBJECT_NOT_FOUND", `No generated output at ${file.path}`, {
|
|
8110
8202
|
path: file.path
|
|
8111
8203
|
});
|
|
8112
8204
|
}
|
|
@@ -8133,7 +8225,7 @@ async function writeRows(outputPrefix, rows, args) {
|
|
|
8133
8225
|
await writeOutputManifest(outStore, args.manifest, manifest);
|
|
8134
8226
|
const bytes = await outStore.get(args.manifest);
|
|
8135
8227
|
if (bytes === null) {
|
|
8136
|
-
throw new
|
|
8228
|
+
throw new LakeqlError("LAKEQL_OBJECT_NOT_FOUND", `No generated output at ${args.manifest}`, {
|
|
8137
8229
|
path: args.manifest
|
|
8138
8230
|
});
|
|
8139
8231
|
}
|
|
@@ -8161,16 +8253,19 @@ function hasAggregation(ast) {
|
|
|
8161
8253
|
async function materializeCteIfNeeded(store, lake, ast) {
|
|
8162
8254
|
if (ast.cte === void 0) return ast;
|
|
8163
8255
|
if (ast.source !== ast.cte.name) {
|
|
8164
|
-
throw new
|
|
8256
|
+
throw new LakeqlError(
|
|
8257
|
+
"LAKEQL_SQL_UNSUPPORTED",
|
|
8258
|
+
"CTEs are only supported as the outer FROM source"
|
|
8259
|
+
);
|
|
8165
8260
|
}
|
|
8166
8261
|
if (ast.join !== void 0 || ast.subqueryJoin !== void 0) {
|
|
8167
|
-
throw new
|
|
8262
|
+
throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "CTEs inside JOINs are not supported yet");
|
|
8168
8263
|
}
|
|
8169
8264
|
const cteRows = hasAggregation(ast.cte.query) ? await aggregateRowsFromAst(lake.path(ast.cte.query.source), ast.cte.query) : await builderFromAst(lake.path(ast.cte.query.source), ast.cte.query).toArray();
|
|
8170
8265
|
if (cteRows.length === 0) {
|
|
8171
|
-
throw new
|
|
8266
|
+
throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Empty CTE results are not supported yet");
|
|
8172
8267
|
}
|
|
8173
|
-
const prefix = `
|
|
8268
|
+
const prefix = `__lakeql_cte/${ast.cte.name}`;
|
|
8174
8269
|
await writePartitionedParquet(store, prefix, {
|
|
8175
8270
|
rows: cteRows,
|
|
8176
8271
|
maxRowsPerFile: cteRows.length
|
|
@@ -8184,7 +8279,7 @@ async function resolveScalarSubqueries(lake, ast) {
|
|
|
8184
8279
|
for (const [id, subquery] of Object.entries(ast.scalarSubqueries)) {
|
|
8185
8280
|
const rows = hasAggregation(subquery.query) ? await aggregateRowsFromAst(lake.path(subquery.query.source), subquery.query) : await builderFromAst(lake.path(subquery.query.source), subquery.query).toArray();
|
|
8186
8281
|
if (rows.length > 1) {
|
|
8187
|
-
throw new
|
|
8282
|
+
throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Scalar subquery returned more than one row");
|
|
8188
8283
|
}
|
|
8189
8284
|
values.set(id, rows.length === 0 ? null : rows[0]?.[subquery.column] ?? null);
|
|
8190
8285
|
}
|
|
@@ -8205,10 +8300,10 @@ async function resolveScalarSubqueries(lake, ast) {
|
|
|
8205
8300
|
function replaceScalarSubqueryExpr(expr, values) {
|
|
8206
8301
|
switch (expr.kind) {
|
|
8207
8302
|
case "call":
|
|
8208
|
-
if (expr.fn === "
|
|
8303
|
+
if (expr.fn === "__lakeql_scalar_subquery") {
|
|
8209
8304
|
const id = expr.args[0];
|
|
8210
8305
|
if (id?.kind !== "literal" || typeof id.value !== "string" || !values.has(id.value)) {
|
|
8211
|
-
throw new
|
|
8306
|
+
throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Invalid scalar subquery placeholder");
|
|
8212
8307
|
}
|
|
8213
8308
|
return { kind: "literal", value: values.get(id.value) };
|
|
8214
8309
|
}
|
|
@@ -8264,10 +8359,10 @@ function replaceScalarSubqueryExpr(expr, values) {
|
|
|
8264
8359
|
}
|
|
8265
8360
|
}
|
|
8266
8361
|
async function joinRowsFromAst(lake, ast, args) {
|
|
8267
|
-
if (ast.join === void 0) throw new
|
|
8362
|
+
if (ast.join === void 0) throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Missing SQL JOIN");
|
|
8268
8363
|
const join = ast.join;
|
|
8269
8364
|
if (hasAggregation(ast)) {
|
|
8270
|
-
throw new
|
|
8365
|
+
throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Aggregate SQL over JOIN is not supported yet");
|
|
8271
8366
|
}
|
|
8272
8367
|
const leftAlias = leftJoinAlias(ast);
|
|
8273
8368
|
const plan = planJoinSides(ast, leftAlias, join.alias);
|
|
@@ -8300,7 +8395,7 @@ async function joinRowsFromAst(lake, ast, args) {
|
|
|
8300
8395
|
return rows;
|
|
8301
8396
|
}
|
|
8302
8397
|
function planJoinSides(ast, leftAlias, rightAlias) {
|
|
8303
|
-
if (ast.join === void 0) throw new
|
|
8398
|
+
if (ast.join === void 0) throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Missing SQL JOIN");
|
|
8304
8399
|
const leftPredicates = [];
|
|
8305
8400
|
const rightPredicates = [];
|
|
8306
8401
|
const residualPredicates = [];
|
|
@@ -8509,11 +8604,11 @@ function fillLeftJoinNulls(rows, rightAlias, rightColumns) {
|
|
|
8509
8604
|
}
|
|
8510
8605
|
async function subqueryJoinRowsFromAst(lake, ast, args) {
|
|
8511
8606
|
if (ast.subqueryJoin === void 0) {
|
|
8512
|
-
throw new
|
|
8607
|
+
throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Missing SQL IN subquery");
|
|
8513
8608
|
}
|
|
8514
8609
|
if (hasAggregation(ast)) {
|
|
8515
|
-
throw new
|
|
8516
|
-
"
|
|
8610
|
+
throw new LakeqlError(
|
|
8611
|
+
"LAKEQL_SQL_UNSUPPORTED",
|
|
8517
8612
|
"Aggregate SQL over IN subquery is not supported yet"
|
|
8518
8613
|
);
|
|
8519
8614
|
}
|
|
@@ -8591,8 +8686,8 @@ function validateAggregateProjection(ast) {
|
|
|
8591
8686
|
const { column } = selectColumn(select);
|
|
8592
8687
|
if (column === "*") continue;
|
|
8593
8688
|
if (!groupColumns.has(column)) {
|
|
8594
|
-
throw new
|
|
8595
|
-
"
|
|
8689
|
+
throw new LakeqlError(
|
|
8690
|
+
"LAKEQL_SQL_UNSUPPORTED",
|
|
8596
8691
|
`Aggregate SQL can only select grouped columns or aggregate expressions, not ${column}`
|
|
8597
8692
|
);
|
|
8598
8693
|
}
|
|
@@ -8622,7 +8717,7 @@ function projectAggregateRows(rows, ast, hiddenCountAlias) {
|
|
|
8622
8717
|
}
|
|
8623
8718
|
function hiddenAggregateAlias(ast) {
|
|
8624
8719
|
const reserved = /* @__PURE__ */ new Set([...ast.select ?? [], ...Object.keys(ast.aggregates ?? {})]);
|
|
8625
|
-
let alias = "
|
|
8720
|
+
let alias = "__lakeql_group_count";
|
|
8626
8721
|
while (reserved.has(alias)) alias = `_${alias}`;
|
|
8627
8722
|
return alias;
|
|
8628
8723
|
}
|
|
@@ -8666,6 +8761,17 @@ function rowsToCsv(rows) {
|
|
|
8666
8761
|
return `${lines.join("\n")}
|
|
8667
8762
|
`;
|
|
8668
8763
|
}
|
|
8764
|
+
function formatRows(rows, format) {
|
|
8765
|
+
if (format === "json") return `${JSON.stringify(rows)}
|
|
8766
|
+
`;
|
|
8767
|
+
if (format === "csv") return rowsToCsv(rows);
|
|
8768
|
+
return rows.map((row) => JSON.stringify(row)).join("\n") + (rows.length > 0 ? "\n" : "");
|
|
8769
|
+
}
|
|
8770
|
+
async function schemaSummary(store, key, displayPath = key) {
|
|
8771
|
+
const metadata = await readParquetMetadata(store, key);
|
|
8772
|
+
const columns = metadata.schema.filter((field) => field.name !== "root").map((field) => ({ name: field.name, type: field.type ?? field.converted_type ?? "group" }));
|
|
8773
|
+
return { path: displayPath, rows: Number(totalRows(metadata.row_groups)), columns };
|
|
8774
|
+
}
|
|
8669
8775
|
function csvCell(value2) {
|
|
8670
8776
|
if (value2 === null || value2 === void 0) return "";
|
|
8671
8777
|
const text = String(value2);
|
|
@@ -8679,20 +8785,38 @@ function parseCliSql(sql, defaultSource, preserveSqlSource = false) {
|
|
|
8679
8785
|
}
|
|
8680
8786
|
return { ...parseSql(addDefaultFrom(trimmed)), source: defaultSource };
|
|
8681
8787
|
}
|
|
8682
|
-
function
|
|
8788
|
+
function parseCliSqlStatement(sql, defaultSource, preserveSqlSource = false) {
|
|
8789
|
+
const trimmed = sql.trim();
|
|
8790
|
+
if (/^\s*describe\b/iu.test(trimmed)) {
|
|
8791
|
+
const statement = parseSqlStatement(trimmed);
|
|
8792
|
+
if (!isDescribeStatement(statement)) return statement;
|
|
8793
|
+
if (preserveSqlSource || statement.source !== "input") return statement;
|
|
8794
|
+
return { ...statement, source: defaultSource };
|
|
8795
|
+
}
|
|
8796
|
+
return parseCliSql(sql, defaultSource, preserveSqlSource);
|
|
8797
|
+
}
|
|
8798
|
+
function isDescribeStatement(statement) {
|
|
8799
|
+
return "type" in statement && statement.type === "describe";
|
|
8800
|
+
}
|
|
8801
|
+
function applyDefaultSource(ast, defaultSource, includeScalarSubqueries = true, seen = /* @__PURE__ */ new WeakSet()) {
|
|
8802
|
+
if (seen.has(ast)) return ast.cte === void 0 ? { ...ast, source: defaultSource } : ast;
|
|
8803
|
+
seen.add(ast);
|
|
8683
8804
|
const out = { ...ast };
|
|
8684
8805
|
if (out.cte !== void 0) {
|
|
8685
|
-
out.cte = {
|
|
8806
|
+
out.cte = {
|
|
8807
|
+
...out.cte,
|
|
8808
|
+
query: applyDefaultSource(out.cte.query, defaultSource, includeScalarSubqueries, seen)
|
|
8809
|
+
};
|
|
8686
8810
|
} else {
|
|
8687
8811
|
out.source = defaultSource;
|
|
8688
8812
|
}
|
|
8689
8813
|
if (out.subqueryJoin !== void 0)
|
|
8690
8814
|
out.subqueryJoin = { ...out.subqueryJoin, source: defaultSource };
|
|
8691
|
-
if (out.scalarSubqueries !== void 0) {
|
|
8815
|
+
if (includeScalarSubqueries && out.scalarSubqueries !== void 0) {
|
|
8692
8816
|
out.scalarSubqueries = Object.fromEntries(
|
|
8693
8817
|
Object.entries(out.scalarSubqueries).map(([id, subquery]) => [
|
|
8694
8818
|
id,
|
|
8695
|
-
{ ...subquery, query: applyDefaultSource(subquery.query, defaultSource) }
|
|
8819
|
+
{ ...subquery, query: applyDefaultSource(subquery.query, defaultSource, false, seen) }
|
|
8696
8820
|
])
|
|
8697
8821
|
);
|
|
8698
8822
|
}
|
|
@@ -8700,7 +8824,7 @@ function applyDefaultSource(ast, defaultSource) {
|
|
|
8700
8824
|
}
|
|
8701
8825
|
function addDefaultFrom(sql) {
|
|
8702
8826
|
if (!/^\s*select\b/iu.test(sql)) {
|
|
8703
|
-
throw new
|
|
8827
|
+
throw new LakeqlError("LAKEQL_PARSE_ERROR", "SQL must start with SELECT");
|
|
8704
8828
|
}
|
|
8705
8829
|
const clause = /\b(where|group\s+by|having|order\s+by|limit|offset)\b/iu.exec(sql);
|
|
8706
8830
|
if (clause === null || clause.index === void 0) return `${sql} from input`;
|
|
@@ -8763,45 +8887,45 @@ function parseArgs(argv) {
|
|
|
8763
8887
|
} else if (arg === "--job-id") {
|
|
8764
8888
|
index += 1;
|
|
8765
8889
|
args.jobId = requireValue(rest, index, arg);
|
|
8766
|
-
} else throw new
|
|
8890
|
+
} else throw new LakeqlError("LAKEQL_PARSE_ERROR", `Unknown argument ${arg}`);
|
|
8767
8891
|
}
|
|
8768
8892
|
return args;
|
|
8769
8893
|
}
|
|
8770
8894
|
function parseTableArg(value2) {
|
|
8771
8895
|
const separator = value2.indexOf("=");
|
|
8772
8896
|
if (separator <= 0 || separator === value2.length - 1) {
|
|
8773
|
-
throw new
|
|
8897
|
+
throw new LakeqlError("LAKEQL_PARSE_ERROR", "--table must be name=path.parquet");
|
|
8774
8898
|
}
|
|
8775
8899
|
const name = value2.slice(0, separator);
|
|
8776
8900
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name)) {
|
|
8777
|
-
throw new
|
|
8901
|
+
throw new LakeqlError("LAKEQL_PARSE_ERROR", "--table name must be a SQL identifier");
|
|
8778
8902
|
}
|
|
8779
8903
|
return { name, path: value2.slice(separator + 1) };
|
|
8780
8904
|
}
|
|
8781
8905
|
function parseFormat(value2) {
|
|
8782
8906
|
if (value2 === "csv" || value2 === "json" || value2 === "ndjson") return value2;
|
|
8783
|
-
throw new
|
|
8907
|
+
throw new LakeqlError("LAKEQL_PARSE_ERROR", "--format must be csv, json, or ndjson");
|
|
8784
8908
|
}
|
|
8785
8909
|
function requireValue(args, index, flag) {
|
|
8786
8910
|
const value2 = args[index];
|
|
8787
8911
|
if (value2 === void 0 || value2.startsWith("--")) {
|
|
8788
|
-
throw new
|
|
8912
|
+
throw new LakeqlError("LAKEQL_PARSE_ERROR", `${flag} requires a value`);
|
|
8789
8913
|
}
|
|
8790
8914
|
return value2;
|
|
8791
8915
|
}
|
|
8792
8916
|
function requireOption(value2, flag) {
|
|
8793
|
-
if (value2 === void 0) throw new
|
|
8917
|
+
if (value2 === void 0) throw new LakeqlError("LAKEQL_PARSE_ERROR", `${flag} is required`);
|
|
8794
8918
|
return value2;
|
|
8795
8919
|
}
|
|
8796
8920
|
function parseCsv(value2, flag) {
|
|
8797
8921
|
const values = value2.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
|
|
8798
|
-
if (values.length === 0) throw new
|
|
8922
|
+
if (values.length === 0) throw new LakeqlError("LAKEQL_PARSE_ERROR", `${flag} must not be empty`);
|
|
8799
8923
|
return values;
|
|
8800
8924
|
}
|
|
8801
8925
|
function parsePositiveInt(value2, flag) {
|
|
8802
8926
|
const parsed = Number(value2);
|
|
8803
8927
|
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
8804
|
-
throw new
|
|
8928
|
+
throw new LakeqlError("LAKEQL_PARSE_ERROR", `${flag} must be a positive integer`);
|
|
8805
8929
|
}
|
|
8806
8930
|
return parsed;
|
|
8807
8931
|
}
|