@rex0220/kintone-sql-tools 3.22.0 → 3.24.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
@@ -5186,8 +5186,8 @@ function selectToFetchAllParams(stmt, appId) {
5186
5186
  // 全件取得なので全フィールドを取得する
5187
5187
  };
5188
5188
  }
5189
- function selectToFetchAllFields(stmt, targetTable) {
5190
- const plan = collectRequiredFieldsByTable(stmt);
5189
+ function selectToFetchAllFields(stmt, targetTable, plainGroupByPlan) {
5190
+ const plan = collectRequiredFieldsByTable(stmt, plainGroupByPlan);
5191
5191
  const target = plan.get(targetTable);
5192
5192
  if (!target) return [];
5193
5193
  if (target.allFields) return [];
@@ -5312,7 +5312,8 @@ function caseResultHasAggregate(result) {
5312
5312
  if (result.type === "ARRAY" || result.type === "FIELD_REF" || result.type === "ARITH") return false;
5313
5313
  return scalarValueHasAggregate(result);
5314
5314
  }
5315
- function collectRequiredFieldsByTable(stmt) {
5315
+ function collectRequiredFieldsByTable(stmt, plainGroupByPlan) {
5316
+ const allTables = [stmt.from, ...stmt.joins.map((j) => j.table)];
5316
5317
  const physicalTables = [stmt.from, ...stmt.joins.map((j) => j.table)].filter((t) => t.cteName === null);
5317
5318
  const states = /* @__PURE__ */ new Map();
5318
5319
  for (const table of physicalTables) {
@@ -5361,7 +5362,7 @@ function collectRequiredFieldsByTable(stmt) {
5361
5362
  if (!fieldName) return;
5362
5363
  st.fields.add(fieldName);
5363
5364
  };
5364
- const addFieldName = (rawName, phase = "select") => {
5365
+ const addFieldName = (rawName, phase = "select", groupResolution) => {
5365
5366
  if (!rawName || rawName === "*") return;
5366
5367
  if (rawName === "_p.*") {
5367
5368
  markAllSubtableTables();
@@ -5384,6 +5385,15 @@ function collectRequiredFieldsByTable(stmt) {
5384
5385
  }
5385
5386
  return;
5386
5387
  }
5388
+ if (phase === "groupBy" && groupResolution !== void 0) {
5389
+ if (groupResolution.kind === "PHYSICAL") {
5390
+ const source = allTables[groupResolution.sourceIndex];
5391
+ if (source?.cteName === null) {
5392
+ addFieldToTable(source, groupResolution.fieldCode);
5393
+ }
5394
+ }
5395
+ return;
5396
+ }
5387
5397
  if ((phase === "orderBy" || phase === "having" || phase === "groupBy") && selectAliases.has(rawName)) {
5388
5398
  return;
5389
5399
  }
@@ -5544,9 +5554,9 @@ function collectRequiredFieldsByTable(stmt) {
5544
5554
  return;
5545
5555
  }
5546
5556
  };
5547
- const walkGroupByKey = (k) => {
5557
+ const walkGroupByKey = (k, resolution) => {
5548
5558
  if (k.type === "FIELD_NAME") {
5549
- addFieldName(k.name, "groupBy");
5559
+ addFieldName(k.name, "groupBy", resolution);
5550
5560
  return;
5551
5561
  }
5552
5562
  if (k.type === "ARITH_KEY") {
@@ -5619,7 +5629,9 @@ function collectRequiredFieldsByTable(stmt) {
5619
5629
  walkWhere(stmt.where, "where");
5620
5630
  const grouping = normalizeGroupingSpec(stmt);
5621
5631
  if (grouping.type === "PLAIN") {
5622
- for (const item of grouping.allItems) walkGroupByKey(item);
5632
+ grouping.allItems.forEach(
5633
+ (item, index) => walkGroupByKey(item, plainGroupByPlan?.items[index])
5634
+ );
5623
5635
  } else if (grouping.type === "GROUPING_SETS") {
5624
5636
  for (const item of grouping.allItems) {
5625
5637
  addFieldRef(item.field, item.tableAlias, "select");
@@ -6853,8 +6865,8 @@ function parseOptionValues(value, fieldType) {
6853
6865
  }
6854
6866
  function optionVector(value, semantics) {
6855
6867
  const order = semantics.optionOrder ?? /* @__PURE__ */ new Map();
6856
- const unique = [...new Set(parseOptionValues(value, semantics.fieldType))];
6857
- const vector = unique.map((label) => {
6868
+ const unique2 = [...new Set(parseOptionValues(value, semantics.fieldType))];
6869
+ const vector = unique2.map((label) => {
6858
6870
  const rank = order.get(label);
6859
6871
  return rank === void 0 ? { knownBand: 1, rank: 0, label } : { knownBand: 0, rank, label };
6860
6872
  });
@@ -10802,10 +10814,10 @@ function planKorder(input) {
10802
10814
  if (stmt.limit !== null && !Number.isSafeInteger(scanRows)) {
10803
10815
  reasons.push(`KORDER_SCAN_ROWS_INVALID(offset=${offset}, limit=${stmt.limit})`);
10804
10816
  }
10805
- const unique = [...new Set(reasons)];
10806
- if (unique.length > 0) {
10817
+ const unique2 = [...new Set(reasons)];
10818
+ if (unique2.length > 0) {
10807
10819
  throw new Error(
10808
- `ArgumentError: KORDER BY cannot be executed (mode=KINTONE_NATIVE; ${unique.join(", ")}). Use ORDER BY for canonical local ordering or simplify the query.`
10820
+ `ArgumentError: KORDER BY cannot be executed (mode=KINTONE_NATIVE; ${unique2.join(", ")}). Use ORDER BY for canonical local ordering or simplify the query.`
10809
10821
  );
10810
10822
  }
10811
10823
  const native = stmt.limit <= 500 && offset <= 1e4 && stmt.limit <= input.maxRecords;
@@ -10905,6 +10917,148 @@ function buildKorderCursorQuery(stmt) {
10905
10917
  return parts.join(" ");
10906
10918
  }
10907
10919
 
10920
+ // src/core/systemFields.ts
10921
+ var APP_SYSTEM_FIELD_CODES = [
10922
+ "$id",
10923
+ "$revision",
10924
+ "\u30EC\u30B3\u30FC\u30C9\u756A\u53F7",
10925
+ "\u4F5C\u6210\u8005",
10926
+ "\u4F5C\u6210\u65E5\u6642",
10927
+ "\u66F4\u65B0\u8005",
10928
+ "\u66F4\u65B0\u65E5\u6642",
10929
+ "\u30B9\u30C6\u30FC\u30BF\u30B9",
10930
+ "\u4F5C\u696D\u8005"
10931
+ ];
10932
+ function isSystemLikeFieldCode(code) {
10933
+ return code.startsWith("_") || code.startsWith("$");
10934
+ }
10935
+
10936
+ // src/core/optimization/plainGroupByPlan.ts
10937
+ var AGGREGATE_REFERENCE_PREFIX = /^(COUNT|SUM|AVG|MAX|MIN|GROUP_CONCAT|STDDEV_POP|STDDEV_SAMP|VAR_POP|VAR_SAMP|MEDIAN|MODE)\(/;
10938
+ function containsAggregateColumnNode(node) {
10939
+ if (node === null || typeof node !== "object") return false;
10940
+ if (Array.isArray(node)) return node.some(containsAggregateColumnNode);
10941
+ const value = node;
10942
+ if (value["type"] === "AGGREGATE" || value["type"] === "ARITH_AGG_COL") return true;
10943
+ if (value["type"] === "SELECT" || value["type"] === "SCALAR_SUBQUERY") return false;
10944
+ if (value["type"] === "FIELD" && typeof value["field"] === "string") {
10945
+ return AGGREGATE_REFERENCE_PREFIX.test(value["field"]);
10946
+ }
10947
+ return Object.values(value).some(containsAggregateColumnNode);
10948
+ }
10949
+ function classifyPreGroupAlias(column) {
10950
+ switch (column.type) {
10951
+ case "AGGREGATE":
10952
+ case "ARITH_AGG_COL":
10953
+ return "AGGREGATE_DEPENDENT";
10954
+ case "GROUPING_COL":
10955
+ case "WINDOW_COL":
10956
+ return "POST_GROUP_ONLY";
10957
+ case "VARIABLE_COL":
10958
+ throw new Error("InternalError: unresolved VARIABLE_COL reached GROUP BY alias planning.");
10959
+ case "WILDCARD":
10960
+ case "PARENT_WILDCARD":
10961
+ case "FIELD":
10962
+ case "LITERAL_COL":
10963
+ case "ARITH_COL":
10964
+ case "CASE_COL":
10965
+ case "STRFUNC_COL":
10966
+ case "SCALAR_VALUE_COL":
10967
+ case "SCALAR_SUBQUERY_COL":
10968
+ return containsAggregate(column) || containsAggregateColumnNode(column) ? "AGGREGATE_DEPENDENT" : "SAFE";
10969
+ }
10970
+ }
10971
+ var SUBTABLE_SYSTEM_COLUMNS = ["_pid", "_rid", "_idx"];
10972
+ function unique(values) {
10973
+ return [...new Set(values)];
10974
+ }
10975
+ function sourceColumns(input) {
10976
+ switch (input.kind) {
10977
+ case "APP":
10978
+ return unique([
10979
+ ...input.fieldCodes,
10980
+ ...APP_SYSTEM_FIELD_CODES
10981
+ ]);
10982
+ case "SUBTABLE":
10983
+ return unique([
10984
+ ...input.childFieldCodes,
10985
+ ...SUBTABLE_SYSTEM_COLUMNS,
10986
+ ...input.parentFieldCodes.map((field) => `_p.${field}`)
10987
+ ]);
10988
+ case "MATERIALIZED":
10989
+ return unique(input.columns);
10990
+ }
10991
+ }
10992
+ function resolvePlainGroupBySourceSchemas(stmt, lookup) {
10993
+ const sources = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
10994
+ return sources.map((source, sourceIndex) => ({
10995
+ sourceIndex,
10996
+ qualifier: source.alias ?? source.cteName,
10997
+ columns: sourceColumns(lookup(source, sourceIndex))
10998
+ }));
10999
+ }
11000
+ function parseGroupName(name) {
11001
+ if (name.startsWith("_p.")) return { qualifier: null, fieldCode: name };
11002
+ const dot = name.indexOf(".");
11003
+ if (dot <= 0 || dot === name.length - 1) {
11004
+ return { qualifier: null, fieldCode: name };
11005
+ }
11006
+ return { qualifier: name.slice(0, dot), fieldCode: name.slice(dot + 1) };
11007
+ }
11008
+ function runtimeKey(source, fieldCode) {
11009
+ return source.qualifier === null ? fieldCode : `${source.qualifier}.${fieldCode}`;
11010
+ }
11011
+ function explicitAlias(column) {
11012
+ return "alias" in column && typeof column.alias === "string" ? column.alias : null;
11013
+ }
11014
+ function resolveFieldName(name, columns, schemas) {
11015
+ const parsed = parseGroupName(name);
11016
+ const candidateSources = parsed.qualifier === null ? schemas : schemas.filter((source) => source.qualifier === parsed.qualifier);
11017
+ const physical = candidateSources.filter((source) => source.columns.includes(parsed.fieldCode));
11018
+ if (physical.length > 1) {
11019
+ throw new Error(
11020
+ `ArgumentError: GROUP BY field ${name} is ambiguous across multiple sources (reason=GROUP_BY_FIELD_AMBIGUOUS).`
11021
+ );
11022
+ }
11023
+ if (physical.length === 1) {
11024
+ const source = physical[0];
11025
+ return {
11026
+ kind: "PHYSICAL",
11027
+ sourceIndex: source.sourceIndex,
11028
+ fieldCode: parsed.fieldCode,
11029
+ runtimeKey: runtimeKey(source, parsed.fieldCode)
11030
+ };
11031
+ }
11032
+ if (parsed.qualifier !== null) return { kind: "UNKNOWN", name };
11033
+ const aliases = columns.flatMap(
11034
+ (column, columnIndex) => explicitAlias(column) === name ? [{ column, columnIndex }] : []
11035
+ );
11036
+ if (aliases.length > 1) return { kind: "ALIAS_REJECT", reason: "DUPLICATE" };
11037
+ if (aliases.length === 1) {
11038
+ const candidate = aliases[0];
11039
+ const classification = classifyPreGroupAlias(candidate.column);
11040
+ if (classification === "SAFE") {
11041
+ return { kind: "ALIAS_SAFE", columnIndex: candidate.columnIndex };
11042
+ }
11043
+ return {
11044
+ kind: "ALIAS_REJECT",
11045
+ reason: classification === "AGGREGATE_DEPENDENT" ? "AGGREGATE" : "POST_GROUP_ONLY"
11046
+ };
11047
+ }
11048
+ const aggregateSyntheticMatch = columns.some(
11049
+ (column) => column.type === "AGGREGATE" && column.alias === null && aggregateSyntheticName(column.func, column.distinct, column.arg) === name
11050
+ );
11051
+ if (aggregateSyntheticMatch) return { kind: "ALIAS_REJECT", reason: "AGGREGATE" };
11052
+ return { kind: "UNKNOWN", name };
11053
+ }
11054
+ function planPlainGroupByResolution(groupBy, columns, schemas) {
11055
+ return {
11056
+ items: groupBy.map(
11057
+ (key) => key.type === "FIELD_NAME" ? resolveFieldName(key.name, columns, schemas) : { kind: "EXPRESSION" }
11058
+ )
11059
+ };
11060
+ }
11061
+
10908
11062
  // src/engine/process.ts
10909
11063
  function flatten(record, alias) {
10910
11064
  const row = {};
@@ -10986,10 +11140,21 @@ function hasAggregateColumns(columns) {
10986
11140
  (c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL" || c.type === "STRFUNC_COL" && hasAggregateInStringFuncExpr2(c.expr) || c.type === "SCALAR_VALUE_COL" && scalarValueHasAggregate2(c.expr)
10987
11141
  );
10988
11142
  }
10989
- function applyGroupBy(rows, groupByKeys, columns, resolveAggSortKind) {
11143
+ function applyGroupBy(rows, groupByKeys, columns, resolveAggSortKind, resolutionPlan, aliasEvaluationContext = {}) {
11144
+ if (resolutionPlan && resolutionPlan.items.length !== groupByKeys.length) {
11145
+ throw new Error("InternalError: plain GROUP BY resolution plan length does not match group keys.");
11146
+ }
10990
11147
  const groups = /* @__PURE__ */ new Map();
10991
11148
  for (const row of rows) {
10992
- const key = groupByKeys.map((k) => evalGroupByKey(k, row)).join("\0");
11149
+ const key = groupByKeys.map(
11150
+ (groupKey, index) => evalGroupByKey(
11151
+ groupKey,
11152
+ row,
11153
+ resolutionPlan?.items[index],
11154
+ columns,
11155
+ aliasEvaluationContext
11156
+ )
11157
+ ).join("\0");
10993
11158
  const bucket = groups.get(key);
10994
11159
  if (bucket) bucket.push(row);
10995
11160
  else groups.set(key, [row]);
@@ -11097,8 +11262,32 @@ function materializeAggregateColumns(outRow, groupRows, columns, resolveAggSortK
11097
11262
  }
11098
11263
  }
11099
11264
  }
11100
- function evalGroupByKey(key, row) {
11101
- if (key.type === "FIELD_NAME") return row[key.name] ?? "";
11265
+ function evalGroupByKey(key, row, resolution, columns, aliasEvaluationContext) {
11266
+ if (key.type === "FIELD_NAME") {
11267
+ if (!resolution) return row[key.name] ?? "";
11268
+ if (resolution.kind === "PHYSICAL") return row[resolution.runtimeKey] ?? "";
11269
+ if (resolution.kind === "ALIAS_SAFE") {
11270
+ const column = columns[resolution.columnIndex];
11271
+ if (!column) {
11272
+ throw new Error(
11273
+ `InternalError: GROUP BY alias column index ${resolution.columnIndex} is out of range.`
11274
+ );
11275
+ }
11276
+ const value = evaluateSelectColumnValue(
11277
+ column,
11278
+ row,
11279
+ resolution.columnIndex,
11280
+ aliasEvaluationContext
11281
+ );
11282
+ if (typeof value !== "string") {
11283
+ throw new Error("InternalError: GROUP BY alias resolved to an expanded SELECT column.");
11284
+ }
11285
+ return value;
11286
+ }
11287
+ throw new Error(
11288
+ `InternalError: unresolved plain GROUP BY item ${resolution.kind} reached evaluation.`
11289
+ );
11290
+ }
11102
11291
  if (key.type === "FUNC_KEY") return evalStringFunc(key.expr, row);
11103
11292
  return String(evalArithExpr(key.expr, row));
11104
11293
  }
@@ -11536,7 +11725,7 @@ function buildDistinctTuple(columns, row, context = {}) {
11536
11725
  return typeof value === "string" ? value : value.entries.map(([, entryValue]) => entryValue);
11537
11726
  });
11538
11727
  }
11539
- function project(rows, columns, scalarCache, resolveFieldType, sourceColumns, resolveFieldSemantics2, hiddenQualifiedAliases) {
11728
+ function project(rows, columns, scalarCache, resolveFieldType, sourceColumns2, resolveFieldSemantics2, hiddenQualifiedAliases) {
11540
11729
  if (columns.length === 1 && columns[0].type === "WILDCARD") {
11541
11730
  const projected2 = rows.map((row) => {
11542
11731
  const visible = stripHiddenQualifiedColumns(
@@ -11554,7 +11743,7 @@ function project(rows, columns, scalarCache, resolveFieldType, sourceColumns, re
11554
11743
  }
11555
11744
  return out;
11556
11745
  });
11557
- const cols = projected2.length > 0 ? Object.keys(projected2[0]) : [...sourceColumns ?? []];
11746
+ const cols = projected2.length > 0 ? Object.keys(projected2[0]) : [...sourceColumns2 ?? []];
11558
11747
  return { rows: projected2, columns: cols };
11559
11748
  }
11560
11749
  const defaultFieldKeys = buildDefaultFieldOutputKeys(columns);
@@ -11898,10 +12087,11 @@ function runFullScan(input) {
11898
12087
  havingFieldSemanticsResolver,
11899
12088
  aggregateSortKindResolver,
11900
12089
  appliedKlikes,
11901
- sourceColumns,
12090
+ sourceColumns: sourceColumns2,
11902
12091
  tableColumns,
11903
12092
  hiddenQualifiedAliases,
11904
- resolvedGroupingSpec
12093
+ resolvedGroupingSpec,
12094
+ plainGroupByPlan
11905
12095
  } = input;
11906
12096
  const effectiveOrderSemantics = deriveOutputOrderSemantics(stmt.columns);
11907
12097
  for (const [key, value] of orderSemantics ?? []) effectiveOrderSemantics.set(key, value);
@@ -11948,7 +12138,13 @@ function runFullScan(input) {
11948
12138
  rows,
11949
12139
  grouping.type === "PLAIN" ? grouping.allItems : [],
11950
12140
  stmt.columns,
11951
- aggregateSortKindResolver
12141
+ aggregateSortKindResolver,
12142
+ grouping.type === "PLAIN" ? plainGroupByPlan : void 0,
12143
+ {
12144
+ scalarCache,
12145
+ resolveFieldType: fieldTypeResolver,
12146
+ resolveFieldSemantics: fieldSemanticsResolver
12147
+ }
11952
12148
  );
11953
12149
  }
11954
12150
  rows = applyHaving(rows, stmt.having, havingFieldTypeResolver, havingFieldSemanticsResolver);
@@ -11976,7 +12172,7 @@ function runFullScan(input) {
11976
12172
  stmt.columns,
11977
12173
  scalarCache,
11978
12174
  fieldTypeResolver,
11979
- sourceColumns,
12175
+ sourceColumns2,
11980
12176
  fieldSemanticsResolver,
11981
12177
  hiddenQualifiedAliases
11982
12178
  );
@@ -12408,6 +12604,133 @@ function requireExactRelativeDatePushdown(result) {
12408
12604
  };
12409
12605
  }
12410
12606
 
12607
+ // src/core/optimization/relativeDateFullScanExactPlan.ts
12608
+ function buildRelativeDateFullScanExactPlan(input) {
12609
+ const {
12610
+ select,
12611
+ selectMode,
12612
+ capability,
12613
+ context,
12614
+ serializedWholeWhere,
12615
+ relativeFunctionNames
12616
+ } = input;
12617
+ if (select.where === null) return null;
12618
+ if (select.from.appId <= 0 || select.from.cteName !== null) return null;
12619
+ if (select.from.subtableCode) return null;
12620
+ if (select.joins.length > 0) return null;
12621
+ if (!context.allowFullScanExact) return null;
12622
+ if (select.orderMode === "KINTONE_NATIVE") return null;
12623
+ const hasCanonicalOrder2 = select.orderMode === "CANONICAL" && select.orderBy.length > 0;
12624
+ if (selectMode !== "FULL_SCAN" && !hasCanonicalOrder2) return null;
12625
+ if (capability.capability !== "EXACT_PUSHDOWN") return null;
12626
+ const occurrences = relativeDateFunctionOccurrencesInWhere(select.where);
12627
+ if (occurrences.length === 0) return null;
12628
+ if (!sameOccurrenceList(occurrences, relativeFunctionNames)) return null;
12629
+ if (serializedWholeWhere === null || !serializedMultisetContains(serializedWholeWhere, occurrences)) {
12630
+ return null;
12631
+ }
12632
+ const prefilterPlan = {
12633
+ prefilterWhere: select.where,
12634
+ residualWhere: null,
12635
+ exactRelativeLeaves: collectExactRelativeLeaves(select.where),
12636
+ relativeFunctionNames: new Set(occurrences),
12637
+ appliedKlikes: /* @__PURE__ */ new Set(),
12638
+ capability: capability.capability,
12639
+ reasons: capability.reasons
12640
+ };
12641
+ const plan = {
12642
+ allowForm: "FULL_SCAN_EXACT",
12643
+ clientWhereEvaluation: false,
12644
+ serializedWholeWhere,
12645
+ prefilterPlan
12646
+ };
12647
+ assertRelativeDateFullScanExactPlan(plan, input, occurrences);
12648
+ return plan;
12649
+ }
12650
+ function assertRelativeDateFullScanExactPlan(plan, input, occurrences) {
12651
+ if (plan.allowForm !== "FULL_SCAN_EXACT") {
12652
+ throw new Error("FULL_SCAN_EXACT invariant: allowForm");
12653
+ }
12654
+ if (plan.clientWhereEvaluation !== false) {
12655
+ throw new Error("FULL_SCAN_EXACT invariant: clientWhereEvaluation");
12656
+ }
12657
+ if (input.capability.capability !== "EXACT_PUSHDOWN") {
12658
+ throw new Error("FULL_SCAN_EXACT invariant: capability");
12659
+ }
12660
+ if (input.select.where === null || plan.prefilterPlan.prefilterWhere !== input.select.where) {
12661
+ throw new Error("FULL_SCAN_EXACT invariant: whole WHERE identity");
12662
+ }
12663
+ if (plan.prefilterPlan.residualWhere !== null) {
12664
+ throw new Error("FULL_SCAN_EXACT invariant: residualWhere");
12665
+ }
12666
+ if (plan.prefilterPlan.capability !== "EXACT_PUSHDOWN") {
12667
+ throw new Error("FULL_SCAN_EXACT invariant: transport capability");
12668
+ }
12669
+ if (!serializedMultisetContains(plan.serializedWholeWhere, occurrences)) {
12670
+ throw new Error("FULL_SCAN_EXACT invariant: relative occurrence serialization");
12671
+ }
12672
+ }
12673
+ function relativeDateFunctionOccurrencesInWhere(where) {
12674
+ const names = [];
12675
+ const visit = (node) => {
12676
+ if (Array.isArray(node)) {
12677
+ node.forEach(visit);
12678
+ return;
12679
+ }
12680
+ if (node === null || typeof node !== "object") return;
12681
+ const value = node;
12682
+ if (value["type"] === "SELECT") return;
12683
+ if (value["type"] === "KINTONE_FUNC" && typeof value["name"] === "string" && isRelativeDateFunctionName(value["name"])) {
12684
+ names.push(value["name"]);
12685
+ return;
12686
+ }
12687
+ Object.values(value).forEach(visit);
12688
+ };
12689
+ visit(where);
12690
+ return names;
12691
+ }
12692
+ function collectExactRelativeLeaves(where) {
12693
+ const leaves = [];
12694
+ const visit = (node) => {
12695
+ switch (node.type) {
12696
+ case "BINARY":
12697
+ if (node.right.type === "KINTONE_FUNC" && isRelativeDateFunctionName(node.right.name)) {
12698
+ leaves.push(node);
12699
+ }
12700
+ return;
12701
+ case "LOGICAL":
12702
+ visit(node.left);
12703
+ visit(node.right);
12704
+ return;
12705
+ case "NOT":
12706
+ case "GROUP":
12707
+ visit(node.expr);
12708
+ return;
12709
+ case "EXISTS":
12710
+ case "NULL_CHECK":
12711
+ case "BOOLEAN":
12712
+ return;
12713
+ }
12714
+ };
12715
+ visit(where);
12716
+ return leaves;
12717
+ }
12718
+ function sameOccurrenceList(actual, expected) {
12719
+ return actual.length === expected.length && actual.every((name, index) => name === expected[index]);
12720
+ }
12721
+ function serializedMultisetContains(query, expectedNames) {
12722
+ const expected = /* @__PURE__ */ new Map();
12723
+ for (const name of expectedNames) {
12724
+ if (!isRelativeDateFunctionName(name)) return false;
12725
+ expected.set(name, (expected.get(name) ?? 0) + 1);
12726
+ }
12727
+ for (const [name, count] of expected) {
12728
+ const matches = query.match(new RegExp(`\\b${name}\\s*\\(`, "g"));
12729
+ if ((matches?.length ?? 0) < count) return false;
12730
+ }
12731
+ return true;
12732
+ }
12733
+
12411
12734
  // src/core/optimization/relativeDatePushdownGuard.ts
12412
12735
  function relativeDateFunctionNamesInNode(node, stopAtNestedSelect) {
12413
12736
  const names = [];
@@ -12453,7 +12776,7 @@ function nestedSelects(node, root) {
12453
12776
  visit(node);
12454
12777
  return found;
12455
12778
  }
12456
- function collectSelect(select, path, candidates, forceForbidden, allowPhase2 = true) {
12779
+ function collectSelect(select, path, candidates, forceForbidden, allowPhase2 = true, allowFullScanExact = true) {
12457
12780
  const functionNames = relativeDateFunctionNamesInWhere(select.where);
12458
12781
  if (functionNames.length > 0) {
12459
12782
  candidates.push({
@@ -12462,7 +12785,9 @@ function collectSelect(select, path, candidates, forceForbidden, allowPhase2 = t
12462
12785
  where: select.where,
12463
12786
  functionNames,
12464
12787
  path,
12465
- allowPhase2Prefilter: allowPhase2
12788
+ allowPhase2Prefilter: allowPhase2,
12789
+ allowFullScanExact,
12790
+ relativeFunctionOccurrences: relativeDateFunctionOccurrencesInWhere(select.where)
12466
12791
  });
12467
12792
  }
12468
12793
  nestedSelects(select, select).forEach(
@@ -12471,31 +12796,35 @@ function collectSelect(select, path, candidates, forceForbidden, allowPhase2 = t
12471
12796
  `${path}.select-source[${index}]`,
12472
12797
  candidates,
12473
12798
  forceForbidden,
12474
- allowPhase2
12799
+ allowPhase2,
12800
+ allowFullScanExact
12475
12801
  )
12476
12802
  );
12477
12803
  }
12478
- function collectUnion(union, path, candidates, forceForbidden) {
12479
- if (union.left.type === "UNION") collectUnion(union.left, `${path}.left`, candidates, forceForbidden);
12480
- else collectSelect(union.left, `${path}.left`, candidates, forceForbidden);
12481
- collectSelect(union.right, `${path}.right`, candidates, forceForbidden);
12804
+ function collectUnion(union, path, candidates, forceForbidden, allowFullScanExact = true) {
12805
+ if (union.left.type === "UNION") {
12806
+ collectUnion(union.left, `${path}.left`, candidates, forceForbidden, allowFullScanExact);
12807
+ } else {
12808
+ collectSelect(union.left, `${path}.left`, candidates, forceForbidden, true, allowFullScanExact);
12809
+ }
12810
+ collectSelect(union.right, `${path}.right`, candidates, forceForbidden, true, allowFullScanExact);
12482
12811
  }
12483
12812
  function collectWith(statement, path, candidates, inheritedForbidden) {
12484
12813
  if (!inheritedForbidden && canInlineSingleCte(statement)) {
12485
- collectSelect(buildInlinedQuery(statement), `${path}.inlined`, candidates, false);
12814
+ collectSelect(buildInlinedQuery(statement), `${path}.inlined`, candidates, false, true, false);
12486
12815
  return;
12487
12816
  }
12488
12817
  statement.ctes.forEach((cte, index) => {
12489
12818
  if (cte.query.type === "SELECT") {
12490
- collectSelect(cte.query, `${path}.cte[${index}]`, candidates, true);
12819
+ collectSelect(cte.query, `${path}.cte[${index}]`, candidates, true, true, false);
12491
12820
  } else if (cte.query.type === "UNION") {
12492
- collectUnion(cte.query, `${path}.cte[${index}]`, candidates, true);
12821
+ collectUnion(cte.query, `${path}.cte[${index}]`, candidates, true, false);
12493
12822
  }
12494
12823
  });
12495
12824
  if (statement.query.type === "SELECT") {
12496
- collectSelect(statement.query, `${path}.main`, candidates, true);
12825
+ collectSelect(statement.query, `${path}.main`, candidates, true, true, false);
12497
12826
  } else {
12498
- collectUnion(statement.query, `${path}.main`, candidates, true);
12827
+ collectUnion(statement.query, `${path}.main`, candidates, true, false);
12499
12828
  }
12500
12829
  }
12501
12830
  function collectStatement(statement, path, candidates, forceForbidden = false) {
@@ -12511,8 +12840,8 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12511
12840
  return;
12512
12841
  case "CREATE_TEMP_TABLE":
12513
12842
  if (statement.query.type === "WITH") collectWith(statement.query, `${path}.query`, candidates, true);
12514
- else if (statement.query.type === "UNION") collectUnion(statement.query, `${path}.query`, candidates, true);
12515
- else collectSelect(statement.query, `${path}.query`, candidates, true);
12843
+ else if (statement.query.type === "UNION") collectUnion(statement.query, `${path}.query`, candidates, true, false);
12844
+ else collectSelect(statement.query, `${path}.query`, candidates, true, true, false);
12516
12845
  return;
12517
12846
  case "EXPLAIN":
12518
12847
  collectStatement(statement.query, `${path}.query`, candidates, forceForbidden);
@@ -12526,11 +12855,13 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12526
12855
  source: statement,
12527
12856
  where: statement.where,
12528
12857
  functionNames,
12529
- path
12858
+ path,
12859
+ allowFullScanExact: false,
12860
+ relativeFunctionOccurrences: relativeDateFunctionOccurrencesInWhere(statement.where)
12530
12861
  });
12531
12862
  }
12532
12863
  nestedSelects(statement, statement).forEach(
12533
- (select, index) => collectSelect(select, `${path}.select-source[${index}]`, candidates, true)
12864
+ (select, index) => collectSelect(select, `${path}.select-source[${index}]`, candidates, true, true, false)
12534
12865
  );
12535
12866
  return;
12536
12867
  }
@@ -12544,7 +12875,9 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12544
12875
  source: statement,
12545
12876
  where: statement.where,
12546
12877
  functionNames,
12547
- path
12878
+ path,
12879
+ allowFullScanExact: false,
12880
+ relativeFunctionOccurrences: relativeDateFunctionOccurrencesInWhere(statement.where)
12548
12881
  });
12549
12882
  }
12550
12883
  if (statement.type === "UPDATE" && statement.applyBlocks?.length) {
@@ -12555,7 +12888,9 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12555
12888
  source: statement,
12556
12889
  where: null,
12557
12890
  functionNames: applyFunctions,
12558
- path: `${path}.apply`
12891
+ path: `${path}.apply`,
12892
+ allowFullScanExact: false,
12893
+ relativeFunctionOccurrences: []
12559
12894
  });
12560
12895
  }
12561
12896
  }
@@ -12565,6 +12900,7 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12565
12900
  `${path}.select-source[${index}]`,
12566
12901
  candidates,
12567
12902
  forceForbidden,
12903
+ false,
12568
12904
  false
12569
12905
  )
12570
12906
  );
@@ -12577,6 +12913,7 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12577
12913
  `${path}.select-source[${index}]`,
12578
12914
  candidates,
12579
12915
  forceForbidden,
12916
+ false,
12580
12917
  false
12581
12918
  )
12582
12919
  );
@@ -12655,6 +12992,21 @@ async function buildRelativeDatePushdownPlan(statement, resolver) {
12655
12992
  allowed2 = true;
12656
12993
  }
12657
12994
  }
12995
+ let fullScanExactPlan;
12996
+ if (!allowed2 && capability2.capability === "EXACT_PUSHDOWN") {
12997
+ fullScanExactPlan = buildRelativeDateFullScanExactPlan({
12998
+ select,
12999
+ selectMode,
13000
+ capability: capability2,
13001
+ context: { allowFullScanExact: candidate.allowFullScanExact },
13002
+ serializedWholeWhere: restQuery2 || null,
13003
+ relativeFunctionNames: candidate.relativeFunctionOccurrences
13004
+ }) ?? void 0;
13005
+ if (fullScanExactPlan) {
13006
+ prefilterPlan = fullScanExactPlan.prefilterPlan;
13007
+ allowed2 = true;
13008
+ }
13009
+ }
12658
13010
  const node2 = {
12659
13011
  kind: candidate.kind,
12660
13012
  source: candidate.source,
@@ -12664,6 +13016,7 @@ async function buildRelativeDatePushdownPlan(statement, resolver) {
12664
13016
  capability: capability2,
12665
13017
  restQuery: restQuery2,
12666
13018
  ...prefilterPlan ? { prefilterPlan, phase2PrefilterEligible } : {},
13019
+ ...fullScanExactPlan ? { fullScanExactPlan, allowForm: fullScanExactPlan.allowForm } : {},
12667
13020
  clientWhereEvaluation: !allowed2,
12668
13021
  allowed: allowed2
12669
13022
  };
@@ -12795,7 +13148,7 @@ function decomposeRelativeDatePrefilter(stmt, resolveField2, testSeam = {}) {
12795
13148
  return reject(["PREFILTER_SERIALIZATION_FAILED"]);
12796
13149
  }
12797
13150
  const expectedNames = confirmedLeaves.map((leaf) => relativeNameOf(leaf));
12798
- if (!containsFunctions(prefilterQuery, [...new Set(expectedNames)]) || !serializedMultisetContains(prefilterQuery, expectedNames)) {
13151
+ if (!containsFunctions(prefilterQuery, [...new Set(expectedNames)]) || !serializedMultisetContains2(prefilterQuery, expectedNames)) {
12799
13152
  return reject(["PREFILTER_FUNCTION_MISSING"]);
12800
13153
  }
12801
13154
  const residualWhere = testSeam.rewriteResidual ? testSeam.rewriteResidual(stmt.where, adoptedLeaves) : replaceAdoptedLeaves(stmt.where, adoptedLeaves);
@@ -13017,7 +13370,7 @@ function collectFieldMetadata(where, resolveField2) {
13017
13370
  }
13018
13371
  return { fieldTypes, fieldOptions };
13019
13372
  }
13020
- function serializedMultisetContains(query, expectedNames) {
13373
+ function serializedMultisetContains2(query, expectedNames) {
13021
13374
  const expected = /* @__PURE__ */ new Map();
13022
13375
  for (const name of expectedNames) {
13023
13376
  if (!RELATIVE_DATE_FUNCTION_NAMES.has(name)) return false;
@@ -15229,12 +15582,20 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
15229
15582
  }
15230
15583
  return result;
15231
15584
  }
15585
+ const plainGroupByPlan = await buildRuntimePlainGroupByPlan(
15586
+ stmt,
15587
+ client,
15588
+ cacheContext,
15589
+ cteCache
15590
+ );
15232
15591
  await resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache);
15233
15592
  const whereCapability = await resolveSelectWhereCapability(stmt, client, cacheContext, cteCache);
15234
15593
  if (whereCapability.capability === "UNSUPPORTED") {
15235
15594
  throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(whereCapability)}).`);
15236
15595
  }
15596
+ const staticMode = resolveSelectMode(stmt);
15237
15597
  let prefilterPlan;
15598
+ let fullScanExactPlan;
15238
15599
  if (whereCapability.capability === "SUPERSET_PREFILTER") {
15239
15600
  const resolver = await buildWhereFieldSemanticsResolver(stmt, client, cacheContext, cteCache);
15240
15601
  const decomposition = decomposeRelativeDatePrefilter(stmt, resolver);
@@ -15242,7 +15603,23 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
15242
15603
  prefilterPlan = decomposition.plan;
15243
15604
  }
15244
15605
  }
15245
- const staticMode = resolveSelectMode(stmt);
15606
+ if (prefilterPlan === void 0 && whereCapability.capability === "EXACT_PUSHDOWN" && stmt.where !== null) {
15607
+ let serializedWholeWhere = null;
15608
+ try {
15609
+ serializedWholeWhere = whereToKintone(stmt.where);
15610
+ } catch {
15611
+ serializedWholeWhere = null;
15612
+ }
15613
+ fullScanExactPlan = buildRelativeDateFullScanExactPlan({
15614
+ select: stmt,
15615
+ selectMode: staticMode,
15616
+ capability: whereCapability,
15617
+ context: { allowFullScanExact: true },
15618
+ serializedWholeWhere,
15619
+ relativeFunctionNames: relativeDateFunctionOccurrencesInWhere(stmt.where)
15620
+ }) ?? void 0;
15621
+ if (fullScanExactPlan) prefilterPlan = fullScanExactPlan.prefilterPlan;
15622
+ }
15246
15623
  const mode = whereCapability.capability === "EXACT_PUSHDOWN" ? staticMode : "FULL_SCAN";
15247
15624
  const orderMeta = await buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
15248
15625
  const orderPlan = hasCanonicalOrder(stmt) ? (stmt.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
@@ -15260,20 +15637,34 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
15260
15637
  client,
15261
15638
  cacheContext
15262
15639
  );
15263
- const completePolicy = buildCompleteInputPolicy(stmt, options, orderPlan);
15640
+ const completePolicy = buildCompleteInputPolicy(
15641
+ stmt,
15642
+ options,
15643
+ orderPlan
15644
+ );
15645
+ const failClosedForB72Local = fullScanExactPlan !== void 0 && (staticMode === "FULL_SCAN" || orderPlan?.kind === "CANONICAL_LOCAL");
15646
+ const executionClient = failClosedForB72Local ? wrapClientWithSearchAbort(client, { aborted: false }, true) : client;
15264
15647
  try {
15265
15648
  if (mode === "SIMPLE") {
15266
- result = await executeSimpleSelect(stmt, client, completePolicy.effectiveOptions, cacheContext, orderPlan, orderMeta);
15649
+ result = await executeSimpleSelect(
15650
+ stmt,
15651
+ executionClient,
15652
+ completePolicy.effectiveOptions,
15653
+ cacheContext,
15654
+ orderPlan,
15655
+ orderMeta
15656
+ );
15267
15657
  } else {
15268
15658
  result = await executeFullScanSelect(
15269
15659
  stmt,
15270
- client,
15660
+ executionClient,
15271
15661
  completePolicy.effectiveOptions,
15272
15662
  cacheContext,
15273
15663
  cteCache,
15274
- whereCapability.capability === "EXACT_PUSHDOWN",
15664
+ whereCapability.capability === "EXACT_PUSHDOWN" && prefilterPlan === void 0,
15275
15665
  orderMeta,
15276
- prefilterPlan
15666
+ prefilterPlan,
15667
+ plainGroupByPlan
15277
15668
  );
15278
15669
  }
15279
15670
  } catch (error) {
@@ -15287,6 +15678,78 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
15287
15678
  }
15288
15679
  return result;
15289
15680
  }
15681
+ async function buildRuntimePlainGroupByPlan(stmt, client, cacheContext, materializedTables) {
15682
+ if (stmt.groupBy.length === 0) return void 0;
15683
+ const sources = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
15684
+ const inputs = await Promise.all(sources.map(async (source) => {
15685
+ if (source.cteName !== null) {
15686
+ const materialized = materializedTables?.get(source.cteName);
15687
+ if (!materialized) {
15688
+ throw new Error(`InternalError: materialized schema ${source.cteName} is not available for GROUP BY planning.`);
15689
+ }
15690
+ return { kind: "MATERIALIZED", columns: materialized.columns };
15691
+ }
15692
+ const fields = await getFieldsCached(source.appId, client, cacheContext);
15693
+ if (!source.subtableCode) {
15694
+ return {
15695
+ kind: "APP",
15696
+ fieldCodes: fields.filter((field) => !field.inSubtable).map((field) => field.code)
15697
+ };
15698
+ }
15699
+ const exactChildren = fields.filter(
15700
+ (field) => field.inSubtable && field.subtableCode === source.subtableCode
15701
+ );
15702
+ const childFields = exactChildren.length > 0 ? exactChildren : fields.filter((field) => field.inSubtable && field.subtableCode === void 0);
15703
+ return {
15704
+ kind: "SUBTABLE",
15705
+ childFieldCodes: childFields.map((field) => field.code),
15706
+ parentFieldCodes: fields.filter((field) => !field.inSubtable).map((field) => field.code)
15707
+ };
15708
+ }));
15709
+ const schemas = resolvePlainGroupBySourceSchemas(
15710
+ stmt,
15711
+ (_source, sourceIndex) => inputs[sourceIndex]
15712
+ );
15713
+ const groupBy = stmt.groupBy;
15714
+ const plan = planPlainGroupByResolution(groupBy, stmt.columns, schemas);
15715
+ assertRuntimePlainGroupByPlan(stmt, groupBy, plan);
15716
+ return plan;
15717
+ }
15718
+ function assertRuntimePlainGroupByPlan(stmt, groupBy, plan) {
15719
+ const physicalAppIds = [
15720
+ stmt.from,
15721
+ ...stmt.joins.map((join2) => join2.table)
15722
+ ].flatMap((source) => source.cteName === null ? [source.appId] : []);
15723
+ const uniquePhysicalAppIds = [...new Set(physicalAppIds)];
15724
+ const primaryPhysicalAppId = uniquePhysicalAppIds.length === 1 ? uniquePhysicalAppIds[0] : stmt.from.cteName === null ? stmt.from.appId : null;
15725
+ plan.items.forEach((item, index) => {
15726
+ if (item.kind === "EXPRESSION" || item.kind === "PHYSICAL" || item.kind === "ALIAS_SAFE") return;
15727
+ const key = groupBy[index];
15728
+ const name = key?.type === "FIELD_NAME" ? key.name : "(expression)";
15729
+ if (item.kind === "UNKNOWN") {
15730
+ const appSuffix = primaryPhysicalAppId === null ? "" : ` (APP${primaryPhysicalAppId})`;
15731
+ throw new Error(`ArgumentError: unknown field code(s): ${item.name}${appSuffix}`);
15732
+ }
15733
+ if (item.kind === "ALIAS_REJECT") {
15734
+ if (item.reason === "DUPLICATE") {
15735
+ throw new Error(
15736
+ `ArgumentError: GROUP BY alias ${name} is ambiguous across multiple SELECT columns (reason=GROUP_BY_ALIAS_AMBIGUOUS).`
15737
+ );
15738
+ }
15739
+ if (item.reason === "POST_GROUP_ONLY") {
15740
+ throw new Error(
15741
+ `ArgumentError: GROUP BY alias ${name} requires post-group evaluation (reason=GROUP_BY_ALIAS_POST_GROUP_ONLY).`
15742
+ );
15743
+ }
15744
+ throw new Error(
15745
+ `ArgumentError: GROUP BY alias ${name} depends on aggregate evaluation (reason=GROUP_BY_ALIAS_AGGREGATE).`
15746
+ );
15747
+ }
15748
+ throw new Error(
15749
+ `InternalError: deferred GROUP BY field ${item.name} reached runtime planning.`
15750
+ );
15751
+ });
15752
+ }
15290
15753
  var resolvedGroupingSpecs = /* @__PURE__ */ new WeakMap();
15291
15754
  async function validateStatementGroupingPlanning(statement, client, cacheContext) {
15292
15755
  const seen = /* @__PURE__ */ new Set();
@@ -16218,7 +16681,7 @@ function buildSelectFieldTypeResolvers(stmt, fieldTypesByApp) {
16218
16681
  };
16219
16682
  return { row, having };
16220
16683
  }
16221
- async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache, allowOriginalWherePushdown = true, preloadedOrderMeta, prefilterPlan) {
16684
+ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache, allowOriginalWherePushdown = true, preloadedOrderMeta, prefilterPlan, plainGroupByPlan) {
16222
16685
  const maxRecords = options.maxRecords ?? 1e4;
16223
16686
  const warnings = /* @__PURE__ */ new Set();
16224
16687
  const parallel = options.fetchParallel ?? 1;
@@ -16248,6 +16711,7 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16248
16711
  throw new Error("internal error: relative-date prefilter must disable original WHERE pushdown.");
16249
16712
  }
16250
16713
  const mainFetchCondition = prefilterPlan ? prefilterPlan.prefilterWhere : mainPushDown;
16714
+ const scalarCache = await resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
16251
16715
  const constantFalse = isConstantFalseWhere(stmt.where);
16252
16716
  const mainFetch = constantFalse ? Promise.resolve([]) : fetchTableRecordsForFullScan(
16253
16717
  stmt,
@@ -16259,7 +16723,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16259
16723
  options.onLimitReached ?? "error",
16260
16724
  warnings,
16261
16725
  mainFetchCondition,
16262
- allowOriginalWherePushdown
16726
+ allowOriginalWherePushdown,
16727
+ plainGroupByPlan
16263
16728
  );
16264
16729
  const parallelJoins = [];
16265
16730
  const onOptJoins = [];
@@ -16281,17 +16746,16 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16281
16746
  false,
16282
16747
  options.onLimitReached ?? "error",
16283
16748
  warnings,
16284
- jCond
16749
+ jCond,
16750
+ true,
16751
+ plainGroupByPlan
16285
16752
  )
16286
16753
  });
16287
16754
  } else {
16288
16755
  onOptJoins.push(join2);
16289
16756
  }
16290
16757
  }
16291
- const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
16292
16758
  const orderByMetaPromise = preloadedOrderMeta ? Promise.resolve(preloadedOrderMeta) : buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
16293
- scalarCachePromise.catch(() => {
16294
- });
16295
16759
  orderByMetaPromise.catch(() => {
16296
16760
  });
16297
16761
  const mainRecords = await mainFetch;
@@ -16310,7 +16774,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16310
16774
  parallel,
16311
16775
  options.onLimitReached ?? "error",
16312
16776
  warnings,
16313
- null
16777
+ null,
16778
+ plainGroupByPlan
16314
16779
  );
16315
16780
  const joinRecords = optimized ?? await fetchTableRecordsForFullScan(
16316
16781
  stmt,
@@ -16321,11 +16786,12 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16321
16786
  false,
16322
16787
  options.onLimitReached ?? "error",
16323
16788
  warnings,
16324
- null
16789
+ null,
16790
+ true,
16791
+ plainGroupByPlan
16325
16792
  );
16326
16793
  tables.set(join2.table.alias, joinRecords);
16327
16794
  }));
16328
- const scalarCache = await scalarCachePromise;
16329
16795
  const { optionOrders, sortKinds, semantics } = await orderByMetaPromise;
16330
16796
  const { rows, columns } = runFullScan({
16331
16797
  tables,
@@ -16341,7 +16807,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16341
16807
  aggregateSortKindResolver,
16342
16808
  appliedKlikes: prefilterPlan?.appliedKlikes ?? pushdownPlan.appliedKlikes,
16343
16809
  ...prefilterPlan ? { residualWhere: prefilterPlan.residualWhere } : {},
16344
- resolvedGroupingSpec: resolvedGroupingSpecs.get(stmt)
16810
+ resolvedGroupingSpec: resolvedGroupingSpecs.get(stmt),
16811
+ plainGroupByPlan
16345
16812
  });
16346
16813
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
16347
16814
  }
@@ -16434,6 +16901,12 @@ async function executeQueryWithCte(query, client, options, cteCache, cacheContex
16434
16901
  async function executeFullScanWithCte(stmt, client, options, cteCache, cacheContext) {
16435
16902
  await validateSelectGroupingPlanning(stmt, client, cacheContext, cteCache);
16436
16903
  const resolvedGroupingSpec = resolvedGroupingSpecs.get(stmt);
16904
+ const plainGroupByPlan = await buildRuntimePlainGroupByPlan(
16905
+ stmt,
16906
+ client,
16907
+ cacheContext,
16908
+ cteCache
16909
+ );
16437
16910
  const hiddenQualifiedAliases = /* @__PURE__ */ new Set();
16438
16911
  const withEffectiveAlias = (table) => {
16439
16912
  if (table.alias !== null || table.cteName === null) return table;
@@ -16505,10 +16978,14 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
16505
16978
  const havingFieldSemanticsResolver = buildHavingFieldSemanticsResolver(stmt, fieldSemanticsResolver);
16506
16979
  const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
16507
16980
  validateKlikePushdownPlan(pushdownPlan);
16508
- const scalarCachePromise = resolveScalarColumns(stmt.columns, client, effectiveOptions, cacheContext, cteCache);
16981
+ const scalarCache = await resolveScalarColumns(
16982
+ stmt.columns,
16983
+ client,
16984
+ effectiveOptions,
16985
+ cacheContext,
16986
+ cteCache
16987
+ );
16509
16988
  const orderByMetaPromise = Promise.resolve(orderMeta);
16510
- scalarCachePromise.catch(() => {
16511
- });
16512
16989
  orderByMetaPromise.catch(() => {
16513
16990
  });
16514
16991
  const tables = /* @__PURE__ */ new Map();
@@ -16528,7 +17005,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
16528
17005
  effectiveOptions.onLimitReached ?? "error",
16529
17006
  warnings,
16530
17007
  pushdownPlan.mainCondition,
16531
- whereCapability.capability === "EXACT_PUSHDOWN"
17008
+ whereCapability.capability === "EXACT_PUSHDOWN",
17009
+ plainGroupByPlan
16532
17010
  ));
16533
17011
  tables.set(stmt.from.alias, mainRecords);
16534
17012
  }
@@ -16548,7 +17026,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
16548
17026
  parallel,
16549
17027
  effectiveOptions.onLimitReached ?? "error",
16550
17028
  warnings,
16551
- pushDownCond
17029
+ pushDownCond,
17030
+ plainGroupByPlan
16552
17031
  );
16553
17032
  const joinRecords = optimized ?? await fetchTableRecordsForFullScan(
16554
17033
  stmt,
@@ -16559,15 +17038,16 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
16559
17038
  false,
16560
17039
  effectiveOptions.onLimitReached ?? "error",
16561
17040
  warnings,
16562
- pushDownCond
17041
+ pushDownCond,
17042
+ true,
17043
+ plainGroupByPlan
16563
17044
  );
16564
17045
  tables.set(join2.table.alias, joinRecords);
16565
17046
  }
16566
17047
  }));
16567
17048
  await Promise.all(joinFetches);
16568
- const scalarCache = await scalarCachePromise;
16569
17049
  const { optionOrders, sortKinds, semantics } = await orderByMetaPromise;
16570
- const sourceColumns = stmt.joins.length === 0 && stmt.from.cteName != null ? requireMaterializedTable(stmt.from.cteName).columns : void 0;
17050
+ const sourceColumns2 = stmt.joins.length === 0 && stmt.from.cteName != null ? requireMaterializedTable(stmt.from.cteName).columns : void 0;
16571
17051
  const { rows, columns } = runFullScan({
16572
17052
  tables,
16573
17053
  stmt,
@@ -16581,10 +17061,11 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
16581
17061
  havingFieldSemanticsResolver,
16582
17062
  aggregateSortKindResolver,
16583
17063
  appliedKlikes: pushdownPlan.appliedKlikes,
16584
- sourceColumns,
17064
+ sourceColumns: sourceColumns2,
16585
17065
  tableColumns,
16586
17066
  hiddenQualifiedAliases,
16587
- resolvedGroupingSpec
17067
+ resolvedGroupingSpec,
17068
+ plainGroupByPlan
16588
17069
  });
16589
17070
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
16590
17071
  }
@@ -16593,8 +17074,8 @@ function processRowToKintoneRecord(row) {
16593
17074
  Object.entries(row).map(([k, v]) => [k, { value: v ?? "" }])
16594
17075
  );
16595
17076
  }
16596
- async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, parallel, isMainTable, onLimit, warnings, pushDownCond = null, allowOriginalWherePushdown = true) {
16597
- const fields = selectToFetchAllFields(stmt, table);
17077
+ async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, parallel, isMainTable, onLimit, warnings, pushDownCond = null, allowOriginalWherePushdown = true, plainGroupByPlan) {
17078
+ const fields = selectToFetchAllFields(stmt, table, plainGroupByPlan);
16598
17079
  const onTruncate = (max) => {
16599
17080
  warnings.add(`\u53D6\u5F97\u4E0A\u9650\uFF08${max} \u4EF6\uFF09\u306B\u9054\u3057\u305F\u305F\u3081\u3001${max} \u4EF6\u3067\u6253\u3061\u5207\u3063\u3066\u8868\u793A\u3057\u3066\u3044\u307E\u3059\u3002`);
16600
17081
  };
@@ -16708,7 +17189,7 @@ function splitChunks(items, size) {
16708
17189
  var JOIN_IN_CHUNK_SIZE = 50;
16709
17190
  var JOIN_IN_MAX_CHUNKS = 6;
16710
17191
  var JOIN_IN_MAX_KEYS = JOIN_IN_CHUNK_SIZE * JOIN_IN_MAX_CHUNKS;
16711
- async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxRecords, parallel, onLimit, warnings, pushDownCond = null) {
17192
+ async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxRecords, parallel, onLimit, warnings, pushDownCond = null, plainGroupByPlan) {
16712
17193
  if (join2.type !== "INNER") return null;
16713
17194
  if (!join2.table.alias) return null;
16714
17195
  if (join2.table.subtableCode) return null;
@@ -16745,7 +17226,7 @@ async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxR
16745
17226
  );
16746
17227
  return null;
16747
17228
  }
16748
- const fields = selectToFetchAllFields(stmt, join2.table);
17229
+ const fields = selectToFetchAllFields(stmt, join2.table, plainGroupByPlan);
16749
17230
  const onTruncate = (max) => {
16750
17231
  warnings.add(`\u53D6\u5F97\u4E0A\u9650\uFF08${max} \u4EF6\uFF09\u306B\u9054\u3057\u305F\u305F\u3081\u3001${max} \u4EF6\u3067\u6253\u3061\u5207\u3063\u3066\u8868\u793A\u3057\u3066\u3044\u307E\u3059\u3002`);
16751
17232
  };
@@ -16787,9 +17268,6 @@ function setScopedCacheValue(root, cacheContext, appId, value) {
16787
17268
  }
16788
17269
  scoped.set(appId, value);
16789
17270
  }
16790
- function isSystemLikeFieldCode(code) {
16791
- return code.startsWith("_") || code.startsWith("$");
16792
- }
16793
17271
  async function getFieldsCached(appId, client, cacheContext) {
16794
17272
  const cached = getScopedCacheValue(fieldInfoCache, cacheContext, appId);
16795
17273
  if (cached) return cached;
@@ -19985,6 +20463,7 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
19985
20463
  };
19986
20464
  const capabilities = /* @__PURE__ */ new Map();
19987
20465
  const orderPlans = /* @__PURE__ */ new Map();
20466
+ const plainGroupByPlans = /* @__PURE__ */ new Map();
19988
20467
  const seen = /* @__PURE__ */ new Set();
19989
20468
  const sharedRelativeDatePlan = relativeDatePlan ?? await resolveRelativeDateExecutionPlan(query, tracedClient, cacheContext);
19990
20469
  const relativeNodeFor = (source) => sharedRelativeDatePlan.nodes.find(
@@ -20002,6 +20481,15 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
20002
20481
  if (typed["type"] === "SELECT") {
20003
20482
  const select = node;
20004
20483
  await validateSelectGroupingPlanning(select, tracedClient, cacheContext);
20484
+ const hasUnmaterializedSource = [select.from, ...select.joins.map((join2) => join2.table)].some((table) => table.cteName !== null);
20485
+ if (normalizeGroupingSpec(select).type === "PLAIN" && !hasUnmaterializedSource) {
20486
+ const plainPlan = await buildRuntimePlainGroupByPlan(
20487
+ select,
20488
+ tracedClient,
20489
+ cacheContext
20490
+ );
20491
+ if (plainPlan) plainGroupByPlans.set(select, plainPlan);
20492
+ }
20005
20493
  const physicalApps = [select.from, ...select.joins.map((join2) => join2.table)].filter((table) => table.cteName === null).map((table) => table.appId);
20006
20494
  const needsWhereSchema = whereNeedsFieldMetadata(select.where);
20007
20495
  if (needsWhereSchema || select.orderBy.length > 0 || select.columns.some((column) => column.type === "WINDOW_COL" && column.orderBy.length > 0)) {
@@ -20022,7 +20510,6 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
20022
20510
  }
20023
20511
  }
20024
20512
  }
20025
- const hasUnmaterializedSource = [select.from, ...select.joins.map((join2) => join2.table)].some((table) => table.cteName !== null);
20026
20513
  if (hasCanonicalOrder(select) && !hasUnmaterializedSource && relativeNode?.allowed !== false) {
20027
20514
  const mode = capability.capability === "EXACT_PUSHDOWN" ? resolveSelectMode(select) : "FULL_SCAN";
20028
20515
  orderPlans.set(select, (select.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
@@ -20136,6 +20623,7 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
20136
20623
  return {
20137
20624
  capabilities,
20138
20625
  orderPlans,
20626
+ plainGroupByPlans,
20139
20627
  fieldApps,
20140
20628
  processStatusApps,
20141
20629
  numberPrecisionApps,
@@ -20258,6 +20746,32 @@ function relativeDateExplainLines(plan) {
20258
20746
  }
20259
20747
  const lines = [];
20260
20748
  for (const node of plan.nodes) {
20749
+ const fullScanExactPlan = node.fullScanExactPlan;
20750
+ if (node.allowed && node.allowForm === "FULL_SCAN_EXACT" && fullScanExactPlan) {
20751
+ for (const leaf of fullScanExactPlan.prefilterPlan.exactRelativeLeaves) {
20752
+ const functionName = leaf.right.type === "KINTONE_FUNC" ? leaf.right.name : "(unknown)";
20753
+ const field = leaf.left.type === "FIELD" ? leaf.left.field : void 0;
20754
+ const operator = relativeReasonOperator(leaf.op);
20755
+ const detail = node.capability?.reasons.find(
20756
+ (reason) => reason.functionName === functionName && (field === void 0 || reason.field === field) && reason.operator === operator
20757
+ );
20758
+ lines.push(
20759
+ ` relative date function: ${functionName}`,
20760
+ " relative date evaluation: kintone server whole-WHERE exact",
20761
+ ` field: ${detail?.field ?? field ?? "(unknown)"} (${detail?.fieldType ?? "unknown"})`,
20762
+ ` operator: ${detail?.operator ?? operator}`
20763
+ );
20764
+ }
20765
+ const wholeWhereQuery = fullScanExactPlan.serializedWholeWhere;
20766
+ lines.push(
20767
+ " where capability: EXACT_PUSHDOWN",
20768
+ ` server predicate: ${wholeWhereQuery}`,
20769
+ " client residual: (none)",
20770
+ " relative date client evaluations: 0",
20771
+ ` kintone query: ${wholeWhereQuery}`
20772
+ );
20773
+ continue;
20774
+ }
20261
20775
  const prefilterPlan = node.prefilterPlan;
20262
20776
  if (node.allowed && prefilterPlan?.prefilterWhere && prefilterPlan.residualWhere) {
20263
20777
  for (const leaf of prefilterPlan.exactRelativeLeaves) {
@@ -20459,7 +20973,8 @@ async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxA
20459
20973
  analysis.orderPlans,
20460
20974
  dmlMaxRows,
20461
20975
  dmlMaxSubtableRows,
20462
- maxRecords
20976
+ maxRecords,
20977
+ analysis.plainGroupByPlans
20463
20978
  ),
20464
20979
  cursorMaxActive
20465
20980
  )
@@ -20482,13 +20997,13 @@ function addCursorConcurrency(lines, cursorMaxActive) {
20482
20997
  }
20483
20998
  return result;
20484
20999
  }
20485
- function buildExplainPlan(query, label, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, maxRecords = 1e4) {
20486
- if (query.type === "UNION") return buildUnionPlan(query, capabilities, orderPlans);
20487
- if (query.type === "WITH") return buildWithPlan(query, capabilities, orderPlans);
21000
+ function buildExplainPlan(query, label, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, maxRecords = 1e4, plainGroupByPlans) {
21001
+ if (query.type === "UNION") return buildUnionPlan(query, capabilities, orderPlans, plainGroupByPlans);
21002
+ if (query.type === "WITH") return buildWithPlan(query, capabilities, orderPlans, plainGroupByPlans);
20488
21003
  if (query.type === "INSERT") return buildInsertPlan(query, label, dmlMaxRows, dmlMaxSubtableRows);
20489
- if (query.type === "INSERT_SELECT") return buildInsertSelectPlan(query, label, capabilities, orderPlans);
21004
+ if (query.type === "INSERT_SELECT") return buildInsertSelectPlan(query, label, capabilities, orderPlans, plainGroupByPlans);
20490
21005
  if (query.type === "UPSERT") return buildUpsertPlan(query, label, dmlMaxRows, dmlMaxSubtableRows);
20491
- if (query.type === "UPSERT_SELECT") return buildUpsertSelectPlan(query, label, capabilities, orderPlans);
21006
+ if (query.type === "UPSERT_SELECT") return buildUpsertSelectPlan(query, label, capabilities, orderPlans, plainGroupByPlans);
20492
21007
  if (query.type === "UPDATE") return buildUpdatePlan(
20493
21008
  query,
20494
21009
  label,
@@ -20574,7 +21089,7 @@ function buildExplainPlan(query, label, capabilities, orderPlans, dmlMaxRows = 1
20574
21089
  ` duplicateKey: preflight before lookup/write (requires load)`
20575
21090
  ];
20576
21091
  }
20577
- return buildSelectPlan(query, label, capabilities, orderPlans);
21092
+ return buildSelectPlan(query, label, capabilities, orderPlans, plainGroupByPlans);
20578
21093
  }
20579
21094
  function buildValidatePlan(stmt, label) {
20580
21095
  const info = validateExplainInfo.get(stmt);
@@ -20601,9 +21116,10 @@ function buildValidatePlan(stmt, label) {
20601
21116
  lines.push(" records/mutation API during EXPLAIN: none; violation count unavailable");
20602
21117
  return lines;
20603
21118
  }
20604
- function buildSelectPlan(stmt, label, capabilities, orderPlans) {
21119
+ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlans) {
20605
21120
  const whereCapability = capabilities?.get(stmt) ?? (capabilities ? [...capabilities].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
20606
21121
  const orderPlan = orderPlans?.get(stmt) ?? (orderPlans ? [...orderPlans].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
21122
+ const plainGroupByPlan = plainGroupByPlans?.get(stmt) ?? (plainGroupByPlans ? [...plainGroupByPlans].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
20607
21123
  const mode = orderPlan?.kind === "CANONICAL_LOCAL" ? "FULL_SCAN" : whereCapability && whereCapability.capability !== "EXACT_PUSHDOWN" ? "FULL_SCAN" : resolveSelectMode(stmt);
20608
21124
  const reasons = collectFullScanReasons(stmt);
20609
21125
  if (whereCapability && whereCapability.capability !== "EXACT_PUSHDOWN") {
@@ -20628,6 +21144,29 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
20628
21144
  ` grouping output rows: runtime checked (limit: ${groupingMetadata.outputRowLimit}, before HAVING/DISTINCT/LIMIT)`
20629
21145
  );
20630
21146
  }
21147
+ const normalizedGrouping = normalizeGroupingSpec(stmt);
21148
+ if (normalizedGrouping.type === "PLAIN") {
21149
+ const groupBy = normalizedGrouping.allItems;
21150
+ if (plainGroupByPlan) {
21151
+ plainGroupByPlan.items.forEach((item, index) => {
21152
+ const key = groupBy[index];
21153
+ if (key?.type !== "FIELD_NAME") return;
21154
+ if (item.kind === "PHYSICAL") {
21155
+ lines.push(
21156
+ ` group key ${key.name}: PHYSICAL (source=${item.sourceIndex}, field=${item.fieldCode})`
21157
+ );
21158
+ }
21159
+ });
21160
+ } else if ([stmt.from, ...stmt.joins.map((join2) => join2.table)].some((table) => table.cteName !== null)) {
21161
+ for (const key of groupBy) {
21162
+ if (key.type === "FIELD_NAME") {
21163
+ lines.push(
21164
+ ` group key ${key.name}: DEFERRED (materialized schema unavailable)`
21165
+ );
21166
+ }
21167
+ }
21168
+ }
21169
+ }
20631
21170
  if (orderPlan) {
20632
21171
  lines.push(` order plan: ${orderPlan.kind}`);
20633
21172
  if (orderPlan.reasonCodes.length > 0) lines.push(` order reason: ${orderPlan.reasonCodes.join(", ")}`);
@@ -20669,7 +21208,7 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
20669
21208
  lines.push(` fields: ${params.fields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : params.fields.join(", ")}`);
20670
21209
  } else {
20671
21210
  const pushdownPlan = buildKlikePushdownPlan(stmt);
20672
- const mainFields = selectToFetchAllFields(stmt, stmt.from);
21211
+ const mainFields = selectToFetchAllFields(stmt, stmt.from, plainGroupByPlan);
20673
21212
  const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
20674
21213
  const mainPushDown = pushdownPlan.mainCondition;
20675
21214
  const mainCandidate = extractMainTypedPushdownCandidate(stmt);
@@ -20682,7 +21221,7 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
20682
21221
  }
20683
21222
  lines.push(` fields: ${mainFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : mainFields.join(", ")}`);
20684
21223
  for (const join2 of stmt.joins) {
20685
- const joinFields = selectToFetchAllFields(stmt, join2.table);
21224
+ const joinFields = selectToFetchAllFields(stmt, join2.table, plainGroupByPlan);
20686
21225
  const joinAliasStr = join2.table.alias ? ` AS ${join2.table.alias}` : "";
20687
21226
  const joinType = join2.type === "INNER" ? "JOIN" : `${join2.type} JOIN`;
20688
21227
  const joinPushDown = join2.table.alias ? pushdownPlan.joinConditions.get(join2.table.alias) ?? null : null;
@@ -20696,10 +21235,10 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
20696
21235
  lines.push(` fields: ${joinFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : joinFields.join(", ")}`);
20697
21236
  }
20698
21237
  }
20699
- lines.push(...collectSubqueryPlans(stmt, capabilities, orderPlans));
21238
+ lines.push(...collectSubqueryPlans(stmt, capabilities, orderPlans, plainGroupByPlans));
20700
21239
  return lines;
20701
21240
  }
20702
- function buildUnionPlan(stmt, capabilities, orderPlans) {
21241
+ function buildUnionPlan(stmt, capabilities, orderPlans, plainGroupByPlans) {
20703
21242
  const selects = [];
20704
21243
  const collect = (u) => {
20705
21244
  if (u.type === "SELECT") {
@@ -20713,25 +21252,34 @@ function buildUnionPlan(stmt, capabilities, orderPlans) {
20713
21252
  const lines = [];
20714
21253
  selects.forEach((sel, i) => {
20715
21254
  if (i > 0) lines.push("");
20716
- lines.push(...buildSelectPlan(sel, `[union:${i + 1}]`, capabilities, orderPlans));
21255
+ lines.push(...buildSelectPlan(sel, `[union:${i + 1}]`, capabilities, orderPlans, plainGroupByPlans));
20717
21256
  });
20718
21257
  return lines;
20719
21258
  }
20720
- function buildWithPlan(stmt, capabilities, orderPlans) {
21259
+ function buildWithPlan(stmt, capabilities, orderPlans, plainGroupByPlans) {
20721
21260
  const lines = [];
20722
21261
  for (const cte of stmt.ctes) {
20723
21262
  if (cte.query.type === "SELECT") {
20724
- lines.push(...buildSelectPlan(cte.query, `[cte: ${cte.name}]`, capabilities, orderPlans));
21263
+ lines.push(...buildSelectPlan(cte.query, `[cte: ${cte.name}]`, capabilities, orderPlans, plainGroupByPlans));
20725
21264
  lines.push("");
20726
21265
  }
20727
21266
  }
20728
21267
  if (stmt.query.type === "SELECT" || stmt.query.type === "UNION") {
20729
- lines.push(...buildExplainPlan(stmt.query, "[main]", capabilities, orderPlans));
21268
+ lines.push(...buildExplainPlan(
21269
+ stmt.query,
21270
+ "[main]",
21271
+ capabilities,
21272
+ orderPlans,
21273
+ 100,
21274
+ DEFAULT_APPLY_MAX_SUBTABLE_ROWS,
21275
+ 1e4,
21276
+ plainGroupByPlans
21277
+ ));
20730
21278
  }
20731
21279
  if (canInlineSingleCte(stmt)) {
20732
21280
  lines.push("");
20733
21281
  const inlined = buildInlinedQuery(stmt);
20734
- lines.push(...buildSelectPlan(inlined, "[effective: inlined CTE]", capabilities, orderPlans));
21282
+ lines.push(...buildSelectPlan(inlined, "[effective: inlined CTE]", capabilities, orderPlans, plainGroupByPlans));
20735
21283
  }
20736
21284
  return lines;
20737
21285
  }
@@ -20764,7 +21312,7 @@ function collectFullScanReasons(stmt) {
20764
21312
  r.push("ORDER BY \u306B\u5F0F");
20765
21313
  return r;
20766
21314
  }
20767
- function collectSubqueryPlans(stmt, capabilities, orderPlans) {
21315
+ function collectSubqueryPlans(stmt, capabilities, orderPlans, plainGroupByPlans) {
20768
21316
  const lines = [];
20769
21317
  let idx = 1;
20770
21318
  const visitWhere = (w) => {
@@ -20773,16 +21321,16 @@ function collectSubqueryPlans(stmt, capabilities, orderPlans) {
20773
21321
  case "BINARY":
20774
21322
  if (w.right.type === "SCALAR_SUBQUERY") {
20775
21323
  lines.push("");
20776
- lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans));
21324
+ lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans));
20777
21325
  }
20778
21326
  if (w.right.type === "SUBQUERY_IN_LIST") {
20779
21327
  lines.push("");
20780
- lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans));
21328
+ lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans));
20781
21329
  }
20782
21330
  break;
20783
21331
  case "EXISTS":
20784
21332
  lines.push("");
20785
- lines.push(...buildSelectPlan(w.query, `[subquery:${idx++}]`, capabilities, orderPlans));
21333
+ lines.push(...buildSelectPlan(w.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans));
20786
21334
  break;
20787
21335
  case "LOGICAL":
20788
21336
  visitWhere(w.left);
@@ -20802,7 +21350,7 @@ function collectSubqueryPlans(stmt, capabilities, orderPlans) {
20802
21350
  for (const col of stmt.columns) {
20803
21351
  if (col.type === "SCALAR_SUBQUERY_COL") {
20804
21352
  lines.push("");
20805
- lines.push(...buildSelectPlan(col.query, `[subquery:${idx++}]`, capabilities, orderPlans));
21353
+ lines.push(...buildSelectPlan(col.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans));
20806
21354
  }
20807
21355
  }
20808
21356
  if (stmt.having) visitWhere(stmt.having);
@@ -20820,7 +21368,7 @@ function buildInsertPlan(stmt, label, dmlMaxRows = DEFAULT_APPLY_MAX_ROWS, dmlMa
20820
21368
  lines.push(` fields: ${stmt.fields.join(", ")}`);
20821
21369
  return stmt.applyBlocks?.length ? [...lines, ...formatStaticApplyDiagnostic(buildStaticApplyDiagnostic(stmt, dmlMaxRows, dmlMaxSubtableRows))] : lines;
20822
21370
  }
20823
- function buildInsertSelectPlan(stmt, label, capabilities, orderPlans) {
21371
+ function buildInsertSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlans) {
20824
21372
  const lines = [];
20825
21373
  if (label) lines.push(label);
20826
21374
  lines.push(` [INSERT SELECT]`);
@@ -20828,7 +21376,7 @@ function buildInsertSelectPlan(stmt, label, capabilities, orderPlans) {
20828
21376
  lines.push(` fields: ${stmt.fields.join(", ")}`);
20829
21377
  lines.push(` api: POST /k/v1/records.json\uFF08\u4EF6\u6570\u306F SELECT \u7D50\u679C\u306B\u4F9D\u5B58\u3001100 \u4EF6\u3054\u3068\u306B\u30D0\u30C3\u30C1\uFF09`);
20830
21378
  lines.push("");
20831
- lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans));
21379
+ lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans, plainGroupByPlans));
20832
21380
  return lines;
20833
21381
  }
20834
21382
  function buildUpdatePlan(stmt, label, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, maxRecords = 1e4) {
@@ -20982,7 +21530,7 @@ function formatStaticApplyDiagnostic(diagnostic2) {
20982
21530
  lines.push("records API: 0", "mutation API: 0");
20983
21531
  return lines;
20984
21532
  }
20985
- function buildUpsertSelectPlan(stmt, label, capabilities, orderPlans) {
21533
+ function buildUpsertSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlans) {
20986
21534
  const lines = [
20987
21535
  ...label ? [label] : [],
20988
21536
  ` [UPSERT SELECT]`,
@@ -20992,7 +21540,7 @@ function buildUpsertSelectPlan(stmt, label, capabilities, orderPlans) {
20992
21540
  ` api: GET /k/v1/records.json\uFF08\u91CD\u8907\u5224\u5B9A\uFF09\u2192 POST \u307E\u305F\u306F PUT /k/v1/records.json\uFF08100 \u4EF6\u3054\u3068\u306B\u30D0\u30C3\u30C1\uFF09`,
20993
21541
  ``
20994
21542
  ];
20995
- lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans));
21543
+ lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans, plainGroupByPlans));
20996
21544
  return lines;
20997
21545
  }
20998
21546
  function buildReorderPlan(stmt, label) {