@rex0220/kintone-sql-tools 3.62.0 → 3.64.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.
package/dist-cli/ksql.js CHANGED
@@ -76,6 +76,7 @@ var KEYWORDS = /* @__PURE__ */ new Map([
76
76
  ["INNER", "INNER" /* INNER */],
77
77
  ["LEFT", "LEFT" /* LEFT */],
78
78
  ["RIGHT", "RIGHT" /* RIGHT */],
79
+ ["CROSS", "CROSS" /* CROSS */],
79
80
  ["JOIN", "JOIN" /* JOIN */],
80
81
  ["ON", "ON" /* ON */],
81
82
  ["GROUP", "GROUP" /* GROUP */],
@@ -3016,6 +3017,13 @@ var Parser = class {
3016
3017
  const joinType = this.tryJoinType();
3017
3018
  if (joinType === null) break;
3018
3019
  const table = this.parseTableRef();
3020
+ if (joinType === "CROSS") {
3021
+ if (this.peek().kind === "ON" /* ON */) {
3022
+ throw new ParseError("CROSS JOIN \u306B ON \u53E5\u306F\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093\u3002", this.peek());
3023
+ }
3024
+ joins.push({ type: "CROSS", table, on: null });
3025
+ continue;
3026
+ }
3019
3027
  this.expect("ON" /* ON */);
3020
3028
  const on = this.parseJoinCondition();
3021
3029
  joins.push({ type: joinType, table, on });
@@ -3024,17 +3032,30 @@ var Parser = class {
3024
3032
  }
3025
3033
  tryJoinType() {
3026
3034
  if (this.consume("INNER" /* INNER */)) {
3035
+ if (this.peek().kind === "CROSS" /* CROSS */) {
3036
+ throw new ParseError("CROSS JOIN \u306B INNER \u306F\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093\u3002", this.peek());
3037
+ }
3027
3038
  this.expect("JOIN" /* JOIN */);
3028
3039
  return "INNER";
3029
3040
  }
3030
3041
  if (this.consume("LEFT" /* LEFT */)) {
3042
+ if (this.peek().kind === "CROSS" /* CROSS */) {
3043
+ throw new ParseError("CROSS JOIN \u306B LEFT / RIGHT \u306F\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093\u3002", this.peek());
3044
+ }
3031
3045
  this.expect("JOIN" /* JOIN */);
3032
3046
  return "LEFT";
3033
3047
  }
3034
3048
  if (this.consume("RIGHT" /* RIGHT */)) {
3049
+ if (this.peek().kind === "CROSS" /* CROSS */) {
3050
+ throw new ParseError("CROSS JOIN \u306B LEFT / RIGHT \u306F\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093\u3002", this.peek());
3051
+ }
3035
3052
  this.expect("JOIN" /* JOIN */);
3036
3053
  return "RIGHT";
3037
3054
  }
3055
+ if (this.consume("CROSS" /* CROSS */)) {
3056
+ this.expect("JOIN" /* JOIN */);
3057
+ return "CROSS";
3058
+ }
3038
3059
  if (this.consume("JOIN" /* JOIN */)) {
3039
3060
  return "INNER";
3040
3061
  }
@@ -4584,6 +4605,8 @@ var Parser = class {
4584
4605
  this.advance();
4585
4606
  return tok.value;
4586
4607
  }
4608
+ const digitPrefixed = this.tryParseDigitPrefixedIdentifier();
4609
+ if (digitPrefixed !== null) return digitPrefixed;
4587
4610
  throw new ParseError(
4588
4611
  "\u30D5\u30A3\u30FC\u30EB\u30C9\u540D\u307E\u305F\u306F\u30C6\u30FC\u30D6\u30EB\u540D\u304C\u5FC5\u8981\u3067\u3059",
4589
4612
  tok
@@ -4597,11 +4620,24 @@ var Parser = class {
4597
4620
  this.advance();
4598
4621
  return tok.value;
4599
4622
  }
4623
+ const digitPrefixed = this.tryParseDigitPrefixedIdentifier();
4624
+ if (digitPrefixed !== null) return digitPrefixed;
4600
4625
  throw new ParseError(
4601
4626
  "\u30D5\u30A3\u30FC\u30EB\u30C9\u540D\u307E\u305F\u306F\u30C6\u30FC\u30D6\u30EB\u540D\u304C\u5FC5\u8981\u3067\u3059",
4602
4627
  tok
4603
4628
  );
4604
4629
  }
4630
+ /** `0埋め` のように数字から始まる日本語識別子を、空白なしの場合だけ読む。 */
4631
+ tryParseDigitPrefixedIdentifier() {
4632
+ const first = this.peek();
4633
+ const second = this.peekAt(1);
4634
+ if (first.kind !== "NUMBER" /* NUMBER */ || !/^\d+$/.test(first.value) || second.kind !== "IDENT" /* IDENT */ || second.pos !== first.pos + first.value.length) {
4635
+ return null;
4636
+ }
4637
+ this.advance();
4638
+ this.advance();
4639
+ return first.value + second.value;
4640
+ }
4605
4641
  // DML の対象テーブル位置に一時テーブルが指定されていたら拒否する
4606
4642
  rejectTempTableDml() {
4607
4643
  const tok = this.peek();
@@ -4781,6 +4817,7 @@ function unionCompleteInputReasons(stmt) {
4781
4817
  }
4782
4818
  function selectCompleteInputReasons(stmt) {
4783
4819
  const reasons = /* @__PURE__ */ new Set();
4820
+ if (stmt.joins.some((join2) => join2.type === "CROSS")) reasons.add("CROSS_JOIN");
4784
4821
  const grouping = normalizeGroupingSpec(stmt);
4785
4822
  if (grouping.type === "GROUPING_SETS") reasons.add("GROUPING_SETS");
4786
4823
  if (grouping.type === "PLAIN") reasons.add("GROUP_BY");
@@ -6887,6 +6924,7 @@ function collectRequiredFieldsByTable(stmt, plainGroupByPlan, sourceAware) {
6887
6924
  }
6888
6925
  }
6889
6926
  for (const join2 of stmt.joins) {
6927
+ if (join2.type === "CROSS") continue;
6890
6928
  addFieldRef(join2.on.left.field, join2.on.left.tableAlias, "where");
6891
6929
  addFieldRef(join2.on.right.field, join2.on.right.tableAlias, "where");
6892
6930
  }
@@ -7831,7 +7869,9 @@ function buildSingleTableKlikePushdownPlan(where, options = {}) {
7831
7869
  return { condition, relation, appliedKlikes, allKlikes: [...allKlikes] };
7832
7870
  }
7833
7871
  function buildKlikePushdownPlan(stmt, options = {}) {
7834
- const joinsAreSafeForKlike = stmt.joins.every((join2) => join2.type === "INNER");
7872
+ const joinsAreSafeForKlike = stmt.joins.every(
7873
+ (join2) => join2.type === "INNER" || join2.type === "CROSS"
7874
+ );
7835
7875
  const common = {
7836
7876
  allowKlike: joinsAreSafeForKlike,
7837
7877
  allowUnresolvedKlikeVariables: options.allowUnresolvedVariables
@@ -7851,7 +7891,7 @@ function buildKlikePushdownPlan(stmt, options = {}) {
7851
7891
  const joinRelations = /* @__PURE__ */ new Map();
7852
7892
  if (stmt.where !== null) {
7853
7893
  for (const join2 of stmt.joins) {
7854
- if (join2.type !== "INNER" || !join2.table.alias || join2.table.subtableCode || join2.table.cteName !== null) continue;
7894
+ if (join2.type !== "INNER" && join2.type !== "CROSS" || !join2.table.alias || join2.table.subtableCode || join2.table.cteName !== null) continue;
7855
7895
  const extracted = extractSafePushdownPlan(stmt.where, {
7856
7896
  ...common,
7857
7897
  tableAlias: join2.table.alias,
@@ -8198,7 +8238,7 @@ function validateSelect(stmt) {
8198
8238
  validateNestedSelects(stmt);
8199
8239
  }
8200
8240
  function canDeferJoinWholeWhereKlikeValidation(stmt) {
8201
- return stmt.where !== null && serverOnlyFunctionOccurrencesInWhere(stmt.where).length > 0 && stmt.joins.length > 0 && stmt.joins.every((join2) => join2.type === "INNER") && [stmt.from, ...stmt.joins.map((join2) => join2.table)].every(
8241
+ return stmt.where !== null && serverOnlyFunctionOccurrencesInWhere(stmt.where).length > 0 && stmt.joins.length > 0 && stmt.joins.every((join2) => join2.type === "INNER" || join2.type === "CROSS") && [stmt.from, ...stmt.joins.map((join2) => join2.table)].every(
8202
8242
  (table) => table.alias !== null && table.cteName === null && !table.subtableCode
8203
8243
  );
8204
8244
  }
@@ -8417,7 +8457,8 @@ function assertStringFunctionArity(func, args) {
8417
8457
  }
8418
8458
 
8419
8459
  // src/core/generateSeries.ts
8420
- var GENERATE_SERIES_MAX_ROWS = 1e4;
8460
+ var GENERATED_ROW_MAX_ROWS = 1e4;
8461
+ var GENERATE_SERIES_MAX_ROWS = GENERATED_ROW_MAX_ROWS;
8421
8462
  var argumentError = (message) => new Error(`ArgumentError: ${message}`);
8422
8463
  var isVariable = (arg) => arg.type === "VARIABLE";
8423
8464
  var isResolvedVariable = (arg) => arg?.type === "STRING" && arg.fromVariable === true;
@@ -8453,19 +8494,26 @@ function dateFromOrdinal(ordinal) {
8453
8494
  }
8454
8495
  function parseDateStep(value) {
8455
8496
  const trimmed = value.trim();
8456
- const match = /^([+-]?\d+)\s+(day|days)$/i.exec(trimmed);
8497
+ const match = /^([+-]?\d+)\s+(day|days|month|months|year|years)$/i.exec(trimmed);
8457
8498
  if (!match) {
8499
+ if (/^\S+\s+(?:day|days|month|months|year|years)$/i.test(trimmed)) {
8500
+ throw argumentError("GENERATE_SERIES \u306E\u65E5\u4ED8 step \u306E\u4FC2\u6570\u306B\u306F\u5B89\u5168\u306A\u6574\u6570\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
8501
+ }
8458
8502
  if (/^[+-]?\d+\s+\S+$/i.test(trimmed)) {
8459
- throw argumentError("GENERATE_SERIES \u306E\u65E5\u4ED8 step \u306F day \u307E\u305F\u306F days \u306E\u307F\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u3059\u3002");
8503
+ throw argumentError("GENERATE_SERIES \u306E\u65E5\u4ED8 step \u306F day\u3001days\u3001month\u3001months\u3001year\u3001years \u306E\u307F\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u3059\u3002");
8460
8504
  }
8461
- throw argumentError("GENERATE_SERIES \u306E step \u304C\u7CFB\u5217\u306E\u578B\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6574\u6570\u7CFB\u5217\u306B\u306F\u6574\u6570\u3001DATE \u7CFB\u5217\u306B\u306F day \u5358\u4F4D\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
8505
+ throw argumentError("GENERATE_SERIES \u306E step \u304C\u7CFB\u5217\u306E\u578B\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6574\u6570\u7CFB\u5217\u306B\u306F\u6574\u6570\u3001DATE \u7CFB\u5217\u306B\u306F day\u3001month\u3001year \u5358\u4F4D\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
8462
8506
  }
8463
- const step = Number(match[1]);
8464
- if (!Number.isSafeInteger(step)) {
8465
- throw argumentError("GENERATE_SERIES \u306E\u6570\u5024\u7CFB\u5217\u306F\u6574\u6570\u306E start\u3001stop\u3001step \u306E\u307F\u3092\u53D7\u3051\u4ED8\u3051\u307E\u3059\u3002");
8507
+ const coefficient = Number(match[1]);
8508
+ if (!Number.isSafeInteger(coefficient)) {
8509
+ throw argumentError("GENERATE_SERIES \u306E\u65E5\u4ED8 step \u306E\u4FC2\u6570\u306B\u306F\u5B89\u5168\u306A\u6574\u6570\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
8466
8510
  }
8467
- if (step === 0) throw argumentError("GENERATE_SERIES \u306E\u65E5\u4ED8 step \u306B 0 day \u306F\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093\u3002");
8468
- return step;
8511
+ const rawUnit = match[2].toLowerCase();
8512
+ const unit = rawUnit.startsWith("month") ? "MONTH" : rawUnit.startsWith("year") ? "YEAR" : "DAY";
8513
+ if (coefficient === 0) {
8514
+ throw argumentError(`GENERATE_SERIES \u306E\u65E5\u4ED8 step \u306B 0 ${unit.toLowerCase()} \u306F\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093\u3002`);
8515
+ }
8516
+ return { coefficient, unit };
8469
8517
  }
8470
8518
  function integerValue(value) {
8471
8519
  if (typeof value === "number") return Number.isSafeInteger(value) ? value : null;
@@ -8493,6 +8541,46 @@ function countRows(start, stop, step) {
8493
8541
  const distance = step > 0 ? BigInt(stop) - BigInt(start) : BigInt(start) - BigInt(stop);
8494
8542
  return Number(distance / BigInt(Math.abs(step)) + 1n);
8495
8543
  }
8544
+ function monthIndex(parts) {
8545
+ return parts.year * 12 + parts.month - 1;
8546
+ }
8547
+ function validateDateAnchor(start, unit) {
8548
+ if (unit === "MONTH" && start.day !== 1) {
8549
+ throw argumentError("GENERATE_SERIES \u306E month step \u3067\u306F start \u306B\u6708\u521D\uFF08YYYY-MM-01\uFF09\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
8550
+ }
8551
+ if (unit === "YEAR" && (start.month !== 1 || start.day !== 1)) {
8552
+ throw argumentError("GENERATE_SERIES \u306E year step \u3067\u306F start \u306B\u5E74\u521D\uFF08YYYY-01-01\uFF09\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
8553
+ }
8554
+ }
8555
+ function countDateRows(start, stop, step) {
8556
+ const startOrdinal = dateOrdinal(
8557
+ `${String(start.year).padStart(4, "0")}-${String(start.month).padStart(2, "0")}-${String(start.day).padStart(2, "0")}`
8558
+ );
8559
+ const stopOrdinal = dateOrdinal(
8560
+ `${String(stop.year).padStart(4, "0")}-${String(stop.month).padStart(2, "0")}-${String(stop.day).padStart(2, "0")}`
8561
+ );
8562
+ if (step.unit === "DAY") return countRows(startOrdinal, stopOrdinal, step.coefficient);
8563
+ if (startOrdinal === stopOrdinal) return 1;
8564
+ if (startOrdinal < stopOrdinal && step.coefficient < 0 || startOrdinal > stopOrdinal && step.coefficient > 0) return 0;
8565
+ if (step.unit === "MONTH") {
8566
+ const boundary2 = monthIndex(stop) + (step.coefficient < 0 && stop.day !== 1 ? 1 : 0);
8567
+ return countRows(monthIndex(start), boundary2, step.coefficient);
8568
+ }
8569
+ const stopIsYearStart = stop.month === 1 && stop.day === 1;
8570
+ const boundary = stop.year + (step.coefficient < 0 && !stopIsYearStart ? 1 : 0);
8571
+ return countRows(start.year, boundary, step.coefficient);
8572
+ }
8573
+ function dateFromParts(year, month, day) {
8574
+ if (year < 1 || year > 9999) {
8575
+ throw argumentError("GENERATE_SERIES \u306E\u65E5\u4ED8\u5F15\u6570\u306B\u306F\u5B9F\u5728\u3059\u308B YYYY-MM-DD \u5F62\u5F0F\u306E DATE \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
8576
+ }
8577
+ return `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
8578
+ }
8579
+ function dateFromMonthIndex(index) {
8580
+ const year = Math.floor(index / 12);
8581
+ const month = index - year * 12 + 1;
8582
+ return dateFromParts(year, month, 1);
8583
+ }
8496
8584
  function planResolved(stmt) {
8497
8585
  if (stmt.args.length < 2 || stmt.args.length > 3) {
8498
8586
  throw argumentError("GENERATE_SERIES \u306F start\u3001stop \u3068\u7701\u7565\u53EF\u80FD\u306A step \u306E2\u500B\u307E\u305F\u306F3\u500B\u306E\u5F15\u6570\u3092\u53D7\u3051\u4ED8\u3051\u307E\u3059\u3002");
@@ -8518,12 +8606,20 @@ function planResolved(stmt) {
8518
8606
  throw argumentError("GENERATE_SERIES \u306E\u65E5\u4ED8\u5F15\u6570\u306B\u306F\u5B9F\u5728\u3059\u308B YYYY-MM-DD \u5F62\u5F0F\u306E DATE \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
8519
8607
  }
8520
8608
  if (stepRaw !== void 0 && typeof stepRaw !== "string") {
8521
- throw argumentError("GENERATE_SERIES \u306E step \u304C\u7CFB\u5217\u306E\u578B\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6574\u6570\u7CFB\u5217\u306B\u306F\u6574\u6570\u3001DATE \u7CFB\u5217\u306B\u306F day \u5358\u4F4D\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
8609
+ throw argumentError("GENERATE_SERIES \u306E step \u304C\u7CFB\u5217\u306E\u578B\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6574\u6570\u7CFB\u5217\u306B\u306F\u6574\u6570\u3001DATE \u7CFB\u5217\u306B\u306F day\u3001month\u3001year \u5358\u4F4D\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
8522
8610
  }
8523
- const step2 = stepRaw === void 0 ? 1 : parseDateStep(stepRaw);
8611
+ const dateStep = stepRaw === void 0 ? { coefficient: 1, unit: "DAY" } : parseDateStep(stepRaw);
8612
+ validateDateAnchor(startDate, dateStep.unit);
8524
8613
  const start2 = startRaw;
8525
8614
  const stop2 = stopRaw;
8526
- return { kind: "DATE", start: start2, stop: stop2, step: step2, rowCount: countRows(dateOrdinal(start2), dateOrdinal(stop2), step2) };
8615
+ return {
8616
+ kind: "DATE",
8617
+ start: start2,
8618
+ stop: stop2,
8619
+ step: dateStep.coefficient,
8620
+ dateUnit: dateStep.unit,
8621
+ rowCount: countDateRows(startDate, stopDate, dateStep)
8622
+ };
8527
8623
  }
8528
8624
  const startInteger = startArg.type === "NUMBER" ? integerNumberLiteral(startArg) : isResolvedVariable(startArg) ? integerValue(startRaw) : null;
8529
8625
  const stopInteger = stopArg.type === "NUMBER" ? integerNumberLiteral(stopArg) : isResolvedVariable(stopArg) ? integerValue(stopRaw) : null;
@@ -8538,7 +8634,7 @@ function planResolved(stmt) {
8538
8634
  if (stepArg?.type === "NUMBER" || isResolvedVariable(stepArg)) {
8539
8635
  throw argumentError("GENERATE_SERIES \u306E\u6570\u5024\u7CFB\u5217\u306F\u6574\u6570\u306E start\u3001stop\u3001step \u306E\u307F\u3092\u53D7\u3051\u4ED8\u3051\u307E\u3059\u3002");
8540
8636
  }
8541
- throw argumentError("GENERATE_SERIES \u306E step \u304C\u7CFB\u5217\u306E\u578B\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6574\u6570\u7CFB\u5217\u306B\u306F\u6574\u6570\u3001DATE \u7CFB\u5217\u306B\u306F day \u5358\u4F4D\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
8637
+ throw argumentError("GENERATE_SERIES \u306E step \u304C\u7CFB\u5217\u306E\u578B\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u6574\u6570\u7CFB\u5217\u306B\u306F\u6574\u6570\u3001DATE \u7CFB\u5217\u306B\u306F day\u3001month\u3001year \u5358\u4F4D\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
8542
8638
  }
8543
8639
  const start = startInteger;
8544
8640
  const stop = stopInteger;
@@ -8574,7 +8670,12 @@ function validateGenerateSeriesStatement(stmt) {
8574
8670
  if (value === null) throw argumentError("GENERATE_SERIES \u306E\u6570\u5024\u7CFB\u5217\u306F\u6574\u6570\u306E start\u3001stop\u3001step \u306E\u307F\u3092\u53D7\u3051\u4ED8\u3051\u307E\u3059\u3002");
8575
8671
  if (value === 0) throw argumentError("GENERATE_SERIES \u306E step \u306B 0 \u306F\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093\u3002");
8576
8672
  } else if (step?.type === "STRING") {
8577
- parseDateStep(step.value);
8673
+ const dateStep = parseDateStep(step.value);
8674
+ const start = stmt.args[0];
8675
+ if (start?.type === "STRING") {
8676
+ const parts = dateParts(start.value);
8677
+ if (parts) validateDateAnchor(parts, dateStep.unit);
8678
+ }
8578
8679
  }
8579
8680
  return null;
8580
8681
  }
@@ -8629,8 +8730,22 @@ function resolveGenerateSeries(stmt) {
8629
8730
  }
8630
8731
  }
8631
8732
  } else {
8632
- const startOrdinal = dateOrdinal(plan.start);
8633
- for (let index = 0; index < plan.rowCount; index++) values.push(dateFromOrdinal(startOrdinal + index * plan.step));
8733
+ const start = dateParts(plan.start);
8734
+ if (plan.dateUnit === "MONTH") {
8735
+ const anchor = monthIndex(start);
8736
+ for (let index = 0; index < plan.rowCount; index++) {
8737
+ values.push(dateFromMonthIndex(anchor + index * plan.step));
8738
+ }
8739
+ } else if (plan.dateUnit === "YEAR") {
8740
+ for (let index = 0; index < plan.rowCount; index++) {
8741
+ values.push(dateFromParts(start.year + index * plan.step, 1, 1));
8742
+ }
8743
+ } else {
8744
+ const startOrdinal = dateOrdinal(plan.start);
8745
+ for (let index = 0; index < plan.rowCount; index++) {
8746
+ values.push(dateFromOrdinal(startOrdinal + index * plan.step));
8747
+ }
8748
+ }
8634
8749
  }
8635
8750
  return { ...plan, values };
8636
8751
  }
@@ -12774,6 +12889,19 @@ function planJoinKeyPrefilter(input) {
12774
12889
  return { kind: "RANGE", min, max, relation: "superset" };
12775
12890
  }
12776
12891
 
12892
+ // src/core/optimization/crossJoinRowPlan.ts
12893
+ var CROSS_JOIN_MAX_ROWS = GENERATED_ROW_MAX_ROWS;
12894
+ function planCrossJoinRows(leftRows, rightRows, limit = CROSS_JOIN_MAX_ROWS) {
12895
+ const outputRows = leftRows === 0 || rightRows === 0 ? 0 : leftRows * rightRows;
12896
+ return {
12897
+ leftRows,
12898
+ rightRows,
12899
+ outputRows,
12900
+ limit,
12901
+ allowed: outputRows <= limit
12902
+ };
12903
+ }
12904
+
12777
12905
  // src/core/explainMetadata.ts
12778
12906
  function buildGroupingExplainMetadata(statement, canonicalItemCount) {
12779
12907
  const grouping = normalizeGroupingSpec(statement);
@@ -12814,11 +12942,30 @@ function valueNeedsFieldMetadata(value) {
12814
12942
  }
12815
12943
  function selectNeedsOwnMetadata(statement) {
12816
12944
  return whereNeedsFieldMetadata(statement.where) || statement.groupBy.length > 0 || normalizeGroupingSpec(statement).type === "GROUPING_SETS" || statement.orderBy.length > 0 || statement.joins.some(
12817
- (join2) => join2.table.appId > 0 && join2.table.cteName === null && (join2.on.left.field !== "$id" || join2.on.right.field !== "$id")
12945
+ (join2) => join2.type !== "CROSS" && join2.table.appId > 0 && join2.table.cteName === null && (join2.on.left.field !== "$id" || join2.on.right.field !== "$id")
12818
12946
  ) || statement.columns.some(
12819
12947
  (column) => column.type === "WINDOW_COL" && column.orderBy.length > 0
12820
12948
  );
12821
12949
  }
12950
+ function cteQueriesContainPhysicalSelect(ctes) {
12951
+ if (!Array.isArray(ctes)) return false;
12952
+ const seen = /* @__PURE__ */ new Set();
12953
+ const visit = (node) => {
12954
+ if (node === null || typeof node !== "object") return false;
12955
+ if (seen.has(node)) return false;
12956
+ seen.add(node);
12957
+ if (Array.isArray(node)) return node.some(visit);
12958
+ const item = node;
12959
+ if (item["type"] === "SELECT") {
12960
+ const select = node;
12961
+ if ([select.from, ...select.joins.map((join2) => join2.table)].some((table) => table.appId > 0 && table.cteName === null)) {
12962
+ return true;
12963
+ }
12964
+ }
12965
+ return Object.values(item).some(visit);
12966
+ };
12967
+ return ctes.some((cte) => visit(cte?.query));
12968
+ }
12822
12969
  function explainNeedsAppMetadata(statement) {
12823
12970
  const seen = /* @__PURE__ */ new Set();
12824
12971
  const visit = (node) => {
@@ -12828,6 +12975,9 @@ function explainNeedsAppMetadata(statement) {
12828
12975
  if (Array.isArray(node)) return node.some(visit);
12829
12976
  const item = node;
12830
12977
  if (item["type"] === "VALIDATE") return true;
12978
+ if (item["type"] === "WITH" && cteQueriesContainPhysicalSelect(item["ctes"])) {
12979
+ return true;
12980
+ }
12831
12981
  if (item["type"] === "SELECT" && selectNeedsOwnMetadata(node)) {
12832
12982
  return true;
12833
12983
  }
@@ -14051,6 +14201,19 @@ function flatten(record, alias) {
14051
14201
  return row;
14052
14202
  }
14053
14203
  function applyJoin(leftRows, rightRows, join2, columns = {}) {
14204
+ if (join2.type === "CROSS") {
14205
+ const plan = planCrossJoinRows(leftRows.length, rightRows.length);
14206
+ if (!plan.allowed) {
14207
+ throw new Error(
14208
+ `ArgumentError: CROSS JOIN \u306E\u751F\u6210\u4EF6\u6570 ${plan.outputRows} \u884C\uFF08\u5DE6 ${plan.leftRows} \u884C \xD7 \u53F3 ${plan.rightRows} \u884C\uFF09\u304C\u4E0A\u9650 ${plan.limit} \u884C\u3092\u8D85\u3048\u3066\u3044\u307E\u3059\u3002`
14209
+ );
14210
+ }
14211
+ const result2 = [];
14212
+ for (const lRow of leftRows) {
14213
+ for (const rRow of rightRows) result2.push({ ...lRow, ...rRow });
14214
+ }
14215
+ return result2;
14216
+ }
14054
14217
  const { on, type: joinType } = join2;
14055
14218
  const leftKey = on.left.tableAlias ? `${on.left.tableAlias}.${on.left.field}` : on.left.field;
14056
14219
  const rightKey = on.right.tableAlias ? `${on.right.tableAlias}.${on.right.field}` : on.right.field;
@@ -15741,7 +15904,7 @@ function allowRelativeDatePrefilterPlan(select, decomposition) {
15741
15904
  return decomposition.eligible === true && resolveSelectMode(select) === "FULL_SCAN" && select.orderMode !== "KINTONE_NATIVE" && select.from.cteName === null && !select.from.subtableCode && select.joins.length === 0;
15742
15905
  }
15743
15906
  function allowJoinServerFunctionPlan(select, plan) {
15744
- return select.where !== null && select.joins.length > 0 && select.joins.every((join2) => join2.type === "INNER") && [select.from, ...select.joins.map((join2) => join2.table)].every(
15907
+ return select.where !== null && select.joins.length > 0 && select.joins.every((join2) => join2.type === "INNER" || join2.type === "CROSS") && [select.from, ...select.joins.map((join2) => join2.table)].every(
15745
15908
  (table) => table.alias !== null && table.cteName === null && !table.subtableCode
15746
15909
  ) && isJoinServerFunctionFetchPlan(plan);
15747
15910
  }
@@ -19056,7 +19219,7 @@ function completeInputErrorPrefix(reasons) {
19056
19219
  const hasAggregateReason = aggregateSubjects.some(([reason]) => reasons.has(reason));
19057
19220
  const hasOrderReason = reasons.has("LOCAL_ORDER") || reasons.has("WINDOW_ORDER");
19058
19221
  const legacySubject = reasons.size === 1 && reasons.has("STATISTICAL_AGGREGATE") ? "\u7D71\u8A08\u96C6\u7D04\u306E\u6B63\u3057\u3044\u7D50\u679C" : reasons.size === 1 && reasons.has("GROUPING_SETS") ? "\u5C0F\u8A08\u30FB\u7DCF\u8A08\u306E\u6B63\u3057\u3044\u7D50\u679C" : reasons.size === 1 && reasons.has("AGGREGATE") ? "\u96C6\u8A08\u306E\u6B63\u3057\u3044\u7D50\u679C" : reasons.size === 1 && reasons.has("GROUP_BY") ? "\u30B0\u30EB\u30FC\u30D7\u96C6\u8A08\u306E\u6B63\u3057\u3044\u7D50\u679C" : reasons.size === 1 && reasons.has("DISTINCT") ? "DISTINCT \u306E\u6B63\u3057\u3044\u7D50\u679C" : reasons.has("GROUPING_SETS") ? "\u30AF\u30A8\u30EA\u306E\u6B63\u3057\u3044\u7D50\u679C" : "ORDER BY\u306E\u6B63\u3057\u3044\u7D50\u679C";
19059
- const subject = reasons.has("DML") || reasons.has("VALIDATE") ? legacySubject : hasAggregateReason && hasOrderReason ? "\u30AF\u30A8\u30EA\u306E\u6B63\u3057\u3044\u7D50\u679C" : hasAggregateReason ? aggregateSubjects.find(([reason]) => reasons.has(reason))[1] : "ORDER BY\u306E\u6B63\u3057\u3044\u7D50\u679C";
19222
+ const subject = reasons.size === 1 && reasons.has("CROSS_JOIN") ? "CROSS JOIN \u306E\u6B63\u3057\u3044\u7D50\u679C" : reasons.has("DML") || reasons.has("VALIDATE") ? legacySubject : hasAggregateReason && hasOrderReason ? "\u30AF\u30A8\u30EA\u306E\u6B63\u3057\u3044\u7D50\u679C" : hasAggregateReason ? aggregateSubjects.find(([reason]) => reasons.has(reason))[1] : "ORDER BY\u306E\u6B63\u3057\u3044\u7D50\u679C";
19060
19223
  return `${subject}\u306B\u306F\u5B8C\u5168\u306A\u5019\u88DC\u96C6\u5408\u304C\u5FC5\u8981\u3067\u3059\u3002complete input reason: ${reasonList}\u3002`;
19061
19224
  }
19062
19225
  function buildCompleteInputPolicy(stmt, options, orderPlan) {
@@ -19427,6 +19590,7 @@ async function validateB86SelectFieldCodes(stmt, client, cteCache, cacheContext)
19427
19590
  for (const alias of b86SourceAliases(schema.table)) sourceByAlias.set(alias, schema);
19428
19591
  }
19429
19592
  for (const join2 of stmt.joins) {
19593
+ if (join2.type === "CROSS") continue;
19430
19594
  for (const ref of [join2.on.left, join2.on.right]) {
19431
19595
  if (!ref.tableAlias) continue;
19432
19596
  const schema = sourceByAlias.get(ref.tableAlias);
@@ -19497,7 +19661,7 @@ function hasPushdownPlaceholder(where) {
19497
19661
  }
19498
19662
  var boundJoinRuntimePlans = /* @__PURE__ */ new WeakMap();
19499
19663
  function buildRuntimeJoinPushdownPlan(stmt, metadata) {
19500
- if (stmt.joins.length === 0 || stmt.where === null || stmt.joins.some((join2) => join2.type !== "INNER")) {
19664
+ if (stmt.joins.length === 0 || stmt.where === null || stmt.joins.some((join2) => join2.type !== "INNER" && join2.type !== "CROSS")) {
19501
19665
  return null;
19502
19666
  }
19503
19667
  const tables = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
@@ -19556,7 +19720,7 @@ async function loadTypedPushdownMeta(stmt, client, cacheContext) {
19556
19720
  else candidatesByApp.set(appId, [candidate]);
19557
19721
  };
19558
19722
  addCandidate(stmt.from.appId, extractMainTypedPushdownCandidate(stmt));
19559
- const directInnerJoin = stmt.joins.length > 0 && stmt.where !== null && stmt.joins.every((join2) => join2.type === "INNER") && [stmt.from, ...stmt.joins.map((join2) => join2.table)].every(
19723
+ const directInnerJoin = stmt.joins.length > 0 && stmt.where !== null && stmt.joins.every((join2) => join2.type === "INNER" || join2.type === "CROSS") && [stmt.from, ...stmt.joins.map((join2) => join2.table)].every(
19560
19724
  (table) => table.alias !== null && table.cteName === null && !table.subtableCode
19561
19725
  );
19562
19726
  if (directInnerJoin) {
@@ -24273,6 +24437,7 @@ var explainJoinPushdownPlans = /* @__PURE__ */ new WeakMap();
24273
24437
  var explainPushdownPlans = /* @__PURE__ */ new WeakMap();
24274
24438
  var explainJoinKeyPrefilters = /* @__PURE__ */ new WeakMap();
24275
24439
  var explainChoiceEqualityRewrites = /* @__PURE__ */ new WeakMap();
24440
+ var explainCrossJoinSteps = /* @__PURE__ */ new WeakMap();
24276
24441
  var validateExplainInfo = /* @__PURE__ */ new WeakMap();
24277
24442
  var applyParentExplainPlan = /* @__PURE__ */ new WeakMap();
24278
24443
  async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords = 1e4, relativeDatePlan, initialRelations) {
@@ -24304,6 +24469,54 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
24304
24469
  );
24305
24470
  const explainRelations = new Map(initialRelations ?? []);
24306
24471
  const staticExplainRelations = new Set(initialRelations?.keys() ?? []);
24472
+ const exactRelationRows = new Map(
24473
+ [...initialRelations ?? /* @__PURE__ */ new Map()].map(([name, relation]) => [name, relation.rows.length])
24474
+ );
24475
+ const staticSelectRows = /* @__PURE__ */ new WeakMap();
24476
+ const tableLabel = (table) => effectiveTableAlias(table) ?? (table.appId > 0 ? `APP${table.appId}` : "source");
24477
+ const tableExactRows = (table) => {
24478
+ if (table.cteName === NO_FROM_CTE_NAME) return 1;
24479
+ if (table.cteName !== null) return exactRelationRows.get(table.cteName) ?? null;
24480
+ return null;
24481
+ };
24482
+ const analyzeStaticSelectRows = (select) => {
24483
+ let currentRows = tableExactRows(select.from);
24484
+ let leftLabel = tableLabel(select.from);
24485
+ const steps = [];
24486
+ for (const join2 of select.joins) {
24487
+ const rightRows = tableExactRows(join2.table);
24488
+ const rightLabel = tableLabel(join2.table);
24489
+ if (join2.type === "CROSS") {
24490
+ const plan = currentRows !== null && rightRows !== null ? planCrossJoinRows(currentRows, rightRows) : null;
24491
+ steps.push({
24492
+ leftLabel,
24493
+ rightLabel,
24494
+ leftRows: currentRows,
24495
+ rightRows,
24496
+ plan,
24497
+ rightRuntimeLabel: join2.table.cteName === null ? `APP${join2.table.appId} fetched rows` : `${rightLabel} materialized rows`
24498
+ });
24499
+ currentRows = plan?.outputRows ?? null;
24500
+ leftLabel = `${leftLabel} \xD7 ${rightLabel}`;
24501
+ } else {
24502
+ currentRows = null;
24503
+ leftLabel = `${leftLabel} ${join2.type} JOIN ${rightLabel}`;
24504
+ }
24505
+ }
24506
+ if (steps.length > 0) explainCrossJoinSteps.set(select, steps);
24507
+ if (isConstantFalseWhere(select.where)) currentRows = 0;
24508
+ else if (select.where !== null) currentRows = null;
24509
+ const grouping = normalizeGroupingSpec(select);
24510
+ if (grouping.type !== "NONE" || select.distinct || select.having !== null || isAggregateQueryBlock(select) || select.columns.some((column) => column.type === "WINDOW_COL")) {
24511
+ currentRows = null;
24512
+ }
24513
+ if (currentRows !== null) {
24514
+ const offset = select.offset ?? 0;
24515
+ currentRows = Math.max(0, currentRows - offset);
24516
+ if (select.limit !== null) currentRows = Math.min(currentRows, select.limit);
24517
+ staticSelectRows.set(select, currentRows);
24518
+ }
24519
+ };
24307
24520
  const explainSourceColumns = async (select) => {
24308
24521
  const tables = [select.from, ...select.joins.map((join2) => join2.table)];
24309
24522
  if (tables.length > 1 && select.columns.some(
@@ -24384,6 +24597,10 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
24384
24597
  for (const cte of withStatement.ctes) {
24385
24598
  await preflightExplainRelations(cte.query);
24386
24599
  if (cte.query.type === "GENERATE_SERIES") {
24600
+ if (cte.query.args.some((arg) => arg.type === "VARIABLE")) {
24601
+ explainRelations.set(cte.name, { rows: [], columns: [cte.query.columnAlias] });
24602
+ continue;
24603
+ }
24387
24604
  const generated = executeGenerateSeries(cte.query);
24388
24605
  explainRelations.set(cte.name, {
24389
24606
  rows: generated.rows,
@@ -24392,10 +24609,15 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
24392
24609
  uniqueGeneratedColumn: cte.query.columnAlias
24393
24610
  });
24394
24611
  staticExplainRelations.add(cte.name);
24612
+ exactRelationRows.set(cte.name, generated.rows.length);
24395
24613
  continue;
24396
24614
  }
24397
24615
  const columns = await inferExplainRelationColumns(cte.query);
24398
24616
  explainRelations.set(cte.name, { rows: [], columns });
24617
+ if (cte.query.type === "SELECT") {
24618
+ const rowCount = staticSelectRows.get(cte.query);
24619
+ if (rowCount !== void 0) exactRelationRows.set(cte.name, rowCount);
24620
+ }
24399
24621
  }
24400
24622
  await preflightExplainRelations(withStatement.query);
24401
24623
  return;
@@ -24407,6 +24629,7 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
24407
24629
  }
24408
24630
  if (typed["type"] === "SELECT") {
24409
24631
  const select = node;
24632
+ analyzeStaticSelectRows(select);
24410
24633
  for (const column of select.columns) {
24411
24634
  if (column.type === "SCALAR_SUBQUERY_COL") await preflightExplainRelations(column.query);
24412
24635
  }
@@ -24418,13 +24641,19 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
24418
24641
  cacheContext,
24419
24642
  explainRelations
24420
24643
  );
24421
- const plainPlan = await buildRuntimePlainGroupByPlan(
24422
- select,
24423
- tracedClient,
24424
- cacheContext,
24425
- explainRelations
24644
+ const sources = [select.from, ...select.joins.map((join2) => join2.table)];
24645
+ const hasUnavailableMaterializedSource = sources.some(
24646
+ (source) => source.cteName !== null && !explainRelations.has(source.cteName)
24426
24647
  );
24427
- if (plainPlan) plainGroupByPlans.set(select, plainPlan);
24648
+ if (!hasUnavailableMaterializedSource) {
24649
+ const plainPlan = await buildRuntimePlainGroupByPlan(
24650
+ select,
24651
+ tracedClient,
24652
+ cacheContext,
24653
+ explainRelations
24654
+ );
24655
+ if (plainPlan) plainGroupByPlans.set(select, plainPlan);
24656
+ }
24428
24657
  return;
24429
24658
  }
24430
24659
  for (const child of Object.values(typed)) await preflightExplainRelations(child);
@@ -24953,6 +25182,106 @@ function serverFunctionClientEvaluationLabel(leaves) {
24953
25182
  (leaf) => leaf.right.type === "KINTONE_FUNC" && isRelativeDateFunctionName(leaf.right.name)
24954
25183
  ) ? "relative date client evaluations" : "kintone function client evaluations";
24955
25184
  }
25185
+ var explainSeriesBindings = /* @__PURE__ */ new WeakMap();
25186
+ function resolveExplainGenerateSeriesDefaults(node, literalDefaults) {
25187
+ if (Array.isArray(node)) {
25188
+ return node.map((value) => resolveExplainGenerateSeriesDefaults(value, literalDefaults));
25189
+ }
25190
+ if (node === null || typeof node !== "object") return node;
25191
+ const object = node;
25192
+ if (object["type"] === "GENERATE_SERIES") {
25193
+ const variableNames = /* @__PURE__ */ new Map();
25194
+ const defaultBoundIndexes = /* @__PURE__ */ new Set();
25195
+ const args = object["args"].map((arg, index) => {
25196
+ if (arg.type !== "STRING" || arg.fromVariable !== true || !arg.value.startsWith("@")) return arg;
25197
+ const name = arg.value.slice(1);
25198
+ variableNames.set(index, name);
25199
+ const defaultValue = literalDefaults.get(name);
25200
+ if (defaultValue === void 0) return { type: "VARIABLE", name };
25201
+ defaultBoundIndexes.add(index);
25202
+ return { type: "STRING", value: defaultValue, fromVariable: true };
25203
+ });
25204
+ const resolved = { ...object, args };
25205
+ explainSeriesBindings.set(resolved, { variableNames, defaultBoundIndexes });
25206
+ return resolved;
25207
+ }
25208
+ return Object.fromEntries(Object.entries(object).map(([key, value]) => [
25209
+ key,
25210
+ resolveExplainGenerateSeriesDefaults(value, literalDefaults)
25211
+ ]));
25212
+ }
25213
+ function inferStaticExplainSchema(node, relations) {
25214
+ if (node.type === "SHOW_APPS") return { status: "STATIC", columns: [...SHOW_APPS_COLUMNS] };
25215
+ if (node.type === "DESCRIBE") return { status: "STATIC", columns: [...DESCRIBE_COLUMNS] };
25216
+ if (node.type === "GENERATE_SERIES") {
25217
+ return { status: "STATIC", columns: [node.columnAlias] };
25218
+ }
25219
+ if (node.type === "WITH") {
25220
+ const local = new Map(relations);
25221
+ for (const cte of node.ctes) {
25222
+ const schema = inferStaticExplainSchema(cte.query, local);
25223
+ local.set(cte.name, schema);
25224
+ }
25225
+ return inferStaticExplainSchema(node.query, local);
25226
+ }
25227
+ if (node.type === "UNION") {
25228
+ const left = inferStaticExplainSchema(node.left, relations);
25229
+ const right = inferStaticExplainSchema(node.right, relations);
25230
+ return left.status === "STATIC" && right.status === "STATIC" ? { status: "STATIC", columns: left.columns } : { status: "DEFERRED" };
25231
+ }
25232
+ const sources = [node.from, ...node.joins.map((join2) => join2.table)];
25233
+ const relationSources = sources.filter((source) => source.cteName !== null && source.cteName !== NO_FROM_CTE_NAME);
25234
+ if (relationSources.some((source) => relations.get(source.cteName)?.status !== "STATIC")) {
25235
+ return { status: "DEFERRED" };
25236
+ }
25237
+ const hasWildcard = node.columns.some(
25238
+ (column) => column.type === "WILDCARD" || column.type === "PARENT_WILDCARD"
25239
+ );
25240
+ if (hasWildcard) {
25241
+ if (sources.length !== 1 || sources[0].cteName === null) return { status: "DEFERRED" };
25242
+ }
25243
+ const sourceColumns2 = relationSources.flatMap((source) => {
25244
+ const schema = relations.get(source.cteName);
25245
+ return schema?.status === "STATIC" ? [...schema.columns] : [];
25246
+ });
25247
+ const columns = [];
25248
+ for (const column of node.columns) {
25249
+ if (column.type === "WILDCARD") columns.push(...sourceColumns2);
25250
+ else if (column.type === "PARENT_WILDCARD") {
25251
+ columns.push(...sourceColumns2.filter((name) => name.startsWith("_p.")));
25252
+ } else {
25253
+ columns.push(...project([], [column]).columns);
25254
+ }
25255
+ }
25256
+ return { status: "STATIC", columns };
25257
+ }
25258
+ function staticSchemaRelations(ledger) {
25259
+ return new Map([...ledger].flatMap(
25260
+ ([name, entry]) => entry.status === "STATIC" ? [[name, entry.relation]] : []
25261
+ ));
25262
+ }
25263
+ async function buildStaticTempPlainGroupByPlans(node, client, cacheContext, relations) {
25264
+ const plans = /* @__PURE__ */ new Map();
25265
+ const visit = async (value) => {
25266
+ if (value === null || typeof value !== "object") return;
25267
+ if (Array.isArray(value)) {
25268
+ for (const child of value) await visit(child);
25269
+ return;
25270
+ }
25271
+ const object = value;
25272
+ if (object["type"] === "SELECT") {
25273
+ const select = value;
25274
+ const sources = [select.from, ...select.joins.map((join2) => join2.table)];
25275
+ if (sources.some((source) => source.cteName !== null) && sources.every((source) => source.cteName === NO_FROM_CTE_NAME || source.cteName !== null && relations.has(source.cteName))) {
25276
+ const plan = await buildRuntimePlainGroupByPlan(select, client, cacheContext, relations);
25277
+ if (plan) plans.set(select, plan);
25278
+ }
25279
+ }
25280
+ for (const child of Object.values(object)) await visit(child);
25281
+ };
25282
+ await visit(node);
25283
+ return plans;
25284
+ }
24956
25285
  var EXPLAIN_FETCH_PLAN = /* @__PURE__ */ Symbol("ksql.explainFetchPlan");
24957
25286
  function setExplainFetchPlan(result, plan) {
24958
25287
  result[EXPLAIN_FETCH_PLAN] = plan;
@@ -24965,19 +25294,27 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
24965
25294
  const normalizedInjectedVariables = validateDeclaredBatchVariables(statements, injectedVariables);
24966
25295
  const relativeDateVariables = prepareRelativeDateVariables(statements, normalizedInjectedVariables);
24967
25296
  const variables = /* @__PURE__ */ new Map();
25297
+ const literalDeclareDefaults = /* @__PURE__ */ new Map();
25298
+ const tempSchemaLedger = /* @__PURE__ */ new Map();
24968
25299
  const plans = [];
24969
25300
  const fetchStatements = [];
24970
25301
  for (let i = 0; i < statements.length; i++) {
24971
25302
  const stmt = statements[i];
24972
- const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveBatchVariableReferences(stmt.expr, variables) } : stmt : resolveBatchVariableReferences(stmt, variables);
25303
+ const placeholderResolvedStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveBatchVariableReferences(stmt.expr, variables) } : stmt : resolveBatchVariableReferences(stmt, variables);
25304
+ const planStmt = resolveExplainGenerateSeriesDefaults(
25305
+ placeholderResolvedStmt,
25306
+ literalDeclareDefaults
25307
+ );
24973
25308
  validateStatementStatic(planStmt);
24974
25309
  const relativeDatePlan = await resolveRelativeDateExecutionPlan(planStmt, client, invocationCacheContext);
25310
+ const initialRelations = staticSchemaRelations(tempSchemaLedger);
24975
25311
  const whereAnalysis = resolveMetadata ? await buildExplainWhereAnalysis(
24976
25312
  planStmt,
24977
25313
  client,
24978
25314
  invocationCacheContext,
24979
25315
  maxRecords,
24980
- relativeDatePlan
25316
+ relativeDatePlan,
25317
+ initialRelations
24981
25318
  ) : {
24982
25319
  capabilities: /* @__PURE__ */ new Map(),
24983
25320
  orderPlans: /* @__PURE__ */ new Map(),
@@ -24987,6 +25324,34 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
24987
25324
  numberPrecisionApps: /* @__PURE__ */ new Set(),
24988
25325
  relativeDatePlan
24989
25326
  };
25327
+ if (!resolveMetadata) {
25328
+ const staticPlans = await buildStaticTempPlainGroupByPlans(
25329
+ planStmt,
25330
+ client,
25331
+ invocationCacheContext,
25332
+ initialRelations
25333
+ );
25334
+ for (const [select, plan] of staticPlans) whereAnalysis.plainGroupByPlans.set(select, plan);
25335
+ }
25336
+ let createdSchema;
25337
+ if (planStmt.type === "CREATE_TEMP_TABLE") {
25338
+ const relationSchemas = new Map([...tempSchemaLedger].map(([name, entry]) => [
25339
+ name,
25340
+ entry.status === "STATIC" ? { status: "STATIC", columns: entry.columns } : { status: "DEFERRED" }
25341
+ ]));
25342
+ const inferred = inferStaticExplainSchema(planStmt.query, relationSchemas);
25343
+ createdSchema = inferred.status === "STATIC" ? {
25344
+ status: "STATIC",
25345
+ columns: inferred.columns,
25346
+ relation: { rows: [], columns: [...inferred.columns] },
25347
+ producerStatement: i + 1
25348
+ } : {
25349
+ status: "DEFERRED",
25350
+ producerStatement: i + 1,
25351
+ reason: "EXPLAIN_TEMP_SCHEMA_UNAVAILABLE"
25352
+ };
25353
+ tempSchemaLedger.set(planStmt.name, createdSchema);
25354
+ }
24990
25355
  const fetchCollector = { sources: [] };
24991
25356
  const statementPlan = relativeDatePlan.hasServerOnlyWhereFunction && !relativeDatePlan.allowed ? relativeDateExplainLines(relativeDatePlan) : [
24992
25357
  ...relativeDateExplainLines(relativeDatePlan),
@@ -24995,9 +25360,12 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
24995
25360
  analysis.statements[i],
24996
25361
  whereAnalysis.capabilities,
24997
25362
  whereAnalysis.orderPlans,
25363
+ whereAnalysis.plainGroupByPlans,
24998
25364
  dmlMaxRows,
24999
25365
  dmlMaxSubtableRows,
25000
- fetchCollector
25366
+ fetchCollector,
25367
+ tempSchemaLedger,
25368
+ createdSchema
25001
25369
  ), cursorMaxActive)
25002
25370
  ];
25003
25371
  const metadataPlan = explainMetadataLines(whereAnalysis);
@@ -25011,8 +25379,15 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
25011
25379
  fetch: worstExplainFetch(fetchCollector.sources),
25012
25380
  sources: fetchCollector.sources
25013
25381
  });
25382
+ if (planStmt.type === "DROP_TEMP_TABLE") tempSchemaLedger.delete(planStmt.name);
25014
25383
  if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
25015
25384
  variables.set(stmt.name, stmt.type === "DECLARE_VARIABLE" && stmt.annotation === "RELATIVE_DATE" ? { type: "relative-date", value: relativeDateVariables.get(stmt.name) } : stmt.type === "SET_VARIABLE" && stmt.expr.type === "ARRAY" ? { type: "array", elements: stmt.expr.elements.map((element) => ({ type: "string", value: element.value })) } : { type: "string", value: `@${stmt.name}`, placeholder: true });
25385
+ if (stmt.type === "DECLARE_VARIABLE" && stmt.annotation === void 0 && (stmt.default.type === "STRING" || stmt.default.type === "NUMBER")) {
25386
+ literalDeclareDefaults.set(
25387
+ stmt.name,
25388
+ stmt.default.type === "STRING" ? stmt.default.value : numberLiteralText(stmt.default)
25389
+ );
25390
+ }
25016
25391
  }
25017
25392
  }
25018
25393
  const result = { statementCount: statements.length, statements: plans };
@@ -25022,19 +25397,28 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
25022
25397
  releaseMetadataCacheScope(invocationCacheContext);
25023
25398
  }
25024
25399
  }
25025
- function buildBatchStatementPlan(stmt, info, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, collector = { sources: [] }) {
25400
+ function buildBatchStatementPlan(stmt, info, capabilities, orderPlans, plainGroupByPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, collector = { sources: [] }, tempSchemaLedger = /* @__PURE__ */ new Map(), createdSchema) {
25026
25401
  if (stmt.type === "CREATE_TEMP_TABLE") {
25027
25402
  return [
25028
25403
  `CREATE TEMP TABLE ${stmt.name}`,
25029
25404
  ` scope: batch\uFF08\u30D0\u30C3\u30C1\u7D42\u4E86\u6642\u306B\u81EA\u52D5\u7834\u68C4\uFF09`,
25405
+ ...createdSchema?.status === "STATIC" ? [
25406
+ ` schema: ${createdSchema.columns.join(", ")}`,
25407
+ ` schema source: SELECT output of statement ${createdSchema.producerStatement}`
25408
+ ] : createdSchema ? [
25409
+ " schema: deferred (could not be derived statically)",
25410
+ ` plan status: deferred (temp table schema; reason=${createdSchema.reason})`
25411
+ ] : [],
25030
25412
  ` rows: \u5B9F\u4F53\u5316\u524D\u306E\u305F\u3081\u4E0D\u660E\uFF08\u65E2\u5B9A\u4E0A\u9650 ${TEMP_TABLE_MAX_ROWS} \u884C\u3001tempTableMaxRows \u3067\u5909\u66F4\u53EF\u3001\u8D85\u904E\u306F\u30A8\u30E9\u30FC\uFF09`,
25031
25413
  ...buildPlanForBatchQuery(
25032
25414
  stmt.query,
25033
25415
  info,
25034
25416
  capabilities,
25035
25417
  orderPlans,
25418
+ plainGroupByPlans,
25036
25419
  collector,
25037
- "main"
25420
+ "main",
25421
+ tempSchemaLedger
25038
25422
  ).map((l) => ` ${l}`)
25039
25423
  ];
25040
25424
  }
@@ -25056,7 +25440,10 @@ function buildBatchStatementPlan(stmt, info, capabilities, orderPlans, dmlMaxRow
25056
25440
  subInfo,
25057
25441
  capabilities,
25058
25442
  orderPlans,
25059
- collector
25443
+ plainGroupByPlans,
25444
+ collector,
25445
+ "main",
25446
+ tempSchemaLedger
25060
25447
  ).map((l) => ` ${l}`)
25061
25448
  ];
25062
25449
  }
@@ -25085,7 +25472,10 @@ function buildBatchStatementPlan(stmt, info, capabilities, orderPlans, dmlMaxRow
25085
25472
  info,
25086
25473
  capabilities,
25087
25474
  orderPlans,
25088
- collector
25475
+ plainGroupByPlans,
25476
+ collector,
25477
+ "main",
25478
+ tempSchemaLedger
25089
25479
  );
25090
25480
  }
25091
25481
  if (stmt.type === "ASSERT") {
@@ -25104,15 +25494,36 @@ function buildBatchStatementPlan(stmt, info, capabilities, orderPlans, dmlMaxRow
25104
25494
  subInfo,
25105
25495
  capabilities,
25106
25496
  orderPlans,
25107
- collector
25497
+ plainGroupByPlans,
25498
+ collector,
25499
+ "main",
25500
+ tempSchemaLedger
25108
25501
  ).map((l) => ` ${l}`));
25109
25502
  });
25110
25503
  return lines;
25111
25504
  }
25112
25505
  if (stmt.type === "UPDATE" && (stmt.applyBlocks?.length ?? 0) > 0) {
25113
- return buildExplainPlan(stmt, void 0, capabilities, orderPlans, dmlMaxRows, dmlMaxSubtableRows);
25506
+ return buildExplainPlan(
25507
+ stmt,
25508
+ void 0,
25509
+ capabilities,
25510
+ orderPlans,
25511
+ dmlMaxRows,
25512
+ dmlMaxSubtableRows,
25513
+ 1e4,
25514
+ plainGroupByPlans
25515
+ );
25114
25516
  }
25115
- return buildPlanForBatchQuery(stmt, info, capabilities, orderPlans, collector);
25517
+ return buildPlanForBatchQuery(
25518
+ stmt,
25519
+ info,
25520
+ capabilities,
25521
+ orderPlans,
25522
+ plainGroupByPlans,
25523
+ collector,
25524
+ "main",
25525
+ tempSchemaLedger
25526
+ );
25116
25527
  }
25117
25528
  function hasTempTableRef(node) {
25118
25529
  if (Array.isArray(node)) return node.some(hasTempTableRef);
@@ -25124,7 +25535,7 @@ function hasTempTableRef(node) {
25124
25535
  }
25125
25536
  return false;
25126
25537
  }
25127
- function buildPlanForBatchQuery(query, info, capabilities, orderPlans, collector = { sources: [] }, sourceRole = "main") {
25538
+ function buildPlanForBatchQuery(query, info, capabilities, orderPlans, plainGroupByPlans, collector = { sources: [] }, sourceRole = "main", tempSchemaLedger = /* @__PURE__ */ new Map()) {
25128
25539
  if (info.tempTablesReferenced.length === 0) {
25129
25540
  return buildExplainPlan(
25130
25541
  query,
@@ -25134,7 +25545,7 @@ function buildPlanForBatchQuery(query, info, capabilities, orderPlans, collector
25134
25545
  100,
25135
25546
  DEFAULT_APPLY_MAX_SUBTABLE_ROWS,
25136
25547
  1e4,
25137
- void 0,
25548
+ plainGroupByPlans,
25138
25549
  true,
25139
25550
  collector,
25140
25551
  sourceRole
@@ -25154,6 +25565,26 @@ function buildPlanForBatchQuery(query, info, capabilities, orderPlans, collector
25154
25565
  lines.push(
25155
25566
  ` temp: ${info.tempTablesReferenced.join(", ")}\uFF08\u30A4\u30F3\u30E1\u30E2\u30EA\u8D70\u67FB\u3002\u5B9F\u4F53\u5316\u524D\u306E\u305F\u3081\u884C\u6570\u4E0D\u660E\uFF09`
25156
25567
  );
25568
+ const entries = info.tempTablesReferenced.map((name) => [name, tempSchemaLedger.get(name)]);
25569
+ for (const [name, entry] of entries) {
25570
+ if (entry?.status === "STATIC") {
25571
+ lines.push(` source: temp table ${name} (schema from statement ${entry.producerStatement})`);
25572
+ } else {
25573
+ lines.push(` source: temp table ${name}`);
25574
+ lines.push(" schema: deferred (could not be derived statically)");
25575
+ }
25576
+ }
25577
+ lines.push(" rows: runtime (not materialized by EXPLAIN)");
25578
+ const directSelect = query.type === "SELECT" ? query : query.type === "EXPLAIN" && query.query.type === "SELECT" ? query.query : void 0;
25579
+ if (directSelect) {
25580
+ lines.push(...renderPlainGroupByExplainLines(
25581
+ directSelect,
25582
+ plainGroupByPlans?.get(directSelect),
25583
+ entries.some(([, entry]) => entry?.status !== "STATIC")
25584
+ ));
25585
+ }
25586
+ lines.push(entries.every(([, entry]) => entry?.status === "STATIC") ? " plan status: static schema / runtime rows" : " plan status: deferred (temp table schema)");
25587
+ lines.push(" records API: none");
25157
25588
  const apps = info.appIds.filter(
25158
25589
  (a) => query.type !== "INSERT_SELECT" && query.type !== "UPSERT_SELECT" || a !== query.appId
25159
25590
  );
@@ -25423,6 +25854,33 @@ function formatChoiceEqualityRewrite(rewrite) {
25423
25854
  const normalizedOperator = rewrite.normalizedOperator === "IN" ? "in" : "not in";
25424
25855
  return ` pushdown normalized: ${field} ${rewrite.originalOperator} '${originalValue}' -> ${field} ${normalizedOperator} ("${normalizedValue}")`;
25425
25856
  }
25857
+ function renderPlainGroupByExplainLines(stmt, plainGroupByPlan, schemaDeferred) {
25858
+ const normalizedGrouping = normalizeGroupingSpec(stmt);
25859
+ if (normalizedGrouping.type !== "PLAIN") return [];
25860
+ const lines = [];
25861
+ if (plainGroupByPlan) {
25862
+ plainGroupByPlan.items.forEach((item, index) => {
25863
+ const key = normalizedGrouping.allItems[index];
25864
+ if (key?.type !== "FIELD_NAME") return;
25865
+ if (item.kind === "PHYSICAL") {
25866
+ lines.push(
25867
+ ` group key ${key.name}: PHYSICAL (source=${item.sourceIndex}, field=${item.fieldCode})`
25868
+ );
25869
+ } else if (item.kind === "ALIAS_SAFE") {
25870
+ lines.push(` group key ${key.name}: ALIAS_SAFE (column=${item.columnIndex})`);
25871
+ } else if (item.kind === "EXPRESSION") {
25872
+ lines.push(` group key ${key.name}: EXPRESSION`);
25873
+ }
25874
+ });
25875
+ } else if (schemaDeferred) {
25876
+ for (const key of normalizedGrouping.allItems) {
25877
+ if (key.type === "FIELD_NAME") {
25878
+ lines.push(` group key ${key.name}: DEFERRED (temp table schema unavailable)`);
25879
+ }
25880
+ }
25881
+ }
25882
+ return lines;
25883
+ }
25426
25884
  function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlans, allowTotalCountPlan = true, emitFetch = true, collector = { sources: [] }, sourceRole = "main") {
25427
25885
  const whereCapability = capabilities?.get(stmt) ?? (capabilities ? [...capabilities].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
25428
25886
  const orderPlan = orderPlans?.get(stmt) ?? (orderPlans ? [...orderPlans].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
@@ -25440,6 +25898,23 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlan
25440
25898
  );
25441
25899
  if (label) lines.push(label);
25442
25900
  lines.push(` mode: ${mode}`);
25901
+ for (const step of explainCrossJoinSteps.get(stmt) ?? []) {
25902
+ lines.push(` cross join: ${step.leftLabel} \xD7 ${step.rightLabel}`);
25903
+ lines.push(` left rows: ${step.leftRows ?? "runtime (left intermediate rows)"}`);
25904
+ lines.push(` right rows: ${step.rightRows ?? `runtime (${step.rightRuntimeLabel})`}`);
25905
+ if (step.plan) {
25906
+ lines.push(` rows: ${step.plan.outputRows}`);
25907
+ lines.push(` row guard: ${step.plan.outputRows} / ${step.plan.limit}`);
25908
+ lines.push(" guard timing: before row materialization");
25909
+ } else {
25910
+ const left = step.leftRows === null ? "left rows" : String(step.leftRows);
25911
+ const right = step.rightRows === null ? "right rows" : String(step.rightRows);
25912
+ lines.push(` rows: runtime (${left} \xD7 ${right})`);
25913
+ lines.push(` row guard: runtime checked / ${CROSS_JOIN_MAX_ROWS}`);
25914
+ lines.push(" guard timing: after complete source fetch, before row materialization");
25915
+ }
25916
+ lines.push(" records API: none");
25917
+ }
25443
25918
  for (const rewrite of explainChoiceEqualityRewrites.get(stmt) ?? choiceEqualityRewritesBySelect.get(stmt) ?? []) {
25444
25919
  lines.push(formatChoiceEqualityRewrite(rewrite));
25445
25920
  }
@@ -25455,35 +25930,11 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlan
25455
25930
  ` grouping output rows: runtime checked (limit: ${groupingMetadata.outputRowLimit}, before HAVING/DISTINCT/LIMIT)`
25456
25931
  );
25457
25932
  }
25458
- const normalizedGrouping = normalizeGroupingSpec(stmt);
25459
- if (normalizedGrouping.type === "PLAIN") {
25460
- const groupBy = normalizedGrouping.allItems;
25461
- if (plainGroupByPlan) {
25462
- plainGroupByPlan.items.forEach((item, index) => {
25463
- const key = groupBy[index];
25464
- if (key?.type !== "FIELD_NAME") return;
25465
- if (item.kind === "PHYSICAL") {
25466
- lines.push(
25467
- ` group key ${key.name}: PHYSICAL (source=${item.sourceIndex}, field=${item.fieldCode})`
25468
- );
25469
- } else if (item.kind === "ALIAS_SAFE") {
25470
- lines.push(
25471
- ` group key ${key.name}: ALIAS_SAFE (column=${item.columnIndex})`
25472
- );
25473
- } else if (item.kind === "EXPRESSION") {
25474
- lines.push(` group key ${key.name}: EXPRESSION`);
25475
- }
25476
- });
25477
- } else if ([stmt.from, ...stmt.joins.map((join2) => join2.table)].some((table) => table.cteName !== null)) {
25478
- for (const key of groupBy) {
25479
- if (key.type === "FIELD_NAME") {
25480
- lines.push(
25481
- ` group key ${key.name}: DEFERRED (materialized schema unavailable)`
25482
- );
25483
- }
25484
- }
25485
- }
25486
- }
25933
+ lines.push(...renderPlainGroupByExplainLines(
25934
+ stmt,
25935
+ plainGroupByPlan,
25936
+ [stmt.from, ...stmt.joins.map((join2) => join2.table)].some((table) => table.cteName !== null)
25937
+ ));
25487
25938
  for (const column of stmt.columns) {
25488
25939
  if (column.type !== "WINDOW_COL" || column.windowKind === void 0 || column.windowKind === "RANKING") continue;
25489
25940
  const clauses = [];
@@ -25604,7 +26055,7 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlan
25604
26055
  }
25605
26056
  }
25606
26057
  } else {
25607
- const reason = stmt.joins.some((join2) => join2.type !== "INNER") ? "OUTER_JOIN" : [stmt.from, ...stmt.joins.map((join2) => join2.table)].some(
26058
+ const reason = stmt.joins.some((join2) => join2.type !== "INNER" && join2.type !== "CROSS") ? "OUTER_JOIN" : [stmt.from, ...stmt.joins.map((join2) => join2.table)].some(
25608
26059
  (table) => table.cteName !== null || Boolean(table.subtableCode)
25609
26060
  ) ? "SOURCE_KIND" : "PLAN_NOT_APPLICABLE";
25610
26061
  lines.push(" join pushdown plan: not applied (join key/WHERE prefilters are reported per source below)");
@@ -25756,7 +26207,69 @@ function buildUnionPlan(stmt, capabilities, orderPlans, plainGroupByPlans, colle
25756
26207
  });
25757
26208
  return lines;
25758
26209
  }
26210
+ function populateWithCrossJoinExplain(stmt) {
26211
+ const exactRows = /* @__PURE__ */ new Map();
26212
+ const labelFor = (table) => effectiveTableAlias(table) ?? (table.appId > 0 ? `APP${table.appId}` : "source");
26213
+ const rowsFor = (table) => {
26214
+ if (table.cteName === NO_FROM_CTE_NAME) return 1;
26215
+ return table.cteName === null ? null : exactRows.get(table.cteName) ?? null;
26216
+ };
26217
+ const analyze = (select) => {
26218
+ let current = rowsFor(select.from);
26219
+ let leftLabel = labelFor(select.from);
26220
+ const steps = [];
26221
+ for (const join2 of select.joins) {
26222
+ const right = rowsFor(join2.table);
26223
+ const rightLabel = labelFor(join2.table);
26224
+ if (join2.type === "CROSS") {
26225
+ const plan = current !== null && right !== null ? planCrossJoinRows(current, right) : null;
26226
+ steps.push({
26227
+ leftLabel,
26228
+ rightLabel,
26229
+ leftRows: current,
26230
+ rightRows: right,
26231
+ plan,
26232
+ rightRuntimeLabel: join2.table.cteName === null ? `APP${join2.table.appId} fetched rows` : `${rightLabel} materialized rows`
26233
+ });
26234
+ current = plan?.outputRows ?? null;
26235
+ leftLabel = `${leftLabel} \xD7 ${rightLabel}`;
26236
+ } else {
26237
+ current = null;
26238
+ leftLabel = `${leftLabel} ${join2.type} JOIN ${rightLabel}`;
26239
+ }
26240
+ }
26241
+ if (steps.length > 0) explainCrossJoinSteps.set(select, steps);
26242
+ if (isConstantFalseWhere(select.where)) current = 0;
26243
+ else if (select.where !== null) current = null;
26244
+ if (normalizeGroupingSpec(select).type !== "NONE" || select.distinct || select.having !== null || isAggregateQueryBlock(select) || select.columns.some((column) => column.type === "WINDOW_COL")) return null;
26245
+ if (current === null) return null;
26246
+ current = Math.max(0, current - (select.offset ?? 0));
26247
+ return select.limit === null ? current : Math.min(current, select.limit);
26248
+ };
26249
+ const analyzeQuery = (query) => {
26250
+ if (query.type === "SELECT") {
26251
+ analyze(query);
26252
+ return;
26253
+ }
26254
+ analyzeQuery(query.left);
26255
+ analyze(query.right);
26256
+ };
26257
+ for (const cte of stmt.ctes) {
26258
+ if (cte.query.type === "GENERATE_SERIES") {
26259
+ if (!cte.query.args.some((arg) => arg.type === "VARIABLE")) {
26260
+ exactRows.set(cte.name, resolveGenerateSeries(cte.query).rowCount);
26261
+ }
26262
+ } else if (cte.query.type === "SELECT") {
26263
+ const rows = analyze(cte.query);
26264
+ if (rows !== null) exactRows.set(cte.name, rows);
26265
+ } else if (cte.query.type === "UNION") {
26266
+ analyzeQuery(cte.query);
26267
+ }
26268
+ }
26269
+ analyzeQuery(stmt.query);
26270
+ }
25759
26271
  function buildWithPlan(stmt, capabilities, orderPlans, plainGroupByPlans, collector = { sources: [] }) {
26272
+ populateWithCrossJoinExplain(stmt);
25760
26273
  const lines = [];
25761
26274
  for (const cte of stmt.ctes) {
25762
26275
  if (cte.query.type === "SELECT") {
@@ -25773,18 +26286,45 @@ function buildWithPlan(stmt, capabilities, orderPlans, plainGroupByPlans, collec
25773
26286
  ));
25774
26287
  lines.push("");
25775
26288
  } else if (cte.query.type === "GENERATE_SERIES") {
25776
- const series = resolveGenerateSeries(cte.query);
25777
- const step = series.kind === "DATE" ? `${series.step} ${Math.abs(series.step) === 1 ? "day" : "days"}` : String(series.step);
26289
+ const seriesStatement = cte.query;
26290
+ const binding = explainSeriesBindings.get(seriesStatement);
26291
+ const unresolved = seriesStatement.args.some((arg) => arg.type === "VARIABLE");
26292
+ if (unresolved) {
26293
+ const argumentLabel = (index) => {
26294
+ const arg = seriesStatement.args[index];
26295
+ if (!arg) return index === 2 ? "runtime" : "deferred";
26296
+ if (arg.type === "VARIABLE") return `@${arg.name} (runtime)`;
26297
+ if (index < 2) return "literal";
26298
+ return arg.type === "NUMBER" ? numberLiteralText(arg) : arg.value;
26299
+ };
26300
+ lines.push(
26301
+ `[cte: ${cte.name}]`,
26302
+ " source: GENERATE_SERIES",
26303
+ ` column: ${seriesStatement.columnAlias}`,
26304
+ " series type: deferred (variable)",
26305
+ ` start: ${argumentLabel(0)}`,
26306
+ ` stop: ${argumentLabel(1)}`,
26307
+ ` step: ${argumentLabel(2)}`,
26308
+ " rows: runtime",
26309
+ ` row guard: runtime / ${GENERATE_SERIES_MAX_ROWS}`,
26310
+ " records API: none",
26311
+ ""
26312
+ );
26313
+ continue;
26314
+ }
26315
+ const series = resolveGenerateSeries(seriesStatement);
26316
+ const step = series.kind === "DATE" ? `${series.step} ${String(series.dateUnit ?? "DAY").toLowerCase()}${Math.abs(series.step) === 1 ? "" : "s"}` : String(series.step);
25778
26317
  lines.push(
25779
26318
  `[cte: ${cte.name}]`,
25780
26319
  " source: GENERATE_SERIES",
25781
- ` column: ${cte.query.columnAlias}`,
25782
- ` series type: ${series.kind}`,
25783
- ` start: ${series.start}`,
25784
- ` stop: ${series.stop}`,
26320
+ ` column: ${seriesStatement.columnAlias}`,
26321
+ ` series type: ${series.kind}${binding?.defaultBoundIndexes.size ? " (DECLARE default)" : ""}`,
26322
+ ` start: ${binding?.variableNames.has(0) ? `@${binding.variableNames.get(0)} (DECLARE default; value hidden)` : series.start}`,
26323
+ ` stop: ${binding?.variableNames.has(1) ? `@${binding.variableNames.get(1)} (DECLARE default; value hidden)` : series.stop}`,
25785
26324
  ` step: ${step}`,
25786
- ` rows: ${series.rowCount}`,
26325
+ ` rows: ${series.rowCount}${binding?.defaultBoundIndexes.size ? " (DECLARE default estimate)" : ""}`,
25787
26326
  ` row guard: ${series.rowCount} / ${GENERATE_SERIES_MAX_ROWS}`,
26327
+ ...binding?.defaultBoundIndexes.size ? [" binding: DECLARE defaults; runtime injection may change this plan"] : [],
25788
26328
  " records API: none",
25789
26329
  ""
25790
26330
  );
@@ -25825,7 +26365,9 @@ function collectFullScanReasons(stmt) {
25825
26365
  const r = [];
25826
26366
  if (stmt.from.subtableCode || stmt.joins.some((j) => j.table.subtableCode))
25827
26367
  r.push("\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u4EEE\u60F3\u30C6\u30FC\u30D6\u30EB");
25828
- if (stmt.joins.length > 0)
26368
+ if (stmt.joins.some((join2) => join2.type === "CROSS"))
26369
+ r.push("CROSS JOIN \u3042\u308A");
26370
+ if (stmt.joins.some((join2) => join2.type !== "CROSS"))
25829
26371
  r.push("JOIN \u3042\u308A");
25830
26372
  const grouping = normalizeGroupingSpec(stmt);
25831
26373
  if (grouping.type === "PLAIN")
@@ -28906,14 +29448,42 @@ function createDryRunClient() {
28906
29448
  function hasStaticTypedPushdownCandidate(statement) {
28907
29449
  if (statement === null || typeof statement !== "object") return false;
28908
29450
  const node = statement;
28909
- if (node["type"] === "WITH") return hasStaticTypedPushdownCandidate(node["query"]);
29451
+ if (node["type"] === "CREATE_TEMP_TABLE") {
29452
+ return hasStaticTypedPushdownCandidate(node["query"]);
29453
+ }
29454
+ if (node["type"] === "WITH") {
29455
+ const query = node["query"];
29456
+ const containsCross = (value) => {
29457
+ if (Array.isArray(value)) return value.some(containsCross);
29458
+ if (value === null || typeof value !== "object") return false;
29459
+ const item = value;
29460
+ if (item["type"] === "SELECT") {
29461
+ if (Array.isArray(item["joins"]) && item["joins"].some(
29462
+ (join2) => typeof join2 === "object" && join2 !== null && join2["type"] === "CROSS"
29463
+ )) return true;
29464
+ }
29465
+ return Object.values(item).some(containsCross);
29466
+ };
29467
+ const hasPhysicalCte = Array.isArray(node["ctes"]) && node["ctes"].some((cte) => {
29468
+ if (typeof cte !== "object" || cte === null) return false;
29469
+ const cteQuery = cte["query"];
29470
+ if (typeof cteQuery !== "object" || cteQuery === null) return false;
29471
+ const from = cteQuery["from"];
29472
+ return cteQuery["type"] === "SELECT" && typeof from === "object" && from !== null && from["cteName"] === null;
29473
+ });
29474
+ return (containsCross(node["ctes"]) || containsCross(query)) && hasPhysicalCte || hasStaticTypedPushdownCandidate(query);
29475
+ }
28910
29476
  if (node["type"] !== "SELECT") return false;
28911
29477
  const select = statement;
28912
- if (!select.where || !Array.isArray(select.joins) || select.joins.length === 0) return false;
29478
+ if (!Array.isArray(select.joins) || select.joins.length === 0) return false;
28913
29479
  if (![select.from, ...select.joins.map((join2) => join2.table)].some((table) => table.cteName !== null)) return false;
29480
+ if (select.joins.some((join2) => join2.type === "CROSS" && join2.table.cteName === null)) {
29481
+ return true;
29482
+ }
29483
+ if (!select.where) return false;
28914
29484
  const where = select.where;
28915
29485
  return select.joins.some(
28916
- (join2) => join2.type === "INNER" && join2.table?.alias && join2.table?.cteName === null && !join2.table?.subtableCode && extractTypedPushdownCandidates(where, { tableAlias: join2.table.alias }) !== null
29486
+ (join2) => (join2.type === "INNER" || join2.type === "CROSS") && join2.table?.alias && join2.table?.cteName === null && !join2.table?.subtableCode && extractTypedPushdownCandidates(where, { tableAlias: join2.table.alias }) !== null
28917
29487
  );
28918
29488
  }
28919
29489
  async function runDiagnosticRecordGet(params) {
@@ -29600,7 +30170,10 @@ async function run() {
29600
30170
  return false;
29601
30171
  });
29602
30172
  dryRunNeedsMetadata = statements.some(explainNeedsAppMetadata);
29603
- dryRunUsesStaticTypedPlan = statements.some(hasStaticTypedPushdownCandidate) && !statements.some(statementUsesRelativeDateResolution);
30173
+ const staticEligible = statements.every(
30174
+ (statement) => !explainNeedsAppMetadata(statement) || hasStaticTypedPushdownCandidate(statement)
30175
+ );
30176
+ dryRunUsesStaticTypedPlan = statements.some(hasStaticTypedPushdownCandidate) && staticEligible && !statements.some(statementUsesRelativeDateResolution);
29604
30177
  if (statements.length > 1) {
29605
30178
  batchAnalysis = analyzeBatch(statements);
29606
30179
  isBatchSql = true;
@@ -29987,7 +30560,7 @@ async function run() {
29987
30560
  Object.keys(args.importCsv).length > 0 || Object.keys(args.importJson).length > 0,
29988
30561
  dmlMaxRows,
29989
30562
  dmlMaxSubtableRows,
29990
- false
30563
+ !dryRunUsesStaticTypedPlan
29991
30564
  );
29992
30565
  const out = [];
29993
30566
  const restoredStatements = sqlDiagnosticContext ? restoreSqlDiagnosticValue(plans.statements, sqlDiagnosticContext.appBindingByMappedApp) : plans.statements;