@rex0220/kintone-sql-tools 3.65.0 → 3.66.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
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  // src/cli/index.ts
22
22
  var index_exports = {};
23
23
  __export(index_exports, {
24
+ CLI_HELP_TEXT: () => CLI_HELP_TEXT,
24
25
  CLI_IMPORT_SOURCE_REQUIRED_MESSAGE: () => CLI_IMPORT_SOURCE_REQUIRED_MESSAGE,
25
26
  HELP_TEXT: () => HELP_TEXT,
26
27
  buildBatchDmlConfirmMessage: () => buildBatchDmlConfirmMessage,
@@ -1085,6 +1086,12 @@ var Parser = class {
1085
1086
  this.tempTableRefs = [];
1086
1087
  /** GROUP BY を読む前に作る B124 候補 leaf の診断位置。AST 公開型へ位置情報を足さない。 */
1087
1088
  this.aggregateGroupKeyTokens = /* @__PURE__ */ new WeakMap();
1089
+ // ----------------------------------------------------------
1090
+ // WITH 句(CTE)
1091
+ // ----------------------------------------------------------
1092
+ /** B53 parser-private state. Kept here so earlier diagnostic source locations remain stable. */
1093
+ this.activeCteDefinition = null;
1094
+ this.provisionalRecursiveCte = null;
1088
1095
  this.allowSelectArithVariable = false;
1089
1096
  }
1090
1097
  // ----------------------------------------------------------
@@ -1919,34 +1926,172 @@ var Parser = class {
1919
1926
  offset
1920
1927
  };
1921
1928
  }
1922
- // ----------------------------------------------------------
1923
- // WITH 句(CTE)
1924
- // ----------------------------------------------------------
1925
1929
  parseWith() {
1926
1930
  this.expect("WITH" /* WITH */);
1931
+ const recursive = this.isSoftKeyword("RECURSIVE") && this.peekAt(1).kind !== "AS" /* AS */ && this.peekAt(1).kind !== "(" /* LPAREN */;
1932
+ if (recursive) this.advance();
1927
1933
  const ctes = [];
1928
- do {
1929
- const name = this.parseIdentifier();
1930
- this.expect("AS" /* AS */);
1931
- this.expect("(" /* LPAREN */);
1932
- let query2;
1933
- const inner = this.peek().kind;
1934
- if (inner === "SHOW" /* SHOW */) {
1935
- query2 = this.parseShow();
1936
- } else if (inner === "DESCRIBE" /* DESCRIBE */ || inner === "DESC" /* DESC */) {
1937
- query2 = this.parseDescribe();
1938
- } else if (inner === "IDENT" /* IDENT */ && this.peek().value.toUpperCase() === "GENERATE_SERIES") {
1939
- query2 = this.parseGenerateSeries();
1940
- } else {
1941
- query2 = this.tryParseUnionChain(this.parseSelect());
1934
+ let recursiveCount = 0;
1935
+ try {
1936
+ do {
1937
+ const name = this.parseIdentifier();
1938
+ const columnAliases = this.parseOptionalCteColumnAliases();
1939
+ this.expect("AS" /* AS */);
1940
+ this.expect("(" /* LPAREN */);
1941
+ this.activeCteDefinition = { name, recursiveWith: recursive, phase: "SEED" };
1942
+ let query2;
1943
+ let recursiveSpec;
1944
+ const inner = this.peek().kind;
1945
+ if (inner === "SHOW" /* SHOW */) {
1946
+ query2 = this.parseShow();
1947
+ } else if (inner === "DESCRIBE" /* DESCRIBE */ || inner === "DESC" /* DESC */) {
1948
+ query2 = this.parseDescribe();
1949
+ } else if (inner === "IDENT" /* IDENT */ && this.peek().value.toUpperCase() === "GENERATE_SERIES") {
1950
+ query2 = this.parseGenerateSeries();
1951
+ } else if (recursive) {
1952
+ const parsed = this.parseRecursiveCteCandidate(name);
1953
+ query2 = parsed.query;
1954
+ recursiveSpec = parsed.recursiveSpec;
1955
+ } else {
1956
+ query2 = this.tryParseUnionChain(this.parseSelect());
1957
+ }
1958
+ this.expect(")" /* RPAREN */);
1959
+ const cycle = recursive && this.isSoftKeyword("CYCLE") ? this.parseRecursiveCycleClause() : null;
1960
+ if (cycle && !recursiveSpec) {
1961
+ throw new ParseError("CYCLE \u53E5\u306F\u81EA\u5DF1\u53C2\u7167\u3059\u308B\u518D\u5E30 CTE \u306B\u3060\u3051\u6307\u5B9A\u3067\u304D\u307E\u3059", this.prev());
1962
+ }
1963
+ if (recursiveSpec) {
1964
+ recursiveSpec = { ...recursiveSpec, cycle };
1965
+ this.validateRecursiveCte(name, columnAliases, recursiveSpec);
1966
+ recursiveCount++;
1967
+ if (recursiveCount > 1) {
1968
+ throw new ParseError("WITH RECURSIVE \u3067\u5B9A\u7FA9\u3067\u304D\u308B\u518D\u5E30 CTE \u306F1\u500B\u307E\u3067\u3067\u3059", this.prev());
1969
+ }
1970
+ }
1971
+ if (columnAliases && !recursiveSpec) {
1972
+ throw new ParseError("CTE \u306E\u5217\u540D\u30EA\u30B9\u30C8\u306F WITH RECURSIVE \u306E\u518D\u5E30 CTE \u306B\u3060\u3051\u6307\u5B9A\u3067\u304D\u307E\u3059", this.prev());
1973
+ }
1974
+ const definition = { name, query: query2 };
1975
+ if (columnAliases) definition.columnAliases = columnAliases;
1976
+ if (recursiveSpec) definition.recursiveSpec = recursiveSpec;
1977
+ ctes.push(definition);
1978
+ this.activeCteDefinition = null;
1979
+ this.provisionalRecursiveCte = null;
1980
+ this.cteNames.add(name);
1981
+ } while (this.consume("," /* COMMA */));
1982
+ const query = this.tryParseUnionChain(this.parseSelect());
1983
+ return recursive ? { type: "WITH", ctes, query, recursive: true } : { type: "WITH", ctes, query };
1984
+ } finally {
1985
+ this.activeCteDefinition = null;
1986
+ this.provisionalRecursiveCte = null;
1987
+ this.cteNames.clear();
1988
+ }
1989
+ }
1990
+ parseOptionalCteColumnAliases() {
1991
+ if (!this.consume("(" /* LPAREN */)) return void 0;
1992
+ const aliases = [];
1993
+ if (this.peek().kind === ")" /* RPAREN */) {
1994
+ throw new ParseError("CTE \u306E\u5217\u540D\u30EA\u30B9\u30C8\u306B\u306F1\u500B\u4EE5\u4E0A\u306E\u5217\u540D\u304C\u5FC5\u8981\u3067\u3059", this.peek());
1995
+ }
1996
+ do
1997
+ aliases.push(this.parseIdentifier());
1998
+ while (this.consume("," /* COMMA */));
1999
+ this.expect(")" /* RPAREN */);
2000
+ if (new Set(aliases).size !== aliases.length) {
2001
+ throw new ParseError("CTE \u306E\u5217\u540D\u30EA\u30B9\u30C8\u306B\u540C\u3058\u5217\u540D\u3092\u91CD\u8907\u3057\u3066\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093", this.prev());
2002
+ }
2003
+ return aliases;
2004
+ }
2005
+ parseRecursiveCteCandidate(name) {
2006
+ const seed = this.parseSelect();
2007
+ if (this.peek().kind !== "UNION" /* UNION */) return { query: seed };
2008
+ this.advance();
2009
+ const all = this.consume("ALL" /* ALL */);
2010
+ this.activeCteDefinition = { name, recursiveWith: true, phase: "RECURSIVE_TERM" };
2011
+ this.provisionalRecursiveCte = { name, references: 0 };
2012
+ const recursiveTerm = this.parseSelect();
2013
+ const references = this.provisionalRecursiveCte.references;
2014
+ this.provisionalRecursiveCte = null;
2015
+ let query = { type: "UNION", all, left: seed, right: recursiveTerm };
2016
+ query = this.tryParseUnionChain(query);
2017
+ this.activeCteDefinition = { name, recursiveWith: true, phase: "SEED" };
2018
+ if (references === 0) return { query };
2019
+ if (!all || query.type !== "UNION" || query.left !== seed || query.right !== recursiveTerm) {
2020
+ throw new ParseError("\u518D\u5E30 CTE \u306F seed SELECT UNION ALL recursive SELECT \u306E2\u5206\u5C90\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044", this.prev());
2021
+ }
2022
+ if (references !== 1) {
2023
+ throw new ParseError("\u518D\u5E30\u9805\u304B\u3089\u306E\u81EA\u5DF1\u53C2\u7167\u306F\u3061\u3087\u3046\u30691\u56DE\u306B\u3057\u3066\u304F\u3060\u3055\u3044", this.prev());
2024
+ }
2025
+ return {
2026
+ query,
2027
+ recursiveSpec: { seed, recursiveTerm, unionAll: true, cycle: null }
2028
+ };
2029
+ }
2030
+ parseRecursiveCycleClause() {
2031
+ this.advance();
2032
+ const column = this.parseIdentifier();
2033
+ this.expect("SET" /* SET */, "CYCLE \u306E\u5217\u540D\u306E\u5F8C\u306B\u306F SET \u304C\u5FC5\u8981\u3067\u3059");
2034
+ const markColumn = this.parseIdentifier();
2035
+ if (!this.isSoftKeyword("TO")) throw new ParseError("CYCLE \u306E mark \u5217\u306E\u5F8C\u306B\u306F TO \u304C\u5FC5\u8981\u3067\u3059", this.peek());
2036
+ this.advance();
2037
+ const mark = this.expect("STRING" /* STRING */, "CYCLE TO \u306B\u306F\u6587\u5B57\u5217\u30EA\u30C6\u30E9\u30EB\u304C\u5FC5\u8981\u3067\u3059");
2038
+ if (!this.isSoftKeyword("DEFAULT")) throw new ParseError("CYCLE TO \u306E\u5024\u306E\u5F8C\u306B\u306F DEFAULT \u304C\u5FC5\u8981\u3067\u3059", this.peek());
2039
+ this.advance();
2040
+ const normal = this.expect("STRING" /* STRING */, "CYCLE DEFAULT \u306B\u306F\u6587\u5B57\u5217\u30EA\u30C6\u30E9\u30EB\u304C\u5FC5\u8981\u3067\u3059");
2041
+ if (mark.value === normal.value) {
2042
+ throw new ParseError("CYCLE \u306E TO \u3068 DEFAULT \u306B\u306F\u7570\u306A\u308B\u6587\u5B57\u5217\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044", normal);
2043
+ }
2044
+ return { column, markColumn, markValue: mark.value, defaultValue: normal.value, exposePath: false };
2045
+ }
2046
+ validateRecursiveCte(name, columnAliases, spec) {
2047
+ const token = this.prev();
2048
+ const seed = spec.seed;
2049
+ const term = spec.recursiveTerm;
2050
+ const { groupBy } = term;
2051
+ if (seed.columns.length !== term.columns.length) {
2052
+ throw new ParseError("\u518D\u5E30 CTE \u306E seed \u3068\u518D\u5E30\u9805\u306E\u5217\u6570\u3092\u4E00\u81F4\u3055\u305B\u3066\u304F\u3060\u3055\u3044", token);
2053
+ }
2054
+ if (columnAliases && columnAliases.length !== seed.columns.length) {
2055
+ throw new ParseError("CTE \u306E\u5217\u540D\u30EA\u30B9\u30C8\u3068 SELECT \u306E\u5217\u6570\u3092\u4E00\u81F4\u3055\u305B\u3066\u304F\u3060\u3055\u3044", token);
2056
+ }
2057
+ if (seed.columns.some((column) => column.type === "WILDCARD" || column.type === "PARENT_WILDCARD") || term.columns.some((column) => column.type === "WILDCARD" || column.type === "PARENT_WILDCARD")) {
2058
+ throw new ParseError("\u518D\u5E30 CTE \u306E seed \u3068\u518D\u5E30\u9805\u3067\u306F\u5217\u3092\u660E\u793A\u7684\u306B\u5C04\u5F71\u3057\u3066\u304F\u3060\u3055\u3044", token);
2059
+ }
2060
+ if (term.distinct || groupBy.length > 0 || term.grouping !== void 0 || term.having !== null || term.orderBy.length > 0 || term.orderMode !== "CANONICAL" || term.limit !== null || term.offset !== null) {
2061
+ throw new ParseError("\u518D\u5E30\u9805\u3067\u306F DISTINCT\u3001\u96C6\u8A08\u3001window\u3001GROUP BY\u3001HAVING\u3001ORDER BY\u3001LIMIT\u3001OFFSET \u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", token);
2062
+ }
2063
+ if (term.joins.length !== 1 || term.joins[0].type !== "INNER") {
2064
+ throw new ParseError("\u518D\u5E30\u9805\u306F\u81EA\u5DF1\u53C2\u7167\u3068\u7269\u7406 source \u307E\u305F\u306F\u5148\u884C CTE \u306E INNER JOIN 1\u500B\u3067\u69CB\u6210\u3057\u3066\u304F\u3060\u3055\u3044", token);
2065
+ }
2066
+ const relationNames = [term.from.cteName, term.joins[0].table.cteName];
2067
+ if (relationNames.filter((value) => value === name).length !== 1) {
2068
+ throw new ParseError("\u518D\u5E30\u9805\u306E INNER JOIN \u306B\u306F\u81EA\u5DF1\u53C2\u7167\u3092\u3061\u3087\u3046\u30691\u56DE\u542B\u3081\u3066\u304F\u3060\u3055\u3044", token);
2069
+ }
2070
+ if (this.containsRecursiveForbiddenNode(term.columns) || this.containsRecursiveForbiddenNode(term.where)) {
2071
+ throw new ParseError("\u518D\u5E30\u9805\u3067\u306F\u96C6\u8A08\u3001window\u3001DISTINCT\u3001subquery \u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", token);
2072
+ }
2073
+ const outputNames = columnAliases ?? seed.columns.map((column) => this.selectColumnOutputName(column));
2074
+ if (spec.cycle) {
2075
+ const cycleMatches = outputNames.filter((value) => value === spec.cycle.column).length;
2076
+ if (cycleMatches !== 1) {
2077
+ throw new ParseError("CYCLE \u5217\u306F\u518D\u5E30 CTE \u306E\u51FA\u529B\u52171\u500B\u3078\u4E00\u610F\u306B\u89E3\u6C7A\u3067\u304D\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059", token);
1942
2078
  }
1943
- this.expect(")" /* RPAREN */);
1944
- ctes.push({ name, query: query2 });
1945
- this.cteNames.add(name);
1946
- } while (this.consume("," /* COMMA */));
1947
- const query = this.tryParseUnionChain(this.parseSelect());
1948
- this.cteNames.clear();
1949
- return { type: "WITH", ctes, query };
2079
+ if (outputNames.includes(spec.cycle.markColumn)) {
2080
+ throw new ParseError("CYCLE \u306E mark \u5217\u306F\u518D\u5E30 CTE \u306E\u65E2\u5B58\u51FA\u529B\u5217\u3068\u540C\u540D\u306B\u3067\u304D\u307E\u305B\u3093", token);
2081
+ }
2082
+ }
2083
+ }
2084
+ containsRecursiveForbiddenNode(value) {
2085
+ if (Array.isArray(value)) return value.some((item) => this.containsRecursiveForbiddenNode(item));
2086
+ if (value === null || typeof value !== "object") return false;
2087
+ const node = value;
2088
+ if (node.type === "AGGREGATE" || node.type === "AGG_REF" || node.type === "ARITH_AGG_COL" || node.type === "WINDOW_COL" || node.type === "SCALAR_SUBQUERY" || node.type === "SCALAR_SUBQUERY_COL" || node.type === "SUBQUERY_IN_LIST" || node.type === "EXISTS") return true;
2089
+ return Object.values(node).some((item) => this.containsRecursiveForbiddenNode(item));
2090
+ }
2091
+ selectColumnOutputName(column) {
2092
+ if ("alias" in column && typeof column.alias === "string") return column.alias;
2093
+ if (column.type === "FIELD") return column.field;
2094
+ return null;
1950
2095
  }
1951
2096
  parseGenerateSeries() {
1952
2097
  const name = this.advance();
@@ -2985,6 +3130,15 @@ var Parser = class {
2985
3130
  const alias2 = this.consume("AS" /* AS */) ? this.parseTableAliasName() : this.tryParseImplicitAlias();
2986
3131
  return { appId: 0, alias: alias2, cteName: name };
2987
3132
  }
3133
+ if (this.activeCteDefinition?.name === name) {
3134
+ if (this.activeCteDefinition.recursiveWith && this.activeCteDefinition.phase === "RECURSIVE_TERM" && this.provisionalRecursiveCte?.name === name) {
3135
+ this.provisionalRecursiveCte.references++;
3136
+ const alias2 = this.consume("AS" /* AS */) ? this.parseTableAliasName() : this.tryParseImplicitAlias();
3137
+ return { appId: 0, alias: alias2, cteName: name };
3138
+ }
3139
+ const message = this.activeCteDefinition.recursiveWith ? this.activeCteDefinition.phase === "RECURSIVE_TERM" ? "\u518D\u5E30 CTE \u306F seed SELECT UNION ALL recursive SELECT \u306E2\u5206\u5C90\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044" : "\u518D\u5E30 CTE \u306E seed \u304B\u3089\u81EA\u5206\u81EA\u8EAB\u3092\u53C2\u7167\u3067\u304D\u307E\u305B\u3093" : "CTE \u306E\u5B9A\u7FA9\u5185\u304B\u3089\u81EA\u5206\u81EA\u8EAB\u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059\u3002\u81EA\u5DF1\u53C2\u7167\u306B\u306F `WITH RECURSIVE` \u304C\u5FC5\u8981\u3067\u3059";
3140
+ throw new ParseError(message, nameTok);
3141
+ }
2988
3142
  const { appId, subtableCode } = extractTableRef(name, this.prev());
2989
3143
  if (subtableCode) {
2990
3144
  const alias2 = this.consume("AS" /* AS */) ? this.parseTableAliasName() : this.tryParseImplicitAlias();
@@ -12804,6 +12958,71 @@ function isOuterJoinNonPreservedTable(statement, table, isMainTable) {
12804
12958
  return false;
12805
12959
  }
12806
12960
 
12961
+ // src/core/recursiveCte.ts
12962
+ var RECURSIVE_CTE_MAX_DEPTH = 100;
12963
+ var RECURSIVE_CTE_MAX_ROWS = 1e4;
12964
+ var RECURSIVE_CTE_MAX_EXPANSIONS = 1e5;
12965
+ function resolveRecursiveCteLimits(options) {
12966
+ const entries = [
12967
+ ["recursiveCteMaxDepth", options.recursiveCteMaxDepth, RECURSIVE_CTE_MAX_DEPTH],
12968
+ ["recursiveCteMaxRows", options.recursiveCteMaxRows, RECURSIVE_CTE_MAX_ROWS],
12969
+ ["recursiveCteMaxExpansions", options.recursiveCteMaxExpansions, RECURSIVE_CTE_MAX_EXPANSIONS]
12970
+ ];
12971
+ for (const [name, value] of entries) {
12972
+ if (value !== void 0 && (!Number.isSafeInteger(value) || value <= 0)) {
12973
+ throw new RangeError(`${name} must be a positive safe integer`);
12974
+ }
12975
+ }
12976
+ return {
12977
+ depth: options.recursiveCteMaxDepth ?? RECURSIVE_CTE_MAX_DEPTH,
12978
+ rows: options.recursiveCteMaxRows ?? RECURSIVE_CTE_MAX_ROWS,
12979
+ expansions: options.recursiveCteMaxExpansions ?? RECURSIVE_CTE_MAX_EXPANSIONS
12980
+ };
12981
+ }
12982
+ var RecursiveCteLimitError = class extends Error {
12983
+ constructor(kind, limit, detected, cteName) {
12984
+ const label = kind === "DEPTH" ? "\u6DF1\u3055" : kind === "ROWS" ? "\u7D50\u679C\u884C\u6570" : "\u4E2D\u9593\u5C55\u958B\u6570";
12985
+ const unit = kind === "DEPTH" ? "" : " \u4EF6";
12986
+ super(`\u518D\u5E30 CTE\u300C${cteName}\u300D\u306E${label}\u304C\u4E0A\u9650 ${limit}${unit}\u3092\u8D85\u3048\u307E\u3057\u305F\uFF08\u691C\u51FA\u5024: ${detected}${unit}\uFF09\u3002`);
12987
+ this.name = "RecursiveCteLimitError";
12988
+ this.kind = kind;
12989
+ this.limit = limit;
12990
+ this.detected = detected;
12991
+ this.cteName = cteName;
12992
+ Object.setPrototypeOf(this, new.target.prototype);
12993
+ }
12994
+ };
12995
+ var RecursiveCteLimitCounter = class {
12996
+ constructor(cteName, limits = resolveRecursiveCteLimits({})) {
12997
+ this.cteName = cteName;
12998
+ this.limits = limits;
12999
+ this.rows = 0;
13000
+ this.expansions = 0;
13001
+ }
13002
+ observeDepth(detected) {
13003
+ if (detected > this.limits.depth) {
13004
+ throw new RecursiveCteLimitError("DEPTH", this.limits.depth, detected, this.cteName);
13005
+ }
13006
+ }
13007
+ addRow() {
13008
+ this.rows++;
13009
+ if (this.rows > this.limits.rows) {
13010
+ throw new RecursiveCteLimitError("ROWS", this.limits.rows, this.rows, this.cteName);
13011
+ }
13012
+ }
13013
+ addExpansion() {
13014
+ this.expansions++;
13015
+ if (this.expansions > this.limits.expansions) {
13016
+ throw new RecursiveCteLimitError(
13017
+ "EXPANSIONS",
13018
+ this.limits.expansions,
13019
+ this.expansions,
13020
+ this.cteName
13021
+ );
13022
+ }
13023
+ }
13024
+ };
13025
+
12807
13026
  // src/core/optimization/joinKeyPrefilter.ts
12808
13027
  var JOIN_KEY_IN_CHUNK_SIZE = 50;
12809
13028
  function buildJoinKeyPrefilterQueries(plan, field, quoteValue) {
@@ -14245,8 +14464,15 @@ function applyJoin(leftRows, rightRows, join2, columns = {}) {
14245
14464
  return result2;
14246
14465
  }
14247
14466
  const { on, type: joinType } = join2;
14248
- const leftKey = on.left.tableAlias ? `${on.left.tableAlias}.${on.left.field}` : on.left.field;
14249
- const rightKey = on.right.tableAlias ? `${on.right.tableAlias}.${on.right.field}` : on.right.field;
14467
+ const normalizedOn = normalizeJoinConditionSides(
14468
+ on,
14469
+ leftRows,
14470
+ rightRows,
14471
+ columns.leftColumns,
14472
+ columns.rightColumns
14473
+ );
14474
+ const leftKey = joinFieldKey(normalizedOn.left);
14475
+ const rightKey = joinFieldKey(normalizedOn.right);
14250
14476
  assertJoinKeyAvailable(leftRows, leftKey, columns.leftColumns);
14251
14477
  assertJoinKeyAvailable(rightRows, rightKey, columns.rightColumns);
14252
14478
  if (joinType === "RIGHT") {
@@ -14294,6 +14520,37 @@ function applyJoin(leftRows, rightRows, join2, columns = {}) {
14294
14520
  }
14295
14521
  return result;
14296
14522
  }
14523
+ function joinFieldKey(ref) {
14524
+ return ref.tableAlias ? `${ref.tableAlias}.${ref.field}` : ref.field;
14525
+ }
14526
+ function normalizeJoinConditionSides(on, leftRows, rightRows, leftColumns, rightColumns) {
14527
+ const leftSchema = leftColumns ?? Object.keys(leftRows[0] ?? {});
14528
+ const rightSchema = rightColumns ?? Object.keys(rightRows[0] ?? {});
14529
+ const leftAliases = qualifiedAliases(leftSchema);
14530
+ const rightAliases = qualifiedAliases(rightSchema);
14531
+ const sideFor = (ref) => {
14532
+ if (ref.tableAlias) {
14533
+ const inLeft2 = leftAliases.has(ref.tableAlias);
14534
+ const inRight2 = rightAliases.has(ref.tableAlias);
14535
+ if (inLeft2 !== inRight2) return inLeft2 ? "LEFT" : "RIGHT";
14536
+ }
14537
+ const key = joinFieldKey(ref);
14538
+ const inLeft = leftSchema.includes(key);
14539
+ const inRight = rightSchema.includes(key);
14540
+ return inLeft !== inRight ? inLeft ? "LEFT" : "RIGHT" : "UNKNOWN";
14541
+ };
14542
+ const leftSide = sideFor(on.left);
14543
+ const rightSide = sideFor(on.right);
14544
+ return leftSide === "RIGHT" && rightSide === "LEFT" ? { left: on.right, right: on.left } : on;
14545
+ }
14546
+ function qualifiedAliases(columns) {
14547
+ const aliases = /* @__PURE__ */ new Set();
14548
+ for (const column of columns) {
14549
+ const separator = column.indexOf(".");
14550
+ if (separator > 0) aliases.add(column.slice(0, separator));
14551
+ }
14552
+ return aliases;
14553
+ }
14297
14554
  function assertJoinKeyAvailable(rows, key, savedColumns) {
14298
14555
  const missing = rows.length > 0 ? rows.some((row) => !Object.prototype.hasOwnProperty.call(row, key)) : savedColumns !== void 0 && !savedColumns.includes(key);
14299
14556
  if (missing) {
@@ -17331,6 +17588,7 @@ function createInvocationCacheContext(cacheContext) {
17331
17588
  return `${cacheContext}\0inv:${nextCacheInvocationId++}`;
17332
17589
  }
17333
17590
  async function execute(sql, client, options = {}) {
17591
+ resolveRecursiveCteLimits(options);
17334
17592
  const startedAt = Date.now();
17335
17593
  const cacheContext = createInvocationCacheContext(
17336
17594
  resolveCacheContext(client, options.cacheContext)
@@ -17642,7 +17900,10 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
17642
17900
  options.cursorMaxActive ?? 2,
17643
17901
  stmt.query.type === "UPDATE" && stmt.query.applyBlocks?.length ? resolveApplyGuardLimit(options.dmlMaxRows, "dmlMaxRows", DEFAULT_APPLY_MAX_ROWS) : DEFAULT_APPLY_MAX_ROWS,
17644
17902
  stmt.query.type === "UPDATE" && stmt.query.applyBlocks?.length ? resolveApplyGuardLimit(options.dmlMaxSubtableRows, "dmlMaxSubtableRows", DEFAULT_APPLY_MAX_SUBTABLE_ROWS) : DEFAULT_APPLY_MAX_SUBTABLE_ROWS,
17645
- relativeDatePlan
17903
+ relativeDatePlan,
17904
+ options.recursiveCteMaxDepth,
17905
+ options.recursiveCteMaxRows,
17906
+ options.recursiveCteMaxExpansions
17646
17907
  );
17647
17908
  // 一時テーブルはバッチスコープのため単文実行では拒否する(executeBatch を使う)
17648
17909
  case "CREATE_TEMP_TABLE":
@@ -17914,6 +18175,7 @@ var BatchTimeoutError = class extends Error {
17914
18175
  }
17915
18176
  };
17916
18177
  async function executeBatch(sql, client, options = {}) {
18178
+ resolveRecursiveCteLimits(options);
17917
18179
  const statements = parseSqlBatch(sql, options.enableImport === true);
17918
18180
  const analysis = analyzeBatch(statements);
17919
18181
  statements.forEach((statement) => assertApplyExecutionScope("phase15b", statement));
@@ -18740,7 +19002,7 @@ function canProveTotalWindowOrder(stmt, orderBy, resolveField2, context, generat
18740
19002
  }
18741
19003
  function tieBreakAdvice(context, kind) {
18742
19004
  if (context !== "DIRECT") {
18743
- return "\u305D\u306E\u8868\u306E\u4E2D\u3067\u4E00\u610F\u306B\u306A\u308B\u5217\uFF08\u5143\u306E\u96C6\u7D04\u306E\u30AD\u30FC\u306A\u3069\uFF09\u3092 ORDER BY \u306B\u542B\u3081\u3066\u304F\u3060\u3055\u3044\u3002\u96C6\u7D04\u7D50\u679C\u306E\u5217\u306F\u4E00\u610F\u3067\u3082\u8A3C\u660E\u3067\u304D\u306A\u3044\u305F\u3081\u3001\u3059\u3067\u306B\u4E00\u610F\u306A\u5834\u5408\u3082\u3053\u306E\u8B66\u544A\u304C\u51FA\u307E\u3059\u3002\u5143\u306E\u96C6\u7D04\u306E\u30AD\u30FC\u3092\u3059\u3079\u3066 ORDER BY \u306B\u542B\u3081\u3066\u3044\u308B\u306A\u3089\u3001\u3053\u306E\u8B66\u544A\u306F\u7121\u8996\u3057\u3066\u69CB\u3044\u307E\u305B\u3093\u3002";
19005
+ return "\u30A6\u30A3\u30F3\u30C9\u30A6\u306E\u5404\u30D1\u30FC\u30C6\u30A3\u30B7\u30E7\u30F3\u5185\u3067\u3001ORDER BY \u306E\u5024\u306E\u7D44\u304C\u5165\u529B\u884C\u3092\u4E00\u610F\u306B\u8B58\u5225\u3059\u308B\u3068\u30AF\u30A8\u30EA\u69CB\u9020\u307E\u305F\u306F\u4FDD\u8A3C\u6E08\u307F\u306E\u30C7\u30FC\u30BF\u5236\u7D04\u304B\u3089\u78BA\u8A8D\u3067\u304D\u308B\u5834\u5408\u306B\u9650\u308A\u3001\u3053\u306E\u8B66\u544A\u306F\u7121\u8996\u3067\u304D\u307E\u3059\u3002\u5143\u306E\u96C6\u7D04\u30AD\u30FC\u3092\u3059\u3079\u3066 ORDER BY \u306B\u542B\u3080\u5F62\u3084\u3001JOIN \u5F8C\u3082\u540C\u3058\u7CFB\u5217\u5024\u304C\u5404\u30D1\u30FC\u30C6\u30A3\u30B7\u30E7\u30F3\u5185\u3067\u9AD8\u30051\u884C\u3068\u4FDD\u8A3C\u3067\u304D\u308B\u5F62\u304C\u8A72\u5F53\u3057\u307E\u3059\u3002\u751F\u6210\u5217\u3001\u518D\u5E30\u306E\u6DF1\u3055\u5217\u3001\u307E\u305F\u306F $id \u306B\u7531\u6765\u3059\u308B\u5217\u3067\u3042\u308B\u3068\u3044\u3046\u7406\u7531\u3060\u3051\u3067\u306F\u7121\u8996\u3067\u304D\u307E\u305B\u3093\u3002";
18744
19006
  }
18745
19007
  return kind === "RANGE" ? "ORDER BY \u306B\u30EC\u30B3\u30FC\u30C9\u756A\u53F7\u306A\u3069\u306E\u30BF\u30A4\u30D6\u30EC\u30FC\u30AF\u30AD\u30FC\u3092\u8DB3\u3057\u3066\u304F\u3060\u3055\u3044\u3002" : "\u30EC\u30B3\u30FC\u30C9\u756A\u53F7\u7B49\u3092 ORDER BY \u306B\u8FFD\u52A0\u3057\u3066\u304F\u3060\u3055\u3044\u3002";
18746
19008
  }
@@ -20629,6 +20891,53 @@ function assertUnionColumnCount(leftColumns, rightColumns) {
20629
20891
  UNION \u306F\u5217\u3092\u4F4D\u7F6E\u3067\u5BFE\u5FDC\u3055\u305B\u308B\u305F\u3081\u3001\u4E21\u8FBA\u306E\u5217\u6570\u3092\u63C3\u3048\u3066\u304F\u3060\u3055\u3044`
20630
20892
  );
20631
20893
  }
20894
+ function alignSelectResultByPosition(result, targetColumns, targetMeta) {
20895
+ assertUnionColumnCount(targetColumns, result.columns);
20896
+ const rows = result.rows.map((row) => {
20897
+ const mapped = {};
20898
+ targetColumns.forEach((column, index) => {
20899
+ mapped[column] = row[result.columns[index] ?? column] ?? "";
20900
+ });
20901
+ return mapped;
20902
+ });
20903
+ const aligned = {
20904
+ ...result,
20905
+ rows,
20906
+ columns: [...targetColumns],
20907
+ rowCount: rows.length
20908
+ };
20909
+ const sourceMeta = materializedMetaBySelectResult.get(result);
20910
+ if (targetMeta || sourceMeta) {
20911
+ const meta = /* @__PURE__ */ new Map();
20912
+ targetColumns.forEach((column, index) => {
20913
+ const sourceColumn = result.columns[index];
20914
+ const inferred = targetMeta?.get(column) ?? (sourceColumn === void 0 ? void 0 : sourceMeta?.get(sourceColumn));
20915
+ if (inferred) meta.set(column, { ...inferred, displayName: column });
20916
+ });
20917
+ materializedMetaBySelectResult.set(aligned, meta);
20918
+ }
20919
+ return aligned;
20920
+ }
20921
+ function combineUnionResults(leftResult, rightResult, all, captureColumnMeta) {
20922
+ const alignedRight = alignSelectResultByPosition(rightResult, leftResult.columns);
20923
+ const combined = [...leftResult.rows, ...alignedRight.rows];
20924
+ const rows = all ? combined : deduplicateRows(combined, leftResult.columns);
20925
+ const warnings = [.../* @__PURE__ */ new Set([
20926
+ ...leftResult.warnings ?? [],
20927
+ ...rightResult.warnings ?? []
20928
+ ])];
20929
+ const result = {
20930
+ type: "SELECT",
20931
+ rows,
20932
+ columns: [...leftResult.columns],
20933
+ rowCount: rows.length,
20934
+ warnings
20935
+ };
20936
+ if (captureColumnMeta) {
20937
+ materializedMetaBySelectResult.set(result, mergeUnionColumnMeta(leftResult, rightResult));
20938
+ }
20939
+ return result;
20940
+ }
20632
20941
  async function executeUnion(stmt, client, options, cacheContext, captureColumnMeta = false, forLibraryCapture = false) {
20633
20942
  const completePolicy = buildCompleteInputPolicy(stmt, options, null);
20634
20943
  return withCompleteInputPolicy(completePolicy, async () => {
@@ -20662,33 +20971,7 @@ async function executeUnion(stmt, client, options, cacheContext, captureColumnMe
20662
20971
  "DERIVED"
20663
20972
  )
20664
20973
  ]);
20665
- const leftCols = leftResult.columns;
20666
- const rightCols = rightResult.columns;
20667
- assertUnionColumnCount(leftCols, rightCols);
20668
- const remappedRight = rightResult.rows.map((row) => {
20669
- const mapped = {};
20670
- leftCols.forEach((col, i) => {
20671
- mapped[col] = row[rightCols[i] ?? col] ?? "";
20672
- });
20673
- return mapped;
20674
- });
20675
- const combined = [...leftResult.rows, ...remappedRight];
20676
- const rows = stmt.all ? combined : deduplicateRows(combined, leftCols);
20677
- const warnings = [.../* @__PURE__ */ new Set([
20678
- ...leftResult.warnings ?? [],
20679
- ...rightResult.warnings ?? []
20680
- ])];
20681
- const result = {
20682
- type: "SELECT",
20683
- rows,
20684
- columns: leftCols,
20685
- rowCount: rows.length,
20686
- warnings
20687
- };
20688
- if (captureColumnMeta) {
20689
- materializedMetaBySelectResult.set(result, mergeUnionColumnMeta(leftResult, rightResult));
20690
- }
20691
- return result;
20974
+ return combineUnionResults(leftResult, rightResult, stmt.all, captureColumnMeta);
20692
20975
  });
20693
20976
  }
20694
20977
  function deduplicateRows(rows, columns) {
@@ -20700,6 +20983,442 @@ function deduplicateRows(rows, columns) {
20700
20983
  return true;
20701
20984
  });
20702
20985
  }
20986
+ function recursiveSelectOutputName(column) {
20987
+ if ("alias" in column && typeof column.alias === "string") return column.alias;
20988
+ if (column.type === "FIELD") return column.field;
20989
+ return null;
20990
+ }
20991
+ function recursiveOutputColumns(cte) {
20992
+ const spec = cte.recursiveSpec;
20993
+ if (cte.columnAliases) return [...cte.columnAliases];
20994
+ const names = spec.seed.columns.map(recursiveSelectOutputName);
20995
+ if (names.some((name) => name === null)) {
20996
+ throw new Error(
20997
+ `PlanningError: \u518D\u5E30 CTE\u300C${cte.name}\u300D\u3067\u5217\u540D\u30EA\u30B9\u30C8\u3092\u7701\u7565\u3059\u308B\u5834\u5408\u3001seed \u306E\u5F0F\u306B\u306F AS \u5225\u540D\u304C\u5FC5\u8981\u3067\u3059`
20998
+ );
20999
+ }
21000
+ const columns = names;
21001
+ if (new Set(columns).size !== columns.length) {
21002
+ throw new Error(
21003
+ `PlanningError: \u518D\u5E30 CTE\u300C${cte.name}\u300D\u3067\u5217\u540D\u30EA\u30B9\u30C8\u3092\u7701\u7565\u3059\u308B\u5834\u5408\u3001seed \u306E\u51FA\u529B\u5217\u540D\u3092\u91CD\u8907\u3055\u305B\u308B\u3053\u3068\u306F\u3067\u304D\u307E\u305B\u3093`
21004
+ );
21005
+ }
21006
+ return columns;
21007
+ }
21008
+ function recursivePlanningColumnNames(stmt) {
21009
+ return stmt.columns.map((column, index) => recursiveSelectOutputName(column) ?? `__b53_column_${index}`);
21010
+ }
21011
+ function recursiveMetaCompatible(left, right) {
21012
+ const a = left.semantics;
21013
+ const b = right.semantics;
21014
+ if (!a || !b || a.compareMode === "unsupported" || b.compareMode === "unsupported") return false;
21015
+ if (a.compareMode !== b.compareMode) return false;
21016
+ if (left.sortKind && right.sortKind && left.sortKind !== right.sortKind) return false;
21017
+ if (a.inSubtable !== b.inSubtable || a.requiresCollectionOperators !== b.requiresCollectionOperators) return false;
21018
+ const synthetic = (fieldType) => fieldType === void 0 || fieldType.startsWith("KSQL_");
21019
+ if (!synthetic(left.fieldType) && !synthetic(right.fieldType) && left.fieldType !== right.fieldType) return false;
21020
+ return true;
21021
+ }
21022
+ async function buildRecursiveFieldResolver(stmt, client, cacheContext, materializedTables) {
21023
+ const tables = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
21024
+ const physical = /* @__PURE__ */ new Map();
21025
+ await Promise.all(tables.filter((table) => table.cteName === null).map(async (table) => {
21026
+ if (physical.has(table.appId)) return;
21027
+ const infos = await getFieldsCached(table.appId, client, cacheContext);
21028
+ physical.set(table.appId, new Map(infos.map((info) => [info.code, info])));
21029
+ }));
21030
+ const resolveInTable = (table, field) => {
21031
+ if (table.cteName !== null) return materializedTables.get(table.cteName)?.columnMeta?.get(field);
21032
+ const info = physical.get(table.appId)?.get(fieldCodeForTypeLookup(table, field));
21033
+ return info ? materializedMetaFromFieldInfo(info, table.appId) : systemColumnMeta(field);
21034
+ };
21035
+ return (ref) => {
21036
+ if (ref.tableAlias !== null) {
21037
+ const table = tables.find((candidate) => effectiveTableAlias(candidate) === ref.tableAlias);
21038
+ return table ? resolveInTable(table, ref.field) : void 0;
21039
+ }
21040
+ if (tables.length === 1) return resolveInTable(tables[0], ref.field);
21041
+ const matches = tables.map((table) => resolveInTable(table, ref.field)).filter(
21042
+ (meta) => meta !== void 0
21043
+ );
21044
+ return matches.length === 1 ? matches[0] : void 0;
21045
+ };
21046
+ }
21047
+ function validateRecursiveProjectionNode(value, resolveField2, numeric = false) {
21048
+ if (value === null || typeof value !== "object") return;
21049
+ if (Array.isArray(value)) {
21050
+ value.forEach((item) => validateRecursiveProjectionNode(item, resolveField2, numeric));
21051
+ return;
21052
+ }
21053
+ const node = value;
21054
+ if (node.type === "FIELD") {
21055
+ const ref = typeof node.tableAlias === "string" || node.tableAlias === null ? node : aggregateFieldRef(String(node.field ?? ""));
21056
+ const meta = resolveField2(ref);
21057
+ if (!meta?.semantics || meta.semantics.compareMode === "unsupported") {
21058
+ throw new Error(`PlanningError: \u518D\u5E30 CTE \u306E\u5C04\u5F71\u5217 ${ref.tableAlias ? `${ref.tableAlias}.` : ""}${ref.field} \u306E\u578B\u3092\u8A3C\u660E\u3067\u304D\u307E\u305B\u3093`);
21059
+ }
21060
+ if (numeric && meta.semantics.compareMode !== "number" && meta.semantics.compareMode !== "recordNumber") {
21061
+ throw new Error(`PlanningError: \u518D\u5E30 CTE \u306E\u6570\u5024\u6F14\u7B97\u306B\u6570\u5024\u3067\u306A\u3044\u5217 ${ref.field} \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`);
21062
+ }
21063
+ return;
21064
+ }
21065
+ if (node.type === "FIELD_REF" && typeof node.field === "string") {
21066
+ const ref = aggregateFieldRef(node.field);
21067
+ const meta = resolveField2(ref);
21068
+ if (!meta?.semantics || meta.semantics.compareMode === "unsupported") {
21069
+ throw new Error(`PlanningError: \u518D\u5E30 CTE \u306E\u5C04\u5F71\u5217 ${node.field} \u306E\u578B\u3092\u8A3C\u660E\u3067\u304D\u307E\u305B\u3093`);
21070
+ }
21071
+ if (numeric && meta.semantics.compareMode !== "number" && meta.semantics.compareMode !== "recordNumber") {
21072
+ throw new Error(`PlanningError: \u518D\u5E30 CTE \u306E\u6570\u5024\u6F14\u7B97\u306B\u6570\u5024\u3067\u306A\u3044\u5217 ${node.field} \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`);
21073
+ }
21074
+ return;
21075
+ }
21076
+ if (node.type === "ARITH" || node.type === "SCALAR_ARITH") {
21077
+ validateRecursiveProjectionNode(node.left, resolveField2, true);
21078
+ validateRecursiveProjectionNode(node.right, resolveField2, true);
21079
+ return;
21080
+ }
21081
+ for (const child of Object.values(node)) validateRecursiveProjectionNode(child, resolveField2, numeric);
21082
+ }
21083
+ async function inferRecursiveProjectionMeta(stmt, outputColumns, client, cacheContext, materializedTables) {
21084
+ const resolveField2 = await buildRecursiveFieldResolver(stmt, client, cacheContext, materializedTables);
21085
+ stmt.columns.forEach((column) => validateRecursiveProjectionNode(column, resolveField2));
21086
+ const inferred = await inferSelectColumnMeta(
21087
+ stmt,
21088
+ recursivePlanningColumnNames(stmt),
21089
+ client,
21090
+ cacheContext,
21091
+ materializedTables
21092
+ );
21093
+ const sourceNames = recursivePlanningColumnNames(stmt);
21094
+ const aligned = /* @__PURE__ */ new Map();
21095
+ outputColumns.forEach((column, index) => {
21096
+ const meta = inferred.get(sourceNames[index]);
21097
+ if (!meta?.semantics || meta.semantics.fieldType === "KSQL_UNKNOWN" || meta.semantics.compareMode === "unsupported") {
21098
+ throw new Error(`PlanningError: \u518D\u5E30 CTE \u306E\u7B2C ${index + 1} \u5217\u306E\u578B\u3092\u9759\u7684\u306B\u8A3C\u660E\u3067\u304D\u307E\u305B\u3093`);
21099
+ }
21100
+ aligned.set(column, { ...meta, displayName: column });
21101
+ });
21102
+ return aligned;
21103
+ }
21104
+ function recursivePhysicalSourceKey(table) {
21105
+ return `${table.appId}:${table.subtableCode ?? ""}`;
21106
+ }
21107
+ function recursivePhysicalTables(...queries) {
21108
+ const byKey = /* @__PURE__ */ new Map();
21109
+ for (const query of queries) {
21110
+ for (const table of [query.from, ...query.joins.map((join2) => join2.table)]) {
21111
+ if (table.cteName === null && !byKey.has(recursivePhysicalSourceKey(table))) {
21112
+ byKey.set(recursivePhysicalSourceKey(table), table);
21113
+ }
21114
+ }
21115
+ }
21116
+ return [...byKey.values()];
21117
+ }
21118
+ function splitRecursiveFieldRef(field) {
21119
+ const dot = field.indexOf(".");
21120
+ return dot < 0 ? { alias: null, field } : { alias: field.slice(0, dot), field: field.slice(dot + 1) };
21121
+ }
21122
+ function collectRecursivePhysicalSourceFields(queries) {
21123
+ const result = /* @__PURE__ */ new Map();
21124
+ const markAll = (tables) => {
21125
+ for (const table of tables) result.set(recursivePhysicalSourceKey(table), null);
21126
+ };
21127
+ for (const query of queries) {
21128
+ const physical = physicalSelectTables(query);
21129
+ for (const table of physical) {
21130
+ const key = recursivePhysicalSourceKey(table);
21131
+ if (!result.has(key)) result.set(key, /* @__PURE__ */ new Set());
21132
+ }
21133
+ const addRef = (rawAlias, field) => {
21134
+ const table = rawAlias === null ? physical.length === 1 ? physical[0] : void 0 : [query.from, ...query.joins.map((join2) => join2.table)].find((candidate) => effectiveTableAlias(candidate)?.toLowerCase() === rawAlias.toLowerCase());
21135
+ if (!table) {
21136
+ markAll(physical);
21137
+ return;
21138
+ }
21139
+ if (table.cteName !== null) return;
21140
+ const current = result.get(recursivePhysicalSourceKey(table));
21141
+ if (current !== null) current?.add(field);
21142
+ };
21143
+ const visit = (node) => {
21144
+ if (node === null || node === void 0 || typeof node !== "object") return;
21145
+ if (Array.isArray(node)) {
21146
+ node.forEach(visit);
21147
+ return;
21148
+ }
21149
+ const value = node;
21150
+ if (value.type === "WILDCARD" || value.type === "PARENT_WILDCARD") {
21151
+ markAll(physical);
21152
+ return;
21153
+ }
21154
+ if ((value.type === "FIELD" || value.type === "FIELD_REF") && typeof value.field === "string") {
21155
+ const encoded = splitRecursiveFieldRef(value.field);
21156
+ const alias = typeof value.tableAlias === "string" ? value.tableAlias : value.tableAlias === null ? null : encoded.alias;
21157
+ addRef(alias, encoded.field);
21158
+ return;
21159
+ }
21160
+ if ((value.type === "FIELD_NAME" || value.type === "GROUPING_REF") && typeof value.name === "string") {
21161
+ markAll(physical);
21162
+ return;
21163
+ }
21164
+ Object.values(value).forEach(visit);
21165
+ };
21166
+ visit(query.columns);
21167
+ visit(query.where);
21168
+ visit(normalizeGroupingSpec(query));
21169
+ visit(query.having);
21170
+ visit(query.orderBy);
21171
+ for (const join2 of query.joins) {
21172
+ if (join2.on === null) {
21173
+ markAll(physical);
21174
+ continue;
21175
+ }
21176
+ addRef(join2.on.left.tableAlias, join2.on.left.field);
21177
+ addRef(join2.on.right.tableAlias, join2.on.right.field);
21178
+ }
21179
+ }
21180
+ return result;
21181
+ }
21182
+ function recursiveSourceSelect(table, fields) {
21183
+ const columns = fields !== null && fields.size > 0 ? [...fields].map((field) => ({ type: "FIELD", field, alias: null })) : [{ type: "WILDCARD" }];
21184
+ return {
21185
+ type: "SELECT",
21186
+ distinct: false,
21187
+ columns,
21188
+ from: { ...table, alias: null },
21189
+ joins: [],
21190
+ where: null,
21191
+ groupBy: [],
21192
+ having: null,
21193
+ orderMode: "CANONICAL",
21194
+ orderBy: [],
21195
+ limit: null,
21196
+ offset: null
21197
+ };
21198
+ }
21199
+ async function materializeRecursivePhysicalSources(queries, client, options, cacheContext) {
21200
+ const fieldsBySource = collectRecursivePhysicalSourceFields(queries);
21201
+ const entries = await Promise.all(recursivePhysicalTables(...queries).map(async (table, index) => {
21202
+ const result = await executeSelect(
21203
+ recursiveSourceSelect(table, fieldsBySource.get(recursivePhysicalSourceKey(table)) ?? null),
21204
+ client,
21205
+ { ...options, onLimitReached: "error" },
21206
+ cacheContext,
21207
+ void 0,
21208
+ true,
21209
+ false,
21210
+ "DERIVED"
21211
+ );
21212
+ const name = `__b53_source_${index}`;
21213
+ return [recursivePhysicalSourceKey(table), {
21214
+ name,
21215
+ warnings: result.warnings ?? [],
21216
+ table: {
21217
+ rows: result.rows,
21218
+ columns: result.columns,
21219
+ columnMeta: materializedMetaBySelectResult.get(result)
21220
+ }
21221
+ }];
21222
+ }));
21223
+ return new Map(entries);
21224
+ }
21225
+ function rewriteRecursivePhysicalSources(stmt, sources) {
21226
+ const rewrite = (table) => {
21227
+ if (table.cteName !== null) return table;
21228
+ const source = sources.get(recursivePhysicalSourceKey(table));
21229
+ if (!source) throw new Error("PlanningError: \u518D\u5E30 CTE \u306E\u5B8C\u5168\u5B9F\u4F53\u5316 source \u3092\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093");
21230
+ return { appId: 0, alias: table.alias, cteName: source.name, subtableCode: null };
21231
+ };
21232
+ return {
21233
+ ...stmt,
21234
+ from: rewrite(stmt.from),
21235
+ joins: stmt.joins.map((join2) => ({ ...join2, table: rewrite(join2.table) }))
21236
+ };
21237
+ }
21238
+ function tableMetaForJoinKey(table, field, cache) {
21239
+ return table.cteName === null ? void 0 : cache.get(table.cteName)?.columnMeta?.get(field);
21240
+ }
21241
+ function recursiveJoinKey(value, semantics) {
21242
+ if (semantics.compareMode !== "number" && semantics.compareMode !== "recordNumber") return value;
21243
+ const decimal = parseExactDecimal(value);
21244
+ return decimal === null ? `invalid:${value}` : `${decimal.sign}:${decimal.coefficient}:${decimal.scale}`;
21245
+ }
21246
+ function recursiveJoinSides(cteName, term) {
21247
+ const join2 = term.joins[0];
21248
+ if (!join2 || join2.type !== "INNER") throw new Error("PlanningError: \u518D\u5E30\u9805\u306E INNER JOIN \u3092\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093");
21249
+ const self = term.from.cteName === cteName ? term.from : join2.table;
21250
+ const source = self === term.from ? join2.table : term.from;
21251
+ const selfAlias = effectiveTableAlias(self);
21252
+ const leftIsSelf = join2.on.left.tableAlias === selfAlias;
21253
+ const rightIsSelf = join2.on.right.tableAlias === selfAlias;
21254
+ if (leftIsSelf === rightIsSelf) throw new Error("PlanningError: \u518D\u5E30\u9805\u306E\u81EA\u5DF1\u53C2\u7167 JOIN \u30AD\u30FC\u3092\u4E00\u610F\u306B\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093");
21255
+ return {
21256
+ self,
21257
+ source,
21258
+ selfField: leftIsSelf ? join2.on.left.field : join2.on.right.field,
21259
+ sourceField: leftIsSelf ? join2.on.right.field : join2.on.left.field
21260
+ };
21261
+ }
21262
+ async function executeRecursiveCte(cte, client, options, cteCache, cacheContext) {
21263
+ const spec = cte.recursiveSpec;
21264
+ const outputColumns = recursiveOutputColumns(cte);
21265
+ const seedMeta = await inferRecursiveProjectionMeta(
21266
+ spec.seed,
21267
+ outputColumns,
21268
+ client,
21269
+ cacheContext,
21270
+ cteCache
21271
+ );
21272
+ const planningCache = new Map(cteCache);
21273
+ planningCache.set(cte.name, { rows: [], columns: outputColumns, columnMeta: seedMeta });
21274
+ const termMeta = await inferRecursiveProjectionMeta(
21275
+ spec.recursiveTerm,
21276
+ outputColumns,
21277
+ client,
21278
+ cacheContext,
21279
+ planningCache
21280
+ );
21281
+ outputColumns.forEach((column, index) => {
21282
+ const left = seedMeta.get(column);
21283
+ const right = termMeta.get(column);
21284
+ if (!left || !right || !recursiveMetaCompatible(left, right)) {
21285
+ throw new Error(`PlanningError: \u518D\u5E30 CTE\u300C${cte.name}\u300D\u306E\u7B2C ${index + 1} \u5217\u3067 seed \u3068\u518D\u5E30\u9805\u306E\u578B\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093`);
21286
+ }
21287
+ });
21288
+ const planningSides = recursiveJoinSides(cte.name, spec.recursiveTerm);
21289
+ const termFieldResolver = await buildRecursiveFieldResolver(
21290
+ spec.recursiveTerm,
21291
+ client,
21292
+ cacheContext,
21293
+ planningCache
21294
+ );
21295
+ const selfJoinMeta = termFieldResolver({
21296
+ type: "FIELD",
21297
+ tableAlias: effectiveTableAlias(planningSides.self),
21298
+ field: planningSides.selfField
21299
+ });
21300
+ const sourceJoinMeta = termFieldResolver({
21301
+ type: "FIELD",
21302
+ tableAlias: effectiveTableAlias(planningSides.source),
21303
+ field: planningSides.sourceField
21304
+ });
21305
+ if (!selfJoinMeta || !sourceJoinMeta || !recursiveMetaCompatible(selfJoinMeta, sourceJoinMeta)) {
21306
+ throw new Error(`PlanningError: \u518D\u5E30 CTE\u300C${cte.name}\u300D\u306E\u81EA\u5DF1\u53C2\u7167 JOIN \u30AD\u30FC\u306E\u578B\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093`);
21307
+ }
21308
+ const sources = await materializeRecursivePhysicalSources(
21309
+ [spec.seed, spec.recursiveTerm],
21310
+ client,
21311
+ options,
21312
+ cacheContext
21313
+ );
21314
+ const runtimeCache = new Map(cteCache);
21315
+ for (const source of sources.values()) runtimeCache.set(source.name, source.table);
21316
+ const seedQuery = rewriteRecursivePhysicalSources(spec.seed, sources);
21317
+ const termQuery = rewriteRecursivePhysicalSources(spec.recursiveTerm, sources);
21318
+ const rawSeed = await executeQueryWithCte(seedQuery, client, options, runtimeCache, cacheContext, true, true);
21319
+ const seed = alignSelectResultByPosition(rawSeed, outputColumns, seedMeta);
21320
+ const cycle = spec.cycle;
21321
+ const cycleMeta = cycle ? seedMeta.get(cycle.column) : void 0;
21322
+ const resultMeta = new Map(seedMeta);
21323
+ if (cycle) resultMeta.set(cycle.markColumn, { ...syntheticColumnMeta("string"), displayName: cycle.markColumn });
21324
+ const resultColumns = cycle ? [...outputColumns, cycle.markColumn] : [...outputColumns];
21325
+ const rows = [];
21326
+ let frontier = [];
21327
+ const warnings = /* @__PURE__ */ new Set([
21328
+ ...seed.warnings ?? [],
21329
+ ...[...sources.values()].flatMap((source) => source.warnings)
21330
+ ]);
21331
+ const limits = new RecursiveCteLimitCounter(cte.name, resolveRecursiveCteLimits(options));
21332
+ const append = (row) => {
21333
+ limits.addRow();
21334
+ rows.push(row);
21335
+ };
21336
+ for (const row of seed.rows) {
21337
+ const value = cycle ? String(row[cycle.column] ?? "") : "";
21338
+ const emitted = cycle ? { ...row, [cycle.markColumn]: cycle.defaultValue } : row;
21339
+ append(emitted);
21340
+ frontier.push({ row, path: cycle ? [value] : [] });
21341
+ }
21342
+ const sides = recursiveJoinSides(cte.name, termQuery);
21343
+ const sourceTable = sides.source.cteName === null ? void 0 : runtimeCache.get(sides.source.cteName);
21344
+ if (!sourceTable) throw new Error("PlanningError: \u518D\u5E30\u9805\u306E\u5B8C\u5168\u5B9F\u4F53\u5316 source \u304C\u3042\u308A\u307E\u305B\u3093");
21345
+ const joinMeta = tableMetaForJoinKey(sides.self, sides.selfField, planningCache) ?? tableMetaForJoinKey(sides.source, sides.sourceField, runtimeCache);
21346
+ if (!joinMeta?.semantics || joinMeta.semantics.compareMode === "unsupported") {
21347
+ throw new Error(`PlanningError: \u518D\u5E30\u9805\u306E JOIN \u30AD\u30FC ${sides.selfField} \u306E\u578B\u3092\u8A3C\u660E\u3067\u304D\u307E\u305B\u3093`);
21348
+ }
21349
+ const sourceRowsByKey = /* @__PURE__ */ new Map();
21350
+ for (const sourceRow of sourceTable.rows) {
21351
+ const value = String(sourceRow[sides.sourceField] ?? "");
21352
+ const key = recursiveJoinKey(value, joinMeta.semantics);
21353
+ const bucket = sourceRowsByKey.get(key);
21354
+ if (bucket) bucket.push(sourceRow);
21355
+ else sourceRowsByKey.set(key, [sourceRow]);
21356
+ }
21357
+ const sourceHasEmptyKey = sourceRowsByKey.has(recursiveJoinKey("", joinMeta.semantics));
21358
+ let depth = 0;
21359
+ let emptyKeyWarned = false;
21360
+ while (frontier.length > 0) {
21361
+ depth++;
21362
+ const next = [];
21363
+ for (const parent of frontier) {
21364
+ const parentKey = String(parent.row[sides.selfField] ?? "");
21365
+ if (!emptyKeyWarned && parentKey === "" && sourceHasEmptyKey) {
21366
+ warnings.add(
21367
+ `\u518D\u5E30 CTE\u300C${cte.name}\u300D\u306E JOIN ${sides.selfField} = ${sides.sourceField} \u3067\u7B2C ${depth} \u53CD\u5FA9\u306B\u4E21\u5074\u306E\u7A7A\u30AD\u30FC\u3092\u691C\u51FA\u3057\u307E\u3057\u305F\u3002\u7A7A\u30AD\u30FC\u3069\u3046\u3057\u306F\u4E00\u81F4\u3057\u3001\u30EB\u30FC\u30C8\u7FA4\u3092\u518D\u5C55\u958B\u3057\u5F97\u307E\u3059\u3002`
21368
+ );
21369
+ emptyKeyWarned = true;
21370
+ }
21371
+ const matchingSourceRows = sourceRowsByKey.get(recursiveJoinKey(parentKey, joinMeta.semantics)) ?? [];
21372
+ for (const sourceRow of matchingSourceRows) {
21373
+ const sourceKey = String(sourceRow[sides.sourceField] ?? "");
21374
+ if (!compareScalarValues("=", parentKey, sourceKey, joinMeta.semantics)) continue;
21375
+ limits.addExpansion();
21376
+ }
21377
+ if (matchingSourceRows.length === 0) continue;
21378
+ const iterationCache = new Map(runtimeCache);
21379
+ iterationCache.set(cte.name, {
21380
+ rows: [parent.row],
21381
+ columns: outputColumns,
21382
+ columnMeta: seedMeta
21383
+ });
21384
+ const rawCandidates = await executeQueryWithCte(
21385
+ termQuery,
21386
+ client,
21387
+ options,
21388
+ iterationCache,
21389
+ cacheContext,
21390
+ true,
21391
+ true
21392
+ );
21393
+ for (const warning of rawCandidates.warnings ?? []) warnings.add(warning);
21394
+ const candidates = alignSelectResultByPosition(rawCandidates, outputColumns, seedMeta);
21395
+ if (candidates.rows.length > 0) limits.observeDepth(depth);
21396
+ for (const candidate of candidates.rows) {
21397
+ if (!cycle) {
21398
+ append(candidate);
21399
+ next.push({ row: candidate, path: [] });
21400
+ continue;
21401
+ }
21402
+ const value = String(candidate[cycle.column] ?? "");
21403
+ const isCycle = parent.path.some(
21404
+ (seen) => compareScalarValues("=", seen, value, cycleMeta.semantics)
21405
+ );
21406
+ append({ ...candidate, [cycle.markColumn]: isCycle ? cycle.markValue : cycle.defaultValue });
21407
+ if (!isCycle) next.push({ row: candidate, path: [...parent.path, value] });
21408
+ }
21409
+ }
21410
+ frontier = next;
21411
+ }
21412
+ const result = {
21413
+ type: "SELECT",
21414
+ rows,
21415
+ columns: resultColumns,
21416
+ rowCount: rows.length,
21417
+ warnings: [...warnings]
21418
+ };
21419
+ materializedMetaBySelectResult.set(result, resultMeta);
21420
+ return result;
21421
+ }
20703
21422
  async function executeWith(stmt, client, options, cacheContext, seed, captureColumnMeta = false) {
20704
21423
  if ((seed == null || seed.size === 0) && canInlineSingleCte(stmt)) {
20705
21424
  return executeSelect(
@@ -20717,7 +21436,9 @@ async function executeWith(stmt, client, options, cacheContext, seed, captureCol
20717
21436
  const warnings = /* @__PURE__ */ new Set();
20718
21437
  for (const cte of stmt.ctes) {
20719
21438
  let result2;
20720
- if (cte.query.type === "SHOW_APPS") {
21439
+ if (cte.recursiveSpec) {
21440
+ result2 = await executeRecursiveCte(cte, client, options, cteCache, cacheContext);
21441
+ } else if (cte.query.type === "SHOW_APPS") {
20721
21442
  result2 = await executeShowApps(client);
20722
21443
  } else if (cte.query.type === "DESCRIBE") {
20723
21444
  result2 = await executeDescribe(cte.query, client, cacheContext);
@@ -20774,33 +21495,7 @@ async function executeQueryWithCte(query, client, options, cteCache, cacheContex
20774
21495
  executeQueryWithCte(query.left, client, options, cteCache, cacheContext, captureColumnMeta, true),
20775
21496
  executeQueryWithCte(query.right, client, options, cteCache, cacheContext, captureColumnMeta, true)
20776
21497
  ]);
20777
- const leftCols = leftResult.columns;
20778
- const rightCols = rightResult.columns;
20779
- assertUnionColumnCount(leftCols, rightCols);
20780
- const remapped = rightResult.rows.map((row) => {
20781
- const mapped = {};
20782
- leftCols.forEach((col, i) => {
20783
- mapped[col] = row[rightCols[i] ?? col] ?? "";
20784
- });
20785
- return mapped;
20786
- });
20787
- const combined = [...leftResult.rows, ...remapped];
20788
- const rows = query.all ? combined : deduplicateRows(combined, leftCols);
20789
- const warnings = [.../* @__PURE__ */ new Set([
20790
- ...leftResult.warnings ?? [],
20791
- ...rightResult.warnings ?? []
20792
- ])];
20793
- const result2 = {
20794
- type: "SELECT",
20795
- rows,
20796
- columns: leftCols,
20797
- rowCount: rows.length,
20798
- warnings
20799
- };
20800
- if (captureColumnMeta) {
20801
- materializedMetaBySelectResult.set(result2, mergeUnionColumnMeta(leftResult, rightResult));
20802
- }
20803
- return result2;
21498
+ return combineUnionResults(leftResult, rightResult, query.all, captureColumnMeta);
20804
21499
  }
20805
21500
  const hasCteRef = query.from.cteName != null && query.from.cteName !== NO_FROM_CTE_NAME || query.joins.some((j) => j.table.cteName != null);
20806
21501
  if (!hasCteRef) {
@@ -25337,7 +26032,12 @@ var EXPLAIN_FETCH_PLAN = /* @__PURE__ */ Symbol("ksql.explainFetchPlan");
25337
26032
  function setExplainFetchPlan(result, plan) {
25338
26033
  result[EXPLAIN_FETCH_PLAN] = plan;
25339
26034
  }
25340
- async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords = 1e4, cursorMaxActive = 2, enableImport = false, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, resolveMetadata = true) {
26035
+ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords = 1e4, cursorMaxActive = 2, enableImport = false, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, resolveMetadata = true, recursiveCteMaxDepth, recursiveCteMaxRows, recursiveCteMaxExpansions) {
26036
+ const recursiveLimits = resolveRecursiveCteLimits({
26037
+ recursiveCteMaxDepth,
26038
+ recursiveCteMaxRows,
26039
+ recursiveCteMaxExpansions
26040
+ });
25341
26041
  const invocationCacheContext = createInvocationCacheContext(cacheContext);
25342
26042
  try {
25343
26043
  const statements = parseSqlBatch(sql, enableImport);
@@ -25416,7 +26116,8 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
25416
26116
  dmlMaxSubtableRows,
25417
26117
  fetchCollector,
25418
26118
  tempSchemaLedger,
25419
- createdSchema
26119
+ createdSchema,
26120
+ { maxRecords, recursiveLimits }
25420
26121
  ), cursorMaxActive)
25421
26122
  ];
25422
26123
  const metadataPlan = explainMetadataLines(whereAnalysis);
@@ -25448,7 +26149,7 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
25448
26149
  releaseMetadataCacheScope(invocationCacheContext);
25449
26150
  }
25450
26151
  }
25451
- function buildBatchStatementPlan(stmt, info, capabilities, orderPlans, plainGroupByPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, collector = { sources: [] }, tempSchemaLedger = /* @__PURE__ */ new Map(), createdSchema) {
26152
+ function buildBatchStatementPlan(stmt, info, capabilities, orderPlans, plainGroupByPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, collector = { sources: [] }, tempSchemaLedger = /* @__PURE__ */ new Map(), createdSchema, explainContext = defaultRecursiveExplainContext()) {
25452
26153
  if (stmt.type === "CREATE_TEMP_TABLE") {
25453
26154
  return [
25454
26155
  `CREATE TEMP TABLE ${stmt.name}`,
@@ -25469,7 +26170,8 @@ function buildBatchStatementPlan(stmt, info, capabilities, orderPlans, plainGrou
25469
26170
  plainGroupByPlans,
25470
26171
  collector,
25471
26172
  "main",
25472
- tempSchemaLedger
26173
+ tempSchemaLedger,
26174
+ explainContext
25473
26175
  ).map((l) => ` ${l}`)
25474
26176
  ];
25475
26177
  }
@@ -25494,7 +26196,8 @@ function buildBatchStatementPlan(stmt, info, capabilities, orderPlans, plainGrou
25494
26196
  plainGroupByPlans,
25495
26197
  collector,
25496
26198
  "main",
25497
- tempSchemaLedger
26199
+ tempSchemaLedger,
26200
+ explainContext
25498
26201
  ).map((l) => ` ${l}`)
25499
26202
  ];
25500
26203
  }
@@ -25526,7 +26229,8 @@ function buildBatchStatementPlan(stmt, info, capabilities, orderPlans, plainGrou
25526
26229
  plainGroupByPlans,
25527
26230
  collector,
25528
26231
  "main",
25529
- tempSchemaLedger
26232
+ tempSchemaLedger,
26233
+ explainContext
25530
26234
  );
25531
26235
  }
25532
26236
  if (stmt.type === "ASSERT") {
@@ -25548,7 +26252,8 @@ function buildBatchStatementPlan(stmt, info, capabilities, orderPlans, plainGrou
25548
26252
  plainGroupByPlans,
25549
26253
  collector,
25550
26254
  "main",
25551
- tempSchemaLedger
26255
+ tempSchemaLedger,
26256
+ explainContext
25552
26257
  ).map((l) => ` ${l}`));
25553
26258
  });
25554
26259
  return lines;
@@ -25573,7 +26278,8 @@ function buildBatchStatementPlan(stmt, info, capabilities, orderPlans, plainGrou
25573
26278
  plainGroupByPlans,
25574
26279
  collector,
25575
26280
  "main",
25576
- tempSchemaLedger
26281
+ tempSchemaLedger,
26282
+ explainContext
25577
26283
  );
25578
26284
  }
25579
26285
  function hasTempTableRef(node) {
@@ -25586,7 +26292,7 @@ function hasTempTableRef(node) {
25586
26292
  }
25587
26293
  return false;
25588
26294
  }
25589
- function buildPlanForBatchQuery(query, info, capabilities, orderPlans, plainGroupByPlans, collector = { sources: [] }, sourceRole = "main", tempSchemaLedger = /* @__PURE__ */ new Map()) {
26295
+ function buildPlanForBatchQuery(query, info, capabilities, orderPlans, plainGroupByPlans, collector = { sources: [] }, sourceRole = "main", tempSchemaLedger = /* @__PURE__ */ new Map(), explainContext = defaultRecursiveExplainContext()) {
25590
26296
  if (info.tempTablesReferenced.length === 0) {
25591
26297
  return buildExplainPlan(
25592
26298
  query,
@@ -25595,11 +26301,12 @@ function buildPlanForBatchQuery(query, info, capabilities, orderPlans, plainGrou
25595
26301
  orderPlans,
25596
26302
  100,
25597
26303
  DEFAULT_APPLY_MAX_SUBTABLE_ROWS,
25598
- 1e4,
26304
+ explainContext.maxRecords,
25599
26305
  plainGroupByPlans,
25600
26306
  true,
25601
26307
  collector,
25602
- sourceRole
26308
+ sourceRole,
26309
+ explainContext.recursiveLimits
25603
26310
  );
25604
26311
  }
25605
26312
  const lines = [];
@@ -25646,7 +26353,15 @@ function buildPlanForBatchQuery(query, info, capabilities, orderPlans, plainGrou
25646
26353
  return lines;
25647
26354
  }
25648
26355
  var explainMaterializedTables = /* @__PURE__ */ new WeakMap();
25649
- async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxActive, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, relativeDatePlan) {
26356
+ function defaultRecursiveExplainContext() {
26357
+ return { maxRecords: 1e4, recursiveLimits: resolveRecursiveCteLimits({}) };
26358
+ }
26359
+ async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxActive, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, relativeDatePlan, recursiveCteMaxDepth, recursiveCteMaxRows, recursiveCteMaxExpansions) {
26360
+ const recursiveLimits = resolveRecursiveCteLimits({
26361
+ recursiveCteMaxDepth,
26362
+ recursiveCteMaxRows,
26363
+ recursiveCteMaxExpansions
26364
+ });
25650
26365
  const sharedPlan = relativeDatePlan ?? await resolveRelativeDateExecutionPlan(stmt.query, client, cacheContext);
25651
26366
  const analysis = await buildExplainWhereAnalysis(
25652
26367
  stmt.query,
@@ -25672,7 +26387,9 @@ async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxA
25672
26387
  maxRecords,
25673
26388
  analysis.plainGroupByPlans,
25674
26389
  true,
25675
- fetchCollector
26390
+ fetchCollector,
26391
+ "main",
26392
+ recursiveLimits
25676
26393
  ),
25677
26394
  cursorMaxActive
25678
26395
  )
@@ -25704,7 +26421,7 @@ function addCursorConcurrency(lines, cursorMaxActive) {
25704
26421
  }
25705
26422
  return result;
25706
26423
  }
25707
- function buildExplainPlan(query, label, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, maxRecords = 1e4, plainGroupByPlans, includeFetchSummary = true, collector, sourceRole = "main") {
26424
+ function buildExplainPlan(query, label, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, maxRecords = 1e4, plainGroupByPlans, includeFetchSummary = true, collector, sourceRole = "main", recursiveLimits = resolveRecursiveCteLimits({})) {
25708
26425
  const fetchCollector = collector ?? { sources: [] };
25709
26426
  if (query.type === "UNION") {
25710
26427
  const lines2 = buildUnionPlan(
@@ -25722,7 +26439,9 @@ function buildExplainPlan(query, label, capabilities, orderPlans, dmlMaxRows = 1
25722
26439
  capabilities,
25723
26440
  orderPlans,
25724
26441
  plainGroupByPlans,
25725
- fetchCollector
26442
+ fetchCollector,
26443
+ maxRecords,
26444
+ recursiveLimits
25726
26445
  );
25727
26446
  return includeFetchSummary ? addFetchSummary(lines2, fetchCollector.sources) : lines2;
25728
26447
  }
@@ -26319,11 +27038,30 @@ function populateWithCrossJoinExplain(stmt) {
26319
27038
  }
26320
27039
  analyzeQuery(stmt.query);
26321
27040
  }
26322
- function buildWithPlan(stmt, capabilities, orderPlans, plainGroupByPlans, collector = { sources: [] }) {
27041
+ function buildWithPlan(stmt, capabilities, orderPlans, plainGroupByPlans, collector = { sources: [] }, maxRecords = 1e4, recursiveLimits = resolveRecursiveCteLimits({})) {
26323
27042
  populateWithCrossJoinExplain(stmt);
26324
27043
  const lines = [];
26325
27044
  for (const cte of stmt.ctes) {
26326
- if (cte.query.type === "SELECT") {
27045
+ if (cte.recursiveSpec) {
27046
+ const cycle = cte.recursiveSpec.cycle;
27047
+ lines.push(
27048
+ `recursive cte: ${cte.name}`,
27049
+ " strategy: B (materialize each source once, iterate in memory)",
27050
+ " union: UNION ALL",
27051
+ " self reference: once",
27052
+ cycle ? ` cycle: path-scoped on ${cycle.column}, mark ${cycle.markColumn} ('${cycle.markValue}'/'${cycle.defaultValue}'), cycle row emitted, expansion stopped` : " cycle: none (absolute limits still enforced)",
27053
+ ` limits: depth=${recursiveLimits.depth}, rows=${recursiveLimits.rows}, expansions=${recursiveLimits.expansions} (always fail-closed)`,
27054
+ " complete input: required (onLimit=truncate disabled)",
27055
+ " empty-key recursive join: runtime checked"
27056
+ );
27057
+ for (const source of recursivePhysicalTables(cte.recursiveSpec.seed, cte.recursiveSpec.recursiveTerm)) {
27058
+ const sourceName = `APP${source.appId}${source.subtableCode ? `$${source.subtableCode}` : ""}`;
27059
+ lines.push(
27060
+ ` source ${sourceName}: R unknown, pageSize=500, estimated calls=ceil(R/500), maxRecords=${maxRecords}`
27061
+ );
27062
+ }
27063
+ lines.push(" iteration rows: unknown until execution", " records API: none", "");
27064
+ } else if (cte.query.type === "SELECT") {
26327
27065
  lines.push(...buildSelectPlan(
26328
27066
  cte.query,
26329
27067
  `[cte: ${cte.name}]`,
@@ -26389,10 +27127,12 @@ function buildWithPlan(stmt, capabilities, orderPlans, plainGroupByPlans, collec
26389
27127
  orderPlans,
26390
27128
  100,
26391
27129
  DEFAULT_APPLY_MAX_SUBTABLE_ROWS,
26392
- 1e4,
27130
+ maxRecords,
26393
27131
  plainGroupByPlans,
26394
27132
  false,
26395
- collector
27133
+ collector,
27134
+ "main",
27135
+ recursiveLimits
26396
27136
  ));
26397
27137
  }
26398
27138
  if (canInlineSingleCte(stmt)) {
@@ -28744,6 +29484,16 @@ Options:
28744
29484
  -h, --help Show help
28745
29485
  -v, --version Show version
28746
29486
  `;
29487
+ var RECURSIVE_CTE_HELP_LINES = [
29488
+ " --recursive-cte-max-depth <n> Max recursive CTE depth (default: 100)",
29489
+ " --recursive-cte-max-rows <n> Max accumulated recursive CTE rows (default: 10000)",
29490
+ " --recursive-cte-max-expansions <n> Max recursive CTE candidate expansions (default: 100000)"
29491
+ ].join("\n");
29492
+ var CLI_HELP_TEXT = HELP_TEXT.replace(
29493
+ " --max-records <n> Max records to fetch (default: 500)",
29494
+ ` --max-records <n> Max records to fetch (default: 500)
29495
+ ${RECURSIVE_CTE_HELP_LINES}`
29496
+ );
28747
29497
  var CLI_IMPORT_SOURCE_REQUIRED_MESSAGE = "IMPORT \u306B\u306F\u30BD\u30FC\u30B9\u304C\u5FC5\u8981\u3067\u3059\u3002--import-csv <name=path> \u307E\u305F\u306F --import-json <name=path> \u3067\u30D5\u30A1\u30A4\u30EB\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002";
28748
29498
  function toCliImportError(error, importEnabled) {
28749
29499
  if (importEnabled || !isImportCapabilityGateError(error)) return error;
@@ -28763,6 +29513,9 @@ function parseArgs(argv) {
28763
29513
  dryRun: false,
28764
29514
  format: null,
28765
29515
  maxRecords: null,
29516
+ recursiveCteMaxDepth: null,
29517
+ recursiveCteMaxRows: null,
29518
+ recursiveCteMaxExpansions: null,
28766
29519
  fetchParallel: null,
28767
29520
  onLimit: null,
28768
29521
  tempTableMaxRows: null,
@@ -29029,6 +29782,17 @@ function parseArgs(argv) {
29029
29782
  i++;
29030
29783
  continue;
29031
29784
  }
29785
+ if (a === "--recursive-cte-max-depth" || a === "--recursive-cte-max-rows" || a === "--recursive-cte-max-expansions") {
29786
+ const n = Number(v);
29787
+ if (!Number.isSafeInteger(n) || n <= 0) {
29788
+ throw new Error(`ArgumentError: ${a} must be a positive safe integer.`);
29789
+ }
29790
+ if (a === "--recursive-cte-max-depth") out.recursiveCteMaxDepth = n;
29791
+ else if (a === "--recursive-cte-max-rows") out.recursiveCteMaxRows = n;
29792
+ else out.recursiveCteMaxExpansions = n;
29793
+ i++;
29794
+ continue;
29795
+ }
29032
29796
  if (a === "--temp-table-max-rows") {
29033
29797
  const n = Number(v);
29034
29798
  if (!Number.isInteger(n) || n <= 0) throw new Error("ArgumentError: --temp-table-max-rows must be a positive integer.");
@@ -29657,6 +30421,9 @@ function buildReplExecArgv(base, sql, dryRun, format) {
29657
30421
  pushOpt(argv, "--token-file", base.tokenFile);
29658
30422
  pushOpt(argv, "--app", base.app);
29659
30423
  pushOpt(argv, "--max-records", base.maxRecords);
30424
+ pushOpt(argv, "--recursive-cte-max-depth", base.recursiveCteMaxDepth);
30425
+ pushOpt(argv, "--recursive-cte-max-rows", base.recursiveCteMaxRows);
30426
+ pushOpt(argv, "--recursive-cte-max-expansions", base.recursiveCteMaxExpansions);
29660
30427
  pushOpt(argv, "--fetch-parallel", base.fetchParallel);
29661
30428
  pushOpt(argv, "--on-limit", base.onLimit);
29662
30429
  pushOpt(argv, "--temp-table-max-rows", base.tempTableMaxRows);
@@ -30137,7 +30904,7 @@ async function run() {
30137
30904
  return 2;
30138
30905
  }
30139
30906
  if (args.help) {
30140
- process.stdout.write(`${HELP_TEXT}
30907
+ process.stdout.write(`${CLI_HELP_TEXT}
30141
30908
  `);
30142
30909
  return 0;
30143
30910
  }
@@ -30255,6 +31022,20 @@ async function run() {
30255
31022
  }
30256
31023
  }
30257
31024
  const maxRecords = args.maxRecords ?? envInt2("KSQL_MAX_RECORDS") ?? profile.query?.maxRecords ?? 500;
31025
+ const recursiveCteMaxDepth = args.recursiveCteMaxDepth ?? envInt2("KSQL_RECURSIVE_CTE_MAX_DEPTH") ?? profile.query?.recursiveCteMaxDepth ?? 100;
31026
+ const recursiveCteMaxRows = args.recursiveCteMaxRows ?? envInt2("KSQL_RECURSIVE_CTE_MAX_ROWS") ?? profile.query?.recursiveCteMaxRows ?? 1e4;
31027
+ const recursiveCteMaxExpansions = args.recursiveCteMaxExpansions ?? envInt2("KSQL_RECURSIVE_CTE_MAX_EXPANSIONS") ?? profile.query?.recursiveCteMaxExpansions ?? 1e5;
31028
+ for (const [name, value] of [
31029
+ ["recursiveCteMaxDepth", recursiveCteMaxDepth],
31030
+ ["recursiveCteMaxRows", recursiveCteMaxRows],
31031
+ ["recursiveCteMaxExpansions", recursiveCteMaxExpansions]
31032
+ ]) {
31033
+ if (!Number.isSafeInteger(value) || value <= 0) {
31034
+ process.stderr.write(`ArgumentError: ${name} must be a positive safe integer.
31035
+ `);
31036
+ return 2;
31037
+ }
31038
+ }
30258
31039
  const fetchParallel = args.fetchParallel ?? envInt2("KSQL_FETCH_PARALLEL") ?? profile.query?.fetchParallel ?? 3;
30259
31040
  const onLimit = args.onLimit ?? envOnLimit2("KSQL_ON_LIMIT") ?? profile.query?.onLimit ?? "error";
30260
31041
  const timeout = args.timeout ?? envInt2("KSQL_TIMEOUT") ?? profile.query?.timeout ?? 3e4;
@@ -30611,7 +31392,10 @@ async function run() {
30611
31392
  Object.keys(args.importCsv).length > 0 || Object.keys(args.importJson).length > 0,
30612
31393
  dmlMaxRows,
30613
31394
  dmlMaxSubtableRows,
30614
- !dryRunUsesStaticTypedPlan
31395
+ !dryRunUsesStaticTypedPlan,
31396
+ recursiveCteMaxDepth,
31397
+ recursiveCteMaxRows,
31398
+ recursiveCteMaxExpansions
30615
31399
  );
30616
31400
  const out = [];
30617
31401
  const restoredStatements = sqlDiagnosticContext ? restoreSqlDiagnosticValue(plans.statements, sqlDiagnosticContext.appBindingByMappedApp) : plans.statements;
@@ -30701,6 +31485,9 @@ query=${label}`);
30701
31485
  tempTableMaxRows,
30702
31486
  timeoutMs: timeout,
30703
31487
  cursorMaxActive,
31488
+ recursiveCteMaxDepth,
31489
+ recursiveCteMaxRows,
31490
+ recursiveCteMaxExpansions,
30704
31491
  variables: args.variables,
30705
31492
  enableImport: importEnabled,
30706
31493
  importSource,
@@ -30752,7 +31539,10 @@ query=${label}`);
30752
31539
  enableImport: importEnabled,
30753
31540
  importSource,
30754
31541
  dmlMaxRows,
30755
- dmlMaxSubtableRows
31542
+ dmlMaxSubtableRows,
31543
+ recursiveCteMaxDepth,
31544
+ recursiveCteMaxRows,
31545
+ recursiveCteMaxExpansions
30756
31546
  }) : await execute(sql, client, {
30757
31547
  maxRecords,
30758
31548
  fetchParallel,
@@ -30760,6 +31550,9 @@ query=${label}`);
30760
31550
  confirm: isDmlStatement ? confirm : void 0,
30761
31551
  cacheContext,
30762
31552
  cursorMaxActive,
31553
+ recursiveCteMaxDepth,
31554
+ recursiveCteMaxRows,
31555
+ recursiveCteMaxExpansions,
30763
31556
  enableImport: importEnabled,
30764
31557
  importSource,
30765
31558
  supportsImportConfirmDetail: true,
@@ -30843,6 +31636,7 @@ if (isDirectCliRun()) {
30843
31636
  }
30844
31637
  // Annotate the CommonJS export names for ESM import in node:
30845
31638
  0 && (module.exports = {
31639
+ CLI_HELP_TEXT,
30846
31640
  CLI_IMPORT_SOURCE_REQUIRED_MESSAGE,
30847
31641
  HELP_TEXT,
30848
31642
  buildBatchDmlConfirmMessage,