lakeql 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
- import { Lake, stableStringify, vectorValue, scalarVectorValue, batchFromColumns, withObjectStoreReadControls, throwIfAborted, readControlSignal, readParquetObjectBatches, readIcebergParquetDeletes } from './chunk-MXGEAVHL.js';
2
- import { LakeqlError, jsonSafeValue, matches } from './chunk-TFD5RFKB.js';
1
+ import { Lake, vectorValue, scalarVectorValue, batchFromColumns, withObjectStoreReadControls, throwIfAborted, readControlSignal, readParquetObjectBatches, readIcebergParquetDeletes, createParquetLake, parseSql, writePartitionedParquet, broadcastJoin } from './chunk-32PE6IK4.js';
2
+ import { stableStringify } from './chunk-LWF5FKHB.js';
3
+ import { LakeqlError, jsonSafeValue, matches, evaluate } from './chunk-WGHR35QU.js';
3
4
 
4
5
  // ../core/dist/in-memory.js
5
6
  var InMemoryRowsScanner = class {
@@ -2017,4 +2018,682 @@ async function* projectIcebergParquetBatches(store, table, path, partition, snap
2017
2018
  }
2018
2019
  }
2019
2020
 
2020
- export { IcebergRestCatalog, IcebergTable, IcebergUnsupportedCatalog, InMemoryRowsScanner, InMemoryRowsStore, ObjectStoreIcebergCommitCatalog, ObjectStoreJsonCache, PACKAGE, applyIcebergDeletes, createInMemoryLake, icebergGlueCatalog, icebergNessieCatalog, icebergRestCatalog, inMemoryRowsScanner, loadIcebergTable, loadIcebergTableFromObjectStore, loadIcebergTableFromRest, loadTable, objectStoreJsonCache, planFiles2 as planFiles, scanBatches, scanPlannedIcebergRows, scanRows, vectorHashJoin };
2021
+ // src/sql.ts
2022
+ var textEncoder = new TextEncoder();
2023
+ var sqlTempOrdinal = 0;
2024
+ function createLake(config) {
2025
+ const lake = createParquetLake(config);
2026
+ lake.sql = (sql, options = {}) => querySql(lake, sql, options);
2027
+ return lake;
2028
+ }
2029
+ function querySql(lake, sql, options = {}) {
2030
+ return new SqlQueryResult(() => executeSql(lake, sql, options));
2031
+ }
2032
+ var SqlQueryResult = class {
2033
+ execute;
2034
+ rowsPromise;
2035
+ constructor(execute) {
2036
+ this.execute = execute;
2037
+ }
2038
+ async toArray() {
2039
+ this.rowsPromise ??= this.execute();
2040
+ return await this.rowsPromise;
2041
+ }
2042
+ async first() {
2043
+ return (await this.toArray())[0];
2044
+ }
2045
+ async count() {
2046
+ return (await this.toArray()).length;
2047
+ }
2048
+ async *rows() {
2049
+ for (const row of await this.toArray()) yield row;
2050
+ }
2051
+ async *batches() {
2052
+ const rows = await this.toArray();
2053
+ if (rows.length > 0) yield rows;
2054
+ }
2055
+ streamJson() {
2056
+ return streamText(async () => jsonStringify(await this.toArray()));
2057
+ }
2058
+ streamNdjson() {
2059
+ return streamText(
2060
+ async () => (await this.toArray()).map((row) => jsonStringify(row)).join("\n")
2061
+ );
2062
+ }
2063
+ streamCsv(options = {}) {
2064
+ return streamText(async () => rowsToCsv(await this.toArray(), options));
2065
+ }
2066
+ };
2067
+ async function executeSql(lake, sql, options) {
2068
+ const ast = sqlAst(sql, options);
2069
+ const bound = bindSources(ast, options);
2070
+ const materialized = await materializeCteIfNeeded(lake.store, lake, bound);
2071
+ try {
2072
+ const resolved = await resolveScalarSubqueries(lake, materialized.ast);
2073
+ if (resolved.subqueryJoin !== void 0)
2074
+ return subqueryJoinRowsFromAst(lake, resolved, options);
2075
+ if (resolved.join !== void 0) return joinRowsFromAst(lake, resolved, options);
2076
+ if (hasAggregation(resolved)) return aggregateRowsFromAst(lake.path(resolved.source), resolved);
2077
+ return await builderFromAst(lake.path(resolved.source), resolved).toArray();
2078
+ } finally {
2079
+ await materialized.cleanup();
2080
+ }
2081
+ }
2082
+ function sqlAst(sql, options) {
2083
+ const trimmed = sql.trim();
2084
+ const sourceSql = /\bfrom\b/iu.test(trimmed) ? trimmed : addDefaultFrom(trimmed);
2085
+ return options.parameters === void 0 ? parseSql(sourceSql) : parseSql(sourceSql, { parameters: options.parameters });
2086
+ }
2087
+ function bindSources(ast, options) {
2088
+ const tables = options.tables ?? {};
2089
+ const bindSource = (source) => {
2090
+ if (source === "input" && options.path !== void 0) return options.path;
2091
+ return tables[source] ?? source;
2092
+ };
2093
+ return mapAstSources(ast, bindSource);
2094
+ }
2095
+ function mapAstSources(ast, bindSource, seen = /* @__PURE__ */ new WeakSet()) {
2096
+ const out = { ...ast, source: bindSource(ast.source) };
2097
+ if (seen.has(ast)) {
2098
+ delete out.scalarSubqueries;
2099
+ return out;
2100
+ }
2101
+ seen.add(ast);
2102
+ if (ast.join !== void 0) out.join = { ...ast.join, source: bindSource(ast.join.source) };
2103
+ if (ast.subqueryJoin !== void 0) {
2104
+ out.subqueryJoin = {
2105
+ ...ast.subqueryJoin,
2106
+ source: bindSource(ast.subqueryJoin.source)
2107
+ };
2108
+ }
2109
+ if (ast.cte !== void 0) {
2110
+ out.cte = { ...ast.cte, query: mapAstSources(ast.cte.query, bindSource, seen) };
2111
+ }
2112
+ if (ast.scalarSubqueries !== void 0) {
2113
+ out.scalarSubqueries = Object.fromEntries(
2114
+ Object.entries(ast.scalarSubqueries).map(([id, subquery]) => [
2115
+ id,
2116
+ { ...subquery, query: mapAstSources(subquery.query, bindSource, seen) }
2117
+ ])
2118
+ );
2119
+ }
2120
+ return out;
2121
+ }
2122
+ function builderFromAst(builder, ast) {
2123
+ let next = builder;
2124
+ for (const [alias, expr] of Object.entries(ast.windows ?? {})) next = next.window(alias, expr);
2125
+ if (ast.select) next = next.select(ast.select);
2126
+ if (ast.projections) next = next.project(ast.projections);
2127
+ if (ast.where) next = next.where(ast.where);
2128
+ if (ast.qualify) next = next.qualify(ast.qualify);
2129
+ if (ast.distinct === true) next = next.distinct();
2130
+ if (ast.orderBy) next = next.orderBy(ast.orderBy);
2131
+ if (ast.offset !== void 0) next = next.offset(ast.offset);
2132
+ if (ast.limit !== void 0) next = next.limit(ast.limit);
2133
+ return next;
2134
+ }
2135
+ function hasAggregation(ast) {
2136
+ return ast.aggregates !== void 0 || ast.groupBy !== void 0 && ast.groupBy.length > 0 || ast.having !== void 0;
2137
+ }
2138
+ function hasWindowSemantics(ast) {
2139
+ return ast.windows !== void 0 && Object.keys(ast.windows).length > 0 || ast.qualify !== void 0;
2140
+ }
2141
+ async function materializeCteIfNeeded(store, lake, ast) {
2142
+ if (ast.cte === void 0) return { ast, cleanup: async () => {
2143
+ } };
2144
+ if (ast.source !== ast.cte.name) {
2145
+ throw new LakeqlError(
2146
+ "LAKEQL_SQL_UNSUPPORTED",
2147
+ "CTEs are only supported as the outer FROM source"
2148
+ );
2149
+ }
2150
+ if (ast.join !== void 0 || ast.subqueryJoin !== void 0) {
2151
+ throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "CTEs inside JOINs are not supported yet");
2152
+ }
2153
+ 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();
2154
+ if (cteRows.length === 0) {
2155
+ throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Empty CTE results are not supported yet");
2156
+ }
2157
+ const prefix = `__lakeql_sql_cte/${safeTempName(ast.cte.name)}/${nextSqlTempId()}`;
2158
+ const written = await writePartitionedParquet(store, prefix, {
2159
+ rows: cteRows,
2160
+ maxRowsPerFile: cteRows.length
2161
+ });
2162
+ const { cte: _cte, ...rest } = ast;
2163
+ return {
2164
+ ast: { ...rest, source: `${prefix}/*.parquet` },
2165
+ cleanup: async () => {
2166
+ await Promise.all(written.files.map((file) => store.delete(file.path)));
2167
+ }
2168
+ };
2169
+ }
2170
+ function nextSqlTempId() {
2171
+ const randomUuid = globalThis.crypto?.randomUUID?.();
2172
+ if (randomUuid !== void 0) return randomUuid;
2173
+ sqlTempOrdinal += 1;
2174
+ return `${Date.now().toString(36)}-${sqlTempOrdinal.toString(36)}`;
2175
+ }
2176
+ function safeTempName(name) {
2177
+ return name.replaceAll(/[^A-Za-z0-9_-]/gu, "_");
2178
+ }
2179
+ async function resolveScalarSubqueries(lake, ast) {
2180
+ if (ast.scalarSubqueries === void 0) return ast;
2181
+ const values = /* @__PURE__ */ new Map();
2182
+ for (const [id, subquery] of Object.entries(ast.scalarSubqueries)) {
2183
+ const rows = hasAggregation(subquery.query) ? await aggregateRowsFromAst(lake.path(subquery.query.source), subquery.query) : await builderFromAst(lake.path(subquery.query.source), subquery.query).toArray();
2184
+ if (rows.length > 1) {
2185
+ throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Scalar subquery returned more than one row");
2186
+ }
2187
+ values.set(id, rows.length === 0 ? null : rows[0]?.[subquery.column] ?? null);
2188
+ }
2189
+ const out = { ...ast };
2190
+ if (ast.where !== void 0) out.where = replaceScalarSubqueryExpr(ast.where, values);
2191
+ if (ast.having !== void 0) out.having = replaceScalarSubqueryExpr(ast.having, values);
2192
+ if (ast.projections !== void 0) {
2193
+ out.projections = Object.fromEntries(
2194
+ Object.entries(ast.projections).map(([alias, expr]) => [
2195
+ alias,
2196
+ replaceScalarSubqueryExpr(expr, values)
2197
+ ])
2198
+ );
2199
+ }
2200
+ delete out.scalarSubqueries;
2201
+ return out;
2202
+ }
2203
+ function replaceScalarSubqueryExpr(expr, values) {
2204
+ switch (expr.kind) {
2205
+ case "call":
2206
+ if (expr.fn === "__lakeql_scalar_subquery") {
2207
+ const id = expr.args[0];
2208
+ if (id?.kind !== "literal" || typeof id.value !== "string" || !values.has(id.value)) {
2209
+ throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Invalid scalar subquery placeholder");
2210
+ }
2211
+ return { kind: "literal", value: values.get(id.value) };
2212
+ }
2213
+ return { ...expr, args: expr.args.map((arg) => replaceScalarSubqueryExpr(arg, values)) };
2214
+ case "compare":
2215
+ return {
2216
+ ...expr,
2217
+ left: replaceScalarSubqueryExpr(expr.left, values),
2218
+ right: replaceScalarSubqueryExpr(expr.right, values)
2219
+ };
2220
+ case "between":
2221
+ return {
2222
+ ...expr,
2223
+ target: replaceScalarSubqueryExpr(expr.target, values),
2224
+ low: replaceScalarSubqueryExpr(expr.low, values),
2225
+ high: replaceScalarSubqueryExpr(expr.high, values)
2226
+ };
2227
+ case "in":
2228
+ return {
2229
+ ...expr,
2230
+ target: replaceScalarSubqueryExpr(expr.target, values),
2231
+ values: expr.values.map((value) => replaceScalarSubqueryExpr(value, values))
2232
+ };
2233
+ case "logical":
2234
+ return {
2235
+ ...expr,
2236
+ operands: expr.operands.map((operand) => replaceScalarSubqueryExpr(operand, values))
2237
+ };
2238
+ case "not":
2239
+ return { ...expr, operand: replaceScalarSubqueryExpr(expr.operand, values) };
2240
+ case "null-check":
2241
+ return { ...expr, target: replaceScalarSubqueryExpr(expr.target, values) };
2242
+ case "like":
2243
+ return { ...expr, target: replaceScalarSubqueryExpr(expr.target, values) };
2244
+ case "arithmetic":
2245
+ return {
2246
+ ...expr,
2247
+ left: replaceScalarSubqueryExpr(expr.left, values),
2248
+ right: replaceScalarSubqueryExpr(expr.right, values)
2249
+ };
2250
+ case "case":
2251
+ return {
2252
+ ...expr,
2253
+ whens: expr.whens.map((branch) => ({
2254
+ when: replaceScalarSubqueryExpr(branch.when, values),
2255
+ value: replaceScalarSubqueryExpr(branch.value, values)
2256
+ })),
2257
+ ...expr.else === void 0 ? {} : { else: replaceScalarSubqueryExpr(expr.else, values) }
2258
+ };
2259
+ case "column":
2260
+ case "literal":
2261
+ return expr;
2262
+ }
2263
+ }
2264
+ async function joinRowsFromAst(lake, ast, options) {
2265
+ if (ast.join === void 0) throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Missing SQL JOIN");
2266
+ const join = ast.join;
2267
+ if (hasWindowSemantics(ast)) {
2268
+ throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Window SQL over JOIN is not supported");
2269
+ }
2270
+ if (hasAggregation(ast)) {
2271
+ throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Aggregate SQL over JOIN is not supported yet");
2272
+ }
2273
+ const leftAlias = ast.join.leftAlias;
2274
+ const plan = planJoinSides(ast, leftAlias, join.alias);
2275
+ let leftBuilder = lake.path(ast.source);
2276
+ let rightBuilder = lake.path(join.source);
2277
+ if (plan.leftWhere !== void 0) leftBuilder = leftBuilder.where(plan.leftWhere);
2278
+ if (plan.rightWhere !== void 0) rightBuilder = rightBuilder.where(plan.rightWhere);
2279
+ if (plan.leftColumns !== void 0) leftBuilder = leftBuilder.select(plan.leftColumns);
2280
+ if (plan.rightColumns !== void 0) rightBuilder = rightBuilder.select(plan.rightColumns);
2281
+ const leftRows = (await leftBuilder.toArray()).map((row) => qualifyRow(row, leftAlias));
2282
+ const rightRows = (await rightBuilder.toArray()).map((row) => qualifyOnlyRow(row, join.alias));
2283
+ let rows = await broadcastJoin(leftRows, rightRows, {
2284
+ leftKey: join.leftKey,
2285
+ rightKey: join.rightKey,
2286
+ type: join.type,
2287
+ rightPrefix: `${join.alias}.`,
2288
+ maxRightRows: options.joinMaxRightRows ?? 1e5
2289
+ });
2290
+ if (join.type === "left") {
2291
+ rows = fillLeftJoinNulls(rows, join.alias, leftJoinRightColumns(ast, join.alias, rightRows));
2292
+ }
2293
+ if (plan.residualWhere !== void 0)
2294
+ rows = rows.filter((row) => matches(plan.residualWhere, row));
2295
+ if (ast.orderBy !== void 0) rows = sortRows(rows, ast.orderBy);
2296
+ rows = projectRows(rows, ast);
2297
+ if (ast.distinct === true) rows = distinctRows(rows);
2298
+ return offsetLimitRows(rows, ast);
2299
+ }
2300
+ function planJoinSides(ast, leftAlias, rightAlias) {
2301
+ if (ast.join === void 0) throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Missing SQL JOIN");
2302
+ const leftPredicates = [];
2303
+ const rightPredicates = [];
2304
+ const residualPredicates = [];
2305
+ for (const predicate of splitAndPredicate(ast.where)) {
2306
+ const columns = exprColumns(predicate);
2307
+ if (columns.length > 0 && columns.every((column) => isQualifiedBy(column, leftAlias))) {
2308
+ leftPredicates.push(stripQualifiedExpr(predicate, leftAlias));
2309
+ } else if (ast.join.type === "inner" && columns.length > 0 && columns.every((column) => isQualifiedBy(column, rightAlias))) {
2310
+ rightPredicates.push(stripQualifiedExpr(predicate, rightAlias));
2311
+ } else {
2312
+ residualPredicates.push(predicate);
2313
+ }
2314
+ }
2315
+ const plan = {};
2316
+ const leftWhere = combineAndPredicate(leftPredicates);
2317
+ const rightWhere = combineAndPredicate(rightPredicates);
2318
+ const residualWhere = combineAndPredicate(residualPredicates);
2319
+ if (leftWhere !== void 0) plan.leftWhere = leftWhere;
2320
+ if (rightWhere !== void 0) plan.rightWhere = rightWhere;
2321
+ if (residualWhere !== void 0) plan.residualWhere = residualWhere;
2322
+ if (canProjectJoinSides(ast, leftAlias, rightAlias)) {
2323
+ plan.leftColumns = joinSideColumns(ast, leftAlias, ast.join.leftKey);
2324
+ plan.rightColumns = joinSideColumns(ast, rightAlias, ast.join.rightKey);
2325
+ }
2326
+ return plan;
2327
+ }
2328
+ function splitAndPredicate(expr) {
2329
+ if (expr === void 0) return [];
2330
+ if (expr.kind === "logical" && expr.op === "and") return expr.operands.flatMap(splitAndPredicate);
2331
+ return [expr];
2332
+ }
2333
+ function combineAndPredicate(predicates) {
2334
+ if (predicates.length === 0) return void 0;
2335
+ if (predicates.length === 1) return predicates[0];
2336
+ return { kind: "logical", op: "and", operands: predicates };
2337
+ }
2338
+ function isQualifiedBy(column, alias) {
2339
+ return column.startsWith(`${alias}.`) && column.length > alias.length + 1;
2340
+ }
2341
+ function stripQualifiedColumn(column, alias) {
2342
+ return isQualifiedBy(column, alias) ? column.slice(alias.length + 1) : column;
2343
+ }
2344
+ function stripQualifiedExpr(expr, alias) {
2345
+ switch (expr.kind) {
2346
+ case "column":
2347
+ return { ...expr, name: stripQualifiedColumn(expr.name, alias) };
2348
+ case "compare":
2349
+ return {
2350
+ ...expr,
2351
+ left: stripQualifiedExpr(expr.left, alias),
2352
+ right: stripQualifiedExpr(expr.right, alias)
2353
+ };
2354
+ case "between":
2355
+ return {
2356
+ ...expr,
2357
+ target: stripQualifiedExpr(expr.target, alias),
2358
+ low: stripQualifiedExpr(expr.low, alias),
2359
+ high: stripQualifiedExpr(expr.high, alias)
2360
+ };
2361
+ case "in":
2362
+ return {
2363
+ ...expr,
2364
+ target: stripQualifiedExpr(expr.target, alias),
2365
+ values: expr.values.map((value) => stripQualifiedExpr(value, alias))
2366
+ };
2367
+ case "logical":
2368
+ return {
2369
+ ...expr,
2370
+ operands: expr.operands.map((operand) => stripQualifiedExpr(operand, alias))
2371
+ };
2372
+ case "not":
2373
+ return { ...expr, operand: stripQualifiedExpr(expr.operand, alias) };
2374
+ case "null-check":
2375
+ return { ...expr, target: stripQualifiedExpr(expr.target, alias) };
2376
+ case "like":
2377
+ return { ...expr, target: stripQualifiedExpr(expr.target, alias) };
2378
+ case "call":
2379
+ return { ...expr, args: expr.args.map((arg) => stripQualifiedExpr(arg, alias)) };
2380
+ case "arithmetic":
2381
+ return {
2382
+ ...expr,
2383
+ left: stripQualifiedExpr(expr.left, alias),
2384
+ right: stripQualifiedExpr(expr.right, alias)
2385
+ };
2386
+ case "case":
2387
+ return {
2388
+ ...expr,
2389
+ whens: expr.whens.map((branch) => ({
2390
+ when: stripQualifiedExpr(branch.when, alias),
2391
+ value: stripQualifiedExpr(branch.value, alias)
2392
+ })),
2393
+ ...expr.else === void 0 ? {} : { else: stripQualifiedExpr(expr.else, alias) }
2394
+ };
2395
+ case "literal":
2396
+ return expr;
2397
+ }
2398
+ }
2399
+ function exprColumns(expr) {
2400
+ const columns = /* @__PURE__ */ new Set();
2401
+ collectExprColumns(expr, columns);
2402
+ return [...columns];
2403
+ }
2404
+ function collectExprColumns(expr, columns) {
2405
+ switch (expr.kind) {
2406
+ case "column":
2407
+ columns.add(expr.name);
2408
+ return;
2409
+ case "compare":
2410
+ collectExprColumns(expr.left, columns);
2411
+ collectExprColumns(expr.right, columns);
2412
+ return;
2413
+ case "between":
2414
+ collectExprColumns(expr.target, columns);
2415
+ collectExprColumns(expr.low, columns);
2416
+ collectExprColumns(expr.high, columns);
2417
+ return;
2418
+ case "in":
2419
+ collectExprColumns(expr.target, columns);
2420
+ for (const value of expr.values) collectExprColumns(value, columns);
2421
+ return;
2422
+ case "logical":
2423
+ for (const operand of expr.operands) collectExprColumns(operand, columns);
2424
+ return;
2425
+ case "not":
2426
+ collectExprColumns(expr.operand, columns);
2427
+ return;
2428
+ case "null-check":
2429
+ collectExprColumns(expr.target, columns);
2430
+ return;
2431
+ case "like":
2432
+ collectExprColumns(expr.target, columns);
2433
+ return;
2434
+ case "call":
2435
+ for (const arg of expr.args) collectExprColumns(arg, columns);
2436
+ return;
2437
+ case "arithmetic":
2438
+ collectExprColumns(expr.left, columns);
2439
+ collectExprColumns(expr.right, columns);
2440
+ return;
2441
+ case "case":
2442
+ for (const branch of expr.whens) {
2443
+ collectExprColumns(branch.when, columns);
2444
+ collectExprColumns(branch.value, columns);
2445
+ }
2446
+ if (expr.else !== void 0) collectExprColumns(expr.else, columns);
2447
+ return;
2448
+ case "literal":
2449
+ return;
2450
+ }
2451
+ }
2452
+ function canProjectJoinSides(ast, leftAlias, rightAlias) {
2453
+ if (ast.select?.includes("*") ?? false) return false;
2454
+ return referencedJoinColumns(ast).every(
2455
+ (column) => isQualifiedBy(column, leftAlias) || isQualifiedBy(column, rightAlias)
2456
+ );
2457
+ }
2458
+ function referencedJoinColumns(ast) {
2459
+ const columns = /* @__PURE__ */ new Set();
2460
+ for (const select of ast.select ?? []) {
2461
+ const { column } = selectColumn(select);
2462
+ columns.add(column);
2463
+ }
2464
+ for (const expr of Object.values(ast.projections ?? {})) collectExprColumns(expr, columns);
2465
+ if (ast.where !== void 0) collectExprColumns(ast.where, columns);
2466
+ for (const term of ast.orderBy ?? []) columns.add(term.column);
2467
+ return [...columns].filter((column) => column !== "*");
2468
+ }
2469
+ function joinSideColumns(ast, alias, joinKey2) {
2470
+ const columns = new Set(
2471
+ (Array.isArray(joinKey2) ? joinKey2 : [joinKey2]).map(
2472
+ (column) => stripQualifiedColumn(column, alias)
2473
+ )
2474
+ );
2475
+ for (const column of referencedJoinColumns(ast)) {
2476
+ if (isQualifiedBy(column, alias)) columns.add(stripQualifiedColumn(column, alias));
2477
+ }
2478
+ return [...columns];
2479
+ }
2480
+ function leftJoinRightColumns(ast, rightAlias, rightRows) {
2481
+ const columns = /* @__PURE__ */ new Set();
2482
+ if (ast.select?.includes("*") ?? false) {
2483
+ for (const row of rightRows) {
2484
+ for (const column of Object.keys(row)) {
2485
+ if (isQualifiedBy(column, rightAlias))
2486
+ columns.add(stripQualifiedColumn(column, rightAlias));
2487
+ }
2488
+ }
2489
+ }
2490
+ for (const column of referencedJoinColumns(ast)) {
2491
+ if (isQualifiedBy(column, rightAlias)) columns.add(stripQualifiedColumn(column, rightAlias));
2492
+ }
2493
+ return [...columns];
2494
+ }
2495
+ function fillLeftJoinNulls(rows, rightAlias, rightColumns) {
2496
+ if (rightColumns.length === 0) return rows;
2497
+ return rows.map((row) => {
2498
+ let out;
2499
+ for (const column of rightColumns) {
2500
+ const qualified = `${rightAlias}.${column}`;
2501
+ if (qualified in row) continue;
2502
+ out ??= { ...row };
2503
+ out[qualified] = null;
2504
+ }
2505
+ return out ?? row;
2506
+ });
2507
+ }
2508
+ async function subqueryJoinRowsFromAst(lake, ast, options) {
2509
+ if (ast.subqueryJoin === void 0) {
2510
+ throw new LakeqlError("LAKEQL_VALIDATION_ERROR", "Missing SQL IN subquery");
2511
+ }
2512
+ if (hasWindowSemantics(ast)) {
2513
+ throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Window SQL over IN subquery is not supported");
2514
+ }
2515
+ if (hasAggregation(ast)) {
2516
+ throw new LakeqlError(
2517
+ "LAKEQL_SQL_UNSUPPORTED",
2518
+ "Aggregate SQL over IN subquery is not supported yet"
2519
+ );
2520
+ }
2521
+ const join = ast.subqueryJoin;
2522
+ let rightRows = await lake.path(join.source).toArray();
2523
+ if (join.where !== void 0) rightRows = rightRows.filter((row) => matches(join.where, row));
2524
+ let rows = await broadcastJoin(await lake.path(ast.source).toArray(), rightRows, {
2525
+ leftKey: join.leftKey,
2526
+ rightKey: join.rightKey,
2527
+ type: join.type,
2528
+ maxRightRows: options.joinMaxRightRows ?? 1e5
2529
+ });
2530
+ if (ast.where !== void 0) rows = rows.filter((row) => matches(ast.where, row));
2531
+ if (ast.orderBy !== void 0) rows = sortRows(rows, ast.orderBy);
2532
+ rows = projectRows(rows, ast);
2533
+ if (ast.distinct === true) rows = distinctRows(rows);
2534
+ return offsetLimitRows(rows, ast);
2535
+ }
2536
+ function qualifyRow(row, alias) {
2537
+ const out = { ...row };
2538
+ for (const [key, value] of Object.entries(row)) out[`${alias}.${key}`] = value;
2539
+ return out;
2540
+ }
2541
+ function qualifyOnlyRow(row, alias) {
2542
+ return Object.fromEntries(Object.entries(row).map(([key, value]) => [`${alias}.${key}`, value]));
2543
+ }
2544
+ function projectRows(rows, ast) {
2545
+ const hasWildcardProjection = ast.select?.includes("*") ?? false;
2546
+ return rows.map((row) => {
2547
+ if (hasWildcardProjection && ast.projections === void 0) return row;
2548
+ const out = {};
2549
+ if (hasWildcardProjection) {
2550
+ for (const [column, value] of Object.entries(row)) out[column] = value;
2551
+ } else {
2552
+ for (const select of ast.select ?? []) {
2553
+ const { column, alias } = selectColumn(select);
2554
+ out[alias] = row[column];
2555
+ }
2556
+ }
2557
+ for (const [alias, expr] of Object.entries(ast.projections ?? {})) {
2558
+ out[alias] = evaluate(expr, row);
2559
+ }
2560
+ return out;
2561
+ });
2562
+ }
2563
+ async function aggregateRowsFromAst(builder, ast) {
2564
+ if (hasWindowSemantics(ast)) {
2565
+ throw new LakeqlError("LAKEQL_SQL_UNSUPPORTED", "Window SQL over aggregates is not supported");
2566
+ }
2567
+ validateAggregateProjection(ast);
2568
+ let next = builder;
2569
+ if (ast.where) next = next.where(ast.where);
2570
+ const aggregates = ast.aggregates ?? {};
2571
+ const hiddenCountAlias = hiddenAggregateAlias(ast);
2572
+ const aggregateSpec = Object.keys(aggregates).length > 0 ? aggregates : { [hiddenCountAlias]: { op: "count" } };
2573
+ let rows = await next.groupBy(ast.groupBy ?? []).aggregate(aggregateSpec, {
2574
+ ...ast.having !== void 0 ? { having: ast.having } : {},
2575
+ ...ast.orderBy !== void 0 ? { orderBy: ast.orderBy } : {},
2576
+ ...ast.distinct === true ? {} : ast.limit !== void 0 ? { limit: ast.limit } : {},
2577
+ ...ast.distinct === true ? {} : ast.offset !== void 0 ? { offset: ast.offset } : {}
2578
+ });
2579
+ rows = projectAggregateRows(rows, ast, hiddenCountAlias);
2580
+ if (ast.distinct === true) rows = distinctRows(rows);
2581
+ if (ast.distinct === true) return offsetLimitRows(rows, ast);
2582
+ return rows;
2583
+ }
2584
+ function validateAggregateProjection(ast) {
2585
+ const groupColumns = new Set(ast.groupBy ?? []);
2586
+ for (const select of ast.select ?? []) {
2587
+ const { column } = selectColumn(select);
2588
+ if (column === "*") continue;
2589
+ if (!groupColumns.has(column)) {
2590
+ throw new LakeqlError(
2591
+ "LAKEQL_SQL_UNSUPPORTED",
2592
+ `Aggregate SQL can only select grouped columns or aggregate expressions, not ${column}`
2593
+ );
2594
+ }
2595
+ }
2596
+ }
2597
+ function projectAggregateRows(rows, ast, hiddenCountAlias) {
2598
+ const aggregates = Object.entries(ast.aggregates ?? {});
2599
+ const hasWildcardProjection = ast.select?.includes("*") ?? false;
2600
+ return rows.map((row) => {
2601
+ const out = {};
2602
+ if (hasWildcardProjection) {
2603
+ for (const [column, value] of Object.entries(row)) {
2604
+ if (column !== hiddenCountAlias) out[column] = value;
2605
+ }
2606
+ } else if (ast.select !== void 0) {
2607
+ for (const select of ast.select ?? []) {
2608
+ const { column, alias } = selectColumn(select);
2609
+ out[alias] = row[column];
2610
+ }
2611
+ }
2612
+ for (const [alias] of aggregates) out[alias] = row[alias];
2613
+ for (const [alias, expr] of Object.entries(ast.projections ?? {})) {
2614
+ out[alias] = evaluate(expr, row);
2615
+ }
2616
+ return out;
2617
+ });
2618
+ }
2619
+ function hiddenAggregateAlias(ast) {
2620
+ const reserved = /* @__PURE__ */ new Set([...ast.select ?? [], ...Object.keys(ast.aggregates ?? {})]);
2621
+ let alias = "__lakeql_group_count";
2622
+ while (reserved.has(alias)) alias = `_${alias}`;
2623
+ return alias;
2624
+ }
2625
+ function selectColumn(select) {
2626
+ const match = /^(.+?)\s+as\s+(.+)$/iu.exec(select);
2627
+ if (match === null) return { column: select, alias: select };
2628
+ const [, column, alias] = match;
2629
+ return { column: column ?? select, alias: alias ?? select };
2630
+ }
2631
+ function distinctRows(rows) {
2632
+ const seen = /* @__PURE__ */ new Set();
2633
+ const out = [];
2634
+ for (const row of rows) {
2635
+ const key = jsonStringify(row);
2636
+ if (seen.has(key)) continue;
2637
+ seen.add(key);
2638
+ out.push(row);
2639
+ }
2640
+ return out;
2641
+ }
2642
+ function sortRows(rows, orderBy) {
2643
+ return [...rows].sort((a, b) => {
2644
+ for (const term of orderBy) {
2645
+ const av = a[term.column] ?? null;
2646
+ const bv = b[term.column] ?? null;
2647
+ if (av === bv) continue;
2648
+ if (av === null) return term.nulls === "first" ? -1 : 1;
2649
+ if (bv === null) return term.nulls === "first" ? 1 : -1;
2650
+ const direction = term.direction === "desc" ? -1 : 1;
2651
+ return (av < bv ? -1 : 1) * direction;
2652
+ }
2653
+ return 0;
2654
+ });
2655
+ }
2656
+ function offsetLimitRows(rows, ast) {
2657
+ const offset = ast.offset ?? 0;
2658
+ if (ast.limit !== void 0) return rows.slice(offset, offset + ast.limit);
2659
+ return offset > 0 ? rows.slice(offset) : rows;
2660
+ }
2661
+ function rowsToCsv(rows, options) {
2662
+ const columns = Object.keys(rows[0] ?? {});
2663
+ const lines = [columns.join(",")];
2664
+ for (const row of rows) {
2665
+ lines.push(columns.map((column) => csvCell(row[column], options)).join(","));
2666
+ }
2667
+ return `${lines.join("\n")}
2668
+ `;
2669
+ }
2670
+ function csvCell(value, options) {
2671
+ if (value === null || value === void 0) return "";
2672
+ const raw = String(value);
2673
+ const text = options.preventFormulae === true && /^[=+\-@]/u.test(raw) ? `'${raw}` : raw;
2674
+ return /[",\n\r]/u.test(text) ? `"${text.replaceAll('"', '""')}"` : text;
2675
+ }
2676
+ function jsonStringify(value) {
2677
+ return JSON.stringify(value, (_key, item) => typeof item === "bigint" ? item.toString() : item);
2678
+ }
2679
+ function streamText(read) {
2680
+ let done = false;
2681
+ return new ReadableStream({
2682
+ async pull(controller) {
2683
+ if (done) return;
2684
+ done = true;
2685
+ controller.enqueue(textEncoder.encode(await read()));
2686
+ controller.close();
2687
+ }
2688
+ });
2689
+ }
2690
+ function addDefaultFrom(sql) {
2691
+ if (!/^\s*select\b/iu.test(sql)) {
2692
+ throw new LakeqlError("LAKEQL_PARSE_ERROR", "SQL must start with SELECT");
2693
+ }
2694
+ const clause = /\b(where|group\s+by|having|order\s+by|limit|offset)\b/iu.exec(sql);
2695
+ if (clause === null || clause.index === void 0) return `${sql} from input`;
2696
+ return `${sql.slice(0, clause.index)}from input ${sql.slice(clause.index)}`;
2697
+ }
2698
+
2699
+ export { IcebergRestCatalog, IcebergTable, IcebergUnsupportedCatalog, InMemoryRowsScanner, InMemoryRowsStore, ObjectStoreIcebergCommitCatalog, ObjectStoreJsonCache, PACKAGE, SqlQueryResult, applyIcebergDeletes, createInMemoryLake, createLake, icebergGlueCatalog, icebergNessieCatalog, icebergRestCatalog, inMemoryRowsScanner, loadIcebergTable, loadIcebergTableFromObjectStore, loadIcebergTableFromRest, loadTable, objectStoreJsonCache, planFiles2 as planFiles, querySql, scanBatches, scanPlannedIcebergRows, scanRows, vectorHashJoin };