lakeql 0.2.0 → 0.5.1
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 +470 -169
- package/dist/{chunk-32PE6IK4.js → chunk-AUL4DYT4.js} +1076 -99
- package/dist/{chunk-3WUY56UD.js → chunk-TR4JNJ47.js} +1274 -172
- package/dist/cloudflare.d.ts +1 -1
- package/dist/cloudflare.js +2 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +2 -2
- package/dist/node.d.ts +1 -1
- package/dist/node.js +3 -3
- package/package.json +6 -6
package/dist/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createParquetLake, readParquetMetadata, memoryStore, parseSql, writePartitionedParquet, broadcastJoin, partitionedParquetOutputEntries, parseSqlStatement } from './chunk-
|
|
2
|
+
import { createParquetLake, readParquetMetadata, memoryStore, parseSql, writePartitionedParquet, broadcastJoin, crossJoin, partitionedParquetOutputEntries, parseSqlStatement } from './chunk-AUL4DYT4.js';
|
|
3
3
|
import { createOutputManifest, fingerprint, writeOutputManifest } from './chunk-LWF5FKHB.js';
|
|
4
4
|
import { LakeqlError, matches, evaluate } from './chunk-WGHR35QU.js';
|
|
5
5
|
import { readFile, mkdir, writeFile } from 'fs/promises';
|
|
@@ -47,7 +47,7 @@ function usage() {
|
|
|
47
47
|
"commands:",
|
|
48
48
|
" compact --path <file.parquet> --output <prefix> [--max-rows-per-file n]",
|
|
49
49
|
" query --path <file.parquet> --sql <query> [--format csv|json|ndjson]",
|
|
50
|
-
" query --table name=file.parquet [--table name=file.parquet ...] --sql <join-query> [--join-max-right-rows n]",
|
|
50
|
+
" query --table name=file.parquet [--table name=file.parquet ...] --sql <join-query> [--join-max-right-rows n] [--join-max-output-rows n]",
|
|
51
51
|
" explain --path <file.parquet> --sql <query>",
|
|
52
52
|
" inspect --path <file.parquet>",
|
|
53
53
|
" write --path <file.parquet> --sql <query> --output <prefix> [--partition-by a,b] [--max-rows-per-file n] [--manifest <path>] [--job-id id]",
|
|
@@ -70,21 +70,20 @@ async function query(args) {
|
|
|
70
70
|
const executableAst = await materializeCteIfNeeded(store, lake, ast);
|
|
71
71
|
const executableLake = createParquetLake({ store });
|
|
72
72
|
const resolvedAst = await resolveScalarSubqueries(executableLake, executableAst);
|
|
73
|
-
if (resolvedAst.
|
|
73
|
+
if (sqlSubqueryJoins(resolvedAst).length > 0) {
|
|
74
74
|
const rows = await subqueryJoinRowsFromAst(executableLake, resolvedAst, args);
|
|
75
75
|
if (args.format === "json") return `${jsonStringify(rows)}
|
|
76
76
|
`;
|
|
77
77
|
if (args.format === "csv") return rowsToCsv(rows);
|
|
78
78
|
return rows.map((row) => jsonStringify(row)).join("\n") + (rows.length > 0 ? "\n" : "");
|
|
79
79
|
}
|
|
80
|
-
if (resolvedAst.
|
|
80
|
+
if (sqlJoins(resolvedAst).length > 0) {
|
|
81
81
|
const rows = await joinRowsFromAst(executableLake, resolvedAst, args);
|
|
82
82
|
if (args.format === "json") return `${jsonStringify(rows)}
|
|
83
83
|
`;
|
|
84
84
|
if (args.format === "csv") return rowsToCsv(rows);
|
|
85
85
|
return rows.map((row) => jsonStringify(row)).join("\n") + (rows.length > 0 ? "\n" : "");
|
|
86
86
|
}
|
|
87
|
-
const result2 = builderFromAst(executableLake.path(resolvedAst.source), resolvedAst);
|
|
88
87
|
if (hasAggregation(resolvedAst)) {
|
|
89
88
|
const rows = await aggregateRowsFromAst(executableLake.path(resolvedAst.source), resolvedAst);
|
|
90
89
|
if (args.format === "json") return `${jsonStringify(rows)}
|
|
@@ -92,6 +91,14 @@ async function query(args) {
|
|
|
92
91
|
if (args.format === "csv") return rowsToCsv(rows);
|
|
93
92
|
return rows.map((row) => jsonStringify(row)).join("\n") + (rows.length > 0 ? "\n" : "");
|
|
94
93
|
}
|
|
94
|
+
if (hasCorrelatedScalarSubqueries(resolvedAst)) {
|
|
95
|
+
const rows = await correlatedScalarRowsFromAst(executableLake, resolvedAst);
|
|
96
|
+
if (args.format === "json") return `${jsonStringify(rows)}
|
|
97
|
+
`;
|
|
98
|
+
if (args.format === "csv") return rowsToCsv(rows);
|
|
99
|
+
return rows.map((row) => jsonStringify(row)).join("\n") + (rows.length > 0 ? "\n" : "");
|
|
100
|
+
}
|
|
101
|
+
const result2 = builderFromAst(executableLake.path(resolvedAst.source), resolvedAst);
|
|
95
102
|
if (args.format === "json") return `${jsonStringify(await result2.toArray())}
|
|
96
103
|
`;
|
|
97
104
|
if (args.format === "csv") return new Response(result2.streamCsv()).text();
|
|
@@ -147,7 +154,7 @@ async function write(args) {
|
|
|
147
154
|
const executableAst = await materializeCteIfNeeded(store, lake, ast);
|
|
148
155
|
const executableLake = createParquetLake({ store });
|
|
149
156
|
const resolvedAst = await resolveScalarSubqueries(executableLake, executableAst);
|
|
150
|
-
const rows = resolvedAst.
|
|
157
|
+
const rows = sqlSubqueryJoins(resolvedAst).length > 0 ? await subqueryJoinRowsFromAst(executableLake, resolvedAst, args) : sqlJoins(resolvedAst).length > 0 ? await joinRowsFromAst(executableLake, resolvedAst, args) : hasAggregation(resolvedAst) ? await aggregateRowsFromAst(executableLake.path(resolvedAst.source), resolvedAst) : hasCorrelatedScalarSubqueries(resolvedAst) ? await correlatedScalarRowsFromAst(executableLake, resolvedAst) : await builderFromAst(executableLake.path(resolvedAst.source), resolvedAst).toArray();
|
|
151
158
|
return writeRows(outputPrefix, rows, args);
|
|
152
159
|
}
|
|
153
160
|
async function writeRows(outputPrefix, rows, args) {
|
|
@@ -226,7 +233,7 @@ async function materializeCteIfNeeded(store, lake, ast) {
|
|
|
226
233
|
"CTEs are only supported as the outer FROM source"
|
|
227
234
|
);
|
|
228
235
|
}
|
|
229
|
-
if (ast.
|
|
236
|
+
if (sqlJoins(ast).length > 0 || sqlSubqueryJoins(ast).length > 0) {
|
|
230
237
|
throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "CTEs inside JOINs are not supported yet");
|
|
231
238
|
}
|
|
232
239
|
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();
|
|
@@ -246,6 +253,10 @@ async function resolveScalarSubqueries(lake, ast) {
|
|
|
246
253
|
const values = /* @__PURE__ */ new Map();
|
|
247
254
|
for (const [id, subquery] of Object.entries(ast.scalarSubqueries)) {
|
|
248
255
|
const rows = hasAggregation(subquery.query) ? await aggregateRowsFromAst(lake.path(subquery.query.source), subquery.query) : await builderFromAst(lake.path(subquery.query.source), subquery.query).toArray();
|
|
256
|
+
if (subquery.mode === "exists") {
|
|
257
|
+
values.set(id, rows.length > 0);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
249
260
|
if (rows.length > 1) {
|
|
250
261
|
throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Scalar subquery returned more than one row");
|
|
251
262
|
}
|
|
@@ -262,6 +273,21 @@ async function resolveScalarSubqueries(lake, ast) {
|
|
|
262
273
|
])
|
|
263
274
|
);
|
|
264
275
|
}
|
|
276
|
+
if (ast.subqueryJoins !== void 0) {
|
|
277
|
+
out.subqueryJoins = ast.subqueryJoins.map((join) => ({
|
|
278
|
+
...join,
|
|
279
|
+
...join.where === void 0 ? {} : { where: replaceScalarSubqueryExpr(join.where, values) },
|
|
280
|
+
...join.predicate === void 0 ? {} : { predicate: replaceScalarSubqueryExpr(join.predicate, values) }
|
|
281
|
+
}));
|
|
282
|
+
const firstSubqueryJoin = out.subqueryJoins[0];
|
|
283
|
+
if (firstSubqueryJoin !== void 0) out.subqueryJoin = firstSubqueryJoin;
|
|
284
|
+
} else if (ast.subqueryJoin?.where !== void 0 || ast.subqueryJoin?.predicate !== void 0) {
|
|
285
|
+
out.subqueryJoin = {
|
|
286
|
+
...ast.subqueryJoin,
|
|
287
|
+
...ast.subqueryJoin.where === void 0 ? {} : { where: replaceScalarSubqueryExpr(ast.subqueryJoin.where, values) },
|
|
288
|
+
...ast.subqueryJoin.predicate === void 0 ? {} : { predicate: replaceScalarSubqueryExpr(ast.subqueryJoin.predicate, values) }
|
|
289
|
+
};
|
|
290
|
+
}
|
|
265
291
|
delete out.scalarSubqueries;
|
|
266
292
|
return out;
|
|
267
293
|
}
|
|
@@ -327,81 +353,91 @@ function replaceScalarSubqueryExpr(expr, values) {
|
|
|
327
353
|
}
|
|
328
354
|
}
|
|
329
355
|
async function joinRowsFromAst(lake, ast, args) {
|
|
330
|
-
|
|
331
|
-
const
|
|
356
|
+
const joins = sqlJoins(ast);
|
|
357
|
+
const firstJoin = joins[0];
|
|
358
|
+
if (firstJoin === void 0) {
|
|
359
|
+
throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Missing SQL JOIN");
|
|
360
|
+
}
|
|
332
361
|
if (hasWindowSemantics(ast)) {
|
|
333
362
|
throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Window SQL over JOIN is not supported");
|
|
334
363
|
}
|
|
335
364
|
if (hasAggregation(ast)) {
|
|
336
365
|
throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Aggregate SQL over JOIN is not supported yet");
|
|
337
366
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
367
|
+
let rows = (await lake.path(ast.source).toArray()).map(
|
|
368
|
+
(row) => qualifyRow(row, firstJoin.leftAlias)
|
|
369
|
+
);
|
|
370
|
+
for (const join of joins) {
|
|
371
|
+
const rightRows = (await lake.path(join.source).toArray()).map(
|
|
372
|
+
(row) => qualifyRow(row, join.alias)
|
|
373
|
+
);
|
|
374
|
+
if (join.type === "right") {
|
|
375
|
+
const leftRows = rows;
|
|
376
|
+
rows = await broadcastJoin(rightRows, leftRows, {
|
|
377
|
+
leftKey: join.rightKey,
|
|
378
|
+
rightKey: join.leftKey,
|
|
379
|
+
type: "left",
|
|
380
|
+
rightPrefix: `${join.leftAlias}.`,
|
|
381
|
+
maxRightRows: args.joinMaxRightRows ?? 1e5
|
|
382
|
+
});
|
|
383
|
+
rows = fillRightJoinNulls(rows, ast, join.alias, leftRows);
|
|
384
|
+
} else if (join.type === "full") {
|
|
385
|
+
const leftRows = rows;
|
|
386
|
+
const leftJoined = await broadcastJoin(leftRows, rightRows, {
|
|
387
|
+
leftKey: join.leftKey,
|
|
388
|
+
rightKey: join.rightKey,
|
|
389
|
+
type: "left",
|
|
390
|
+
rightPrefix: `${join.alias}.`,
|
|
391
|
+
maxRightRows: args.joinMaxRightRows ?? 1e5
|
|
392
|
+
});
|
|
393
|
+
const unmatchedRight = await broadcastJoin(rightRows, leftRows, {
|
|
394
|
+
leftKey: join.rightKey,
|
|
395
|
+
rightKey: join.leftKey,
|
|
396
|
+
type: "anti",
|
|
397
|
+
rightPrefix: `${join.leftAlias}.`,
|
|
398
|
+
maxRightRows: args.joinMaxRightRows ?? 1e5
|
|
399
|
+
});
|
|
400
|
+
rows = [
|
|
401
|
+
...fillLeftJoinNulls(
|
|
402
|
+
leftJoined,
|
|
403
|
+
join.alias,
|
|
404
|
+
leftJoinRightColumns(ast, join.alias, rightRows)
|
|
405
|
+
),
|
|
406
|
+
...fillRightJoinNulls(unmatchedRight, ast, join.alias, leftRows)
|
|
407
|
+
];
|
|
408
|
+
} else {
|
|
409
|
+
rows = join.type === "cross" ? await crossJoin(rows, rightRows, {
|
|
410
|
+
rightPrefix: `${join.alias}.`,
|
|
411
|
+
maxRightRows: args.joinMaxRightRows ?? 1e5,
|
|
412
|
+
maxOutputRows: args.joinMaxOutputRows ?? 1e5
|
|
413
|
+
}) : await broadcastJoin(rows, rightRows, {
|
|
414
|
+
leftKey: join.leftKey,
|
|
415
|
+
rightKey: join.rightKey,
|
|
416
|
+
type: join.type,
|
|
417
|
+
rightPrefix: `${join.alias}.`,
|
|
418
|
+
maxRightRows: args.joinMaxRightRows ?? 1e5
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
if (join.type === "left") {
|
|
422
|
+
rows = fillLeftJoinNulls(rows, join.alias, leftJoinRightColumns(ast, join.alias, rightRows));
|
|
423
|
+
}
|
|
357
424
|
}
|
|
358
|
-
if (
|
|
359
|
-
rows = rows.filter((row) => matches(plan.residualWhere, row));
|
|
425
|
+
if (ast.where !== void 0) rows = rows.filter((row) => matches(ast.where, row));
|
|
360
426
|
if (ast.orderBy !== void 0) rows = sortRows(rows, ast.orderBy);
|
|
361
|
-
rows = projectRows(rows, ast);
|
|
427
|
+
rows = await projectRows(lake, rows, ast);
|
|
362
428
|
if (ast.distinct === true) rows = distinctRows(rows);
|
|
363
429
|
const offset = ast.offset ?? 0;
|
|
364
430
|
if (ast.limit !== void 0) rows = rows.slice(offset, offset + ast.limit);
|
|
365
431
|
else if (offset > 0) rows = rows.slice(offset);
|
|
366
432
|
return rows;
|
|
367
433
|
}
|
|
368
|
-
function
|
|
369
|
-
if (ast.
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
if (columns.length > 0 && columns.every((column) => isQualifiedBy(column, leftAlias))) {
|
|
376
|
-
leftPredicates.push(stripQualifiedExpr(predicate, leftAlias));
|
|
377
|
-
} else if (ast.join.type === "inner" && columns.length > 0 && columns.every((column) => isQualifiedBy(column, rightAlias))) {
|
|
378
|
-
rightPredicates.push(stripQualifiedExpr(predicate, rightAlias));
|
|
379
|
-
} else {
|
|
380
|
-
residualPredicates.push(predicate);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
const plan = {};
|
|
384
|
-
const leftWhere = combineAndPredicate(leftPredicates);
|
|
385
|
-
const rightWhere = combineAndPredicate(rightPredicates);
|
|
386
|
-
const residualWhere = combineAndPredicate(residualPredicates);
|
|
387
|
-
if (leftWhere !== void 0) plan.leftWhere = leftWhere;
|
|
388
|
-
if (rightWhere !== void 0) plan.rightWhere = rightWhere;
|
|
389
|
-
if (residualWhere !== void 0) plan.residualWhere = residualWhere;
|
|
390
|
-
if (canProjectJoinSides(ast, leftAlias, rightAlias)) {
|
|
391
|
-
plan.leftColumns = joinSideColumns(ast, leftAlias, ast.join.leftKey);
|
|
392
|
-
plan.rightColumns = joinSideColumns(ast, rightAlias, ast.join.rightKey);
|
|
393
|
-
}
|
|
394
|
-
return plan;
|
|
395
|
-
}
|
|
396
|
-
function splitAndPredicate(expr) {
|
|
397
|
-
if (expr === void 0) return [];
|
|
398
|
-
if (expr.kind === "logical" && expr.op === "and") return expr.operands.flatMap(splitAndPredicate);
|
|
399
|
-
return [expr];
|
|
400
|
-
}
|
|
401
|
-
function combineAndPredicate(predicates) {
|
|
402
|
-
if (predicates.length === 0) return void 0;
|
|
403
|
-
if (predicates.length === 1) return predicates[0];
|
|
404
|
-
return { kind: "logical", op: "and", operands: predicates };
|
|
434
|
+
function sqlJoins(ast) {
|
|
435
|
+
if (ast.joins !== void 0) return ast.joins;
|
|
436
|
+
return ast.join === void 0 ? [] : [ast.join];
|
|
437
|
+
}
|
|
438
|
+
function sqlSubqueryJoins(ast) {
|
|
439
|
+
if (ast.subqueryJoins !== void 0) return ast.subqueryJoins;
|
|
440
|
+
return ast.subqueryJoin === void 0 ? [] : [ast.subqueryJoin];
|
|
405
441
|
}
|
|
406
442
|
function isQualifiedBy(column, alias) {
|
|
407
443
|
return column.startsWith(`${alias}.`) && column.length > alias.length + 1;
|
|
@@ -409,66 +445,6 @@ function isQualifiedBy(column, alias) {
|
|
|
409
445
|
function stripQualifiedColumn(column, alias) {
|
|
410
446
|
return isQualifiedBy(column, alias) ? column.slice(alias.length + 1) : column;
|
|
411
447
|
}
|
|
412
|
-
function stripQualifiedExpr(expr, alias) {
|
|
413
|
-
switch (expr.kind) {
|
|
414
|
-
case "column":
|
|
415
|
-
return { ...expr, name: stripQualifiedColumn(expr.name, alias) };
|
|
416
|
-
case "compare":
|
|
417
|
-
return {
|
|
418
|
-
...expr,
|
|
419
|
-
left: stripQualifiedExpr(expr.left, alias),
|
|
420
|
-
right: stripQualifiedExpr(expr.right, alias)
|
|
421
|
-
};
|
|
422
|
-
case "between":
|
|
423
|
-
return {
|
|
424
|
-
...expr,
|
|
425
|
-
target: stripQualifiedExpr(expr.target, alias),
|
|
426
|
-
low: stripQualifiedExpr(expr.low, alias),
|
|
427
|
-
high: stripQualifiedExpr(expr.high, alias)
|
|
428
|
-
};
|
|
429
|
-
case "in":
|
|
430
|
-
return {
|
|
431
|
-
...expr,
|
|
432
|
-
target: stripQualifiedExpr(expr.target, alias),
|
|
433
|
-
values: expr.values.map((value) => stripQualifiedExpr(value, alias))
|
|
434
|
-
};
|
|
435
|
-
case "logical":
|
|
436
|
-
return {
|
|
437
|
-
...expr,
|
|
438
|
-
operands: expr.operands.map((operand) => stripQualifiedExpr(operand, alias))
|
|
439
|
-
};
|
|
440
|
-
case "not":
|
|
441
|
-
return { ...expr, operand: stripQualifiedExpr(expr.operand, alias) };
|
|
442
|
-
case "null-check":
|
|
443
|
-
return { ...expr, target: stripQualifiedExpr(expr.target, alias) };
|
|
444
|
-
case "like":
|
|
445
|
-
return { ...expr, target: stripQualifiedExpr(expr.target, alias) };
|
|
446
|
-
case "call":
|
|
447
|
-
return { ...expr, args: expr.args.map((arg) => stripQualifiedExpr(arg, alias)) };
|
|
448
|
-
case "arithmetic":
|
|
449
|
-
return {
|
|
450
|
-
...expr,
|
|
451
|
-
left: stripQualifiedExpr(expr.left, alias),
|
|
452
|
-
right: stripQualifiedExpr(expr.right, alias)
|
|
453
|
-
};
|
|
454
|
-
case "case":
|
|
455
|
-
return {
|
|
456
|
-
...expr,
|
|
457
|
-
whens: expr.whens.map((branch) => ({
|
|
458
|
-
when: stripQualifiedExpr(branch.when, alias),
|
|
459
|
-
value: stripQualifiedExpr(branch.value, alias)
|
|
460
|
-
})),
|
|
461
|
-
...expr.else === void 0 ? {} : { else: stripQualifiedExpr(expr.else, alias) }
|
|
462
|
-
};
|
|
463
|
-
case "literal":
|
|
464
|
-
return expr;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
function exprColumns(expr) {
|
|
468
|
-
const columns = /* @__PURE__ */ new Set();
|
|
469
|
-
collectExprColumns(expr, columns);
|
|
470
|
-
return [...columns];
|
|
471
|
-
}
|
|
472
448
|
function collectExprColumns(expr, columns) {
|
|
473
449
|
switch (expr.kind) {
|
|
474
450
|
case "column":
|
|
@@ -517,12 +493,6 @@ function collectExprColumns(expr, columns) {
|
|
|
517
493
|
return;
|
|
518
494
|
}
|
|
519
495
|
}
|
|
520
|
-
function canProjectJoinSides(ast, leftAlias, rightAlias) {
|
|
521
|
-
if (ast.select?.includes("*") ?? false) return false;
|
|
522
|
-
return referencedJoinColumns(ast).every(
|
|
523
|
-
(column) => isQualifiedBy(column, leftAlias) || isQualifiedBy(column, rightAlias)
|
|
524
|
-
);
|
|
525
|
-
}
|
|
526
496
|
function referencedJoinColumns(ast) {
|
|
527
497
|
const columns = /* @__PURE__ */ new Set();
|
|
528
498
|
for (const select of ast.select ?? []) {
|
|
@@ -534,17 +504,6 @@ function referencedJoinColumns(ast) {
|
|
|
534
504
|
for (const term of ast.orderBy ?? []) columns.add(term.column);
|
|
535
505
|
return [...columns].filter((column) => column !== "*");
|
|
536
506
|
}
|
|
537
|
-
function joinSideColumns(ast, alias, joinKey) {
|
|
538
|
-
const columns = new Set(
|
|
539
|
-
(Array.isArray(joinKey) ? joinKey : [joinKey]).map(
|
|
540
|
-
(column) => stripQualifiedColumn(column, alias)
|
|
541
|
-
)
|
|
542
|
-
);
|
|
543
|
-
for (const column of referencedJoinColumns(ast)) {
|
|
544
|
-
if (isQualifiedBy(column, alias)) columns.add(stripQualifiedColumn(column, alias));
|
|
545
|
-
}
|
|
546
|
-
return [...columns];
|
|
547
|
-
}
|
|
548
507
|
function leftJoinRightColumns(ast, rightAlias, rightRows) {
|
|
549
508
|
const columns = /* @__PURE__ */ new Set();
|
|
550
509
|
if (ast.select?.includes("*") ?? false) {
|
|
@@ -573,47 +532,241 @@ function fillLeftJoinNulls(rows, rightAlias, rightColumns) {
|
|
|
573
532
|
return out ?? row;
|
|
574
533
|
});
|
|
575
534
|
}
|
|
535
|
+
function fillRightJoinNulls(rows, ast, preservedAlias, leftRows) {
|
|
536
|
+
const columns = rightJoinLeftColumns(ast, preservedAlias, leftRows);
|
|
537
|
+
if (columns.length === 0) return rows;
|
|
538
|
+
return rows.map((row) => {
|
|
539
|
+
let out;
|
|
540
|
+
for (const column of columns) {
|
|
541
|
+
if (column in row) continue;
|
|
542
|
+
out ??= { ...row };
|
|
543
|
+
out[column] = null;
|
|
544
|
+
}
|
|
545
|
+
return out ?? row;
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
function rightJoinLeftColumns(ast, preservedAlias, leftRows) {
|
|
549
|
+
const columns = /* @__PURE__ */ new Set();
|
|
550
|
+
if (ast.select?.includes("*") ?? false) {
|
|
551
|
+
for (const row of leftRows) {
|
|
552
|
+
for (const column of Object.keys(row)) {
|
|
553
|
+
if (!isQualifiedBy(column, preservedAlias)) columns.add(column);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
for (const column of referencedJoinColumns(ast)) {
|
|
558
|
+
if (!isQualifiedBy(column, preservedAlias)) columns.add(column);
|
|
559
|
+
}
|
|
560
|
+
return [...columns];
|
|
561
|
+
}
|
|
576
562
|
async function subqueryJoinRowsFromAst(lake, ast, args) {
|
|
577
|
-
|
|
563
|
+
const joins = sqlSubqueryJoins(ast);
|
|
564
|
+
if (joins.length === 0) {
|
|
578
565
|
throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Missing SQL IN subquery");
|
|
579
566
|
}
|
|
580
567
|
if (hasWindowSemantics(ast)) {
|
|
581
568
|
throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Window SQL over IN subquery is not supported");
|
|
582
569
|
}
|
|
583
570
|
if (hasAggregation(ast)) {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
571
|
+
return await aggregateSubqueryJoinRowsFromAst(lake, ast, joins, args);
|
|
572
|
+
}
|
|
573
|
+
let rows = await lake.path(ast.source).toArray();
|
|
574
|
+
for (const join of joins) {
|
|
575
|
+
rows = await applySubqueryJoinRows(lake, rows, join, args);
|
|
588
576
|
}
|
|
589
|
-
const join = ast.subqueryJoin;
|
|
590
|
-
let rightRows = await lake.path(join.source).toArray();
|
|
591
|
-
if (join.where !== void 0) rightRows = rightRows.filter((row) => matches(join.where, row));
|
|
592
|
-
let rows = await broadcastJoin(await lake.path(ast.source).toArray(), rightRows, {
|
|
593
|
-
leftKey: join.leftKey,
|
|
594
|
-
rightKey: join.rightKey,
|
|
595
|
-
type: join.type,
|
|
596
|
-
maxRightRows: args.joinMaxRightRows ?? 1e5
|
|
597
|
-
});
|
|
598
577
|
if (ast.where !== void 0) rows = rows.filter((row) => matches(ast.where, row));
|
|
599
578
|
if (ast.orderBy !== void 0) rows = sortRows(rows, ast.orderBy);
|
|
600
|
-
rows = projectRows(rows, ast);
|
|
579
|
+
rows = await projectRows(lake, rows, ast);
|
|
601
580
|
if (ast.distinct === true) rows = distinctRows(rows);
|
|
602
581
|
const offset = ast.offset ?? 0;
|
|
603
582
|
if (ast.limit !== void 0) rows = rows.slice(offset, offset + ast.limit);
|
|
604
583
|
else if (offset > 0) rows = rows.slice(offset);
|
|
605
584
|
return rows;
|
|
606
585
|
}
|
|
607
|
-
function
|
|
608
|
-
|
|
609
|
-
|
|
586
|
+
async function aggregateSubqueryJoinRowsFromAst(lake, ast, joins, args) {
|
|
587
|
+
let rows = await lake.path(ast.source).toArray();
|
|
588
|
+
for (const join of joins) rows = await applySubqueryJoinRows(lake, rows, join, args);
|
|
589
|
+
if (ast.where !== void 0) rows = rows.filter((row) => matches(ast.where, row));
|
|
590
|
+
const aggregateAst = subqueryJoinAggregateAst(ast);
|
|
591
|
+
if (rows.length === 0) {
|
|
592
|
+
if ((ast.groupBy?.length ?? 0) > 0) return [];
|
|
593
|
+
return emptyAggregateRowsFromAst(aggregateAst);
|
|
594
|
+
}
|
|
595
|
+
const prefix = `__lakeql_subquery/${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
596
|
+
const written = await writePartitionedParquet(lake.store, prefix, {
|
|
597
|
+
rows,
|
|
598
|
+
maxRowsPerFile: rows.length
|
|
599
|
+
});
|
|
600
|
+
try {
|
|
601
|
+
return await aggregateRowsFromAst(lake.path(`${prefix}/*.parquet`), {
|
|
602
|
+
...aggregateAst,
|
|
603
|
+
source: `${prefix}/*.parquet`
|
|
604
|
+
});
|
|
605
|
+
} finally {
|
|
606
|
+
await Promise.all(written.files.map((file) => lake.store.delete(file.path)));
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
function subqueryJoinAggregateAst(ast) {
|
|
610
|
+
const {
|
|
611
|
+
subqueryJoin: _subqueryJoin,
|
|
612
|
+
subqueryJoins: _subqueryJoins,
|
|
613
|
+
where: _where,
|
|
614
|
+
...rest
|
|
615
|
+
} = ast;
|
|
616
|
+
return rest;
|
|
617
|
+
}
|
|
618
|
+
function emptyAggregateRowsFromAst(ast) {
|
|
619
|
+
validateAggregateProjection(ast);
|
|
620
|
+
const hiddenCountAlias = hiddenAggregateAlias(ast);
|
|
621
|
+
const aggregateSpec = ast.aggregates === void 0 || Object.keys(ast.aggregates).length === 0 ? { [hiddenCountAlias]: { op: "count" } } : ast.aggregates;
|
|
622
|
+
let rows = [emptyAggregateRow(aggregateSpec)];
|
|
623
|
+
if (ast.having !== void 0) rows = rows.filter((row) => matches(ast.having, row));
|
|
624
|
+
rows = projectAggregateRows(rows, ast, hiddenCountAlias);
|
|
625
|
+
if (ast.distinct === true) rows = distinctRows(rows);
|
|
626
|
+
if (ast.orderBy !== void 0) rows = sortRows(rows, ast.orderBy);
|
|
627
|
+
const offset = ast.offset ?? 0;
|
|
628
|
+
if (ast.limit !== void 0) rows = rows.slice(offset, offset + ast.limit);
|
|
629
|
+
else if (offset > 0) rows = rows.slice(offset);
|
|
630
|
+
return rows;
|
|
631
|
+
}
|
|
632
|
+
function emptyAggregateRow(spec) {
|
|
633
|
+
return Object.fromEntries(
|
|
634
|
+
Object.entries(spec).map(([alias, aggregate]) => [alias, emptyAggregateValue(aggregate.op)])
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
function emptyAggregateValue(op) {
|
|
638
|
+
switch (op) {
|
|
639
|
+
case "count":
|
|
640
|
+
case "count_distinct":
|
|
641
|
+
case "approx_count_distinct":
|
|
642
|
+
return 0;
|
|
643
|
+
case "sum":
|
|
644
|
+
case "avg":
|
|
645
|
+
case "var_samp":
|
|
646
|
+
case "var_pop":
|
|
647
|
+
case "stddev_samp":
|
|
648
|
+
case "stddev_pop":
|
|
649
|
+
case "median":
|
|
650
|
+
case "quantile":
|
|
651
|
+
case "min":
|
|
652
|
+
case "max":
|
|
653
|
+
case "mode":
|
|
654
|
+
case "first":
|
|
655
|
+
case "last":
|
|
656
|
+
case "any":
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
async function applySubqueryJoinRows(lake, leftRows, join, args) {
|
|
661
|
+
let rightRows = await subqueryJoinRightRows(lake, join);
|
|
662
|
+
rightRows = offsetLimitSubqueryRows(rightRows, join);
|
|
663
|
+
return join.predicate === void 0 ? await broadcastJoin(leftRows, rightRows, {
|
|
664
|
+
leftKey: join.leftKey,
|
|
665
|
+
rightKey: join.rightKey,
|
|
666
|
+
type: join.type,
|
|
667
|
+
maxRightRows: args.joinMaxRightRows ?? 1e5
|
|
668
|
+
}) : predicateSubqueryJoinRows(leftRows, rightRows, join, args);
|
|
669
|
+
}
|
|
670
|
+
async function subqueryJoinRightRows(lake, join) {
|
|
671
|
+
if (subqueryJoinHasAggregation(join)) {
|
|
672
|
+
return await aggregateRowsFromAst(lake.path(join.source), {
|
|
673
|
+
source: join.source,
|
|
674
|
+
select: join.rightKey,
|
|
675
|
+
...join.where === void 0 ? {} : { where: join.where },
|
|
676
|
+
...join.groupBy === void 0 ? {} : { groupBy: join.groupBy },
|
|
677
|
+
...join.aggregates === void 0 ? {} : { aggregates: join.aggregates },
|
|
678
|
+
...join.hiddenAggregates === void 0 ? {} : { hiddenAggregates: join.hiddenAggregates },
|
|
679
|
+
...join.having === void 0 ? {} : { having: join.having },
|
|
680
|
+
...join.orderBy === void 0 ? {} : { orderBy: join.orderBy }
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
let rows = await lake.path(join.source).toArray();
|
|
684
|
+
if (join.where !== void 0) rows = rows.filter((row) => matches(join.where, row));
|
|
685
|
+
if (join.orderBy !== void 0) rows = sortRows(rows, join.orderBy);
|
|
686
|
+
return rows;
|
|
687
|
+
}
|
|
688
|
+
function subqueryJoinHasAggregation(join) {
|
|
689
|
+
return join.aggregates !== void 0 || join.having !== void 0 || join.groupBy !== void 0 && join.groupBy.length > 0;
|
|
690
|
+
}
|
|
691
|
+
function predicateSubqueryJoinRows(leftRows, rightRows, join, args) {
|
|
692
|
+
if (join.predicate === void 0) return leftRows;
|
|
693
|
+
const maxRightRows = args.joinMaxRightRows ?? 1e5;
|
|
694
|
+
if (rightRows.length > maxRightRows) {
|
|
695
|
+
throw new LakeqlError(
|
|
696
|
+
"LAKEQL_BUDGET_EXCEEDED",
|
|
697
|
+
`Predicate subquery join exceeded maxRightRows (${rightRows.length} > ${maxRightRows})`,
|
|
698
|
+
{ metric: "maxRightRows", limit: maxRightRows, actual: rightRows.length }
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
const maxOutputRows = args.joinMaxOutputRows ?? 1e5;
|
|
702
|
+
const out = [];
|
|
703
|
+
const leftAlias = join.leftAlias ?? "";
|
|
704
|
+
const rightAlias = join.alias ?? join.source;
|
|
705
|
+
let candidateRows = 0;
|
|
706
|
+
for (const leftRow of leftRows) {
|
|
707
|
+
let matched = false;
|
|
708
|
+
for (const rightRow of rightRows) {
|
|
709
|
+
candidateRows += 1;
|
|
710
|
+
if (candidateRows > maxOutputRows) {
|
|
711
|
+
throw new LakeqlError(
|
|
712
|
+
"LAKEQL_BUDGET_EXCEEDED",
|
|
713
|
+
`Predicate subquery join exceeded maxOutputRows (${candidateRows} > ${maxOutputRows})`,
|
|
714
|
+
{ metric: "maxOutputRows", limit: maxOutputRows, actual: candidateRows }
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
if (!subqueryKeysMatch(leftRow, rightRow, join)) continue;
|
|
718
|
+
const row = {
|
|
719
|
+
...leftRow,
|
|
720
|
+
...leftAlias.length === 0 ? {} : qualifyOnlyRow(leftRow, leftAlias),
|
|
721
|
+
...qualifyOnlyRow(rightRow, rightAlias)
|
|
722
|
+
};
|
|
723
|
+
if (!matches(join.predicate, row)) continue;
|
|
724
|
+
matched = true;
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
727
|
+
if (join.type === "semi" && matched || join.type === "anti" && !matched) out.push(leftRow);
|
|
728
|
+
}
|
|
729
|
+
return out;
|
|
730
|
+
}
|
|
731
|
+
function subqueryKeysMatch(leftRow, rightRow, join) {
|
|
732
|
+
for (const [index, leftKey] of join.leftKey.entries()) {
|
|
733
|
+
const rightKey = join.rightKey[index];
|
|
734
|
+
if (rightKey === void 0) return false;
|
|
735
|
+
const left = leftRow[leftKey];
|
|
736
|
+
const right = rightRow[rightKey];
|
|
737
|
+
if (left === null || right === null || left !== right) return false;
|
|
738
|
+
}
|
|
739
|
+
return true;
|
|
740
|
+
}
|
|
741
|
+
function offsetLimitSubqueryRows(rows, subquery) {
|
|
742
|
+
const offset = subquery.offset ?? 0;
|
|
743
|
+
if (subquery.limit !== void 0) return rows.slice(offset, offset + subquery.limit);
|
|
744
|
+
return offset > 0 ? rows.slice(offset) : rows;
|
|
610
745
|
}
|
|
611
746
|
function qualifyRow(row, alias) {
|
|
612
747
|
const out = { ...row };
|
|
613
748
|
for (const [key, value] of Object.entries(row)) out[`${alias}.${key}`] = value;
|
|
614
749
|
return out;
|
|
615
750
|
}
|
|
616
|
-
function
|
|
751
|
+
function qualifyOnlyRow(row, alias) {
|
|
752
|
+
return Object.fromEntries(Object.entries(row).map(([key, value]) => [`${alias}.${key}`, value]));
|
|
753
|
+
}
|
|
754
|
+
async function correlatedScalarRowsFromAst(lake, ast) {
|
|
755
|
+
let rows = await lake.path(ast.source).toArray();
|
|
756
|
+
if (ast.where !== void 0) rows = rows.filter((row) => matches(ast.where, row));
|
|
757
|
+
if (ast.orderBy !== void 0) rows = sortRows(rows, ast.orderBy);
|
|
758
|
+
rows = await projectRows(lake, rows, ast);
|
|
759
|
+
if (ast.distinct === true) rows = distinctRows(rows);
|
|
760
|
+
const offset = ast.offset ?? 0;
|
|
761
|
+
if (ast.limit !== void 0) rows = rows.slice(offset, offset + ast.limit);
|
|
762
|
+
else if (offset > 0) rows = rows.slice(offset);
|
|
763
|
+
return rows;
|
|
764
|
+
}
|
|
765
|
+
function hasCorrelatedScalarSubqueries(ast) {
|
|
766
|
+
return ast.correlatedScalarSubqueries !== void 0 && Object.keys(ast.correlatedScalarSubqueries).length > 0;
|
|
767
|
+
}
|
|
768
|
+
async function projectRows(lake, rows, ast) {
|
|
769
|
+
const lookups = await correlatedScalarLookups(lake, ast);
|
|
617
770
|
const hasWildcardProjection = ast.select?.includes("*") ?? false;
|
|
618
771
|
return rows.map((row) => {
|
|
619
772
|
if (hasWildcardProjection && ast.projections === void 0) return row;
|
|
@@ -627,11 +780,123 @@ function projectRows(rows, ast) {
|
|
|
627
780
|
}
|
|
628
781
|
}
|
|
629
782
|
for (const [alias, expr] of Object.entries(ast.projections ?? {})) {
|
|
630
|
-
out[alias] = evaluate(expr, row);
|
|
783
|
+
out[alias] = evaluate(resolveCorrelatedScalarExpr(expr, row, lookups), row);
|
|
631
784
|
}
|
|
632
785
|
return out;
|
|
633
786
|
});
|
|
634
787
|
}
|
|
788
|
+
async function correlatedScalarLookups(lake, ast) {
|
|
789
|
+
const lookups = /* @__PURE__ */ new Map();
|
|
790
|
+
for (const [id, subquery] of Object.entries(ast.correlatedScalarSubqueries ?? {})) {
|
|
791
|
+
const rightRows = await aggregateRowsFromAst(lake.path(subquery.source), {
|
|
792
|
+
source: subquery.source,
|
|
793
|
+
select: subquery.rightKey,
|
|
794
|
+
groupBy: subquery.groupBy,
|
|
795
|
+
aggregates: subquery.aggregates,
|
|
796
|
+
...subquery.hiddenAggregates === void 0 ? {} : { hiddenAggregates: subquery.hiddenAggregates },
|
|
797
|
+
...subquery.where === void 0 ? {} : { where: subquery.where },
|
|
798
|
+
...subquery.having === void 0 ? {} : { having: subquery.having }
|
|
799
|
+
});
|
|
800
|
+
lookups.set(id, {
|
|
801
|
+
subquery,
|
|
802
|
+
rows: new Map(rightRows.map((row) => [correlatedScalarRightKey(row, subquery), row]))
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
return lookups;
|
|
806
|
+
}
|
|
807
|
+
function resolveCorrelatedScalarExpr(expr, row, lookups) {
|
|
808
|
+
switch (expr.kind) {
|
|
809
|
+
case "call":
|
|
810
|
+
if (expr.fn === "__lakeql_correlated_scalar_subquery") {
|
|
811
|
+
return {
|
|
812
|
+
kind: "literal",
|
|
813
|
+
value: correlatedScalarValue(expr, row, lookups)
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
return {
|
|
817
|
+
...expr,
|
|
818
|
+
args: expr.args.map((arg) => resolveCorrelatedScalarExpr(arg, row, lookups))
|
|
819
|
+
};
|
|
820
|
+
case "compare":
|
|
821
|
+
return {
|
|
822
|
+
...expr,
|
|
823
|
+
left: resolveCorrelatedScalarExpr(expr.left, row, lookups),
|
|
824
|
+
right: resolveCorrelatedScalarExpr(expr.right, row, lookups)
|
|
825
|
+
};
|
|
826
|
+
case "arithmetic":
|
|
827
|
+
return {
|
|
828
|
+
...expr,
|
|
829
|
+
left: resolveCorrelatedScalarExpr(expr.left, row, lookups),
|
|
830
|
+
right: resolveCorrelatedScalarExpr(expr.right, row, lookups)
|
|
831
|
+
};
|
|
832
|
+
case "case":
|
|
833
|
+
return {
|
|
834
|
+
...expr,
|
|
835
|
+
whens: expr.whens.map((branch) => ({
|
|
836
|
+
when: resolveCorrelatedScalarExpr(branch.when, row, lookups),
|
|
837
|
+
value: resolveCorrelatedScalarExpr(branch.value, row, lookups)
|
|
838
|
+
})),
|
|
839
|
+
...expr.else === void 0 ? {} : { else: resolveCorrelatedScalarExpr(expr.else, row, lookups) }
|
|
840
|
+
};
|
|
841
|
+
case "logical":
|
|
842
|
+
return {
|
|
843
|
+
...expr,
|
|
844
|
+
operands: expr.operands.map(
|
|
845
|
+
(operand) => resolveCorrelatedScalarExpr(operand, row, lookups)
|
|
846
|
+
)
|
|
847
|
+
};
|
|
848
|
+
case "not":
|
|
849
|
+
return { ...expr, operand: resolveCorrelatedScalarExpr(expr.operand, row, lookups) };
|
|
850
|
+
case "between":
|
|
851
|
+
return {
|
|
852
|
+
...expr,
|
|
853
|
+
target: resolveCorrelatedScalarExpr(expr.target, row, lookups),
|
|
854
|
+
low: resolveCorrelatedScalarExpr(expr.low, row, lookups),
|
|
855
|
+
high: resolveCorrelatedScalarExpr(expr.high, row, lookups)
|
|
856
|
+
};
|
|
857
|
+
case "in":
|
|
858
|
+
return {
|
|
859
|
+
...expr,
|
|
860
|
+
target: resolveCorrelatedScalarExpr(expr.target, row, lookups),
|
|
861
|
+
values: expr.values.map((value) => resolveCorrelatedScalarExpr(value, row, lookups))
|
|
862
|
+
};
|
|
863
|
+
case "like":
|
|
864
|
+
return { ...expr, target: resolveCorrelatedScalarExpr(expr.target, row, lookups) };
|
|
865
|
+
case "null-check":
|
|
866
|
+
return { ...expr, target: resolveCorrelatedScalarExpr(expr.target, row, lookups) };
|
|
867
|
+
case "column":
|
|
868
|
+
case "literal":
|
|
869
|
+
return expr;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
function correlatedScalarValue(expr, row, lookups) {
|
|
873
|
+
const id = expr.args[0];
|
|
874
|
+
if (id?.kind !== "literal" || typeof id.value !== "string") {
|
|
875
|
+
throw new LakeqlError(
|
|
876
|
+
"LAKEQL_SQL_UNSUPPORTED",
|
|
877
|
+
"Invalid correlated scalar subquery placeholder"
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
const lookup = lookups.get(id.value);
|
|
881
|
+
if (lookup === void 0) {
|
|
882
|
+
throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Missing correlated scalar subquery metadata");
|
|
883
|
+
}
|
|
884
|
+
const key = correlatedScalarLeftKey(row, lookup.subquery);
|
|
885
|
+
if (key === void 0) return null;
|
|
886
|
+
return lookup.rows.get(key)?.[lookup.subquery.column] ?? null;
|
|
887
|
+
}
|
|
888
|
+
function correlatedScalarLeftKey(row, subquery) {
|
|
889
|
+
const values = [];
|
|
890
|
+
for (const key of subquery.leftKey) {
|
|
891
|
+
const value = row[key];
|
|
892
|
+
if (value === null || value === void 0) return void 0;
|
|
893
|
+
values.push(value);
|
|
894
|
+
}
|
|
895
|
+
return JSON.stringify(values);
|
|
896
|
+
}
|
|
897
|
+
function correlatedScalarRightKey(row, subquery) {
|
|
898
|
+
return JSON.stringify(subquery.rightKey.map((key) => row[key]));
|
|
899
|
+
}
|
|
635
900
|
async function aggregateRowsFromAst(builder, ast) {
|
|
636
901
|
if (hasWindowSemantics(ast)) {
|
|
637
902
|
throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Window SQL over aggregates is not supported");
|
|
@@ -639,6 +904,8 @@ async function aggregateRowsFromAst(builder, ast) {
|
|
|
639
904
|
validateAggregateProjection(ast);
|
|
640
905
|
let next = builder;
|
|
641
906
|
if (ast.where) next = next.where(ast.where);
|
|
907
|
+
const preProjections = aggregatePreProjections(ast);
|
|
908
|
+
if (preProjections !== void 0) next = next.select(["*"]).project(preProjections);
|
|
642
909
|
const aggregates = ast.aggregates ?? {};
|
|
643
910
|
const hiddenCountAlias = hiddenAggregateAlias(ast);
|
|
644
911
|
const aggregateSpec = Object.keys(aggregates).length > 0 ? aggregates : { [hiddenCountAlias]: { op: "count" } };
|
|
@@ -672,12 +939,13 @@ function validateAggregateProjection(ast) {
|
|
|
672
939
|
}
|
|
673
940
|
function projectAggregateRows(rows, ast, hiddenCountAlias) {
|
|
674
941
|
const aggregates = Object.entries(ast.aggregates ?? {});
|
|
942
|
+
const hiddenAggregates = new Set(ast.hiddenAggregates ?? []);
|
|
675
943
|
const hasWildcardProjection = ast.select?.includes("*") ?? false;
|
|
676
944
|
return rows.map((row) => {
|
|
677
945
|
const out = {};
|
|
678
946
|
if (hasWildcardProjection) {
|
|
679
947
|
for (const [column, value] of Object.entries(row)) {
|
|
680
|
-
if (column !== hiddenCountAlias) out[column] = value;
|
|
948
|
+
if (column !== hiddenCountAlias && !hiddenAggregates.has(column)) out[column] = value;
|
|
681
949
|
}
|
|
682
950
|
} else if (ast.select !== void 0) {
|
|
683
951
|
for (const select of ast.select ?? []) {
|
|
@@ -685,13 +953,24 @@ function projectAggregateRows(rows, ast, hiddenCountAlias) {
|
|
|
685
953
|
out[alias] = row[column];
|
|
686
954
|
}
|
|
687
955
|
}
|
|
688
|
-
for (const [alias] of aggregates)
|
|
956
|
+
for (const [alias] of aggregates) {
|
|
957
|
+
if (!hiddenAggregates.has(alias)) out[alias] = row[alias];
|
|
958
|
+
}
|
|
689
959
|
for (const [alias, expr] of Object.entries(ast.projections ?? {})) {
|
|
690
|
-
out[alias] =
|
|
960
|
+
out[alias] = aggregateProjectionValue(alias, expr, row, ast);
|
|
691
961
|
}
|
|
692
962
|
return out;
|
|
693
963
|
});
|
|
694
964
|
}
|
|
965
|
+
function aggregatePreProjections(ast) {
|
|
966
|
+
const groupBy = new Set(ast.groupBy ?? []);
|
|
967
|
+
const entries = Object.entries(ast.projections ?? {}).filter(([alias]) => groupBy.has(alias));
|
|
968
|
+
return entries.length === 0 ? void 0 : Object.fromEntries(entries);
|
|
969
|
+
}
|
|
970
|
+
function aggregateProjectionValue(alias, expr, row, ast) {
|
|
971
|
+
if (ast.groupBy?.includes(alias) && alias in row) return row[alias];
|
|
972
|
+
return evaluate(expr, row);
|
|
973
|
+
}
|
|
695
974
|
function hiddenAggregateAlias(ast) {
|
|
696
975
|
const reserved = /* @__PURE__ */ new Set([...ast.select ?? [], ...Object.keys(ast.aggregates ?? {})]);
|
|
697
976
|
let alias = "__lakeql_group_count";
|
|
@@ -790,8 +1069,19 @@ function applyDefaultSource(ast, defaultSource, includeScalarSubqueries = true,
|
|
|
790
1069
|
} else {
|
|
791
1070
|
out.source = defaultSource;
|
|
792
1071
|
}
|
|
793
|
-
if (out.
|
|
1072
|
+
if (out.join !== void 0) out.join = { ...out.join, source: defaultSource };
|
|
1073
|
+
if (out.joins !== void 0) {
|
|
1074
|
+
out.joins = out.joins.map((join) => ({ ...join, source: defaultSource }));
|
|
1075
|
+
const firstJoin = out.joins[0];
|
|
1076
|
+
if (firstJoin !== void 0) out.join = firstJoin;
|
|
1077
|
+
}
|
|
1078
|
+
if (out.subqueryJoin !== void 0 && out.subqueryJoins === void 0)
|
|
794
1079
|
out.subqueryJoin = { ...out.subqueryJoin, source: defaultSource };
|
|
1080
|
+
if (out.subqueryJoins !== void 0) {
|
|
1081
|
+
out.subqueryJoins = out.subqueryJoins.map((join) => ({ ...join, source: defaultSource }));
|
|
1082
|
+
const firstSubqueryJoin = out.subqueryJoins[0];
|
|
1083
|
+
if (firstSubqueryJoin !== void 0) out.subqueryJoin = firstSubqueryJoin;
|
|
1084
|
+
}
|
|
795
1085
|
if (includeScalarSubqueries && out.scalarSubqueries !== void 0) {
|
|
796
1086
|
out.scalarSubqueries = Object.fromEntries(
|
|
797
1087
|
Object.entries(out.scalarSubqueries).map(([id, subquery]) => [
|
|
@@ -800,6 +1090,14 @@ function applyDefaultSource(ast, defaultSource, includeScalarSubqueries = true,
|
|
|
800
1090
|
])
|
|
801
1091
|
);
|
|
802
1092
|
}
|
|
1093
|
+
if (out.correlatedScalarSubqueries !== void 0) {
|
|
1094
|
+
out.correlatedScalarSubqueries = Object.fromEntries(
|
|
1095
|
+
Object.entries(out.correlatedScalarSubqueries).map(([id, subquery]) => [
|
|
1096
|
+
id,
|
|
1097
|
+
{ ...subquery, source: defaultSource }
|
|
1098
|
+
])
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
803
1101
|
return out;
|
|
804
1102
|
}
|
|
805
1103
|
function addDefaultFrom(sql) {
|
|
@@ -861,6 +1159,9 @@ function parseArgs(argv) {
|
|
|
861
1159
|
} else if (arg === "--join-max-right-rows") {
|
|
862
1160
|
index += 1;
|
|
863
1161
|
args.joinMaxRightRows = parsePositiveInt(requireValue(rest, index, arg), arg);
|
|
1162
|
+
} else if (arg === "--join-max-output-rows") {
|
|
1163
|
+
index += 1;
|
|
1164
|
+
args.joinMaxOutputRows = parsePositiveInt(requireValue(rest, index, arg), arg);
|
|
864
1165
|
} else if (arg === "--manifest") {
|
|
865
1166
|
index += 1;
|
|
866
1167
|
args.manifest = requireValue(rest, index, arg);
|