@rex0220/kintone-sql-tools 3.22.0 → 3.23.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
  );
@@ -15229,6 +15425,12 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
15229
15425
  }
15230
15426
  return result;
15231
15427
  }
15428
+ const plainGroupByPlan = await buildRuntimePlainGroupByPlan(
15429
+ stmt,
15430
+ client,
15431
+ cacheContext,
15432
+ cteCache
15433
+ );
15232
15434
  await resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache);
15233
15435
  const whereCapability = await resolveSelectWhereCapability(stmt, client, cacheContext, cteCache);
15234
15436
  if (whereCapability.capability === "UNSUPPORTED") {
@@ -15273,7 +15475,8 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
15273
15475
  cteCache,
15274
15476
  whereCapability.capability === "EXACT_PUSHDOWN",
15275
15477
  orderMeta,
15276
- prefilterPlan
15478
+ prefilterPlan,
15479
+ plainGroupByPlan
15277
15480
  );
15278
15481
  }
15279
15482
  } catch (error) {
@@ -15287,6 +15490,78 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
15287
15490
  }
15288
15491
  return result;
15289
15492
  }
15493
+ async function buildRuntimePlainGroupByPlan(stmt, client, cacheContext, materializedTables) {
15494
+ if (stmt.groupBy.length === 0) return void 0;
15495
+ const sources = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
15496
+ const inputs = await Promise.all(sources.map(async (source) => {
15497
+ if (source.cteName !== null) {
15498
+ const materialized = materializedTables?.get(source.cteName);
15499
+ if (!materialized) {
15500
+ throw new Error(`InternalError: materialized schema ${source.cteName} is not available for GROUP BY planning.`);
15501
+ }
15502
+ return { kind: "MATERIALIZED", columns: materialized.columns };
15503
+ }
15504
+ const fields = await getFieldsCached(source.appId, client, cacheContext);
15505
+ if (!source.subtableCode) {
15506
+ return {
15507
+ kind: "APP",
15508
+ fieldCodes: fields.filter((field) => !field.inSubtable).map((field) => field.code)
15509
+ };
15510
+ }
15511
+ const exactChildren = fields.filter(
15512
+ (field) => field.inSubtable && field.subtableCode === source.subtableCode
15513
+ );
15514
+ const childFields = exactChildren.length > 0 ? exactChildren : fields.filter((field) => field.inSubtable && field.subtableCode === void 0);
15515
+ return {
15516
+ kind: "SUBTABLE",
15517
+ childFieldCodes: childFields.map((field) => field.code),
15518
+ parentFieldCodes: fields.filter((field) => !field.inSubtable).map((field) => field.code)
15519
+ };
15520
+ }));
15521
+ const schemas = resolvePlainGroupBySourceSchemas(
15522
+ stmt,
15523
+ (_source, sourceIndex) => inputs[sourceIndex]
15524
+ );
15525
+ const groupBy = stmt.groupBy;
15526
+ const plan = planPlainGroupByResolution(groupBy, stmt.columns, schemas);
15527
+ assertRuntimePlainGroupByPlan(stmt, groupBy, plan);
15528
+ return plan;
15529
+ }
15530
+ function assertRuntimePlainGroupByPlan(stmt, groupBy, plan) {
15531
+ const physicalAppIds = [
15532
+ stmt.from,
15533
+ ...stmt.joins.map((join2) => join2.table)
15534
+ ].flatMap((source) => source.cteName === null ? [source.appId] : []);
15535
+ const uniquePhysicalAppIds = [...new Set(physicalAppIds)];
15536
+ const primaryPhysicalAppId = uniquePhysicalAppIds.length === 1 ? uniquePhysicalAppIds[0] : stmt.from.cteName === null ? stmt.from.appId : null;
15537
+ plan.items.forEach((item, index) => {
15538
+ if (item.kind === "EXPRESSION" || item.kind === "PHYSICAL" || item.kind === "ALIAS_SAFE") return;
15539
+ const key = groupBy[index];
15540
+ const name = key?.type === "FIELD_NAME" ? key.name : "(expression)";
15541
+ if (item.kind === "UNKNOWN") {
15542
+ const appSuffix = primaryPhysicalAppId === null ? "" : ` (APP${primaryPhysicalAppId})`;
15543
+ throw new Error(`ArgumentError: unknown field code(s): ${item.name}${appSuffix}`);
15544
+ }
15545
+ if (item.kind === "ALIAS_REJECT") {
15546
+ if (item.reason === "DUPLICATE") {
15547
+ throw new Error(
15548
+ `ArgumentError: GROUP BY alias ${name} is ambiguous across multiple SELECT columns (reason=GROUP_BY_ALIAS_AMBIGUOUS).`
15549
+ );
15550
+ }
15551
+ if (item.reason === "POST_GROUP_ONLY") {
15552
+ throw new Error(
15553
+ `ArgumentError: GROUP BY alias ${name} requires post-group evaluation (reason=GROUP_BY_ALIAS_POST_GROUP_ONLY).`
15554
+ );
15555
+ }
15556
+ throw new Error(
15557
+ `ArgumentError: GROUP BY alias ${name} depends on aggregate evaluation (reason=GROUP_BY_ALIAS_AGGREGATE).`
15558
+ );
15559
+ }
15560
+ throw new Error(
15561
+ `InternalError: deferred GROUP BY field ${item.name} reached runtime planning.`
15562
+ );
15563
+ });
15564
+ }
15290
15565
  var resolvedGroupingSpecs = /* @__PURE__ */ new WeakMap();
15291
15566
  async function validateStatementGroupingPlanning(statement, client, cacheContext) {
15292
15567
  const seen = /* @__PURE__ */ new Set();
@@ -16218,7 +16493,7 @@ function buildSelectFieldTypeResolvers(stmt, fieldTypesByApp) {
16218
16493
  };
16219
16494
  return { row, having };
16220
16495
  }
16221
- async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache, allowOriginalWherePushdown = true, preloadedOrderMeta, prefilterPlan) {
16496
+ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache, allowOriginalWherePushdown = true, preloadedOrderMeta, prefilterPlan, plainGroupByPlan) {
16222
16497
  const maxRecords = options.maxRecords ?? 1e4;
16223
16498
  const warnings = /* @__PURE__ */ new Set();
16224
16499
  const parallel = options.fetchParallel ?? 1;
@@ -16248,6 +16523,7 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16248
16523
  throw new Error("internal error: relative-date prefilter must disable original WHERE pushdown.");
16249
16524
  }
16250
16525
  const mainFetchCondition = prefilterPlan ? prefilterPlan.prefilterWhere : mainPushDown;
16526
+ const scalarCache = await resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
16251
16527
  const constantFalse = isConstantFalseWhere(stmt.where);
16252
16528
  const mainFetch = constantFalse ? Promise.resolve([]) : fetchTableRecordsForFullScan(
16253
16529
  stmt,
@@ -16259,7 +16535,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16259
16535
  options.onLimitReached ?? "error",
16260
16536
  warnings,
16261
16537
  mainFetchCondition,
16262
- allowOriginalWherePushdown
16538
+ allowOriginalWherePushdown,
16539
+ plainGroupByPlan
16263
16540
  );
16264
16541
  const parallelJoins = [];
16265
16542
  const onOptJoins = [];
@@ -16281,17 +16558,16 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16281
16558
  false,
16282
16559
  options.onLimitReached ?? "error",
16283
16560
  warnings,
16284
- jCond
16561
+ jCond,
16562
+ true,
16563
+ plainGroupByPlan
16285
16564
  )
16286
16565
  });
16287
16566
  } else {
16288
16567
  onOptJoins.push(join2);
16289
16568
  }
16290
16569
  }
16291
- const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
16292
16570
  const orderByMetaPromise = preloadedOrderMeta ? Promise.resolve(preloadedOrderMeta) : buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
16293
- scalarCachePromise.catch(() => {
16294
- });
16295
16571
  orderByMetaPromise.catch(() => {
16296
16572
  });
16297
16573
  const mainRecords = await mainFetch;
@@ -16310,7 +16586,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16310
16586
  parallel,
16311
16587
  options.onLimitReached ?? "error",
16312
16588
  warnings,
16313
- null
16589
+ null,
16590
+ plainGroupByPlan
16314
16591
  );
16315
16592
  const joinRecords = optimized ?? await fetchTableRecordsForFullScan(
16316
16593
  stmt,
@@ -16321,11 +16598,12 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16321
16598
  false,
16322
16599
  options.onLimitReached ?? "error",
16323
16600
  warnings,
16324
- null
16601
+ null,
16602
+ true,
16603
+ plainGroupByPlan
16325
16604
  );
16326
16605
  tables.set(join2.table.alias, joinRecords);
16327
16606
  }));
16328
- const scalarCache = await scalarCachePromise;
16329
16607
  const { optionOrders, sortKinds, semantics } = await orderByMetaPromise;
16330
16608
  const { rows, columns } = runFullScan({
16331
16609
  tables,
@@ -16341,7 +16619,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16341
16619
  aggregateSortKindResolver,
16342
16620
  appliedKlikes: prefilterPlan?.appliedKlikes ?? pushdownPlan.appliedKlikes,
16343
16621
  ...prefilterPlan ? { residualWhere: prefilterPlan.residualWhere } : {},
16344
- resolvedGroupingSpec: resolvedGroupingSpecs.get(stmt)
16622
+ resolvedGroupingSpec: resolvedGroupingSpecs.get(stmt),
16623
+ plainGroupByPlan
16345
16624
  });
16346
16625
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
16347
16626
  }
@@ -16434,6 +16713,12 @@ async function executeQueryWithCte(query, client, options, cteCache, cacheContex
16434
16713
  async function executeFullScanWithCte(stmt, client, options, cteCache, cacheContext) {
16435
16714
  await validateSelectGroupingPlanning(stmt, client, cacheContext, cteCache);
16436
16715
  const resolvedGroupingSpec = resolvedGroupingSpecs.get(stmt);
16716
+ const plainGroupByPlan = await buildRuntimePlainGroupByPlan(
16717
+ stmt,
16718
+ client,
16719
+ cacheContext,
16720
+ cteCache
16721
+ );
16437
16722
  const hiddenQualifiedAliases = /* @__PURE__ */ new Set();
16438
16723
  const withEffectiveAlias = (table) => {
16439
16724
  if (table.alias !== null || table.cteName === null) return table;
@@ -16505,10 +16790,14 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
16505
16790
  const havingFieldSemanticsResolver = buildHavingFieldSemanticsResolver(stmt, fieldSemanticsResolver);
16506
16791
  const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
16507
16792
  validateKlikePushdownPlan(pushdownPlan);
16508
- const scalarCachePromise = resolveScalarColumns(stmt.columns, client, effectiveOptions, cacheContext, cteCache);
16793
+ const scalarCache = await resolveScalarColumns(
16794
+ stmt.columns,
16795
+ client,
16796
+ effectiveOptions,
16797
+ cacheContext,
16798
+ cteCache
16799
+ );
16509
16800
  const orderByMetaPromise = Promise.resolve(orderMeta);
16510
- scalarCachePromise.catch(() => {
16511
- });
16512
16801
  orderByMetaPromise.catch(() => {
16513
16802
  });
16514
16803
  const tables = /* @__PURE__ */ new Map();
@@ -16528,7 +16817,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
16528
16817
  effectiveOptions.onLimitReached ?? "error",
16529
16818
  warnings,
16530
16819
  pushdownPlan.mainCondition,
16531
- whereCapability.capability === "EXACT_PUSHDOWN"
16820
+ whereCapability.capability === "EXACT_PUSHDOWN",
16821
+ plainGroupByPlan
16532
16822
  ));
16533
16823
  tables.set(stmt.from.alias, mainRecords);
16534
16824
  }
@@ -16548,7 +16838,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
16548
16838
  parallel,
16549
16839
  effectiveOptions.onLimitReached ?? "error",
16550
16840
  warnings,
16551
- pushDownCond
16841
+ pushDownCond,
16842
+ plainGroupByPlan
16552
16843
  );
16553
16844
  const joinRecords = optimized ?? await fetchTableRecordsForFullScan(
16554
16845
  stmt,
@@ -16559,15 +16850,16 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
16559
16850
  false,
16560
16851
  effectiveOptions.onLimitReached ?? "error",
16561
16852
  warnings,
16562
- pushDownCond
16853
+ pushDownCond,
16854
+ true,
16855
+ plainGroupByPlan
16563
16856
  );
16564
16857
  tables.set(join2.table.alias, joinRecords);
16565
16858
  }
16566
16859
  }));
16567
16860
  await Promise.all(joinFetches);
16568
- const scalarCache = await scalarCachePromise;
16569
16861
  const { optionOrders, sortKinds, semantics } = await orderByMetaPromise;
16570
- const sourceColumns = stmt.joins.length === 0 && stmt.from.cteName != null ? requireMaterializedTable(stmt.from.cteName).columns : void 0;
16862
+ const sourceColumns2 = stmt.joins.length === 0 && stmt.from.cteName != null ? requireMaterializedTable(stmt.from.cteName).columns : void 0;
16571
16863
  const { rows, columns } = runFullScan({
16572
16864
  tables,
16573
16865
  stmt,
@@ -16581,10 +16873,11 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
16581
16873
  havingFieldSemanticsResolver,
16582
16874
  aggregateSortKindResolver,
16583
16875
  appliedKlikes: pushdownPlan.appliedKlikes,
16584
- sourceColumns,
16876
+ sourceColumns: sourceColumns2,
16585
16877
  tableColumns,
16586
16878
  hiddenQualifiedAliases,
16587
- resolvedGroupingSpec
16879
+ resolvedGroupingSpec,
16880
+ plainGroupByPlan
16588
16881
  });
16589
16882
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
16590
16883
  }
@@ -16593,8 +16886,8 @@ function processRowToKintoneRecord(row) {
16593
16886
  Object.entries(row).map(([k, v]) => [k, { value: v ?? "" }])
16594
16887
  );
16595
16888
  }
16596
- async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, parallel, isMainTable, onLimit, warnings, pushDownCond = null, allowOriginalWherePushdown = true) {
16597
- const fields = selectToFetchAllFields(stmt, table);
16889
+ async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, parallel, isMainTable, onLimit, warnings, pushDownCond = null, allowOriginalWherePushdown = true, plainGroupByPlan) {
16890
+ const fields = selectToFetchAllFields(stmt, table, plainGroupByPlan);
16598
16891
  const onTruncate = (max) => {
16599
16892
  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
16893
  };
@@ -16708,7 +17001,7 @@ function splitChunks(items, size) {
16708
17001
  var JOIN_IN_CHUNK_SIZE = 50;
16709
17002
  var JOIN_IN_MAX_CHUNKS = 6;
16710
17003
  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) {
17004
+ async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxRecords, parallel, onLimit, warnings, pushDownCond = null, plainGroupByPlan) {
16712
17005
  if (join2.type !== "INNER") return null;
16713
17006
  if (!join2.table.alias) return null;
16714
17007
  if (join2.table.subtableCode) return null;
@@ -16745,7 +17038,7 @@ async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxR
16745
17038
  );
16746
17039
  return null;
16747
17040
  }
16748
- const fields = selectToFetchAllFields(stmt, join2.table);
17041
+ const fields = selectToFetchAllFields(stmt, join2.table, plainGroupByPlan);
16749
17042
  const onTruncate = (max) => {
16750
17043
  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
17044
  };
@@ -16787,9 +17080,6 @@ function setScopedCacheValue(root, cacheContext, appId, value) {
16787
17080
  }
16788
17081
  scoped.set(appId, value);
16789
17082
  }
16790
- function isSystemLikeFieldCode(code) {
16791
- return code.startsWith("_") || code.startsWith("$");
16792
- }
16793
17083
  async function getFieldsCached(appId, client, cacheContext) {
16794
17084
  const cached = getScopedCacheValue(fieldInfoCache, cacheContext, appId);
16795
17085
  if (cached) return cached;
@@ -19985,6 +20275,7 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
19985
20275
  };
19986
20276
  const capabilities = /* @__PURE__ */ new Map();
19987
20277
  const orderPlans = /* @__PURE__ */ new Map();
20278
+ const plainGroupByPlans = /* @__PURE__ */ new Map();
19988
20279
  const seen = /* @__PURE__ */ new Set();
19989
20280
  const sharedRelativeDatePlan = relativeDatePlan ?? await resolveRelativeDateExecutionPlan(query, tracedClient, cacheContext);
19990
20281
  const relativeNodeFor = (source) => sharedRelativeDatePlan.nodes.find(
@@ -20002,6 +20293,15 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
20002
20293
  if (typed["type"] === "SELECT") {
20003
20294
  const select = node;
20004
20295
  await validateSelectGroupingPlanning(select, tracedClient, cacheContext);
20296
+ const hasUnmaterializedSource = [select.from, ...select.joins.map((join2) => join2.table)].some((table) => table.cteName !== null);
20297
+ if (normalizeGroupingSpec(select).type === "PLAIN" && !hasUnmaterializedSource) {
20298
+ const plainPlan = await buildRuntimePlainGroupByPlan(
20299
+ select,
20300
+ tracedClient,
20301
+ cacheContext
20302
+ );
20303
+ if (plainPlan) plainGroupByPlans.set(select, plainPlan);
20304
+ }
20005
20305
  const physicalApps = [select.from, ...select.joins.map((join2) => join2.table)].filter((table) => table.cteName === null).map((table) => table.appId);
20006
20306
  const needsWhereSchema = whereNeedsFieldMetadata(select.where);
20007
20307
  if (needsWhereSchema || select.orderBy.length > 0 || select.columns.some((column) => column.type === "WINDOW_COL" && column.orderBy.length > 0)) {
@@ -20022,7 +20322,6 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
20022
20322
  }
20023
20323
  }
20024
20324
  }
20025
- const hasUnmaterializedSource = [select.from, ...select.joins.map((join2) => join2.table)].some((table) => table.cteName !== null);
20026
20325
  if (hasCanonicalOrder(select) && !hasUnmaterializedSource && relativeNode?.allowed !== false) {
20027
20326
  const mode = capability.capability === "EXACT_PUSHDOWN" ? resolveSelectMode(select) : "FULL_SCAN";
20028
20327
  orderPlans.set(select, (select.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
@@ -20136,6 +20435,7 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
20136
20435
  return {
20137
20436
  capabilities,
20138
20437
  orderPlans,
20438
+ plainGroupByPlans,
20139
20439
  fieldApps,
20140
20440
  processStatusApps,
20141
20441
  numberPrecisionApps,
@@ -20459,7 +20759,8 @@ async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxA
20459
20759
  analysis.orderPlans,
20460
20760
  dmlMaxRows,
20461
20761
  dmlMaxSubtableRows,
20462
- maxRecords
20762
+ maxRecords,
20763
+ analysis.plainGroupByPlans
20463
20764
  ),
20464
20765
  cursorMaxActive
20465
20766
  )
@@ -20482,13 +20783,13 @@ function addCursorConcurrency(lines, cursorMaxActive) {
20482
20783
  }
20483
20784
  return result;
20484
20785
  }
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);
20786
+ function buildExplainPlan(query, label, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, maxRecords = 1e4, plainGroupByPlans) {
20787
+ if (query.type === "UNION") return buildUnionPlan(query, capabilities, orderPlans, plainGroupByPlans);
20788
+ if (query.type === "WITH") return buildWithPlan(query, capabilities, orderPlans, plainGroupByPlans);
20488
20789
  if (query.type === "INSERT") return buildInsertPlan(query, label, dmlMaxRows, dmlMaxSubtableRows);
20489
- if (query.type === "INSERT_SELECT") return buildInsertSelectPlan(query, label, capabilities, orderPlans);
20790
+ if (query.type === "INSERT_SELECT") return buildInsertSelectPlan(query, label, capabilities, orderPlans, plainGroupByPlans);
20490
20791
  if (query.type === "UPSERT") return buildUpsertPlan(query, label, dmlMaxRows, dmlMaxSubtableRows);
20491
- if (query.type === "UPSERT_SELECT") return buildUpsertSelectPlan(query, label, capabilities, orderPlans);
20792
+ if (query.type === "UPSERT_SELECT") return buildUpsertSelectPlan(query, label, capabilities, orderPlans, plainGroupByPlans);
20492
20793
  if (query.type === "UPDATE") return buildUpdatePlan(
20493
20794
  query,
20494
20795
  label,
@@ -20574,7 +20875,7 @@ function buildExplainPlan(query, label, capabilities, orderPlans, dmlMaxRows = 1
20574
20875
  ` duplicateKey: preflight before lookup/write (requires load)`
20575
20876
  ];
20576
20877
  }
20577
- return buildSelectPlan(query, label, capabilities, orderPlans);
20878
+ return buildSelectPlan(query, label, capabilities, orderPlans, plainGroupByPlans);
20578
20879
  }
20579
20880
  function buildValidatePlan(stmt, label) {
20580
20881
  const info = validateExplainInfo.get(stmt);
@@ -20601,9 +20902,10 @@ function buildValidatePlan(stmt, label) {
20601
20902
  lines.push(" records/mutation API during EXPLAIN: none; violation count unavailable");
20602
20903
  return lines;
20603
20904
  }
20604
- function buildSelectPlan(stmt, label, capabilities, orderPlans) {
20905
+ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlans) {
20605
20906
  const whereCapability = capabilities?.get(stmt) ?? (capabilities ? [...capabilities].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
20606
20907
  const orderPlan = orderPlans?.get(stmt) ?? (orderPlans ? [...orderPlans].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
20908
+ const plainGroupByPlan = plainGroupByPlans?.get(stmt) ?? (plainGroupByPlans ? [...plainGroupByPlans].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
20607
20909
  const mode = orderPlan?.kind === "CANONICAL_LOCAL" ? "FULL_SCAN" : whereCapability && whereCapability.capability !== "EXACT_PUSHDOWN" ? "FULL_SCAN" : resolveSelectMode(stmt);
20608
20910
  const reasons = collectFullScanReasons(stmt);
20609
20911
  if (whereCapability && whereCapability.capability !== "EXACT_PUSHDOWN") {
@@ -20628,6 +20930,29 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
20628
20930
  ` grouping output rows: runtime checked (limit: ${groupingMetadata.outputRowLimit}, before HAVING/DISTINCT/LIMIT)`
20629
20931
  );
20630
20932
  }
20933
+ const normalizedGrouping = normalizeGroupingSpec(stmt);
20934
+ if (normalizedGrouping.type === "PLAIN") {
20935
+ const groupBy = normalizedGrouping.allItems;
20936
+ if (plainGroupByPlan) {
20937
+ plainGroupByPlan.items.forEach((item, index) => {
20938
+ const key = groupBy[index];
20939
+ if (key?.type !== "FIELD_NAME") return;
20940
+ if (item.kind === "PHYSICAL") {
20941
+ lines.push(
20942
+ ` group key ${key.name}: PHYSICAL (source=${item.sourceIndex}, field=${item.fieldCode})`
20943
+ );
20944
+ }
20945
+ });
20946
+ } else if ([stmt.from, ...stmt.joins.map((join2) => join2.table)].some((table) => table.cteName !== null)) {
20947
+ for (const key of groupBy) {
20948
+ if (key.type === "FIELD_NAME") {
20949
+ lines.push(
20950
+ ` group key ${key.name}: DEFERRED (materialized schema unavailable)`
20951
+ );
20952
+ }
20953
+ }
20954
+ }
20955
+ }
20631
20956
  if (orderPlan) {
20632
20957
  lines.push(` order plan: ${orderPlan.kind}`);
20633
20958
  if (orderPlan.reasonCodes.length > 0) lines.push(` order reason: ${orderPlan.reasonCodes.join(", ")}`);
@@ -20669,7 +20994,7 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
20669
20994
  lines.push(` fields: ${params.fields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : params.fields.join(", ")}`);
20670
20995
  } else {
20671
20996
  const pushdownPlan = buildKlikePushdownPlan(stmt);
20672
- const mainFields = selectToFetchAllFields(stmt, stmt.from);
20997
+ const mainFields = selectToFetchAllFields(stmt, stmt.from, plainGroupByPlan);
20673
20998
  const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
20674
20999
  const mainPushDown = pushdownPlan.mainCondition;
20675
21000
  const mainCandidate = extractMainTypedPushdownCandidate(stmt);
@@ -20682,7 +21007,7 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
20682
21007
  }
20683
21008
  lines.push(` fields: ${mainFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : mainFields.join(", ")}`);
20684
21009
  for (const join2 of stmt.joins) {
20685
- const joinFields = selectToFetchAllFields(stmt, join2.table);
21010
+ const joinFields = selectToFetchAllFields(stmt, join2.table, plainGroupByPlan);
20686
21011
  const joinAliasStr = join2.table.alias ? ` AS ${join2.table.alias}` : "";
20687
21012
  const joinType = join2.type === "INNER" ? "JOIN" : `${join2.type} JOIN`;
20688
21013
  const joinPushDown = join2.table.alias ? pushdownPlan.joinConditions.get(join2.table.alias) ?? null : null;
@@ -20696,10 +21021,10 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
20696
21021
  lines.push(` fields: ${joinFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : joinFields.join(", ")}`);
20697
21022
  }
20698
21023
  }
20699
- lines.push(...collectSubqueryPlans(stmt, capabilities, orderPlans));
21024
+ lines.push(...collectSubqueryPlans(stmt, capabilities, orderPlans, plainGroupByPlans));
20700
21025
  return lines;
20701
21026
  }
20702
- function buildUnionPlan(stmt, capabilities, orderPlans) {
21027
+ function buildUnionPlan(stmt, capabilities, orderPlans, plainGroupByPlans) {
20703
21028
  const selects = [];
20704
21029
  const collect = (u) => {
20705
21030
  if (u.type === "SELECT") {
@@ -20713,25 +21038,34 @@ function buildUnionPlan(stmt, capabilities, orderPlans) {
20713
21038
  const lines = [];
20714
21039
  selects.forEach((sel, i) => {
20715
21040
  if (i > 0) lines.push("");
20716
- lines.push(...buildSelectPlan(sel, `[union:${i + 1}]`, capabilities, orderPlans));
21041
+ lines.push(...buildSelectPlan(sel, `[union:${i + 1}]`, capabilities, orderPlans, plainGroupByPlans));
20717
21042
  });
20718
21043
  return lines;
20719
21044
  }
20720
- function buildWithPlan(stmt, capabilities, orderPlans) {
21045
+ function buildWithPlan(stmt, capabilities, orderPlans, plainGroupByPlans) {
20721
21046
  const lines = [];
20722
21047
  for (const cte of stmt.ctes) {
20723
21048
  if (cte.query.type === "SELECT") {
20724
- lines.push(...buildSelectPlan(cte.query, `[cte: ${cte.name}]`, capabilities, orderPlans));
21049
+ lines.push(...buildSelectPlan(cte.query, `[cte: ${cte.name}]`, capabilities, orderPlans, plainGroupByPlans));
20725
21050
  lines.push("");
20726
21051
  }
20727
21052
  }
20728
21053
  if (stmt.query.type === "SELECT" || stmt.query.type === "UNION") {
20729
- lines.push(...buildExplainPlan(stmt.query, "[main]", capabilities, orderPlans));
21054
+ lines.push(...buildExplainPlan(
21055
+ stmt.query,
21056
+ "[main]",
21057
+ capabilities,
21058
+ orderPlans,
21059
+ 100,
21060
+ DEFAULT_APPLY_MAX_SUBTABLE_ROWS,
21061
+ 1e4,
21062
+ plainGroupByPlans
21063
+ ));
20730
21064
  }
20731
21065
  if (canInlineSingleCte(stmt)) {
20732
21066
  lines.push("");
20733
21067
  const inlined = buildInlinedQuery(stmt);
20734
- lines.push(...buildSelectPlan(inlined, "[effective: inlined CTE]", capabilities, orderPlans));
21068
+ lines.push(...buildSelectPlan(inlined, "[effective: inlined CTE]", capabilities, orderPlans, plainGroupByPlans));
20735
21069
  }
20736
21070
  return lines;
20737
21071
  }
@@ -20764,7 +21098,7 @@ function collectFullScanReasons(stmt) {
20764
21098
  r.push("ORDER BY \u306B\u5F0F");
20765
21099
  return r;
20766
21100
  }
20767
- function collectSubqueryPlans(stmt, capabilities, orderPlans) {
21101
+ function collectSubqueryPlans(stmt, capabilities, orderPlans, plainGroupByPlans) {
20768
21102
  const lines = [];
20769
21103
  let idx = 1;
20770
21104
  const visitWhere = (w) => {
@@ -20773,16 +21107,16 @@ function collectSubqueryPlans(stmt, capabilities, orderPlans) {
20773
21107
  case "BINARY":
20774
21108
  if (w.right.type === "SCALAR_SUBQUERY") {
20775
21109
  lines.push("");
20776
- lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans));
21110
+ lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans));
20777
21111
  }
20778
21112
  if (w.right.type === "SUBQUERY_IN_LIST") {
20779
21113
  lines.push("");
20780
- lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans));
21114
+ lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans));
20781
21115
  }
20782
21116
  break;
20783
21117
  case "EXISTS":
20784
21118
  lines.push("");
20785
- lines.push(...buildSelectPlan(w.query, `[subquery:${idx++}]`, capabilities, orderPlans));
21119
+ lines.push(...buildSelectPlan(w.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans));
20786
21120
  break;
20787
21121
  case "LOGICAL":
20788
21122
  visitWhere(w.left);
@@ -20802,7 +21136,7 @@ function collectSubqueryPlans(stmt, capabilities, orderPlans) {
20802
21136
  for (const col of stmt.columns) {
20803
21137
  if (col.type === "SCALAR_SUBQUERY_COL") {
20804
21138
  lines.push("");
20805
- lines.push(...buildSelectPlan(col.query, `[subquery:${idx++}]`, capabilities, orderPlans));
21139
+ lines.push(...buildSelectPlan(col.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans));
20806
21140
  }
20807
21141
  }
20808
21142
  if (stmt.having) visitWhere(stmt.having);
@@ -20820,7 +21154,7 @@ function buildInsertPlan(stmt, label, dmlMaxRows = DEFAULT_APPLY_MAX_ROWS, dmlMa
20820
21154
  lines.push(` fields: ${stmt.fields.join(", ")}`);
20821
21155
  return stmt.applyBlocks?.length ? [...lines, ...formatStaticApplyDiagnostic(buildStaticApplyDiagnostic(stmt, dmlMaxRows, dmlMaxSubtableRows))] : lines;
20822
21156
  }
20823
- function buildInsertSelectPlan(stmt, label, capabilities, orderPlans) {
21157
+ function buildInsertSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlans) {
20824
21158
  const lines = [];
20825
21159
  if (label) lines.push(label);
20826
21160
  lines.push(` [INSERT SELECT]`);
@@ -20828,7 +21162,7 @@ function buildInsertSelectPlan(stmt, label, capabilities, orderPlans) {
20828
21162
  lines.push(` fields: ${stmt.fields.join(", ")}`);
20829
21163
  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
21164
  lines.push("");
20831
- lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans));
21165
+ lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans, plainGroupByPlans));
20832
21166
  return lines;
20833
21167
  }
20834
21168
  function buildUpdatePlan(stmt, label, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, maxRecords = 1e4) {
@@ -20982,7 +21316,7 @@ function formatStaticApplyDiagnostic(diagnostic2) {
20982
21316
  lines.push("records API: 0", "mutation API: 0");
20983
21317
  return lines;
20984
21318
  }
20985
- function buildUpsertSelectPlan(stmt, label, capabilities, orderPlans) {
21319
+ function buildUpsertSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlans) {
20986
21320
  const lines = [
20987
21321
  ...label ? [label] : [],
20988
21322
  ` [UPSERT SELECT]`,
@@ -20992,7 +21326,7 @@ function buildUpsertSelectPlan(stmt, label, capabilities, orderPlans) {
20992
21326
  ` 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
21327
  ``
20994
21328
  ];
20995
- lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans));
21329
+ lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans, plainGroupByPlans));
20996
21330
  return lines;
20997
21331
  }
20998
21332
  function buildReorderPlan(stmt, label) {