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.
@@ -9207,6 +9207,48 @@ function compareSortValues(left, right, term) {
9207
9207
  }
9208
9208
 
9209
9209
  // ../core/dist/query-json.js
9210
+ var COMPARE_OPS = ["eq", "ne", "lt", "lte", "gt", "gte"];
9211
+ var ARITHMETIC_OPS = ["add", "sub", "mul", "div", "mod"];
9212
+ var AGGREGATE_OPS = [
9213
+ "count",
9214
+ "sum",
9215
+ "avg",
9216
+ "var_samp",
9217
+ "var_pop",
9218
+ "stddev_samp",
9219
+ "stddev_pop",
9220
+ "median",
9221
+ "quantile",
9222
+ "min",
9223
+ "max",
9224
+ "count_distinct",
9225
+ "approx_count_distinct",
9226
+ "mode",
9227
+ "first",
9228
+ "last"
9229
+ ];
9230
+ var WINDOW_FN_NAMES = [
9231
+ "row_number",
9232
+ "rank",
9233
+ "dense_rank",
9234
+ "percent_rank",
9235
+ "cume_dist",
9236
+ "ntile",
9237
+ "lag",
9238
+ "lead",
9239
+ "first_value",
9240
+ "last_value",
9241
+ "nth_value"
9242
+ ];
9243
+ var WINDOW_FRAME_MODES = ["rows", "range", "groups"];
9244
+ var WINDOW_FRAME_EXCLUDES = ["no-others", "current-row", "group", "ties"];
9245
+ var WINDOW_FRAME_BOUND_KINDS = [
9246
+ "unbounded-preceding",
9247
+ "preceding",
9248
+ "current-row",
9249
+ "following",
9250
+ "unbounded-following"
9251
+ ];
9210
9252
  function parseJsonQuery(input) {
9211
9253
  if (!isRecord(input))
9212
9254
  throwParse("JSON query must be an object");
@@ -9223,6 +9265,10 @@ function parseJsonQuery(input) {
9223
9265
  }
9224
9266
  if (input.where !== void 0)
9225
9267
  init.where = parseJsonExpr(input.where);
9268
+ if (input.windows !== void 0)
9269
+ init.windows = parseJsonWindows(input.windows);
9270
+ if (input.qualify !== void 0)
9271
+ init.qualify = parseJsonExpr(input.qualify);
9226
9272
  if (input.distinct !== void 0) {
9227
9273
  if (typeof input.distinct !== "boolean")
9228
9274
  throwParse("JSON query distinct must be a boolean");
@@ -9262,6 +9308,8 @@ function parseJsonOrderByTerm(input) {
9262
9308
  function parseJsonExpr(input) {
9263
9309
  if (!isRecord(input))
9264
9310
  throwParse("JSON expression must be an object");
9311
+ if (typeof input.kind === "string")
9312
+ return parseJsonExprAst(input);
9265
9313
  const entries = Object.entries(input);
9266
9314
  if (entries.length !== 1)
9267
9315
  throwParse("JSON expression must have exactly one operator");
@@ -9314,6 +9362,213 @@ function parseJsonExpr(input) {
9314
9362
  throwParse(`Unsupported JSON expression operator ${op}`);
9315
9363
  }
9316
9364
  }
9365
+ function parseJsonExprAst(input) {
9366
+ switch (input.kind) {
9367
+ case "literal":
9368
+ return { kind: "literal", value: requireScalar(input.value, "literal") };
9369
+ case "column":
9370
+ if (typeof input.name !== "string")
9371
+ throwParse("column expression name must be a string");
9372
+ return { kind: "column", name: input.name };
9373
+ case "compare": {
9374
+ if (!isCompareOp(input.op))
9375
+ throwParse("compare expression op must be eq, ne, lt, lte, gt, or gte");
9376
+ return {
9377
+ kind: "compare",
9378
+ op: input.op,
9379
+ left: parseJsonExpr(input.left),
9380
+ right: parseJsonExpr(input.right)
9381
+ };
9382
+ }
9383
+ case "in": {
9384
+ if (typeof input.negated !== "boolean")
9385
+ throwParse("in expression negated must be a boolean");
9386
+ if (!Array.isArray(input.values))
9387
+ throwParse("in expression values must be an array");
9388
+ return {
9389
+ kind: "in",
9390
+ negated: input.negated,
9391
+ target: parseJsonExpr(input.target),
9392
+ values: input.values.map(parseJsonExpr)
9393
+ };
9394
+ }
9395
+ case "between":
9396
+ return {
9397
+ kind: "between",
9398
+ target: parseJsonExpr(input.target),
9399
+ low: parseJsonExpr(input.low),
9400
+ high: parseJsonExpr(input.high)
9401
+ };
9402
+ case "null-check":
9403
+ if (typeof input.negated !== "boolean") {
9404
+ throwParse("null-check expression negated must be a boolean");
9405
+ }
9406
+ return { kind: "null-check", negated: input.negated, target: parseJsonExpr(input.target) };
9407
+ case "logical": {
9408
+ if (input.op !== "and" && input.op !== "or") {
9409
+ throwParse("logical expression op must be and or or");
9410
+ }
9411
+ if (!Array.isArray(input.operands))
9412
+ throwParse("logical expression operands must be an array");
9413
+ if (input.operands.length < 2) {
9414
+ throwParse("logical expression operands must include at least two expressions");
9415
+ }
9416
+ return { kind: "logical", op: input.op, operands: input.operands.map(parseJsonExpr) };
9417
+ }
9418
+ case "not":
9419
+ return { kind: "not", operand: parseJsonExpr(input.operand) };
9420
+ case "like":
9421
+ if (typeof input.caseInsensitive !== "boolean") {
9422
+ throwParse("like expression caseInsensitive must be a boolean");
9423
+ }
9424
+ if (typeof input.pattern !== "string")
9425
+ throwParse("like expression pattern must be a string");
9426
+ return {
9427
+ kind: "like",
9428
+ caseInsensitive: input.caseInsensitive,
9429
+ target: parseJsonExpr(input.target),
9430
+ pattern: input.pattern
9431
+ };
9432
+ case "call":
9433
+ if (typeof input.fn !== "string")
9434
+ throwParse("call expression fn must be a string");
9435
+ if (!Array.isArray(input.args))
9436
+ throwParse("call expression args must be an array");
9437
+ return { kind: "call", fn: input.fn, args: input.args.map(parseJsonExpr) };
9438
+ case "arithmetic":
9439
+ if (!isArithmeticOp(input.op))
9440
+ throwParse("arithmetic expression op is invalid");
9441
+ return {
9442
+ kind: "arithmetic",
9443
+ op: input.op,
9444
+ left: parseJsonExpr(input.left),
9445
+ right: parseJsonExpr(input.right)
9446
+ };
9447
+ case "case": {
9448
+ if (!Array.isArray(input.whens))
9449
+ throwParse("case expression whens must be an array");
9450
+ return {
9451
+ kind: "case",
9452
+ whens: input.whens.map((when) => {
9453
+ if (!isRecord(when))
9454
+ throwParse("case expression whens must be objects");
9455
+ return { when: parseJsonExpr(when.when), value: parseJsonExpr(when.value) };
9456
+ }),
9457
+ ...input.else === void 0 ? {} : { else: parseJsonExpr(input.else) }
9458
+ };
9459
+ }
9460
+ default:
9461
+ throwParse(`Unsupported JSON expression kind ${String(input.kind)}`);
9462
+ }
9463
+ }
9464
+ function parseJsonWindows(input) {
9465
+ if (!isRecord(input))
9466
+ throwParse("JSON query windows must be an object");
9467
+ const windows = {};
9468
+ for (const [alias, value2] of Object.entries(input)) {
9469
+ if (alias.length === 0 || isPrototypeMutationKey2(alias)) {
9470
+ throwParse("JSON query window aliases must be valid object keys");
9471
+ }
9472
+ windows[alias] = parseJsonWindowExpr(value2);
9473
+ }
9474
+ return windows;
9475
+ }
9476
+ function parseJsonWindowExpr(input) {
9477
+ if (!isRecord(input))
9478
+ throwParse("JSON window expression must be an object");
9479
+ const over = input.over;
9480
+ if (!isRecord(over))
9481
+ throwParse("JSON window expression over must be an object");
9482
+ if (!Array.isArray(input.args))
9483
+ throwParse("JSON window expression args must be an array");
9484
+ const expr = {
9485
+ fn: parseJsonWindowFn(input.fn),
9486
+ args: input.args.map(parseJsonExpr),
9487
+ over: {
9488
+ partitionBy: parseExprArray(over.partitionBy, "JSON window partitionBy"),
9489
+ orderBy: parseJsonWindowOrderBy(over.orderBy),
9490
+ ...over.frame === void 0 ? {} : { frame: parseJsonWindowFrame(over.frame) }
9491
+ }
9492
+ };
9493
+ if (input.filter !== void 0)
9494
+ expr.filter = parseJsonExpr(input.filter);
9495
+ if (input.ignoreNulls !== void 0) {
9496
+ if (typeof input.ignoreNulls !== "boolean") {
9497
+ throwParse("JSON window expression ignoreNulls must be a boolean");
9498
+ }
9499
+ expr.ignoreNulls = input.ignoreNulls;
9500
+ }
9501
+ if (input.distinct !== void 0) {
9502
+ if (typeof input.distinct !== "boolean") {
9503
+ throwParse("JSON window expression distinct must be a boolean");
9504
+ }
9505
+ expr.distinct = input.distinct;
9506
+ }
9507
+ return expr;
9508
+ }
9509
+ function parseJsonWindowFn(input) {
9510
+ if (isWindowFnName(input))
9511
+ return input;
9512
+ if (isRecord(input) && isAggregateOp(input.aggregate))
9513
+ return { aggregate: input.aggregate };
9514
+ throwParse("JSON window function is invalid");
9515
+ }
9516
+ function parseJsonWindowOrderBy(input) {
9517
+ if (input === void 0)
9518
+ return [];
9519
+ if (!Array.isArray(input))
9520
+ throwParse("JSON window orderBy must be an array");
9521
+ return input.map((term) => {
9522
+ if (!isRecord(term))
9523
+ throwParse("JSON window orderBy terms must be objects");
9524
+ const out = { expr: parseJsonExpr(term.expr) };
9525
+ if (term.direction !== void 0) {
9526
+ if (term.direction !== "asc" && term.direction !== "desc") {
9527
+ throwParse("JSON window orderBy direction must be asc or desc");
9528
+ }
9529
+ out.direction = term.direction;
9530
+ }
9531
+ if (term.nulls !== void 0) {
9532
+ if (term.nulls !== "first" && term.nulls !== "last") {
9533
+ throwParse("JSON window orderBy nulls must be first or last");
9534
+ }
9535
+ out.nulls = term.nulls;
9536
+ }
9537
+ return out;
9538
+ });
9539
+ }
9540
+ function parseJsonWindowFrame(input) {
9541
+ if (!isRecord(input))
9542
+ throwParse("JSON window frame must be an object");
9543
+ if (!isOneOf(WINDOW_FRAME_MODES, input.mode)) {
9544
+ throwParse("JSON window frame mode must be rows, range, or groups");
9545
+ }
9546
+ if (!isOneOf(WINDOW_FRAME_EXCLUDES, input.exclude))
9547
+ throwParse("JSON window frame exclude is invalid");
9548
+ return {
9549
+ mode: input.mode,
9550
+ start: parseJsonWindowFrameBound(input.start),
9551
+ end: parseJsonWindowFrameBound(input.end),
9552
+ exclude: input.exclude
9553
+ };
9554
+ }
9555
+ function parseJsonWindowFrameBound(input) {
9556
+ if (!isRecord(input))
9557
+ throwParse("JSON window frame bound must be an object");
9558
+ if (!isOneOf(WINDOW_FRAME_BOUND_KINDS, input.kind))
9559
+ throwParse("JSON window frame bound kind is invalid");
9560
+ return {
9561
+ kind: input.kind,
9562
+ ...input.offset === void 0 ? {} : { offset: parseJsonExpr(input.offset) }
9563
+ };
9564
+ }
9565
+ function parseExprArray(input, field) {
9566
+ if (input === void 0)
9567
+ return [];
9568
+ if (!Array.isArray(input))
9569
+ throwParse(`${field} must be an array`);
9570
+ return input.map(parseJsonExpr);
9571
+ }
9317
9572
  function tuple2(value2, op, cb) {
9318
9573
  const tuple = requireTuple(value2, op, 2);
9319
9574
  return cb(requireColumn(tuple[0], op), requireScalar(tuple[1], op));
@@ -9351,6 +9606,24 @@ function requireScalar(value2, op) {
9351
9606
  }
9352
9607
  throwParse(`${op} value must be a scalar`);
9353
9608
  }
9609
+ function isCompareOp(value2) {
9610
+ return isOneOf(COMPARE_OPS, value2);
9611
+ }
9612
+ function isArithmeticOp(value2) {
9613
+ return isOneOf(ARITHMETIC_OPS, value2);
9614
+ }
9615
+ function isAggregateOp(value2) {
9616
+ return isOneOf(AGGREGATE_OPS, value2);
9617
+ }
9618
+ function isWindowFnName(value2) {
9619
+ return isOneOf(WINDOW_FN_NAMES, value2);
9620
+ }
9621
+ function isPrototypeMutationKey2(value2) {
9622
+ return value2 === "__proto__" || value2 === "prototype" || value2 === "constructor";
9623
+ }
9624
+ function isOneOf(values, value2) {
9625
+ return typeof value2 === "string" && values.includes(value2);
9626
+ }
9354
9627
  function parseNonNegativeInt(value2, field) {
9355
9628
  if (typeof value2 !== "number" || !Number.isInteger(value2) || value2 < 0) {
9356
9629
  throwParse(`${field} must be a non-negative integer`);
@@ -12744,7 +13017,7 @@ var QueryResult = class {
12744
13017
  if (config.scanner.scanColumns === void 0)
12745
13018
  return void 0;
12746
13019
  const state = createVectorGroupByState(groupColumns, spec);
12747
- for await (const batch of this.columnBatches(aggregateReadColumns(groupColumns, spec, config.where), startedAt, columnarBatchSize(config.batchSize))) {
13020
+ for await (const batch of this.columnBatches(aggregateReadColumns(groupColumns, spec, config.where, config.projections), startedAt, columnarBatchSize(config.batchSize))) {
12748
13021
  const selection = predicateSelection(batch, config.where);
12749
13022
  this.stats.rowsMatched += updateVectorGroupByState(state, batch, selection, {
12750
13023
  budget: config.budget,
@@ -12892,9 +13165,10 @@ var QueryResult = class {
12892
13165
  ]);
12893
13166
  const groups = await aggregateGroupsFromState(groupColumns, spec, options);
12894
13167
  const startedAt = this.config.now();
12895
- const readColumns = aggregateReadColumns(groupColumns, spec, this.config.where);
13168
+ const readColumns = aggregateReadColumns(groupColumns, spec, this.config.where, this.config.projections);
12896
13169
  for await (const row of this.matchedRows(startedAt, readColumns)) {
12897
- const keyValues = groupColumns.map((column) => valueForColumn(row, column));
13170
+ const aggregateRow = this.config.projections === void 0 ? row : project(row, ["*"], this.config.projections);
13171
+ const keyValues = groupColumns.map((column) => valueForColumn(aggregateRow, column));
12898
13172
  const key = stableStringify(keyValues);
12899
13173
  let group = groups.get(key);
12900
13174
  if (!group) {
@@ -12904,7 +13178,7 @@ var QueryResult = class {
12904
13178
  group = createAggregateGroup(groupColumns, keyValues, spec);
12905
13179
  groups.set(key, group);
12906
13180
  }
12907
- group.add(row);
13181
+ group.add(aggregateRow);
12908
13182
  enforceBufferedRowsBudget(this.config.budget, estimateAggregateBufferedRows(groups));
12909
13183
  enforceOperatorMemoryBudget(this.config.budget, estimateAggregateOperatorMemoryBytes(groupColumns, spec, groups));
12910
13184
  }
@@ -14480,10 +14754,15 @@ async function* rowsAsBatches(rows, batchSize) {
14480
14754
  yield rows.slice(index, index + batchSize);
14481
14755
  }
14482
14756
  }
14483
- function aggregateReadColumns(groupColumns, spec, where) {
14757
+ function aggregateReadColumns(groupColumns, spec, where, projections) {
14484
14758
  const columns = /* @__PURE__ */ new Set();
14485
- for (const column of groupColumns)
14486
- columns.add(column);
14759
+ for (const column of groupColumns) {
14760
+ const projection = projections?.[column];
14761
+ if (projection === void 0)
14762
+ columns.add(column);
14763
+ else
14764
+ collectExprColumns(projection, columns);
14765
+ }
14487
14766
  for (const aggregate of Object.values(spec)) {
14488
14767
  if (aggregate.column !== void 0)
14489
14768
  columns.add(aggregate.column);
@@ -14955,6 +15234,20 @@ async function lookupJoin(left, lookup, options) {
14955
15234
  }
14956
15235
  return out;
14957
15236
  }
15237
+ async function crossJoin(left, right, options) {
15238
+ validateCrossJoinOptions(options);
15239
+ const rightRows = await collectRightRows(right, options.maxRightRows);
15240
+ const out = [];
15241
+ for await (const leftRow of left) {
15242
+ for (const rightRow of rightRows) {
15243
+ if (out.length >= options.maxOutputRows) {
15244
+ enforceMaxOutputRows(out.length + 1, options.maxOutputRows);
15245
+ }
15246
+ out.push(mergeCrossRows(leftRow, rightRow, options.rightPrefix));
15247
+ }
15248
+ }
15249
+ return out;
15250
+ }
14958
15251
  async function collectRightRows(rows, maxRightRows) {
14959
15252
  const out = [];
14960
15253
  for await (const row of rows) {
@@ -14965,6 +15258,14 @@ async function collectRightRows(rows, maxRightRows) {
14965
15258
  }
14966
15259
  return out;
14967
15260
  }
15261
+ function validateCrossJoinOptions(options) {
15262
+ if (!Number.isInteger(options.maxRightRows) || options.maxRightRows < 1) {
15263
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "Cross join maxRightRows must be a positive integer");
15264
+ }
15265
+ if (!Number.isInteger(options.maxOutputRows) || options.maxOutputRows < 1) {
15266
+ throw new LakeqlError("LAKEQL_TYPE_ERROR", "Cross join maxOutputRows must be a positive integer");
15267
+ }
15268
+ }
14968
15269
  function validateJoinOptions(options, strategy) {
14969
15270
  const leftKeys = normalizeJoinKeys(options.leftKey, "leftKey", strategy);
14970
15271
  const rightKeys = normalizeJoinKeys(options.rightKey, "rightKey", strategy);
@@ -15028,11 +15329,24 @@ function mergeRows(left, right, options) {
15028
15329
  }
15029
15330
  return out;
15030
15331
  }
15332
+ function mergeCrossRows(left, right, rightPrefix = "right.") {
15333
+ const out = { ...left };
15334
+ for (const [key, value2] of Object.entries(right)) {
15335
+ const outKey = key in out ? `${rightPrefix}${key}` : key;
15336
+ out[outKey] = value2;
15337
+ }
15338
+ return out;
15339
+ }
15031
15340
  function enforceMaxRightRows(strategy, actual, limit) {
15032
15341
  if (actual <= limit)
15033
15342
  return;
15034
15343
  throw new LakeqlError("LAKEQL_BUDGET_EXCEEDED", `${strategy} join exceeded maxRightRows (${actual} > ${limit})`, { metric: "maxRightRows", limit, actual });
15035
15344
  }
15345
+ function enforceMaxOutputRows(actual, limit) {
15346
+ if (actual <= limit)
15347
+ return;
15348
+ throw new LakeqlError("LAKEQL_BUDGET_EXCEEDED", `Cross join exceeded maxOutputRows (${actual} > ${limit})`, { metric: "maxOutputRows", limit, actual });
15349
+ }
15036
15350
 
15037
15351
  // ../core/dist/memory-store.js
15038
15352
  async function collect(body) {
@@ -19757,6 +20071,7 @@ function parseSql(sql, options = {}) {
19757
20071
  if (sql.length > MAX_SQL_LENGTH) {
19758
20072
  throwParse2(`SQL input length exceeds ${MAX_SQL_LENGTH}`);
19759
20073
  }
20074
+ rejectRecursiveCte(sql);
19760
20075
  const windowPrepass = extractWindowFrames(sql);
19761
20076
  const prepass = extractTopLevelQualify(windowPrepass.sql);
19762
20077
  let statements;
@@ -19781,6 +20096,18 @@ function parseSql(sql, options = {}) {
19781
20096
  throwUnsupported("Only SELECT statements are supported");
19782
20097
  return selectStatementToAst(statement, context, context.qualifySql);
19783
20098
  }
20099
+ function rejectRecursiveCte(sql) {
20100
+ const withIndex = findTopLevelKeyword(sql, "with", 0);
20101
+ if (withIndex === -1)
20102
+ return;
20103
+ const recursiveIndex = findTopLevelKeyword(sql, "recursive", withIndex + "with".length);
20104
+ if (recursiveIndex === -1)
20105
+ return;
20106
+ const between2 = sql.slice(withIndex + "with".length, recursiveIndex);
20107
+ if (between2.trim().length > 0)
20108
+ return;
20109
+ throwUnsupported("Recursive CTEs are not supported");
20110
+ }
19784
20111
  function parseSqlStatement(sql, options = {}) {
19785
20112
  const describe = parseDescribeStatement(sql);
19786
20113
  if (describe !== void 0)
@@ -19811,28 +20138,27 @@ function withStatementToAst(statement, context) {
19811
20138
  const binding = asNode(bindings[0], "CTE binding");
19812
20139
  const name = nameNodeToString(binding.alias);
19813
20140
  const inner = asNode(binding.statement, "CTE statement");
19814
- if (inner.type !== "select")
20141
+ if (inner.type !== "select" && inner.type !== "with") {
19815
20142
  throwUnsupported("Only SELECT CTEs are supported");
19816
- const cteQuery = selectStatementToAst(inner, context);
19817
- validateCteQuery(cteQuery);
20143
+ }
20144
+ const cteQuery = inner.type === "with" ? withStatementToAst(inner, context) : selectStatementToAst(inner, context);
19818
20145
  const outer = asNode(statement.in, "CTE outer query");
19819
20146
  if (outer.type !== "select")
19820
20147
  throwUnsupported("Only SELECT after WITH is supported");
19821
20148
  const ast = selectStatementToAst(outer, context, context.qualifySql);
20149
+ if (ast.cte !== void 0)
20150
+ throwUnsupported("WITH queries cannot also use derived tables");
19822
20151
  ast.cte = { name, query: cteQuery };
19823
20152
  return ast;
19824
20153
  }
19825
- function validateCteQuery(ast) {
19826
- if (ast.join !== void 0 || ast.subqueryJoin !== void 0 || ast.scalarSubqueries !== void 0 || ast.cte !== void 0) {
19827
- throwUnsupported("Nested CTE joins and subqueries are not supported");
19828
- }
19829
- }
19830
20154
  function selectStatementToAst(statement, context = newSqlParseContext(), qualifySql) {
19831
20155
  const previousWindows = context.windows;
19832
20156
  const previousNextWindowId = context.nextWindowId;
20157
+ const previousNextHavingAggregateId = context.nextHavingAggregateId;
19833
20158
  const previousQualifySql = context.qualifySql;
19834
20159
  context.windows = {};
19835
20160
  context.nextWindowId = 0;
20161
+ context.nextHavingAggregateId = 0;
19836
20162
  if (qualifySql === void 0)
19837
20163
  delete context.qualifySql;
19838
20164
  else
@@ -19840,6 +20166,7 @@ function selectStatementToAst(statement, context = newSqlParseContext(), qualify
19840
20166
  const ast = selectStatementToAstInScope(statement, context);
19841
20167
  context.windows = previousWindows;
19842
20168
  context.nextWindowId = previousNextWindowId;
20169
+ context.nextHavingAggregateId = previousNextHavingAggregateId;
19843
20170
  if (previousQualifySql === void 0)
19844
20171
  delete context.qualifySql;
19845
20172
  else
@@ -19850,16 +20177,28 @@ function selectStatementToAstInScope(statement, context) {
19850
20177
  rejectPresent(statement, "with", "CTEs are not supported");
19851
20178
  rejectPresent(statement, "windows", "Window functions are not supported");
19852
20179
  const from = optionalArray(statement.from);
19853
- if (from.length !== 1 && from.length !== 2) {
19854
- throwUnsupported("SELECT must have exactly one FROM table or one bounded JOIN");
20180
+ if (from.length < 1) {
20181
+ throwUnsupported("SELECT must have a FROM table");
19855
20182
  }
19856
- const leftSource = sourceTable(from[0]);
20183
+ const leftSource = sourceTable(from[0], context);
19857
20184
  const ast = { source: leftSource.source };
20185
+ if (leftSource.cte !== void 0)
20186
+ ast.cte = leftSource.cte;
19858
20187
  const scope = new SqlScope(leftSource);
19859
- if (from.length === 2) {
19860
- const join = joinToAst(from[1], leftSource);
19861
- ast.join = join;
19862
- scope.add({ source: join.source, alias: join.alias });
20188
+ const joinedSources = [leftSource];
20189
+ const joins = [];
20190
+ for (const source of from.slice(1)) {
20191
+ const { join, right } = joinToAst(source, joinedSources);
20192
+ joins.push(join);
20193
+ joinedSources.push(right);
20194
+ scope.add(right);
20195
+ }
20196
+ if (joins.length > 0) {
20197
+ const firstJoin = joins[0];
20198
+ if (firstJoin === void 0)
20199
+ throwUnsupported("JOIN chain must contain at least one join");
20200
+ ast.join = firstJoin;
20201
+ ast.joins = joins;
19863
20202
  }
19864
20203
  if (statement.distinct !== void 0) {
19865
20204
  if (statement.distinct !== "distinct") {
@@ -19874,6 +20213,7 @@ function selectStatementToAstInScope(statement, context) {
19874
20213
  const projections = {};
19875
20214
  const aggregates = {};
19876
20215
  const outputAliases = /* @__PURE__ */ new Set();
20216
+ const hiddenAggregates = /* @__PURE__ */ new Set();
19877
20217
  for (const column of columns) {
19878
20218
  const item = asNode(column, "select item");
19879
20219
  const expr = asNode(item.expr, "select expression");
@@ -19924,17 +20264,25 @@ function selectStatementToAstInScope(statement, context) {
19924
20264
  const where = whereToAst(asNode(statement.where, "WHERE"), scope, context);
19925
20265
  if (where.where !== void 0)
19926
20266
  ast.where = where.where;
19927
- if (where.subqueryJoin !== void 0)
19928
- ast.subqueryJoin = where.subqueryJoin;
20267
+ if (where.subqueryJoins !== void 0) {
20268
+ ast.subqueryJoins = where.subqueryJoins;
20269
+ const firstSubqueryJoin = where.subqueryJoins[0];
20270
+ if (firstSubqueryJoin !== void 0)
20271
+ ast.subqueryJoin = firstSubqueryJoin;
20272
+ }
19929
20273
  }
19930
20274
  if (statement.groupBy !== void 0) {
19931
20275
  ast.groupBy = optionalArray(statement.groupBy).map((expr) => scope.refName(asNode(expr, "GROUP BY expression")));
19932
20276
  }
19933
20277
  if (statement.having !== void 0) {
19934
- ast.having = exprToLakeql(asNode(statement.having, "HAVING"), scope, context);
20278
+ ast.having = havingToExpr(asNode(statement.having, "HAVING"), scope, context, aggregates, hiddenAggregates, outputAliases);
20279
+ if (Object.keys(aggregates).length > 0)
20280
+ ast.aggregates = aggregates;
20281
+ if (hiddenAggregates.size > 0)
20282
+ ast.hiddenAggregates = [...hiddenAggregates];
19935
20283
  }
19936
20284
  if (context.qualifySql !== void 0) {
19937
- ast.qualify = qualifyToExpr(context.qualifySql, scope, context);
20285
+ ast.qualify = expandProjectionAliasesInExpr(qualifyToExpr(context.qualifySql, scope, context), projections, context.windows);
19938
20286
  }
19939
20287
  if (Object.keys(context.windows).length > 0)
19940
20288
  ast.windows = context.windows;
@@ -19952,6 +20300,9 @@ function selectStatementToAstInScope(statement, context) {
19952
20300
  if (Object.keys(context.scalarSubqueries).length > 0) {
19953
20301
  ast.scalarSubqueries = context.scalarSubqueries;
19954
20302
  }
20303
+ if (Object.keys(context.correlatedScalarSubqueries).length > 0) {
20304
+ ast.correlatedScalarSubqueries = context.correlatedScalarSubqueries;
20305
+ }
19955
20306
  return ast;
19956
20307
  }
19957
20308
  function qualifyToExpr(sql, scope, context) {
@@ -19967,12 +20318,106 @@ function qualifyToExpr(sql, scope, context) {
19967
20318
  const where = asNode(statement.where, "QUALIFY predicate");
19968
20319
  return exprToLakeql(where, scope, context);
19969
20320
  }
20321
+ function expandProjectionAliasesInExpr(expr, projections, windows) {
20322
+ switch (expr.kind) {
20323
+ case "column":
20324
+ return expr.name in windows ? expr : projections[expr.name] ?? expr;
20325
+ case "compare":
20326
+ return {
20327
+ ...expr,
20328
+ left: expandProjectionAliasesInExpr(expr.left, projections, windows),
20329
+ right: expandProjectionAliasesInExpr(expr.right, projections, windows)
20330
+ };
20331
+ case "between":
20332
+ return {
20333
+ ...expr,
20334
+ target: expandProjectionAliasesInExpr(expr.target, projections, windows),
20335
+ low: expandProjectionAliasesInExpr(expr.low, projections, windows),
20336
+ high: expandProjectionAliasesInExpr(expr.high, projections, windows)
20337
+ };
20338
+ case "in":
20339
+ return {
20340
+ ...expr,
20341
+ target: expandProjectionAliasesInExpr(expr.target, projections, windows),
20342
+ values: expr.values.map((value2) => expandProjectionAliasesInExpr(value2, projections, windows))
20343
+ };
20344
+ case "null-check":
20345
+ return { ...expr, target: expandProjectionAliasesInExpr(expr.target, projections, windows) };
20346
+ case "logical":
20347
+ return {
20348
+ ...expr,
20349
+ operands: expr.operands.map((operand) => expandProjectionAliasesInExpr(operand, projections, windows))
20350
+ };
20351
+ case "not":
20352
+ return {
20353
+ ...expr,
20354
+ operand: expandProjectionAliasesInExpr(expr.operand, projections, windows)
20355
+ };
20356
+ case "like":
20357
+ return { ...expr, target: expandProjectionAliasesInExpr(expr.target, projections, windows) };
20358
+ case "call":
20359
+ return {
20360
+ ...expr,
20361
+ args: expr.args.map((arg) => expandProjectionAliasesInExpr(arg, projections, windows))
20362
+ };
20363
+ case "arithmetic":
20364
+ return {
20365
+ ...expr,
20366
+ left: expandProjectionAliasesInExpr(expr.left, projections, windows),
20367
+ right: expandProjectionAliasesInExpr(expr.right, projections, windows)
20368
+ };
20369
+ case "case":
20370
+ return {
20371
+ ...expr,
20372
+ whens: expr.whens.map((branch) => ({
20373
+ when: expandProjectionAliasesInExpr(branch.when, projections, windows),
20374
+ value: expandProjectionAliasesInExpr(branch.value, projections, windows)
20375
+ })),
20376
+ ...expr.else === void 0 ? {} : { else: expandProjectionAliasesInExpr(expr.else, projections, windows) }
20377
+ };
20378
+ case "literal":
20379
+ return expr;
20380
+ }
20381
+ }
20382
+ function havingToExpr(expr, scope, context, aggregates, hiddenAggregates, outputAliases) {
20383
+ const previous = context.havingAggregates;
20384
+ context.havingAggregates = { aggregates, hiddenAggregates, outputAliases };
20385
+ try {
20386
+ return exprToLakeql(expr, scope, context);
20387
+ } finally {
20388
+ if (previous === void 0)
20389
+ delete context.havingAggregates;
20390
+ else
20391
+ context.havingAggregates = previous;
20392
+ }
20393
+ }
20394
+ function registerHavingAggregate(expr, scope, context) {
20395
+ const havingAggregates = context.havingAggregates;
20396
+ if (havingAggregates === void 0)
20397
+ throwUnsupported("HAVING aggregate context is missing");
20398
+ const spec = aggregateCallToSpec(expr, scope, context);
20399
+ for (const [alias2, aggregate] of Object.entries(havingAggregates.aggregates)) {
20400
+ if (JSON.stringify(aggregate) === JSON.stringify(spec))
20401
+ return alias2;
20402
+ }
20403
+ let alias = `__lakeql_having_${context.nextHavingAggregateId}`;
20404
+ context.nextHavingAggregateId += 1;
20405
+ while (havingAggregates.outputAliases.has(alias) || havingAggregates.aggregates[alias] !== void 0) {
20406
+ alias = `__lakeql_having_${context.nextHavingAggregateId}`;
20407
+ context.nextHavingAggregateId += 1;
20408
+ }
20409
+ havingAggregates.aggregates[alias] = spec;
20410
+ havingAggregates.hiddenAggregates.add(alias);
20411
+ return alias;
20412
+ }
19970
20413
  function newSqlParseContext(options = {}) {
19971
20414
  return {
19972
20415
  scalarSubqueries: {},
20416
+ correlatedScalarSubqueries: {},
19973
20417
  nextScalarSubqueryId: 0,
19974
20418
  windows: {},
19975
20419
  nextWindowId: 0,
20420
+ nextHavingAggregateId: 0,
19976
20421
  windowFrames: [],
19977
20422
  nextWindowFrameId: 0,
19978
20423
  ...options.parameters === void 0 ? {} : { parameters: options.parameters }
@@ -20000,13 +20445,19 @@ function exprToLakeql(expr, scope = SqlScope.empty(), context = newSqlParseConte
20000
20445
  case "case":
20001
20446
  return caseToExpr(expr, scope, context);
20002
20447
  case "select":
20003
- return scalarSubqueryToExpr(expr, context);
20448
+ return scalarSubqueryToExpr(expr, scope, context);
20004
20449
  case "cast":
20005
20450
  return castToExpr(expr, scope, context);
20006
20451
  case "call":
20452
+ if (context.havingAggregates !== void 0 && isAggregateCall(expr)) {
20453
+ return { kind: "column", name: registerHavingAggregate(expr, scope, context) };
20454
+ }
20007
20455
  if ("over" in expr && expr.over !== void 0) {
20008
20456
  return { kind: "column", name: registerSyntheticWindow(expr, scope, context) };
20009
20457
  }
20458
+ if (functionName(expr.function) === "exists") {
20459
+ return existsSubqueryToExpr(expr, context);
20460
+ }
20010
20461
  return {
20011
20462
  kind: "call",
20012
20463
  fn: functionName(expr.function),
@@ -20070,17 +20521,15 @@ function binaryToExpr(expr, scope = SqlScope.empty(), context = newSqlParseConte
20070
20521
  }
20071
20522
  function whereToAst(expr, scope, context) {
20072
20523
  const predicates = flattenWhereConjuncts(expr);
20073
- let subqueryJoin;
20524
+ const subqueryJoins = [];
20074
20525
  const residual = [];
20075
20526
  for (const predicate of predicates) {
20076
- const extracted = maybeSubqueryJoin(predicate, scope);
20527
+ const extracted = maybeSubqueryJoin(predicate, scope, context);
20077
20528
  if (extracted === void 0) {
20078
20529
  residual.push(predicate);
20079
20530
  continue;
20080
20531
  }
20081
- if (subqueryJoin !== void 0)
20082
- throwUnsupported("Only one IN subquery is supported");
20083
- subqueryJoin = extracted;
20532
+ subqueryJoins.push(extracted);
20084
20533
  }
20085
20534
  const out = {};
20086
20535
  if (residual.length === 1)
@@ -20092,35 +20541,159 @@ function whereToAst(expr, scope, context) {
20092
20541
  operands: residual.map((predicate) => exprToLakeql(predicate, scope, context))
20093
20542
  };
20094
20543
  }
20095
- if (subqueryJoin !== void 0)
20096
- out.subqueryJoin = subqueryJoin;
20544
+ if (subqueryJoins.length > 0)
20545
+ out.subqueryJoins = subqueryJoins;
20097
20546
  return out;
20098
20547
  }
20099
- function maybeSubqueryJoin(expr, scope) {
20100
- if (expr.type !== "binary")
20548
+ function maybeSubqueryJoin(expr, scope, context) {
20549
+ if (expr.type === "binary") {
20550
+ const scalarAggregateJoin = scalarAggregatePredicateSubqueryJoinToAst(expr, scope, context);
20551
+ if (scalarAggregateJoin !== void 0)
20552
+ return scalarAggregateJoin;
20553
+ const op = String(expr.op).toUpperCase();
20554
+ if (op !== "IN" && op !== "NOT IN")
20555
+ return void 0;
20556
+ const right = asNode(expr.right, "IN right side");
20557
+ if (right.type !== "select")
20558
+ return void 0;
20559
+ return subqueryJoinToAst(asNode(expr.left, "IN left side"), right, scope, op === "NOT IN", context);
20560
+ }
20561
+ const exists = existsCallFromPredicate(expr);
20562
+ if (exists === void 0)
20101
20563
  return void 0;
20564
+ return existsSubqueryJoinToAst(exists.call, scope, exists.negated, context);
20565
+ }
20566
+ function scalarAggregatePredicateSubqueryJoinToAst(expr, outerScope, context) {
20102
20567
  const op = String(expr.op).toUpperCase();
20103
- if (op !== "IN" && op !== "NOT IN")
20568
+ if (!["=", "!=", "<>", "<", "<=", ">", ">="].includes(op))
20569
+ return void 0;
20570
+ const left = asNode(expr.left, "scalar aggregate predicate left side");
20571
+ const right = asNode(expr.right, "scalar aggregate predicate right side");
20572
+ const scalarSide = left.type === "select" && right.type !== "select" ? { subquery: left, other: right, subqueryOnLeft: true } : right.type === "select" && left.type !== "select" ? { subquery: right, other: left, subqueryOnLeft: false } : void 0;
20573
+ if (scalarSide === void 0)
20574
+ return void 0;
20575
+ const scalar = scalarAggregateSubqueryToAst(scalarSide.subquery, outerScope, context);
20576
+ if (scalar === void 0)
20577
+ return void 0;
20578
+ const subqueryScope = new SqlScope({
20579
+ source: scalar.source,
20580
+ alias: scalar.alias ?? scalar.source
20581
+ });
20582
+ const aggregate = scalar.aggregates[scalar.column];
20583
+ if (aggregate === void 0)
20584
+ throwUnsupported("Correlated scalar aggregate is missing output");
20585
+ const alias = scalar.alias;
20586
+ const out = {
20587
+ source: scalar.source,
20588
+ type: "semi",
20589
+ leftKey: scalar.leftKey,
20590
+ rightKey: scalar.rightKey,
20591
+ groupBy: scalar.groupBy,
20592
+ aggregates: scalar.aggregates,
20593
+ ...scalar.hiddenAggregates === void 0 ? {} : { hiddenAggregates: scalar.hiddenAggregates },
20594
+ ...scalar.having === void 0 ? {} : { having: scalar.having },
20595
+ ...scalar.leftAlias === void 0 ? {} : { leftAlias: scalar.leftAlias },
20596
+ ...alias === void 0 ? {} : { alias },
20597
+ ...scalar.where === void 0 ? {} : { where: scalar.where }
20598
+ };
20599
+ const aggregateExpr = {
20600
+ kind: "column",
20601
+ name: alias === void 0 ? scalar.column : `${alias}.${scalar.column}`
20602
+ };
20603
+ const other = exprToLakeql(scalarSide.other, combinedScope(outerScope, subqueryScope), context);
20604
+ out.predicate = {
20605
+ kind: "compare",
20606
+ op: compareOp(op),
20607
+ left: scalarSide.subqueryOnLeft ? aggregateExpr : other,
20608
+ right: scalarSide.subqueryOnLeft ? other : aggregateExpr
20609
+ };
20610
+ return out;
20611
+ }
20612
+ function scalarAggregateSubqueryToAst(subquery, outerScope, context) {
20613
+ const from = optionalArray(subquery.from);
20614
+ if (from.length !== 1)
20615
+ return void 0;
20616
+ const source = sourceTable(from[0]);
20617
+ const subqueryScope = new SqlScope(source);
20618
+ const aggregate = scalarAggregateSelectItem(subquery, subqueryScope, context);
20619
+ if (aggregate === void 0)
20620
+ return void 0;
20621
+ rejectPresent(subquery, "orderBy", "ORDER BY in correlated scalar aggregate subqueries is not supported");
20622
+ rejectPresent(subquery, "limit", "LIMIT in correlated scalar aggregate subqueries is not supported");
20623
+ const out = {
20624
+ source: source.source,
20625
+ leftKey: [],
20626
+ rightKey: [],
20627
+ groupBy: [],
20628
+ aggregates: { [aggregate.alias]: aggregate.spec },
20629
+ column: aggregate.alias
20630
+ };
20631
+ applyCorrelatedSubqueryWhere(out, subquery, outerScope, subqueryScope, context);
20632
+ if (out.leftKey.length === 0) {
20633
+ if (predicateReferencesOuterScope(subquery, outerScope)) {
20634
+ throwUnsupported("Correlated scalar aggregate subqueries require equality correlation keys");
20635
+ }
20104
20636
  return void 0;
20105
- const right = asNode(expr.right, "IN right side");
20106
- if (right.type !== "select")
20637
+ }
20638
+ if (out.predicate !== void 0) {
20639
+ throwUnsupported("Correlated scalar aggregate subqueries with non-equality predicates are not supported");
20640
+ }
20641
+ if (subquery.groupBy !== void 0) {
20642
+ out.groupBy = optionalArray(subquery.groupBy).map((expr) => subqueryScope.refName(asNode(expr, "scalar aggregate subquery GROUP BY expression")));
20643
+ const rightKeys = new Set(out.rightKey);
20644
+ if (out.groupBy.some((column) => !rightKeys.has(column))) {
20645
+ throwUnsupported("Correlated scalar aggregate subqueries can only group by equality correlation keys");
20646
+ }
20647
+ }
20648
+ out.groupBy = appendUnique(out.groupBy ?? [], out.rightKey);
20649
+ if (subquery.having !== void 0) {
20650
+ const aggregates = out.aggregates ?? {};
20651
+ const hiddenAggregates = /* @__PURE__ */ new Set();
20652
+ const outputAliases = /* @__PURE__ */ new Set([...out.groupBy, aggregate.alias]);
20653
+ out.having = havingToExpr(asNode(subquery.having, "scalar aggregate subquery HAVING"), subqueryScope, context, aggregates, hiddenAggregates, outputAliases);
20654
+ if (Object.keys(aggregates).length > 0)
20655
+ out.aggregates = aggregates;
20656
+ if (hiddenAggregates.size > 0)
20657
+ out.hiddenAggregates = [...hiddenAggregates];
20658
+ }
20659
+ const leftAlias = outerScope.primaryAlias();
20660
+ if (leftAlias !== void 0)
20661
+ out.leftAlias = leftAlias;
20662
+ const alias = subqueryScope.primaryAlias();
20663
+ if (alias !== void 0)
20664
+ out.alias = alias;
20665
+ return out;
20666
+ }
20667
+ function scalarAggregateSelectItem(subquery, scope, context) {
20668
+ const columns = optionalArray(subquery.columns);
20669
+ if (columns.length !== 1) {
20670
+ throwUnsupported("Scalar aggregate subqueries must return exactly one column");
20671
+ }
20672
+ const item = asNode(columns[0], "scalar aggregate subquery select item");
20673
+ const expr = asNode(item.expr, "scalar aggregate subquery select expression");
20674
+ if (expr.type !== "call" || !isAggregateCall(expr))
20107
20675
  return void 0;
20108
- return subqueryJoinToAst(asNode(expr.left, "IN left side"), right, scope, op === "NOT IN");
20676
+ return {
20677
+ alias: aliasName(item.alias) ?? functionName(expr.function),
20678
+ spec: aggregateCallToSpec(expr, scope, context)
20679
+ };
20109
20680
  }
20110
- function subqueryJoinToAst(left, subquery, outerScope, negated) {
20111
- rejectPresent(subquery, "groupBy", "Grouped IN subqueries are not supported");
20112
- rejectPresent(subquery, "having", "HAVING in IN subqueries is not supported");
20113
- rejectPresent(subquery, "orderBy", "ORDER BY in IN subqueries is not supported");
20114
- rejectPresent(subquery, "limit", "LIMIT in IN subqueries is not supported");
20681
+ function subqueryJoinToAst(left, subquery, outerScope, negated, context) {
20115
20682
  const from = optionalArray(subquery.from);
20116
20683
  if (from.length !== 1)
20117
20684
  throwUnsupported("IN subqueries must select from one table");
20118
20685
  const source = sourceTable(from[0]);
20119
20686
  const subqueryScope = new SqlScope(source);
20687
+ const aggregates = {};
20688
+ const hiddenAggregates = /* @__PURE__ */ new Set();
20689
+ const outputAliases = /* @__PURE__ */ new Set();
20120
20690
  const leftKey = subqueryLeftKeys(left, outerScope);
20691
+ const directLeftKeyCount = leftKey.length;
20121
20692
  const rightKey = optionalArray(subquery.columns).map((column) => {
20122
20693
  const item = asNode(column, "IN subquery select item");
20123
- return subqueryScope.refName(asNode(item.expr, "IN subquery key"));
20694
+ const key = subqueryScope.refName(asNode(item.expr, "IN subquery key"));
20695
+ ensureUniqueOutputAlias(aliasName(item.alias) ?? key, outputAliases);
20696
+ return key;
20124
20697
  });
20125
20698
  if (leftKey.length !== rightKey.length || leftKey.length === 0) {
20126
20699
  throwUnsupported("IN subquery key counts must match");
@@ -20131,11 +20704,206 @@ function subqueryJoinToAst(left, subquery, outerScope, negated) {
20131
20704
  leftKey,
20132
20705
  rightKey
20133
20706
  };
20134
- if (subquery.where !== void 0) {
20135
- out.where = exprToLakeql(asNode(subquery.where, "IN subquery WHERE"), subqueryScope);
20707
+ applyCorrelatedSubqueryWhere(out, subquery, outerScope, subqueryScope, context);
20708
+ if ((subquery.groupBy !== void 0 || subquery.having !== void 0) && out.predicate !== void 0) {
20709
+ throwUnsupported("Correlated grouped IN subqueries with non-equality predicates are not supported");
20710
+ }
20711
+ if (subquery.groupBy !== void 0) {
20712
+ out.groupBy = optionalArray(subquery.groupBy).map((expr) => subqueryScope.refName(asNode(expr, "IN subquery GROUP BY expression")));
20713
+ }
20714
+ if (subquery.groupBy !== void 0 || subquery.having !== void 0) {
20715
+ out.groupBy = appendUnique(out.groupBy ?? [], out.rightKey.slice(directLeftKeyCount));
20716
+ }
20717
+ if (subquery.having !== void 0) {
20718
+ out.having = havingToExpr(asNode(subquery.having, "IN subquery HAVING"), subqueryScope, context, aggregates, hiddenAggregates, outputAliases);
20719
+ }
20720
+ if (Object.keys(aggregates).length > 0)
20721
+ out.aggregates = aggregates;
20722
+ if (hiddenAggregates.size > 0)
20723
+ out.hiddenAggregates = [...hiddenAggregates];
20724
+ if (subquery.orderBy !== void 0) {
20725
+ out.orderBy = optionalArray(subquery.orderBy).map((term) => orderByToTerm(term, subqueryScope));
20726
+ }
20727
+ const limit = subquery.limit;
20728
+ if (limit !== void 0) {
20729
+ const node = asNode(limit, "IN subquery LIMIT");
20730
+ if (node.limit !== void 0)
20731
+ out.limit = nonNegativeInteger(node.limit, "LIMIT", context);
20732
+ if (node.offset !== void 0)
20733
+ out.offset = nonNegativeInteger(node.offset, "OFFSET", context);
20734
+ }
20735
+ return out;
20736
+ }
20737
+ function existsCallFromPredicate(expr) {
20738
+ if (expr.type === "call" && functionName(expr.function) === "exists") {
20739
+ return { call: expr, negated: false };
20740
+ }
20741
+ if (expr.type !== "unary" || String(expr.op).toUpperCase() !== "NOT")
20742
+ return void 0;
20743
+ const operand = asNode(expr.operand, "NOT operand");
20744
+ if (operand.type !== "call" || functionName(operand.function) !== "exists")
20745
+ return void 0;
20746
+ return { call: operand, negated: true };
20747
+ }
20748
+ function existsSubqueryJoinToAst(expr, outerScope, negated, context) {
20749
+ const subquery = existsSubquery(expr);
20750
+ rejectPresent(subquery, "orderBy", "ORDER BY in EXISTS subqueries is not supported");
20751
+ rejectPresent(subquery, "limit", "LIMIT in EXISTS subqueries is not supported");
20752
+ const from = optionalArray(subquery.from);
20753
+ if (from.length !== 1)
20754
+ throwUnsupported("EXISTS subqueries must select from one table");
20755
+ const source = sourceTable(from[0]);
20756
+ const subqueryScope = new SqlScope(source);
20757
+ const aggregates = {};
20758
+ const hiddenAggregates = /* @__PURE__ */ new Set();
20759
+ const outputAliases = /* @__PURE__ */ new Set();
20760
+ const out = {
20761
+ source: source.source,
20762
+ type: negated ? "anti" : "semi",
20763
+ leftKey: [],
20764
+ rightKey: []
20765
+ };
20766
+ applyCorrelatedSubqueryWhere(out, subquery, outerScope, subqueryScope, context);
20767
+ if (subquery.groupBy !== void 0 || subquery.having !== void 0) {
20768
+ if (out.predicate !== void 0) {
20769
+ throwUnsupported("Correlated grouped EXISTS subqueries with non-equality predicates are not supported");
20770
+ }
20771
+ if (out.leftKey.length === 0)
20772
+ return void 0;
20773
+ if (subquery.groupBy !== void 0) {
20774
+ out.groupBy = optionalArray(subquery.groupBy).map((expr2) => subqueryScope.refName(asNode(expr2, "EXISTS subquery GROUP BY expression")));
20775
+ }
20776
+ out.groupBy = appendUnique(out.groupBy ?? [], out.rightKey);
20777
+ if (subquery.having !== void 0) {
20778
+ out.having = havingToExpr(asNode(subquery.having, "EXISTS subquery HAVING"), subqueryScope, context, aggregates, hiddenAggregates, outputAliases);
20779
+ }
20780
+ if (Object.keys(aggregates).length > 0)
20781
+ out.aggregates = aggregates;
20782
+ if (hiddenAggregates.size > 0)
20783
+ out.hiddenAggregates = [...hiddenAggregates];
20784
+ return out;
20785
+ }
20786
+ return out.leftKey.length === 0 && out.predicate === void 0 ? void 0 : out;
20787
+ }
20788
+ function appendUnique(values, additions) {
20789
+ const out = [...values];
20790
+ const seen = new Set(out);
20791
+ for (const value2 of additions) {
20792
+ if (seen.has(value2))
20793
+ continue;
20794
+ seen.add(value2);
20795
+ out.push(value2);
20136
20796
  }
20137
20797
  return out;
20138
20798
  }
20799
+ function existsSubquery(expr) {
20800
+ const args = optionalArray(expr.args);
20801
+ if (args.length !== 1)
20802
+ throwUnsupported("EXISTS requires exactly one subquery");
20803
+ const subquery = asNode(args[0], "EXISTS subquery");
20804
+ if (subquery.type !== "select")
20805
+ throwUnsupported("EXISTS requires a SELECT subquery");
20806
+ return subquery;
20807
+ }
20808
+ function applyCorrelatedSubqueryWhere(out, subquery, outerScope, subqueryScope, context) {
20809
+ if (subquery.where === void 0)
20810
+ return;
20811
+ const residual = [];
20812
+ const correlated = [];
20813
+ for (const predicate of flattenWhereConjuncts(asNode(subquery.where, "subquery WHERE"))) {
20814
+ const pair = correlatedJoinKeyPair(predicate, outerScope, subqueryScope);
20815
+ if (pair === void 0) {
20816
+ if (predicateReferencesOuterScope(predicate, outerScope)) {
20817
+ correlated.push(predicate);
20818
+ continue;
20819
+ }
20820
+ residual.push(predicate);
20821
+ continue;
20822
+ }
20823
+ out.leftKey.push(pair.leftKey);
20824
+ out.rightKey.push(pair.rightKey);
20825
+ }
20826
+ if (correlated.length === 1) {
20827
+ const leftAlias = outerScope.primaryAlias();
20828
+ if (leftAlias !== void 0)
20829
+ out.leftAlias = leftAlias;
20830
+ const alias = subqueryScope.primaryAlias();
20831
+ if (alias !== void 0)
20832
+ out.alias = alias;
20833
+ out.predicate = exprToLakeql(correlated[0], combinedScope(outerScope, subqueryScope), context);
20834
+ } else if (correlated.length > 1) {
20835
+ const leftAlias = outerScope.primaryAlias();
20836
+ if (leftAlias !== void 0)
20837
+ out.leftAlias = leftAlias;
20838
+ const alias = subqueryScope.primaryAlias();
20839
+ if (alias !== void 0)
20840
+ out.alias = alias;
20841
+ out.predicate = {
20842
+ kind: "logical",
20843
+ op: "and",
20844
+ operands: correlated.map((predicate) => exprToLakeql(predicate, combinedScope(outerScope, subqueryScope), context))
20845
+ };
20846
+ }
20847
+ if (residual.length === 1) {
20848
+ out.where = exprToLakeql(residual[0], subqueryScope, context);
20849
+ } else if (residual.length > 1) {
20850
+ out.where = {
20851
+ kind: "logical",
20852
+ op: "and",
20853
+ operands: residual.map((predicate) => exprToLakeql(predicate, subqueryScope, context))
20854
+ };
20855
+ }
20856
+ }
20857
+ function combinedScope(left, right) {
20858
+ const scope = SqlScope.empty();
20859
+ for (const source of left.sources())
20860
+ scope.add(source);
20861
+ for (const source of right.sources())
20862
+ scope.add(source);
20863
+ return scope;
20864
+ }
20865
+ function predicateReferencesOuterScope(predicate, outerScope) {
20866
+ let found = false;
20867
+ visitPgRefs(predicate, (ref) => {
20868
+ if (outerScope.qualifiedRefName(ref) !== void 0)
20869
+ found = true;
20870
+ });
20871
+ return found;
20872
+ }
20873
+ function visitPgRefs(value2, visit) {
20874
+ if (!isRecord4(value2))
20875
+ return;
20876
+ if (value2.type === "ref")
20877
+ visit(value2);
20878
+ for (const child of Object.values(value2)) {
20879
+ if (Array.isArray(child)) {
20880
+ for (const item of child)
20881
+ visitPgRefs(item, visit);
20882
+ } else {
20883
+ visitPgRefs(child, visit);
20884
+ }
20885
+ }
20886
+ }
20887
+ function isRecord4(value2) {
20888
+ return typeof value2 === "object" && value2 !== null;
20889
+ }
20890
+ function correlatedJoinKeyPair(predicate, outerScope, subqueryScope) {
20891
+ if (predicate.type !== "binary" || String(predicate.op) !== "=")
20892
+ return void 0;
20893
+ const left = asNode(predicate.left, "correlated left key");
20894
+ const right = asNode(predicate.right, "correlated right key");
20895
+ const leftOuter = outerScope.qualifiedRefName(left);
20896
+ const leftSubquery = subqueryScope.qualifiedRefName(left);
20897
+ const rightOuter = outerScope.qualifiedRefName(right);
20898
+ const rightSubquery = subqueryScope.qualifiedRefName(right);
20899
+ if (leftOuter !== void 0 && rightSubquery !== void 0) {
20900
+ return { leftKey: leftOuter, rightKey: rightSubquery };
20901
+ }
20902
+ if (rightOuter !== void 0 && leftSubquery !== void 0) {
20903
+ return { leftKey: rightOuter, rightKey: leftSubquery };
20904
+ }
20905
+ return void 0;
20906
+ }
20139
20907
  function subqueryLeftKeys(expr, scope) {
20140
20908
  if (expr.type === "list") {
20141
20909
  return optionalArray(expr.expressions).map((value2) => scope.refName(asNode(value2, "IN left key")));
@@ -20151,7 +20919,10 @@ function flattenWhereConjuncts(expr) {
20151
20919
  }
20152
20920
  return [expr];
20153
20921
  }
20154
- function scalarSubqueryToExpr(subquery, context) {
20922
+ function scalarSubqueryToExpr(subquery, outerScope, context) {
20923
+ const correlated = scalarAggregateSubqueryToAst(subquery, outerScope, context);
20924
+ if (correlated !== void 0)
20925
+ return registerCorrelatedScalarSubquery(correlated, context);
20155
20926
  const query = selectStatementToAst(subquery, context);
20156
20927
  const outputColumns = scalarSubqueryOutputColumns(query);
20157
20928
  if (outputColumns.length !== 1) {
@@ -20160,9 +20931,72 @@ function scalarSubqueryToExpr(subquery, context) {
20160
20931
  if (query.aggregates === void 0 && query.limit !== 1) {
20161
20932
  throwUnsupported("Scalar subqueries must be aggregate queries or use LIMIT 1");
20162
20933
  }
20934
+ return registerScalarSubquery(query, outputColumns[0], context);
20935
+ }
20936
+ function existsSubqueryToExpr(expr, context) {
20937
+ const subquery = existsSubquery(expr);
20938
+ rejectPresent(subquery, "orderBy", "ORDER BY in EXISTS subqueries is not supported");
20939
+ rejectPresent(subquery, "limit", "LIMIT in EXISTS subqueries is not supported");
20940
+ rejectPresent(subquery, "windows", "Window functions in EXISTS subqueries are not supported");
20941
+ const from = optionalArray(subquery.from);
20942
+ if (from.length !== 1)
20943
+ throwUnsupported("EXISTS subqueries must select from one table");
20944
+ const source = sourceTable(from[0]);
20945
+ if (subquery.groupBy !== void 0 || subquery.having !== void 0) {
20946
+ return registerExistsSubquery(groupedExistsSubqueryToAst(subquery, source, context), context);
20947
+ }
20948
+ const query = {
20949
+ source: source.source,
20950
+ aggregates: { __lakeql_exists_count: { op: "count" } }
20951
+ };
20952
+ if (subquery.where !== void 0) {
20953
+ query.where = exprToLakeql(asNode(subquery.where, "EXISTS subquery WHERE"), new SqlScope(source), context);
20954
+ }
20955
+ const count = registerScalarSubquery(query, "__lakeql_exists_count", context);
20956
+ return { kind: "compare", op: "gt", left: count, right: literal2(0) };
20957
+ }
20958
+ function groupedExistsSubqueryToAst(subquery, source, context) {
20959
+ const scope = new SqlScope(source);
20960
+ const query = { source: source.source };
20961
+ if (subquery.where !== void 0) {
20962
+ query.where = exprToLakeql(asNode(subquery.where, "EXISTS subquery WHERE"), scope, context);
20963
+ }
20964
+ if (subquery.groupBy !== void 0) {
20965
+ query.groupBy = optionalArray(subquery.groupBy).map((expr) => scope.refName(asNode(expr, "EXISTS subquery GROUP BY expression")));
20966
+ query.select = query.groupBy;
20967
+ }
20968
+ const aggregates = {};
20969
+ const hiddenAggregates = /* @__PURE__ */ new Set();
20970
+ const outputAliases = new Set(query.select ?? []);
20971
+ if (subquery.having !== void 0) {
20972
+ query.having = havingToExpr(asNode(subquery.having, "EXISTS subquery HAVING"), scope, context, aggregates, hiddenAggregates, outputAliases);
20973
+ }
20974
+ if (Object.keys(aggregates).length > 0)
20975
+ query.aggregates = aggregates;
20976
+ if (hiddenAggregates.size > 0)
20977
+ query.hiddenAggregates = [...hiddenAggregates];
20978
+ return query;
20979
+ }
20980
+ function registerScalarSubquery(query, column, context) {
20163
20981
  const id = `scalar_${context.nextScalarSubqueryId}`;
20164
20982
  context.nextScalarSubqueryId += 1;
20165
- context.scalarSubqueries[id] = { query, column: outputColumns[0] };
20983
+ context.scalarSubqueries[id] = { query, column };
20984
+ return { kind: "call", fn: "__lakeql_scalar_subquery", args: [{ kind: "literal", value: id }] };
20985
+ }
20986
+ function registerCorrelatedScalarSubquery(query, context) {
20987
+ const id = `correlated_scalar_${context.nextScalarSubqueryId}`;
20988
+ context.nextScalarSubqueryId += 1;
20989
+ context.correlatedScalarSubqueries[id] = query;
20990
+ return {
20991
+ kind: "call",
20992
+ fn: "__lakeql_correlated_scalar_subquery",
20993
+ args: [{ kind: "literal", value: id }]
20994
+ };
20995
+ }
20996
+ function registerExistsSubquery(query, context) {
20997
+ const id = `scalar_${context.nextScalarSubqueryId}`;
20998
+ context.nextScalarSubqueryId += 1;
20999
+ context.scalarSubqueries[id] = { query, column: "__lakeql_exists", mode: "exists" };
20166
21000
  return { kind: "call", fn: "__lakeql_scalar_subquery", args: [{ kind: "literal", value: id }] };
20167
21001
  }
20168
21002
  function scalarSubqueryOutputColumns(query) {
@@ -20187,13 +21021,12 @@ function unaryToExpr(expr, scope = SqlScope.empty(), context = newSqlParseContex
20187
21021
  throwUnsupported(`Unsupported unary operator ${op}`);
20188
21022
  }
20189
21023
  function caseToExpr(expr, scope = SqlScope.empty(), context = newSqlParseContext()) {
20190
- if (expr.value !== null && expr.value !== void 0) {
20191
- throwUnsupported("Simple CASE expressions are not supported yet");
20192
- }
21024
+ const caseValue = expr.value === null || expr.value === void 0 ? void 0 : exprToLakeql(asNode(expr.value, "CASE expression"), scope, context);
20193
21025
  const whens = optionalArray(expr.whens).map((branch) => {
20194
21026
  const node = asNode(branch, "CASE branch");
21027
+ const when = exprToLakeql(asNode(node.when, "CASE WHEN expression"), scope, context);
20195
21028
  return {
20196
- when: exprToLakeql(asNode(node.when, "CASE WHEN expression"), scope, context),
21029
+ when: caseValue === void 0 ? when : { kind: "compare", op: "eq", left: caseValue, right: when },
20197
21030
  value: exprToLakeql(asNode(node.value, "CASE THEN expression"), scope, context)
20198
21031
  };
20199
21032
  });
@@ -20201,8 +21034,71 @@ function caseToExpr(expr, scope = SqlScope.empty(), context = newSqlParseContext
20201
21034
  if (expr.else !== void 0) {
20202
21035
  out.else = exprToLakeql(asNode(expr.else, "CASE ELSE expression"), scope, context);
20203
21036
  }
21037
+ validateCaseResultTypes(out);
20204
21038
  return out;
20205
21039
  }
21040
+ function validateCaseResultTypes(expr) {
21041
+ const resultTypes = caseResultTypes(expr);
21042
+ if (resultTypes.size <= 1)
21043
+ return;
21044
+ throwType("CASE result branches must have compatible literal types", {
21045
+ types: [...resultTypes].sort()
21046
+ });
21047
+ }
21048
+ function caseResultTypes(expr) {
21049
+ const types = /* @__PURE__ */ new Set();
21050
+ for (const branch of expr.whens) {
21051
+ const type = staticExprResultType(branch.value);
21052
+ if (type !== void 0)
21053
+ types.add(type);
21054
+ }
21055
+ if (expr.else !== void 0) {
21056
+ const type = staticExprResultType(expr.else);
21057
+ if (type !== void 0)
21058
+ types.add(type);
21059
+ }
21060
+ return types;
21061
+ }
21062
+ function staticExprResultType(expr) {
21063
+ switch (expr.kind) {
21064
+ case "literal":
21065
+ return staticScalarType(expr.value);
21066
+ case "compare":
21067
+ case "between":
21068
+ case "in":
21069
+ case "like":
21070
+ case "logical":
21071
+ case "not":
21072
+ case "null-check":
21073
+ return "boolean";
21074
+ case "arithmetic":
21075
+ return "number";
21076
+ case "case": {
21077
+ const resultTypes = caseResultTypes(expr);
21078
+ return resultTypes.size === 1 ? [...resultTypes][0] : void 0;
21079
+ }
21080
+ case "call":
21081
+ case "column":
21082
+ return void 0;
21083
+ }
21084
+ }
21085
+ function staticScalarType(value2) {
21086
+ if (value2 === null)
21087
+ return void 0;
21088
+ if (value2 instanceof Uint8Array)
21089
+ return "binary";
21090
+ if (isTimestampValue(value2))
21091
+ return "timestamp";
21092
+ if (isIntervalValue(value2))
21093
+ return "interval";
21094
+ if (typeof value2 === "boolean")
21095
+ return "boolean";
21096
+ if (typeof value2 === "bigint")
21097
+ return "bigint";
21098
+ if (typeof value2 === "number")
21099
+ return "number";
21100
+ return "string";
21101
+ }
20206
21102
  function ternaryToExpr(expr, scope = SqlScope.empty(), context = newSqlParseContext()) {
20207
21103
  const op = String(expr.op).toUpperCase();
20208
21104
  if (op !== "BETWEEN" && op !== "NOT BETWEEN") {
@@ -20478,7 +21374,7 @@ function isAggregateCall(expr) {
20478
21374
  return false;
20479
21375
  return aggregateOpOrUndefined(functionName(expr.function)) !== void 0;
20480
21376
  }
20481
- function isAggregateOp(op) {
21377
+ function isAggregateOp2(op) {
20482
21378
  return [
20483
21379
  "count",
20484
21380
  "sum",
@@ -20529,31 +21425,66 @@ var SqlScope = class _SqlScope {
20529
21425
  this.aliases.set(source.source, source);
20530
21426
  this.aliases.set(source.alias, source);
20531
21427
  }
21428
+ sources() {
21429
+ return [...new Set(this.aliases.values())];
21430
+ }
21431
+ primaryAlias() {
21432
+ return this.sources()[0]?.alias;
21433
+ }
20532
21434
  refName(expr) {
21435
+ const name = this.resolveQualifiedRefName(expr, true);
21436
+ if (name !== void 0)
21437
+ return name;
21438
+ if (expr.type !== "ref" || typeof expr.name !== "string") {
21439
+ throwUnsupported("Only column references are supported here");
21440
+ }
21441
+ const qualifier = expr.table === void 0 ? "" : nameNodeToString(expr.table);
21442
+ throwUnsupported(`Unknown SQL table qualifier ${qualifier}`);
21443
+ }
21444
+ qualifiedRefName(expr) {
21445
+ if (expr.type !== "ref" || typeof expr.name !== "string")
21446
+ return void 0;
21447
+ return this.resolveQualifiedRefName(expr, false);
21448
+ }
21449
+ resolveQualifiedRefName(expr, allowUnqualified) {
20533
21450
  if (expr.type !== "ref" || typeof expr.name !== "string") {
20534
21451
  throwUnsupported("Only column references are supported here");
20535
21452
  }
20536
21453
  if (expr.table === void 0)
20537
- return expr.name;
21454
+ return allowUnqualified ? expr.name : void 0;
20538
21455
  const qualifier = nameNodeToString(expr.table);
20539
21456
  const source = this.aliases.get(qualifier);
20540
21457
  if (source === void 0)
20541
- throwUnsupported(`Unknown SQL table qualifier ${qualifier}`);
21458
+ return void 0;
20542
21459
  if (this.aliases.size <= 2)
20543
21460
  return expr.name;
20544
21461
  return `${source.alias}.${expr.name}`;
20545
21462
  }
20546
21463
  };
20547
- function sourceTable(value2) {
21464
+ function sourceTable(value2, context) {
20548
21465
  const node = asNode(value2, "FROM item");
20549
21466
  if (node.type === "call")
20550
21467
  return readParquetSourceTable(node);
21468
+ if (node.type === "statement")
21469
+ return derivedSourceTable(node, context);
20551
21470
  if (node.type !== "table")
20552
21471
  throwUnsupported("Only table FROM sources are supported");
20553
21472
  if (node.join !== void 0)
20554
21473
  throwUnsupported("Unexpected JOIN position");
20555
21474
  const source = nameNodeToString(node.name);
20556
- return { source, alias: aliasName(node.name) ?? source };
21475
+ return { source, alias: aliasName(node.alias) ?? aliasName(node.name) ?? source };
21476
+ }
21477
+ function derivedSourceTable(node, context) {
21478
+ if (context === void 0)
21479
+ throwUnsupported("Derived tables are not supported here");
21480
+ const alias = aliasName(node.alias);
21481
+ if (alias === void 0)
21482
+ throwUnsupported("Derived tables require an alias");
21483
+ const statement = asNode(node.statement, "derived table SELECT");
21484
+ if (statement.type !== "select")
21485
+ throwUnsupported("Derived tables must be SELECT statements");
21486
+ const query = selectStatementToAst(statement, context);
21487
+ return { source: alias, alias, cte: { name: alias, query } };
20557
21488
  }
20558
21489
  function readParquetSourceTable(node) {
20559
21490
  const functionName2 = nameNodeToString(node.function).toLowerCase();
@@ -20569,74 +21500,120 @@ function readParquetSourceTable(node) {
20569
21500
  }
20570
21501
  return { source: arg.value, alias: aliasName(node.alias) ?? arg.value };
20571
21502
  }
20572
- function joinToAst(value2, left) {
21503
+ function joinToAst(value2, joinedSources) {
20573
21504
  const node = asNode(value2, "JOIN source");
20574
21505
  if (node.type !== "table" && node.type !== "call") {
20575
21506
  throwUnsupported("Only table JOIN sources are supported");
20576
21507
  }
20577
- const join = asNode(node.join, "JOIN");
20578
- const joinType = String(join.type).toUpperCase();
20579
- if (joinType !== "INNER JOIN" && joinType !== "LEFT JOIN") {
21508
+ const join = node.join === void 0 ? void 0 : asNode(node.join, "JOIN");
21509
+ const joinType = join === void 0 ? "" : String(join.type).toUpperCase();
21510
+ if (join === void 0 || joinType === "CROSS JOIN") {
21511
+ const right2 = sourceTable({ ...node, join: void 0 });
21512
+ const left2 = joinedSources[0];
21513
+ if (left2 === void 0)
21514
+ throwUnsupported("JOIN requires a left source");
21515
+ return {
21516
+ right: right2,
21517
+ join: {
21518
+ source: right2.source,
21519
+ leftAlias: left2.alias,
21520
+ alias: right2.alias,
21521
+ type: "cross",
21522
+ leftKey: [],
21523
+ rightKey: []
21524
+ }
21525
+ };
21526
+ }
21527
+ if (joinType !== "INNER JOIN" && joinType !== "LEFT JOIN" && joinType !== "RIGHT JOIN" && joinType !== "FULL JOIN") {
20580
21528
  throwUnsupported(`Unsupported JOIN type ${joinType}`);
20581
21529
  }
20582
21530
  const right = sourceTable({ ...node, join: void 0 });
21531
+ const left = joinedSources[0];
21532
+ if (left === void 0)
21533
+ throwUnsupported("JOIN requires a left source");
20583
21534
  if (join.using !== void 0) {
20584
21535
  const using = optionalArray(join.using);
20585
21536
  if (using.length === 0)
20586
21537
  throwUnsupported("JOIN USING requires at least one column");
20587
- const keys = using.map(nameNodeToString);
21538
+ const keys2 = using.map(nameNodeToString);
20588
21539
  return {
20589
- source: right.source,
20590
- leftAlias: left.alias,
20591
- alias: right.alias,
20592
- type: joinType === "LEFT JOIN" ? "left" : "inner",
20593
- leftKey: keys.map((key) => `${left.alias}.${key}`),
20594
- rightKey: keys.map((key) => `${right.alias}.${key}`)
21540
+ right,
21541
+ join: {
21542
+ source: right.source,
21543
+ leftAlias: left.alias,
21544
+ alias: right.alias,
21545
+ type: sqlJoinType(joinType),
21546
+ leftKey: keys2.map((key) => `${left.alias}.${key}`),
21547
+ rightKey: keys2.map((key) => `${right.alias}.${key}`)
21548
+ }
20595
21549
  };
20596
21550
  }
20597
21551
  const on = asNode(join.on, "JOIN ON");
20598
- const { leftKey, rightKey } = joinKeysFromPredicate(on, left, right);
21552
+ const keys = joinKeysFromPredicate(on, joinedSources, right);
20599
21553
  return {
20600
- source: right.source,
20601
- leftAlias: left.alias,
20602
- alias: right.alias,
20603
- type: joinType === "LEFT JOIN" ? "left" : "inner",
20604
- leftKey,
20605
- rightKey
21554
+ right,
21555
+ join: {
21556
+ source: right.source,
21557
+ leftAlias: left.alias,
21558
+ alias: right.alias,
21559
+ type: sqlJoinType(joinType),
21560
+ leftKey: keys?.leftKey ?? [],
21561
+ rightKey: keys?.rightKey ?? [],
21562
+ ...keys === void 0 ? { predicate: exprToLakeql(on, joinScope(joinedSources, right)) } : {}
21563
+ }
20606
21564
  };
20607
21565
  }
20608
- function qualifiedJoinKey(expr, left, right) {
20609
- if (expr.type !== "ref" || typeof expr.name !== "string" || expr.table === void 0) {
20610
- throwUnsupported("JOIN keys must be qualified column references");
20611
- }
21566
+ function sqlJoinType(joinType) {
21567
+ if (joinType === "LEFT JOIN")
21568
+ return "left";
21569
+ if (joinType === "RIGHT JOIN")
21570
+ return "right";
21571
+ if (joinType === "FULL JOIN")
21572
+ return "full";
21573
+ return "inner";
21574
+ }
21575
+ function maybeQualifiedJoinKey(expr, joinedSources, right) {
21576
+ if (expr.type !== "ref" || typeof expr.name !== "string" || expr.table === void 0)
21577
+ return void 0;
20612
21578
  const qualifier = nameNodeToString(expr.table);
20613
- if (qualifier === left.alias || qualifier === left.source)
20614
- return `${left.alias}.${expr.name}`;
20615
- if (qualifier === right.alias || qualifier === right.source)
20616
- return `${right.alias}.${expr.name}`;
20617
- throwUnsupported(`Unknown JOIN qualifier ${qualifier}`);
21579
+ if (qualifier === right.alias || qualifier === right.source) {
21580
+ return { side: "right", key: `${right.alias}.${expr.name}` };
21581
+ }
21582
+ const left = joinedSources.find((source) => qualifier === source.alias || qualifier === source.source);
21583
+ if (left !== void 0)
21584
+ return { side: "left", key: `${left.alias}.${expr.name}` };
21585
+ return void 0;
20618
21586
  }
20619
- function joinKeysFromPredicate(expr, left, right) {
21587
+ function joinKeysFromPredicate(expr, joinedSources, right) {
20620
21588
  const conjuncts2 = flattenJoinConjuncts(expr);
20621
21589
  const leftKey = [];
20622
21590
  const rightKey = [];
20623
21591
  for (const conjunct of conjuncts2) {
20624
- if (String(conjunct.op) !== "=")
20625
- throwUnsupported("Only equi-joins are supported");
20626
- const first = qualifiedJoinKey(asNode(conjunct.left, "JOIN left key"), left, right);
20627
- const second = qualifiedJoinKey(asNode(conjunct.right, "JOIN right key"), left, right);
20628
- if (first.startsWith(`${left.alias}.`) && second.startsWith(`${right.alias}.`)) {
20629
- leftKey.push(first);
20630
- rightKey.push(second);
20631
- } else if (second.startsWith(`${left.alias}.`) && first.startsWith(`${right.alias}.`)) {
20632
- leftKey.push(second);
20633
- rightKey.push(first);
21592
+ if (conjunct.type !== "binary" || String(conjunct.op) !== "=")
21593
+ return void 0;
21594
+ const first = maybeQualifiedJoinKey(asNode(conjunct.left, "JOIN left key"), joinedSources, right);
21595
+ const second = maybeQualifiedJoinKey(asNode(conjunct.right, "JOIN right key"), joinedSources, right);
21596
+ if (first === void 0 || second === void 0)
21597
+ return void 0;
21598
+ if (first.side === "left" && second.side === "right") {
21599
+ leftKey.push(first.key);
21600
+ rightKey.push(second.key);
21601
+ } else if (second.side === "left" && first.side === "right") {
21602
+ leftKey.push(second.key);
21603
+ rightKey.push(first.key);
20634
21604
  } else {
20635
21605
  throwUnsupported("JOIN ON must compare left table key to right table key");
20636
21606
  }
20637
21607
  }
20638
21608
  return { leftKey, rightKey };
20639
21609
  }
21610
+ function joinScope(joinedSources, right) {
21611
+ const scope = SqlScope.empty();
21612
+ for (const source of joinedSources)
21613
+ scope.add(source);
21614
+ scope.add(right);
21615
+ return scope;
21616
+ }
20640
21617
  function flattenJoinConjuncts(expr) {
20641
21618
  if (expr.type === "binary" && String(expr.op).toUpperCase() === "AND") {
20642
21619
  return [
@@ -20734,7 +21711,7 @@ function aggregateOp(op) {
20734
21711
  throwUnsupported(`Unsupported aggregate ${op}`);
20735
21712
  }
20736
21713
  function aggregateOpOrUndefined(op) {
20737
- if (isAggregateOp(op))
21714
+ if (isAggregateOp2(op))
20738
21715
  return op;
20739
21716
  if (op === "var" || op === "variance")
20740
21717
  return "var_samp";
@@ -20800,4 +21777,4 @@ function throwType(message, details) {
20800
21777
  throw new LakeqlError("LAKEQL_TYPE_ERROR", message, details);
20801
21778
  }
20802
21779
 
20803
- export { AggregationBuilder, CacheApiCache, Lake, MemoryCache, MemoryObjectStore, MemorySpillAdapter, ParquetScanAdapter, QueryBuilder, QueryResult, ResumedQuery, SharedMemoryCache, add, aggregateParquetGroupTask, aggregateParquetGroupTasks, aggregateParquetGroupTasksBatch, aggregateParquetTask, aggregateParquetTasks, and, asyncBufferFromStore, batchExprValues, batchFromColumns, batchFromVectors, bboxMayIntersect, between, broadcastJoin, buildBBoxIndex, buildMinMaxIndex, cacheApiCache, cachedObjectStore, classifyPredicate, col, concatBatches, createParquetLake, createParquetTableAs, createVectorAggregateStates, createVectorGroupByState, deserializeAggregateOperatorState, deserializeSortOperatorState, deserializeTopKOperatorState, deserializeWindowOperatorState, div, enforceVectorGroupByBudget, eq, evaluateWindowWorkUnitPartials, fanInWorkUnits, finalizeVectorAggregateStates, finalizeVectorGroupByBatch, finalizeVectorGroupByRows, fn, gatherBatch, getOrCreateVectorGroup, gt, gte, ilike, isIn, isNotNull, isNull, jsonWorkUnitBoundary, like, lit, lookupJoin, lt, lte, materializeBatchRow, materializeBatchRows, materializeSelectedBatchRows, memoryCache, memorySpillAdapter, memoryStore, mergeVectorAggregateStateSnapshots, mergeVectorAggregateStates, mergeVectorGroupByStates, mod, mul, ne, not, notIn, or, parquetScanner, parseHivePartitions, parseJsonQuery, parseSql, parseSqlStatement, partitionWindowWorkUnitRows, partitionedParquetOutputEntries, planParquetTaskWorkUnits, planRowGroups, planRowGroupsFromMetadata, predicateSelection, pruneFilesWithIndex, readControlSignal, readIcebergParquetDeletes, readParquetColumnBatches, readParquetMetadata, readParquetObjectBatches, readParquetObjects, rejectUnsupportedParquetSchema, restoreVectorAggregateStates, restoreVectorGroupByState, rowGroupMayMatch, rowGroupMustMatch, scalarVectorValue, scanParquetTaskBatches, scanParquetTaskColumnBatches, selectedRowCount, selectedRowIndices, serializeAggregateOperatorState, serializeSortOperatorState, serializeTopKOperatorState, serializeWindowOperatorState, snapshotVectorAggregateStates, snapshotVectorGroupByState, sub, throwIfAborted, tryPredicateSelection, updateVectorAggregateStateValue, updateVectorAggregateStates, updateVectorGroupAggregateValue, updateVectorGroupByState, uriObjectStore, vectorAggregateBatch, vectorFromValues, vectorGroupByBatch, vectorLength, vectorOrderByBatch, vectorProjectBatch, vectorSortIndices, vectorTopKBatch, vectorTopKIndices, vectorValue, windowOperatorState, windowOperatorStateFromRuns, windowParquetTask, windowParquetTasks, windowPartitionBucket, windowRowsFromState, withObjectStoreReadControls, writeParquet, writePartitionedParquet, writePartitionedParquetTask };
21780
+ export { AggregationBuilder, CacheApiCache, Lake, MemoryCache, MemoryObjectStore, MemorySpillAdapter, ParquetScanAdapter, QueryBuilder, QueryResult, ResumedQuery, SharedMemoryCache, add, aggregateParquetGroupTask, aggregateParquetGroupTasks, aggregateParquetGroupTasksBatch, aggregateParquetTask, aggregateParquetTasks, and, asyncBufferFromStore, batchExprValues, batchFromColumns, batchFromVectors, bboxMayIntersect, between, broadcastJoin, buildBBoxIndex, buildMinMaxIndex, cacheApiCache, cachedObjectStore, classifyPredicate, col, concatBatches, createParquetLake, createParquetTableAs, createVectorAggregateStates, createVectorGroupByState, crossJoin, deserializeAggregateOperatorState, deserializeSortOperatorState, deserializeTopKOperatorState, deserializeWindowOperatorState, div, enforceVectorGroupByBudget, eq, evaluateWindowWorkUnitPartials, fanInWorkUnits, finalizeVectorAggregateStates, finalizeVectorGroupByBatch, finalizeVectorGroupByRows, fn, gatherBatch, getOrCreateVectorGroup, gt, gte, ilike, isIn, isNotNull, isNull, jsonWorkUnitBoundary, like, lit, lookupJoin, lt, lte, materializeBatchRow, materializeBatchRows, materializeSelectedBatchRows, memoryCache, memorySpillAdapter, memoryStore, mergeVectorAggregateStateSnapshots, mergeVectorAggregateStates, mergeVectorGroupByStates, mod, mul, ne, not, notIn, or, parquetScanner, parseHivePartitions, parseJsonQuery, parseSql, parseSqlStatement, partitionWindowWorkUnitRows, partitionedParquetOutputEntries, planParquetTaskWorkUnits, planRowGroups, planRowGroupsFromMetadata, predicateSelection, pruneFilesWithIndex, readControlSignal, readIcebergParquetDeletes, readParquetColumnBatches, readParquetMetadata, readParquetObjectBatches, readParquetObjects, rejectUnsupportedParquetSchema, restoreVectorAggregateStates, restoreVectorGroupByState, rowGroupMayMatch, rowGroupMustMatch, scalarVectorValue, scanParquetTaskBatches, scanParquetTaskColumnBatches, selectedRowCount, selectedRowIndices, serializeAggregateOperatorState, serializeSortOperatorState, serializeTopKOperatorState, serializeWindowOperatorState, snapshotVectorAggregateStates, snapshotVectorGroupByState, sub, throwIfAborted, tryPredicateSelection, updateVectorAggregateStateValue, updateVectorAggregateStates, updateVectorGroupAggregateValue, updateVectorGroupByState, uriObjectStore, vectorAggregateBatch, vectorFromValues, vectorGroupByBatch, vectorLength, vectorOrderByBatch, vectorProjectBatch, vectorSortIndices, vectorTopKBatch, vectorTopKIndices, vectorValue, windowOperatorState, windowOperatorStateFromRuns, windowParquetTask, windowParquetTasks, windowPartitionBucket, windowRowsFromState, withObjectStoreReadControls, writeParquet, writePartitionedParquet, writePartitionedParquetTask };