@rex0220/kintone-sql-tools 3.63.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
@@ -24597,6 +24597,10 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
24597
24597
  for (const cte of withStatement.ctes) {
24598
24598
  await preflightExplainRelations(cte.query);
24599
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
+ }
24600
24604
  const generated = executeGenerateSeries(cte.query);
24601
24605
  explainRelations.set(cte.name, {
24602
24606
  rows: generated.rows,
@@ -24637,13 +24641,19 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
24637
24641
  cacheContext,
24638
24642
  explainRelations
24639
24643
  );
24640
- const plainPlan = await buildRuntimePlainGroupByPlan(
24641
- select,
24642
- tracedClient,
24643
- cacheContext,
24644
- 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)
24645
24647
  );
24646
- 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
+ }
24647
24657
  return;
24648
24658
  }
24649
24659
  for (const child of Object.values(typed)) await preflightExplainRelations(child);
@@ -25172,6 +25182,106 @@ function serverFunctionClientEvaluationLabel(leaves) {
25172
25182
  (leaf) => leaf.right.type === "KINTONE_FUNC" && isRelativeDateFunctionName(leaf.right.name)
25173
25183
  ) ? "relative date client evaluations" : "kintone function client evaluations";
25174
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
+ }
25175
25285
  var EXPLAIN_FETCH_PLAN = /* @__PURE__ */ Symbol("ksql.explainFetchPlan");
25176
25286
  function setExplainFetchPlan(result, plan) {
25177
25287
  result[EXPLAIN_FETCH_PLAN] = plan;
@@ -25184,19 +25294,27 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
25184
25294
  const normalizedInjectedVariables = validateDeclaredBatchVariables(statements, injectedVariables);
25185
25295
  const relativeDateVariables = prepareRelativeDateVariables(statements, normalizedInjectedVariables);
25186
25296
  const variables = /* @__PURE__ */ new Map();
25297
+ const literalDeclareDefaults = /* @__PURE__ */ new Map();
25298
+ const tempSchemaLedger = /* @__PURE__ */ new Map();
25187
25299
  const plans = [];
25188
25300
  const fetchStatements = [];
25189
25301
  for (let i = 0; i < statements.length; i++) {
25190
25302
  const stmt = statements[i];
25191
- 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
+ );
25192
25308
  validateStatementStatic(planStmt);
25193
25309
  const relativeDatePlan = await resolveRelativeDateExecutionPlan(planStmt, client, invocationCacheContext);
25310
+ const initialRelations = staticSchemaRelations(tempSchemaLedger);
25194
25311
  const whereAnalysis = resolveMetadata ? await buildExplainWhereAnalysis(
25195
25312
  planStmt,
25196
25313
  client,
25197
25314
  invocationCacheContext,
25198
25315
  maxRecords,
25199
- relativeDatePlan
25316
+ relativeDatePlan,
25317
+ initialRelations
25200
25318
  ) : {
25201
25319
  capabilities: /* @__PURE__ */ new Map(),
25202
25320
  orderPlans: /* @__PURE__ */ new Map(),
@@ -25206,6 +25324,34 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
25206
25324
  numberPrecisionApps: /* @__PURE__ */ new Set(),
25207
25325
  relativeDatePlan
25208
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
+ }
25209
25355
  const fetchCollector = { sources: [] };
25210
25356
  const statementPlan = relativeDatePlan.hasServerOnlyWhereFunction && !relativeDatePlan.allowed ? relativeDateExplainLines(relativeDatePlan) : [
25211
25357
  ...relativeDateExplainLines(relativeDatePlan),
@@ -25214,9 +25360,12 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
25214
25360
  analysis.statements[i],
25215
25361
  whereAnalysis.capabilities,
25216
25362
  whereAnalysis.orderPlans,
25363
+ whereAnalysis.plainGroupByPlans,
25217
25364
  dmlMaxRows,
25218
25365
  dmlMaxSubtableRows,
25219
- fetchCollector
25366
+ fetchCollector,
25367
+ tempSchemaLedger,
25368
+ createdSchema
25220
25369
  ), cursorMaxActive)
25221
25370
  ];
25222
25371
  const metadataPlan = explainMetadataLines(whereAnalysis);
@@ -25230,8 +25379,15 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
25230
25379
  fetch: worstExplainFetch(fetchCollector.sources),
25231
25380
  sources: fetchCollector.sources
25232
25381
  });
25382
+ if (planStmt.type === "DROP_TEMP_TABLE") tempSchemaLedger.delete(planStmt.name);
25233
25383
  if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
25234
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
+ }
25235
25391
  }
25236
25392
  }
25237
25393
  const result = { statementCount: statements.length, statements: plans };
@@ -25241,19 +25397,28 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
25241
25397
  releaseMetadataCacheScope(invocationCacheContext);
25242
25398
  }
25243
25399
  }
25244
- 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) {
25245
25401
  if (stmt.type === "CREATE_TEMP_TABLE") {
25246
25402
  return [
25247
25403
  `CREATE TEMP TABLE ${stmt.name}`,
25248
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
+ ] : [],
25249
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`,
25250
25413
  ...buildPlanForBatchQuery(
25251
25414
  stmt.query,
25252
25415
  info,
25253
25416
  capabilities,
25254
25417
  orderPlans,
25418
+ plainGroupByPlans,
25255
25419
  collector,
25256
- "main"
25420
+ "main",
25421
+ tempSchemaLedger
25257
25422
  ).map((l) => ` ${l}`)
25258
25423
  ];
25259
25424
  }
@@ -25275,7 +25440,10 @@ function buildBatchStatementPlan(stmt, info, capabilities, orderPlans, dmlMaxRow
25275
25440
  subInfo,
25276
25441
  capabilities,
25277
25442
  orderPlans,
25278
- collector
25443
+ plainGroupByPlans,
25444
+ collector,
25445
+ "main",
25446
+ tempSchemaLedger
25279
25447
  ).map((l) => ` ${l}`)
25280
25448
  ];
25281
25449
  }
@@ -25304,7 +25472,10 @@ function buildBatchStatementPlan(stmt, info, capabilities, orderPlans, dmlMaxRow
25304
25472
  info,
25305
25473
  capabilities,
25306
25474
  orderPlans,
25307
- collector
25475
+ plainGroupByPlans,
25476
+ collector,
25477
+ "main",
25478
+ tempSchemaLedger
25308
25479
  );
25309
25480
  }
25310
25481
  if (stmt.type === "ASSERT") {
@@ -25323,15 +25494,36 @@ function buildBatchStatementPlan(stmt, info, capabilities, orderPlans, dmlMaxRow
25323
25494
  subInfo,
25324
25495
  capabilities,
25325
25496
  orderPlans,
25326
- collector
25497
+ plainGroupByPlans,
25498
+ collector,
25499
+ "main",
25500
+ tempSchemaLedger
25327
25501
  ).map((l) => ` ${l}`));
25328
25502
  });
25329
25503
  return lines;
25330
25504
  }
25331
25505
  if (stmt.type === "UPDATE" && (stmt.applyBlocks?.length ?? 0) > 0) {
25332
- 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
+ );
25333
25516
  }
25334
- 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
+ );
25335
25527
  }
25336
25528
  function hasTempTableRef(node) {
25337
25529
  if (Array.isArray(node)) return node.some(hasTempTableRef);
@@ -25343,7 +25535,7 @@ function hasTempTableRef(node) {
25343
25535
  }
25344
25536
  return false;
25345
25537
  }
25346
- 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()) {
25347
25539
  if (info.tempTablesReferenced.length === 0) {
25348
25540
  return buildExplainPlan(
25349
25541
  query,
@@ -25353,7 +25545,7 @@ function buildPlanForBatchQuery(query, info, capabilities, orderPlans, collector
25353
25545
  100,
25354
25546
  DEFAULT_APPLY_MAX_SUBTABLE_ROWS,
25355
25547
  1e4,
25356
- void 0,
25548
+ plainGroupByPlans,
25357
25549
  true,
25358
25550
  collector,
25359
25551
  sourceRole
@@ -25373,6 +25565,26 @@ function buildPlanForBatchQuery(query, info, capabilities, orderPlans, collector
25373
25565
  lines.push(
25374
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`
25375
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");
25376
25588
  const apps = info.appIds.filter(
25377
25589
  (a) => query.type !== "INSERT_SELECT" && query.type !== "UPSERT_SELECT" || a !== query.appId
25378
25590
  );
@@ -25642,6 +25854,33 @@ function formatChoiceEqualityRewrite(rewrite) {
25642
25854
  const normalizedOperator = rewrite.normalizedOperator === "IN" ? "in" : "not in";
25643
25855
  return ` pushdown normalized: ${field} ${rewrite.originalOperator} '${originalValue}' -> ${field} ${normalizedOperator} ("${normalizedValue}")`;
25644
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
+ }
25645
25884
  function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlans, allowTotalCountPlan = true, emitFetch = true, collector = { sources: [] }, sourceRole = "main") {
25646
25885
  const whereCapability = capabilities?.get(stmt) ?? (capabilities ? [...capabilities].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
25647
25886
  const orderPlan = orderPlans?.get(stmt) ?? (orderPlans ? [...orderPlans].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
@@ -25691,35 +25930,11 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlan
25691
25930
  ` grouping output rows: runtime checked (limit: ${groupingMetadata.outputRowLimit}, before HAVING/DISTINCT/LIMIT)`
25692
25931
  );
25693
25932
  }
25694
- const normalizedGrouping = normalizeGroupingSpec(stmt);
25695
- if (normalizedGrouping.type === "PLAIN") {
25696
- const groupBy = normalizedGrouping.allItems;
25697
- if (plainGroupByPlan) {
25698
- plainGroupByPlan.items.forEach((item, index) => {
25699
- const key = groupBy[index];
25700
- if (key?.type !== "FIELD_NAME") return;
25701
- if (item.kind === "PHYSICAL") {
25702
- lines.push(
25703
- ` group key ${key.name}: PHYSICAL (source=${item.sourceIndex}, field=${item.fieldCode})`
25704
- );
25705
- } else if (item.kind === "ALIAS_SAFE") {
25706
- lines.push(
25707
- ` group key ${key.name}: ALIAS_SAFE (column=${item.columnIndex})`
25708
- );
25709
- } else if (item.kind === "EXPRESSION") {
25710
- lines.push(` group key ${key.name}: EXPRESSION`);
25711
- }
25712
- });
25713
- } else if ([stmt.from, ...stmt.joins.map((join2) => join2.table)].some((table) => table.cteName !== null)) {
25714
- for (const key of groupBy) {
25715
- if (key.type === "FIELD_NAME") {
25716
- lines.push(
25717
- ` group key ${key.name}: DEFERRED (materialized schema unavailable)`
25718
- );
25719
- }
25720
- }
25721
- }
25722
- }
25933
+ lines.push(...renderPlainGroupByExplainLines(
25934
+ stmt,
25935
+ plainGroupByPlan,
25936
+ [stmt.from, ...stmt.joins.map((join2) => join2.table)].some((table) => table.cteName !== null)
25937
+ ));
25723
25938
  for (const column of stmt.columns) {
25724
25939
  if (column.type !== "WINDOW_COL" || column.windowKind === void 0 || column.windowKind === "RANKING") continue;
25725
25940
  const clauses = [];
@@ -26041,7 +26256,9 @@ function populateWithCrossJoinExplain(stmt) {
26041
26256
  };
26042
26257
  for (const cte of stmt.ctes) {
26043
26258
  if (cte.query.type === "GENERATE_SERIES") {
26044
- exactRows.set(cte.name, resolveGenerateSeries(cte.query).rowCount);
26259
+ if (!cte.query.args.some((arg) => arg.type === "VARIABLE")) {
26260
+ exactRows.set(cte.name, resolveGenerateSeries(cte.query).rowCount);
26261
+ }
26045
26262
  } else if (cte.query.type === "SELECT") {
26046
26263
  const rows = analyze(cte.query);
26047
26264
  if (rows !== null) exactRows.set(cte.name, rows);
@@ -26069,18 +26286,45 @@ function buildWithPlan(stmt, capabilities, orderPlans, plainGroupByPlans, collec
26069
26286
  ));
26070
26287
  lines.push("");
26071
26288
  } else if (cte.query.type === "GENERATE_SERIES") {
26072
- const series = resolveGenerateSeries(cte.query);
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);
26073
26316
  const step = series.kind === "DATE" ? `${series.step} ${String(series.dateUnit ?? "DAY").toLowerCase()}${Math.abs(series.step) === 1 ? "" : "s"}` : String(series.step);
26074
26317
  lines.push(
26075
26318
  `[cte: ${cte.name}]`,
26076
26319
  " source: GENERATE_SERIES",
26077
- ` column: ${cte.query.columnAlias}`,
26078
- ` series type: ${series.kind}`,
26079
- ` start: ${series.start}`,
26080
- ` 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}`,
26081
26324
  ` step: ${step}`,
26082
- ` rows: ${series.rowCount}`,
26325
+ ` rows: ${series.rowCount}${binding?.defaultBoundIndexes.size ? " (DECLARE default estimate)" : ""}`,
26083
26326
  ` row guard: ${series.rowCount} / ${GENERATE_SERIES_MAX_ROWS}`,
26327
+ ...binding?.defaultBoundIndexes.size ? [" binding: DECLARE defaults; runtime injection may change this plan"] : [],
26084
26328
  " records API: none",
26085
26329
  ""
26086
26330
  );
@@ -29204,6 +29448,9 @@ function createDryRunClient() {
29204
29448
  function hasStaticTypedPushdownCandidate(statement) {
29205
29449
  if (statement === null || typeof statement !== "object") return false;
29206
29450
  const node = statement;
29451
+ if (node["type"] === "CREATE_TEMP_TABLE") {
29452
+ return hasStaticTypedPushdownCandidate(node["query"]);
29453
+ }
29207
29454
  if (node["type"] === "WITH") {
29208
29455
  const query = node["query"];
29209
29456
  const containsCross = (value) => {
@@ -29923,7 +30170,10 @@ async function run() {
29923
30170
  return false;
29924
30171
  });
29925
30172
  dryRunNeedsMetadata = statements.some(explainNeedsAppMetadata);
29926
- 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);
29927
30177
  if (statements.length > 1) {
29928
30178
  batchAnalysis = analyzeBatch(statements);
29929
30179
  isBatchSql = true;