@rex0220/kintone-sql-tools 3.21.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
  );
@@ -13927,7 +14123,12 @@ async function execute(sql, client, options = {}) {
13927
14123
  cacheContext
13928
14124
  );
13929
14125
  metrics.elapsedMs = Date.now() - startedAt;
13930
- return { ...attachSearchAbortWarning(result, collector), metrics };
14126
+ const finalResult = { ...attachSearchAbortWarning(result, collector), metrics };
14127
+ if (result.type === "SELECT") {
14128
+ const columnMeta = materializedMetaBySelectResult.get(result);
14129
+ if (columnMeta) materializedMetaBySelectResult.set(finalResult, columnMeta);
14130
+ }
14131
+ return finalResult;
13931
14132
  }
13932
14133
  function createEmptyMetrics() {
13933
14134
  return {
@@ -14138,11 +14339,26 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
14138
14339
  case "VALIDATE":
14139
14340
  return executeExistingRecordValidation(stmt, client, options, cacheContext);
14140
14341
  case "SELECT":
14141
- return executeSelect(stmt, client, options, cacheContext);
14342
+ return executeSelect(
14343
+ stmt,
14344
+ client,
14345
+ options,
14346
+ cacheContext,
14347
+ void 0,
14348
+ options.captureColumnMeta === true,
14349
+ options.captureColumnMeta === true
14350
+ );
14142
14351
  case "UNION":
14143
- return executeUnion(stmt, client, options, cacheContext);
14352
+ return executeUnion(
14353
+ stmt,
14354
+ client,
14355
+ options,
14356
+ cacheContext,
14357
+ options.captureColumnMeta === true,
14358
+ options.captureColumnMeta === true
14359
+ );
14144
14360
  case "WITH":
14145
- return executeWith(stmt, client, options, cacheContext);
14361
+ return executeWith(stmt, client, options, cacheContext, void 0, options.captureColumnMeta === true);
14146
14362
  case "INSERT":
14147
14363
  return executeInsert(stmt, client, options, cacheContext);
14148
14364
  case "INSERT_SELECT":
@@ -15196,16 +15412,25 @@ async function assertDmlWhereCapability(stmt, client, cacheContext) {
15196
15412
  );
15197
15413
  }
15198
15414
  }
15199
- async function executeSelect(stmt, client, options, cacheContext, cteCache, captureColumnMeta = false) {
15415
+ async function executeSelect(stmt, client, options, cacheContext, cteCache, captureColumnMeta = false, forLibraryCapture = false) {
15200
15416
  let result;
15201
15417
  await validateSelectGroupingPlanning(stmt, client, cacheContext, cteCache);
15202
15418
  if (isNoFromSelect(stmt)) {
15203
15419
  result = executeNoFromSelect(stmt);
15204
15420
  if (captureColumnMeta) {
15205
- materializedMetaBySelectResult.set(result, await inferSelectColumnMeta(stmt, result.columns, client, cacheContext, cteCache));
15421
+ materializedMetaBySelectResult.set(
15422
+ result,
15423
+ await inferSelectColumnMeta(stmt, result.columns, client, cacheContext, cteCache, forLibraryCapture)
15424
+ );
15206
15425
  }
15207
15426
  return result;
15208
15427
  }
15428
+ const plainGroupByPlan = await buildRuntimePlainGroupByPlan(
15429
+ stmt,
15430
+ client,
15431
+ cacheContext,
15432
+ cteCache
15433
+ );
15209
15434
  await resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache);
15210
15435
  const whereCapability = await resolveSelectWhereCapability(stmt, client, cacheContext, cteCache);
15211
15436
  if (whereCapability.capability === "UNSUPPORTED") {
@@ -15250,17 +15475,93 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
15250
15475
  cteCache,
15251
15476
  whereCapability.capability === "EXACT_PUSHDOWN",
15252
15477
  orderMeta,
15253
- prefilterPlan
15478
+ prefilterPlan,
15479
+ plainGroupByPlan
15254
15480
  );
15255
15481
  }
15256
15482
  } catch (error) {
15257
15483
  throwCompleteInputError(completePolicy, error);
15258
15484
  }
15259
15485
  if (captureColumnMeta) {
15260
- materializedMetaBySelectResult.set(result, await inferSelectColumnMeta(stmt, result.columns, client, cacheContext, cteCache));
15486
+ materializedMetaBySelectResult.set(
15487
+ result,
15488
+ await inferSelectColumnMeta(stmt, result.columns, client, cacheContext, cteCache, forLibraryCapture)
15489
+ );
15261
15490
  }
15262
15491
  return result;
15263
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
+ }
15264
15565
  var resolvedGroupingSpecs = /* @__PURE__ */ new WeakMap();
15265
15566
  async function validateStatementGroupingPlanning(statement, client, cacheContext) {
15266
15567
  const seen = /* @__PURE__ */ new Set();
@@ -16025,7 +16326,7 @@ function selectNeedsSourceColumnMeta(stmt) {
16025
16326
  (column) => column.type === "FIELD" || column.type === "WILDCARD" || column.type === "PARENT_WILDCARD" || column.type === "CASE_COL" || column.type === "AGGREGATE" && (column.func === "MIN" || column.func === "MAX" || column.func === "MODE")
16026
16327
  );
16027
16328
  }
16028
- async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext, materializedTables) {
16329
+ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext, materializedTables, forLibraryCapture = false) {
16029
16330
  const physicalInfos = /* @__PURE__ */ new Map();
16030
16331
  if (selectNeedsSourceColumnMeta(stmt)) {
16031
16332
  await Promise.all(physicalSelectTables(stmt).map(async (table) => {
@@ -16035,6 +16336,7 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
16035
16336
  }));
16036
16337
  }
16037
16338
  const tables = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
16339
+ const canExposePublicSource = forLibraryCapture && materializedTables === void 0 && tables.every((table) => table.cteName === null);
16038
16340
  const resolveField2 = (ref) => {
16039
16341
  if (ref.tableAlias !== null) {
16040
16342
  if (ref.tableAlias === "_p" && stmt.from.subtableCode && stmt.from.cteName === null) {
@@ -16064,18 +16366,33 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
16064
16366
  });
16065
16367
  return matches.length === 1 ? matches[0] : void 0;
16066
16368
  };
16369
+ const resolvePublicSourceApp = (ref) => {
16370
+ if (!canExposePublicSource) return void 0;
16371
+ const matches = tables.filter((table) => {
16372
+ if (ref.tableAlias !== null && effectiveTableAlias(table) !== ref.tableAlias) return false;
16373
+ const fieldCode = fieldCodeForTypeLookup(table, ref.field);
16374
+ return physicalInfos.get(table.appId)?.has(fieldCode) === true || systemColumnMeta(ref.field) !== void 0;
16375
+ });
16376
+ return matches.length === 1 ? matches[0].appId : void 0;
16377
+ };
16378
+ const withPublicSource = (meta, ref) => {
16379
+ const publicSourceApp = resolvePublicSourceApp(ref);
16380
+ return meta && publicSourceApp !== void 0 ? { ...meta, publicSourceApp } : meta;
16381
+ };
16067
16382
  const inferred = /* @__PURE__ */ new Map();
16068
16383
  const hasWildcard = stmt.columns.some((column) => column.type === "WILDCARD" || column.type === "PARENT_WILDCARD");
16069
16384
  if (stmt.columns.length === 1 && (stmt.columns[0].type === "WILDCARD" || stmt.columns[0].type === "PARENT_WILDCARD")) {
16070
16385
  for (const output of outputColumns) {
16071
- const meta = resolveField2(aggregateFieldRef(output));
16386
+ const ref = aggregateFieldRef(output);
16387
+ const meta = withPublicSource(resolveField2(ref), ref);
16072
16388
  if (meta) inferred.set(output, meta);
16073
16389
  }
16074
16390
  return inferred;
16075
16391
  }
16076
16392
  if (hasWildcard) {
16077
16393
  for (const output of outputColumns) {
16078
- const meta = resolveField2(aggregateFieldRef(output));
16394
+ const ref = aggregateFieldRef(output);
16395
+ const meta = withPublicSource(resolveField2(ref), ref);
16079
16396
  if (meta) inferred.set(output, meta);
16080
16397
  }
16081
16398
  }
@@ -16087,7 +16404,8 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
16087
16404
  if (!output) return;
16088
16405
  let meta;
16089
16406
  if (column.type === "FIELD") {
16090
- meta = resolveField2(aggregateFieldRef(column.field));
16407
+ const ref = aggregateFieldRef(column.field);
16408
+ meta = withPublicSource(resolveField2(ref), ref);
16091
16409
  } else if (column.type === "AGGREGATE") {
16092
16410
  if (column.func === "GROUP_CONCAT") {
16093
16411
  meta = syntheticColumnMeta("string");
@@ -16131,8 +16449,15 @@ function mergeUnionColumnMeta(left, right) {
16131
16449
  const a = leftMeta?.get(column);
16132
16450
  const rightColumn = right.columns[index];
16133
16451
  const b = rightColumn === void 0 ? void 0 : rightMeta?.get(rightColumn);
16134
- if (a && b) merged.set(column, mergeExpressionColumnMeta([a, b]));
16135
- else if (a || b) merged.set(column, unknownStringColumnMeta());
16452
+ if (a && b) {
16453
+ const combined = mergeExpressionColumnMeta([a, b]);
16454
+ const publicSourceApp = a.publicSourceApp === b.publicSourceApp ? a.publicSourceApp : void 0;
16455
+ const { publicSourceApp: _discarded, ...withoutPublicSource } = combined;
16456
+ merged.set(
16457
+ column,
16458
+ publicSourceApp === void 0 ? withoutPublicSource : { ...withoutPublicSource, publicSourceApp }
16459
+ );
16460
+ } else if (a || b) merged.set(column, unknownStringColumnMeta());
16136
16461
  });
16137
16462
  return merged;
16138
16463
  }
@@ -16168,7 +16493,7 @@ function buildSelectFieldTypeResolvers(stmt, fieldTypesByApp) {
16168
16493
  };
16169
16494
  return { row, having };
16170
16495
  }
16171
- 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) {
16172
16497
  const maxRecords = options.maxRecords ?? 1e4;
16173
16498
  const warnings = /* @__PURE__ */ new Set();
16174
16499
  const parallel = options.fetchParallel ?? 1;
@@ -16198,6 +16523,7 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16198
16523
  throw new Error("internal error: relative-date prefilter must disable original WHERE pushdown.");
16199
16524
  }
16200
16525
  const mainFetchCondition = prefilterPlan ? prefilterPlan.prefilterWhere : mainPushDown;
16526
+ const scalarCache = await resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
16201
16527
  const constantFalse = isConstantFalseWhere(stmt.where);
16202
16528
  const mainFetch = constantFalse ? Promise.resolve([]) : fetchTableRecordsForFullScan(
16203
16529
  stmt,
@@ -16209,7 +16535,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16209
16535
  options.onLimitReached ?? "error",
16210
16536
  warnings,
16211
16537
  mainFetchCondition,
16212
- allowOriginalWherePushdown
16538
+ allowOriginalWherePushdown,
16539
+ plainGroupByPlan
16213
16540
  );
16214
16541
  const parallelJoins = [];
16215
16542
  const onOptJoins = [];
@@ -16231,17 +16558,16 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16231
16558
  false,
16232
16559
  options.onLimitReached ?? "error",
16233
16560
  warnings,
16234
- jCond
16561
+ jCond,
16562
+ true,
16563
+ plainGroupByPlan
16235
16564
  )
16236
16565
  });
16237
16566
  } else {
16238
16567
  onOptJoins.push(join2);
16239
16568
  }
16240
16569
  }
16241
- const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
16242
16570
  const orderByMetaPromise = preloadedOrderMeta ? Promise.resolve(preloadedOrderMeta) : buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
16243
- scalarCachePromise.catch(() => {
16244
- });
16245
16571
  orderByMetaPromise.catch(() => {
16246
16572
  });
16247
16573
  const mainRecords = await mainFetch;
@@ -16260,7 +16586,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16260
16586
  parallel,
16261
16587
  options.onLimitReached ?? "error",
16262
16588
  warnings,
16263
- null
16589
+ null,
16590
+ plainGroupByPlan
16264
16591
  );
16265
16592
  const joinRecords = optimized ?? await fetchTableRecordsForFullScan(
16266
16593
  stmt,
@@ -16271,11 +16598,12 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16271
16598
  false,
16272
16599
  options.onLimitReached ?? "error",
16273
16600
  warnings,
16274
- null
16601
+ null,
16602
+ true,
16603
+ plainGroupByPlan
16275
16604
  );
16276
16605
  tables.set(join2.table.alias, joinRecords);
16277
16606
  }));
16278
- const scalarCache = await scalarCachePromise;
16279
16607
  const { optionOrders, sortKinds, semantics } = await orderByMetaPromise;
16280
16608
  const { rows, columns } = runFullScan({
16281
16609
  tables,
@@ -16291,14 +16619,15 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16291
16619
  aggregateSortKindResolver,
16292
16620
  appliedKlikes: prefilterPlan?.appliedKlikes ?? pushdownPlan.appliedKlikes,
16293
16621
  ...prefilterPlan ? { residualWhere: prefilterPlan.residualWhere } : {},
16294
- resolvedGroupingSpec: resolvedGroupingSpecs.get(stmt)
16622
+ resolvedGroupingSpec: resolvedGroupingSpecs.get(stmt),
16623
+ plainGroupByPlan
16295
16624
  });
16296
16625
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
16297
16626
  }
16298
- async function executeUnion(stmt, client, options, cacheContext) {
16627
+ async function executeUnion(stmt, client, options, cacheContext, captureColumnMeta = false, forLibraryCapture = false) {
16299
16628
  const [leftResult, rightResult] = await Promise.all([
16300
- stmt.left.type === "UNION" ? executeUnion(stmt.left, client, options, cacheContext) : executeSelect(stmt.left, client, options, cacheContext),
16301
- executeSelect(stmt.right, client, options, cacheContext)
16629
+ stmt.left.type === "UNION" ? executeUnion(stmt.left, client, options, cacheContext, captureColumnMeta, forLibraryCapture) : executeSelect(stmt.left, client, options, cacheContext, void 0, captureColumnMeta, forLibraryCapture),
16630
+ executeSelect(stmt.right, client, options, cacheContext, void 0, captureColumnMeta, forLibraryCapture)
16302
16631
  ]);
16303
16632
  const leftCols = leftResult.columns;
16304
16633
  const rightCols = rightResult.columns;
@@ -16311,7 +16640,11 @@ async function executeUnion(stmt, client, options, cacheContext) {
16311
16640
  });
16312
16641
  const combined = [...leftResult.rows, ...remappedRight];
16313
16642
  const rows = stmt.all ? combined : deduplicateRows(combined, leftCols);
16314
- return { type: "SELECT", rows, columns: leftCols, rowCount: rows.length };
16643
+ const result = { type: "SELECT", rows, columns: leftCols, rowCount: rows.length };
16644
+ if (captureColumnMeta) {
16645
+ materializedMetaBySelectResult.set(result, mergeUnionColumnMeta(leftResult, rightResult));
16646
+ }
16647
+ return result;
16315
16648
  }
16316
16649
  function deduplicateRows(rows, columns) {
16317
16650
  const seen = /* @__PURE__ */ new Set();
@@ -16324,7 +16657,7 @@ function deduplicateRows(rows, columns) {
16324
16657
  }
16325
16658
  async function executeWith(stmt, client, options, cacheContext, seed, captureColumnMeta = false) {
16326
16659
  if ((seed == null || seed.size === 0) && canInlineSingleCte(stmt)) {
16327
- return executeSelect(buildInlinedQuery(stmt), client, options, cacheContext, void 0, captureColumnMeta);
16660
+ return executeSelect(buildInlinedQuery(stmt), client, options, cacheContext, void 0, captureColumnMeta, false);
16328
16661
  }
16329
16662
  const cteCache = new Map(seed ?? []);
16330
16663
  for (const cte of stmt.ctes) {
@@ -16369,7 +16702,7 @@ async function executeQueryWithCte(query, client, options, cteCache, cacheContex
16369
16702
  }
16370
16703
  const hasCteRef = query.from.cteName != null && query.from.cteName !== NO_FROM_CTE_NAME || query.joins.some((j) => j.table.cteName != null);
16371
16704
  if (!hasCteRef) {
16372
- return executeSelect(query, client, options, cacheContext, cteCache, captureColumnMeta);
16705
+ return executeSelect(query, client, options, cacheContext, cteCache, captureColumnMeta, false);
16373
16706
  }
16374
16707
  const result = await executeFullScanWithCte(query, client, options, cteCache, cacheContext);
16375
16708
  if (captureColumnMeta) {
@@ -16380,6 +16713,12 @@ async function executeQueryWithCte(query, client, options, cteCache, cacheContex
16380
16713
  async function executeFullScanWithCte(stmt, client, options, cteCache, cacheContext) {
16381
16714
  await validateSelectGroupingPlanning(stmt, client, cacheContext, cteCache);
16382
16715
  const resolvedGroupingSpec = resolvedGroupingSpecs.get(stmt);
16716
+ const plainGroupByPlan = await buildRuntimePlainGroupByPlan(
16717
+ stmt,
16718
+ client,
16719
+ cacheContext,
16720
+ cteCache
16721
+ );
16383
16722
  const hiddenQualifiedAliases = /* @__PURE__ */ new Set();
16384
16723
  const withEffectiveAlias = (table) => {
16385
16724
  if (table.alias !== null || table.cteName === null) return table;
@@ -16451,10 +16790,14 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
16451
16790
  const havingFieldSemanticsResolver = buildHavingFieldSemanticsResolver(stmt, fieldSemanticsResolver);
16452
16791
  const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
16453
16792
  validateKlikePushdownPlan(pushdownPlan);
16454
- 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
+ );
16455
16800
  const orderByMetaPromise = Promise.resolve(orderMeta);
16456
- scalarCachePromise.catch(() => {
16457
- });
16458
16801
  orderByMetaPromise.catch(() => {
16459
16802
  });
16460
16803
  const tables = /* @__PURE__ */ new Map();
@@ -16474,7 +16817,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
16474
16817
  effectiveOptions.onLimitReached ?? "error",
16475
16818
  warnings,
16476
16819
  pushdownPlan.mainCondition,
16477
- whereCapability.capability === "EXACT_PUSHDOWN"
16820
+ whereCapability.capability === "EXACT_PUSHDOWN",
16821
+ plainGroupByPlan
16478
16822
  ));
16479
16823
  tables.set(stmt.from.alias, mainRecords);
16480
16824
  }
@@ -16494,7 +16838,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
16494
16838
  parallel,
16495
16839
  effectiveOptions.onLimitReached ?? "error",
16496
16840
  warnings,
16497
- pushDownCond
16841
+ pushDownCond,
16842
+ plainGroupByPlan
16498
16843
  );
16499
16844
  const joinRecords = optimized ?? await fetchTableRecordsForFullScan(
16500
16845
  stmt,
@@ -16505,15 +16850,16 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
16505
16850
  false,
16506
16851
  effectiveOptions.onLimitReached ?? "error",
16507
16852
  warnings,
16508
- pushDownCond
16853
+ pushDownCond,
16854
+ true,
16855
+ plainGroupByPlan
16509
16856
  );
16510
16857
  tables.set(join2.table.alias, joinRecords);
16511
16858
  }
16512
16859
  }));
16513
16860
  await Promise.all(joinFetches);
16514
- const scalarCache = await scalarCachePromise;
16515
16861
  const { optionOrders, sortKinds, semantics } = await orderByMetaPromise;
16516
- 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;
16517
16863
  const { rows, columns } = runFullScan({
16518
16864
  tables,
16519
16865
  stmt,
@@ -16527,10 +16873,11 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
16527
16873
  havingFieldSemanticsResolver,
16528
16874
  aggregateSortKindResolver,
16529
16875
  appliedKlikes: pushdownPlan.appliedKlikes,
16530
- sourceColumns,
16876
+ sourceColumns: sourceColumns2,
16531
16877
  tableColumns,
16532
16878
  hiddenQualifiedAliases,
16533
- resolvedGroupingSpec
16879
+ resolvedGroupingSpec,
16880
+ plainGroupByPlan
16534
16881
  });
16535
16882
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
16536
16883
  }
@@ -16539,8 +16886,8 @@ function processRowToKintoneRecord(row) {
16539
16886
  Object.entries(row).map(([k, v]) => [k, { value: v ?? "" }])
16540
16887
  );
16541
16888
  }
16542
- async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, parallel, isMainTable, onLimit, warnings, pushDownCond = null, allowOriginalWherePushdown = true) {
16543
- 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);
16544
16891
  const onTruncate = (max) => {
16545
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`);
16546
16893
  };
@@ -16654,7 +17001,7 @@ function splitChunks(items, size) {
16654
17001
  var JOIN_IN_CHUNK_SIZE = 50;
16655
17002
  var JOIN_IN_MAX_CHUNKS = 6;
16656
17003
  var JOIN_IN_MAX_KEYS = JOIN_IN_CHUNK_SIZE * JOIN_IN_MAX_CHUNKS;
16657
- 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) {
16658
17005
  if (join2.type !== "INNER") return null;
16659
17006
  if (!join2.table.alias) return null;
16660
17007
  if (join2.table.subtableCode) return null;
@@ -16691,7 +17038,7 @@ async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxR
16691
17038
  );
16692
17039
  return null;
16693
17040
  }
16694
- const fields = selectToFetchAllFields(stmt, join2.table);
17041
+ const fields = selectToFetchAllFields(stmt, join2.table, plainGroupByPlan);
16695
17042
  const onTruncate = (max) => {
16696
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`);
16697
17044
  };
@@ -16733,9 +17080,6 @@ function setScopedCacheValue(root, cacheContext, appId, value) {
16733
17080
  }
16734
17081
  scoped.set(appId, value);
16735
17082
  }
16736
- function isSystemLikeFieldCode(code) {
16737
- return code.startsWith("_") || code.startsWith("$");
16738
- }
16739
17083
  async function getFieldsCached(appId, client, cacheContext) {
16740
17084
  const cached = getScopedCacheValue(fieldInfoCache, cacheContext, appId);
16741
17085
  if (cached) return cached;
@@ -19931,6 +20275,7 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
19931
20275
  };
19932
20276
  const capabilities = /* @__PURE__ */ new Map();
19933
20277
  const orderPlans = /* @__PURE__ */ new Map();
20278
+ const plainGroupByPlans = /* @__PURE__ */ new Map();
19934
20279
  const seen = /* @__PURE__ */ new Set();
19935
20280
  const sharedRelativeDatePlan = relativeDatePlan ?? await resolveRelativeDateExecutionPlan(query, tracedClient, cacheContext);
19936
20281
  const relativeNodeFor = (source) => sharedRelativeDatePlan.nodes.find(
@@ -19948,6 +20293,15 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
19948
20293
  if (typed["type"] === "SELECT") {
19949
20294
  const select = node;
19950
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
+ }
19951
20305
  const physicalApps = [select.from, ...select.joins.map((join2) => join2.table)].filter((table) => table.cteName === null).map((table) => table.appId);
19952
20306
  const needsWhereSchema = whereNeedsFieldMetadata(select.where);
19953
20307
  if (needsWhereSchema || select.orderBy.length > 0 || select.columns.some((column) => column.type === "WINDOW_COL" && column.orderBy.length > 0)) {
@@ -19968,7 +20322,6 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
19968
20322
  }
19969
20323
  }
19970
20324
  }
19971
- const hasUnmaterializedSource = [select.from, ...select.joins.map((join2) => join2.table)].some((table) => table.cteName !== null);
19972
20325
  if (hasCanonicalOrder(select) && !hasUnmaterializedSource && relativeNode?.allowed !== false) {
19973
20326
  const mode = capability.capability === "EXACT_PUSHDOWN" ? resolveSelectMode(select) : "FULL_SCAN";
19974
20327
  orderPlans.set(select, (select.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
@@ -20082,6 +20435,7 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
20082
20435
  return {
20083
20436
  capabilities,
20084
20437
  orderPlans,
20438
+ plainGroupByPlans,
20085
20439
  fieldApps,
20086
20440
  processStatusApps,
20087
20441
  numberPrecisionApps,
@@ -20405,7 +20759,8 @@ async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxA
20405
20759
  analysis.orderPlans,
20406
20760
  dmlMaxRows,
20407
20761
  dmlMaxSubtableRows,
20408
- maxRecords
20762
+ maxRecords,
20763
+ analysis.plainGroupByPlans
20409
20764
  ),
20410
20765
  cursorMaxActive
20411
20766
  )
@@ -20428,13 +20783,13 @@ function addCursorConcurrency(lines, cursorMaxActive) {
20428
20783
  }
20429
20784
  return result;
20430
20785
  }
20431
- function buildExplainPlan(query, label, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, maxRecords = 1e4) {
20432
- if (query.type === "UNION") return buildUnionPlan(query, capabilities, orderPlans);
20433
- 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);
20434
20789
  if (query.type === "INSERT") return buildInsertPlan(query, label, dmlMaxRows, dmlMaxSubtableRows);
20435
- if (query.type === "INSERT_SELECT") return buildInsertSelectPlan(query, label, capabilities, orderPlans);
20790
+ if (query.type === "INSERT_SELECT") return buildInsertSelectPlan(query, label, capabilities, orderPlans, plainGroupByPlans);
20436
20791
  if (query.type === "UPSERT") return buildUpsertPlan(query, label, dmlMaxRows, dmlMaxSubtableRows);
20437
- if (query.type === "UPSERT_SELECT") return buildUpsertSelectPlan(query, label, capabilities, orderPlans);
20792
+ if (query.type === "UPSERT_SELECT") return buildUpsertSelectPlan(query, label, capabilities, orderPlans, plainGroupByPlans);
20438
20793
  if (query.type === "UPDATE") return buildUpdatePlan(
20439
20794
  query,
20440
20795
  label,
@@ -20520,7 +20875,7 @@ function buildExplainPlan(query, label, capabilities, orderPlans, dmlMaxRows = 1
20520
20875
  ` duplicateKey: preflight before lookup/write (requires load)`
20521
20876
  ];
20522
20877
  }
20523
- return buildSelectPlan(query, label, capabilities, orderPlans);
20878
+ return buildSelectPlan(query, label, capabilities, orderPlans, plainGroupByPlans);
20524
20879
  }
20525
20880
  function buildValidatePlan(stmt, label) {
20526
20881
  const info = validateExplainInfo.get(stmt);
@@ -20547,9 +20902,10 @@ function buildValidatePlan(stmt, label) {
20547
20902
  lines.push(" records/mutation API during EXPLAIN: none; violation count unavailable");
20548
20903
  return lines;
20549
20904
  }
20550
- function buildSelectPlan(stmt, label, capabilities, orderPlans) {
20905
+ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlans) {
20551
20906
  const whereCapability = capabilities?.get(stmt) ?? (capabilities ? [...capabilities].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
20552
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);
20553
20909
  const mode = orderPlan?.kind === "CANONICAL_LOCAL" ? "FULL_SCAN" : whereCapability && whereCapability.capability !== "EXACT_PUSHDOWN" ? "FULL_SCAN" : resolveSelectMode(stmt);
20554
20910
  const reasons = collectFullScanReasons(stmt);
20555
20911
  if (whereCapability && whereCapability.capability !== "EXACT_PUSHDOWN") {
@@ -20574,6 +20930,29 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
20574
20930
  ` grouping output rows: runtime checked (limit: ${groupingMetadata.outputRowLimit}, before HAVING/DISTINCT/LIMIT)`
20575
20931
  );
20576
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
+ }
20577
20956
  if (orderPlan) {
20578
20957
  lines.push(` order plan: ${orderPlan.kind}`);
20579
20958
  if (orderPlan.reasonCodes.length > 0) lines.push(` order reason: ${orderPlan.reasonCodes.join(", ")}`);
@@ -20615,7 +20994,7 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
20615
20994
  lines.push(` fields: ${params.fields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : params.fields.join(", ")}`);
20616
20995
  } else {
20617
20996
  const pushdownPlan = buildKlikePushdownPlan(stmt);
20618
- const mainFields = selectToFetchAllFields(stmt, stmt.from);
20997
+ const mainFields = selectToFetchAllFields(stmt, stmt.from, plainGroupByPlan);
20619
20998
  const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
20620
20999
  const mainPushDown = pushdownPlan.mainCondition;
20621
21000
  const mainCandidate = extractMainTypedPushdownCandidate(stmt);
@@ -20628,7 +21007,7 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
20628
21007
  }
20629
21008
  lines.push(` fields: ${mainFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : mainFields.join(", ")}`);
20630
21009
  for (const join2 of stmt.joins) {
20631
- const joinFields = selectToFetchAllFields(stmt, join2.table);
21010
+ const joinFields = selectToFetchAllFields(stmt, join2.table, plainGroupByPlan);
20632
21011
  const joinAliasStr = join2.table.alias ? ` AS ${join2.table.alias}` : "";
20633
21012
  const joinType = join2.type === "INNER" ? "JOIN" : `${join2.type} JOIN`;
20634
21013
  const joinPushDown = join2.table.alias ? pushdownPlan.joinConditions.get(join2.table.alias) ?? null : null;
@@ -20642,10 +21021,10 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
20642
21021
  lines.push(` fields: ${joinFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : joinFields.join(", ")}`);
20643
21022
  }
20644
21023
  }
20645
- lines.push(...collectSubqueryPlans(stmt, capabilities, orderPlans));
21024
+ lines.push(...collectSubqueryPlans(stmt, capabilities, orderPlans, plainGroupByPlans));
20646
21025
  return lines;
20647
21026
  }
20648
- function buildUnionPlan(stmt, capabilities, orderPlans) {
21027
+ function buildUnionPlan(stmt, capabilities, orderPlans, plainGroupByPlans) {
20649
21028
  const selects = [];
20650
21029
  const collect = (u) => {
20651
21030
  if (u.type === "SELECT") {
@@ -20659,25 +21038,34 @@ function buildUnionPlan(stmt, capabilities, orderPlans) {
20659
21038
  const lines = [];
20660
21039
  selects.forEach((sel, i) => {
20661
21040
  if (i > 0) lines.push("");
20662
- lines.push(...buildSelectPlan(sel, `[union:${i + 1}]`, capabilities, orderPlans));
21041
+ lines.push(...buildSelectPlan(sel, `[union:${i + 1}]`, capabilities, orderPlans, plainGroupByPlans));
20663
21042
  });
20664
21043
  return lines;
20665
21044
  }
20666
- function buildWithPlan(stmt, capabilities, orderPlans) {
21045
+ function buildWithPlan(stmt, capabilities, orderPlans, plainGroupByPlans) {
20667
21046
  const lines = [];
20668
21047
  for (const cte of stmt.ctes) {
20669
21048
  if (cte.query.type === "SELECT") {
20670
- lines.push(...buildSelectPlan(cte.query, `[cte: ${cte.name}]`, capabilities, orderPlans));
21049
+ lines.push(...buildSelectPlan(cte.query, `[cte: ${cte.name}]`, capabilities, orderPlans, plainGroupByPlans));
20671
21050
  lines.push("");
20672
21051
  }
20673
21052
  }
20674
21053
  if (stmt.query.type === "SELECT" || stmt.query.type === "UNION") {
20675
- 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
+ ));
20676
21064
  }
20677
21065
  if (canInlineSingleCte(stmt)) {
20678
21066
  lines.push("");
20679
21067
  const inlined = buildInlinedQuery(stmt);
20680
- lines.push(...buildSelectPlan(inlined, "[effective: inlined CTE]", capabilities, orderPlans));
21068
+ lines.push(...buildSelectPlan(inlined, "[effective: inlined CTE]", capabilities, orderPlans, plainGroupByPlans));
20681
21069
  }
20682
21070
  return lines;
20683
21071
  }
@@ -20710,7 +21098,7 @@ function collectFullScanReasons(stmt) {
20710
21098
  r.push("ORDER BY \u306B\u5F0F");
20711
21099
  return r;
20712
21100
  }
20713
- function collectSubqueryPlans(stmt, capabilities, orderPlans) {
21101
+ function collectSubqueryPlans(stmt, capabilities, orderPlans, plainGroupByPlans) {
20714
21102
  const lines = [];
20715
21103
  let idx = 1;
20716
21104
  const visitWhere = (w) => {
@@ -20719,16 +21107,16 @@ function collectSubqueryPlans(stmt, capabilities, orderPlans) {
20719
21107
  case "BINARY":
20720
21108
  if (w.right.type === "SCALAR_SUBQUERY") {
20721
21109
  lines.push("");
20722
- lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans));
21110
+ lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans));
20723
21111
  }
20724
21112
  if (w.right.type === "SUBQUERY_IN_LIST") {
20725
21113
  lines.push("");
20726
- lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans));
21114
+ lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans));
20727
21115
  }
20728
21116
  break;
20729
21117
  case "EXISTS":
20730
21118
  lines.push("");
20731
- lines.push(...buildSelectPlan(w.query, `[subquery:${idx++}]`, capabilities, orderPlans));
21119
+ lines.push(...buildSelectPlan(w.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans));
20732
21120
  break;
20733
21121
  case "LOGICAL":
20734
21122
  visitWhere(w.left);
@@ -20748,7 +21136,7 @@ function collectSubqueryPlans(stmt, capabilities, orderPlans) {
20748
21136
  for (const col of stmt.columns) {
20749
21137
  if (col.type === "SCALAR_SUBQUERY_COL") {
20750
21138
  lines.push("");
20751
- lines.push(...buildSelectPlan(col.query, `[subquery:${idx++}]`, capabilities, orderPlans));
21139
+ lines.push(...buildSelectPlan(col.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans));
20752
21140
  }
20753
21141
  }
20754
21142
  if (stmt.having) visitWhere(stmt.having);
@@ -20766,7 +21154,7 @@ function buildInsertPlan(stmt, label, dmlMaxRows = DEFAULT_APPLY_MAX_ROWS, dmlMa
20766
21154
  lines.push(` fields: ${stmt.fields.join(", ")}`);
20767
21155
  return stmt.applyBlocks?.length ? [...lines, ...formatStaticApplyDiagnostic(buildStaticApplyDiagnostic(stmt, dmlMaxRows, dmlMaxSubtableRows))] : lines;
20768
21156
  }
20769
- function buildInsertSelectPlan(stmt, label, capabilities, orderPlans) {
21157
+ function buildInsertSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlans) {
20770
21158
  const lines = [];
20771
21159
  if (label) lines.push(label);
20772
21160
  lines.push(` [INSERT SELECT]`);
@@ -20774,7 +21162,7 @@ function buildInsertSelectPlan(stmt, label, capabilities, orderPlans) {
20774
21162
  lines.push(` fields: ${stmt.fields.join(", ")}`);
20775
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`);
20776
21164
  lines.push("");
20777
- lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans));
21165
+ lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans, plainGroupByPlans));
20778
21166
  return lines;
20779
21167
  }
20780
21168
  function buildUpdatePlan(stmt, label, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, maxRecords = 1e4) {
@@ -20928,7 +21316,7 @@ function formatStaticApplyDiagnostic(diagnostic2) {
20928
21316
  lines.push("records API: 0", "mutation API: 0");
20929
21317
  return lines;
20930
21318
  }
20931
- function buildUpsertSelectPlan(stmt, label, capabilities, orderPlans) {
21319
+ function buildUpsertSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlans) {
20932
21320
  const lines = [
20933
21321
  ...label ? [label] : [],
20934
21322
  ` [UPSERT SELECT]`,
@@ -20938,7 +21326,7 @@ function buildUpsertSelectPlan(stmt, label, capabilities, orderPlans) {
20938
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`,
20939
21327
  ``
20940
21328
  ];
20941
- lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans));
21329
+ lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans, plainGroupByPlans));
20942
21330
  return lines;
20943
21331
  }
20944
21332
  function buildReorderPlan(stmt, label) {