@rex0220/kintone-sql-tools 3.16.1 → 3.17.1

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
@@ -599,6 +599,10 @@ function fieldValueLabel(value) {
599
599
  if (value.type === "FIELD") return value.tableAlias ? `${value.tableAlias}.${value.field}` : value.field;
600
600
  if (value.type === "FUNC_FIELD") return stringFuncLabel(value.expr);
601
601
  if (value.type === "ARITH_FIELD") return arithLabel(value.expr);
602
+ if (value.type === "GROUPING_FIELD") {
603
+ const field = value.ref.field;
604
+ return `GROUPING(${field.tableAlias ? `${field.tableAlias}.` : ""}${field.field})`;
605
+ }
602
606
  return caseLabel(value.expr);
603
607
  }
604
608
  function sqlValueLabel(value) {
@@ -902,6 +906,8 @@ var Parser = class {
902
906
  this.scalarAllowsCase = true;
903
907
  this.pos = 0;
904
908
  this.insideAggregateArg = 0;
909
+ /** GROUPING(field) is only legal while parsing a SELECT CASE condition. */
910
+ this.groupingFieldAllowedDepth = 0;
905
911
  /** WITH 句で定義された CTE 名のセット(parseTableRef で参照) */
906
912
  this.cteNames = /* @__PURE__ */ new Set();
907
913
  /** パース中に出現した一時テーブル参照(#name)のトークン。単文 API での拒否に使う */
@@ -1273,7 +1279,7 @@ var Parser = class {
1273
1279
  if (projection.from.cteName !== NO_FROM_CTE_NAME || projection.joins.length > 0) {
1274
1280
  throw new ParseError("IMPORT projection cannot use FROM or JOIN.", this.prev());
1275
1281
  }
1276
- if (projection.where || projection.groupBy.length || projection.having || projection.orderBy.length || projection.limit !== null || projection.offset !== null) {
1282
+ if (projection.where || projection.groupBy.length || projection.grouping !== void 0 || projection.having || projection.orderBy.length || projection.limit !== null || projection.offset !== null) {
1277
1283
  throw new ParseError("IMPORT projection supports SELECT expressions only.", this.prev());
1278
1284
  }
1279
1285
  this.validateImportProjectionScope(projection, this.prev());
@@ -1634,10 +1640,25 @@ var Parser = class {
1634
1640
  const joins = hasFrom ? this.parseJoins() : [];
1635
1641
  const where = this.consume("WHERE" /* WHERE */) ? this.parseWhereExpr() : null;
1636
1642
  let groupBy = [];
1643
+ let grouping;
1637
1644
  let having = null;
1638
1645
  if (this.consume("GROUP" /* GROUP */)) {
1639
1646
  this.expect("BY" /* BY */);
1640
- groupBy = this.parseGroupByKeys();
1647
+ if (this.peek().kind === "DISTINCT" /* DISTINCT */ || this.isSoftKeyword("DISTINCT")) {
1648
+ throw new ParseError("B65: GROUP BY DISTINCT is not supported in Phase1.", this.peek());
1649
+ }
1650
+ if (this.isGroupingSetsStart()) {
1651
+ grouping = this.parseGroupingSetsClause();
1652
+ } else if (this.isRollupStart()) {
1653
+ grouping = this.parseRollupClause();
1654
+ } else if (this.isCubeStart()) {
1655
+ throw new ParseError("B65: CUBE is not supported in Phase1.", this.peek());
1656
+ } else {
1657
+ groupBy = this.parseGroupByKeys();
1658
+ }
1659
+ if (grouping && this.peek().kind === "," /* COMMA */) {
1660
+ throw new ParseError("B65: ordinary GROUP BY items cannot be mixed with grouping elements.", this.peek());
1661
+ }
1641
1662
  if (this.consume("HAVING" /* HAVING */)) {
1642
1663
  having = this.parseWhereExpr();
1643
1664
  }
@@ -1659,9 +1680,12 @@ var Parser = class {
1659
1680
  const offset = this.consume("OFFSET" /* OFFSET */) ? this.parseUnsignedInt() : null;
1660
1681
  const hasWindow = columns.some((column) => column.type === "WINDOW_COL");
1661
1682
  const hasAggregate = columns.some((column) => this.selectColumnHasAggregate(column));
1662
- if (hasWindow && (groupBy.length > 0 || hasAggregate)) {
1683
+ if (hasWindow && (groupBy.length > 0 || grouping !== void 0 || hasAggregate)) {
1663
1684
  throw new ParseError("\u30A6\u30A3\u30F3\u30C9\u30A6\u95A2\u6570\u306F GROUP BY / \u96C6\u8A08\u95A2\u6570\u3068\u540C\u3058 SELECT \u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", this.peek());
1664
1685
  }
1686
+ if (grouping && orderMode === "KINTONE_NATIVE") {
1687
+ throw new ParseError("B65: KORDER BY cannot be combined with grouping sets in Phase1.", this.peek());
1688
+ }
1665
1689
  return {
1666
1690
  type: "SELECT",
1667
1691
  distinct,
@@ -1670,6 +1694,7 @@ var Parser = class {
1670
1694
  joins,
1671
1695
  where,
1672
1696
  groupBy,
1697
+ ...grouping ? { grouping } : {},
1673
1698
  having,
1674
1699
  orderMode,
1675
1700
  orderBy,
@@ -1730,6 +1755,14 @@ var Parser = class {
1730
1755
  if (this.consume("*" /* STAR */)) {
1731
1756
  return { type: "WILDCARD" };
1732
1757
  }
1758
+ if (this.isUnsupportedGroupingIdStart()) {
1759
+ throw new ParseError("B65: GROUPING_ID is not supported in Phase1.", this.peek());
1760
+ }
1761
+ if (this.isGroupingFunctionStart()) {
1762
+ const ref = this.parseGroupingRef();
1763
+ const alias2 = this.consume("AS" /* AS */) ? this.parseAliasName() : null;
1764
+ return { type: "GROUPING_COL", ref, alias: alias2 };
1765
+ }
1733
1766
  if (this.tryAggregateFunc() === null && this.hasTopLevelTokenBeforeValueEnd("||" /* CONCAT_OP */)) {
1734
1767
  const expr = this.parseScalarValueExpr({ allowAggregateArgs: true });
1735
1768
  const alias2 = this.consume("AS" /* AS */) ? this.parseAliasName() : null;
@@ -1751,12 +1784,12 @@ var Parser = class {
1751
1784
  return this.parseWindowColumn(windowFunc);
1752
1785
  }
1753
1786
  if (this.peek().kind === "CASE" /* CASE */) {
1754
- const expr = this.parseCaseWhenExpr();
1787
+ const expr = this.parseCaseWhenExpr(true);
1755
1788
  const alias2 = this.consume("AS" /* AS */) ? this.parseAliasName() : null;
1756
1789
  return { type: "CASE_COL", expr, alias: alias2 };
1757
1790
  }
1758
1791
  if (this.peek().kind === "IF" /* IF */) {
1759
- const expr = this.parseIfExpr();
1792
+ const expr = this.parseIfExpr(true);
1760
1793
  const alias2 = this.consume("AS" /* AS */) ? this.parseAliasName() : null;
1761
1794
  return { type: "CASE_COL", expr, alias: alias2 };
1762
1795
  }
@@ -1842,7 +1875,7 @@ var Parser = class {
1842
1875
  partitionBy.push({ type: "FIELD", tableAlias: ref.tableAlias, field: ref.field });
1843
1876
  } while (this.consume("," /* COMMA */));
1844
1877
  }
1845
- const orderBy = this.consume("ORDER" /* ORDER */) ? (this.expect("BY" /* BY */), this.parseOrderBy()) : [];
1878
+ const orderBy = this.consume("ORDER" /* ORDER */) ? (this.expect("BY" /* BY */), this.parseOrderBy(false)) : [];
1846
1879
  this.expect(")" /* RPAREN */);
1847
1880
  if (!this.consume("AS" /* AS */)) {
1848
1881
  throw new ParseError("\u30A6\u30A3\u30F3\u30C9\u30A6\u95A2\u6570\u306B\u306F AS alias \u304C\u5FC5\u8981\u3067\u3059", this.peek());
@@ -2118,10 +2151,10 @@ var Parser = class {
2118
2151
  // result: 文字列リテラル / 算術式(フィールド参照・数値含む)
2119
2152
  // ──────────────────────────────────────────────────
2120
2153
  /** IF(条件, then値, else値) → CaseWhenExpr に変換 */
2121
- parseIfExpr() {
2154
+ parseIfExpr(allowGroupingCondition = false) {
2122
2155
  this.advance();
2123
2156
  this.expect("(" /* LPAREN */);
2124
- const condition = this.parseWhereExpr();
2157
+ const condition = this.parseCaseCondition(allowGroupingCondition);
2125
2158
  this.expect("," /* COMMA */);
2126
2159
  const thenResult = this.parseCaseResult();
2127
2160
  this.expect("," /* COMMA */);
@@ -2133,12 +2166,12 @@ var Parser = class {
2133
2166
  elseResult
2134
2167
  };
2135
2168
  }
2136
- parseCaseWhenExpr() {
2169
+ parseCaseWhenExpr(allowGroupingCondition = false) {
2137
2170
  this.expect("CASE" /* CASE */);
2138
2171
  const branches = [];
2139
2172
  while (this.peek().kind === "WHEN" /* WHEN */) {
2140
2173
  this.advance();
2141
- const condition = this.parseWhereExpr();
2174
+ const condition = this.parseCaseCondition(allowGroupingCondition);
2142
2175
  this.expect("THEN" /* THEN */);
2143
2176
  const result = this.parseCaseResult();
2144
2177
  branches.push({ condition, result });
@@ -2153,6 +2186,15 @@ var Parser = class {
2153
2186
  this.expect("END" /* END */);
2154
2187
  return { type: "CASE_WHEN", branches, elseResult };
2155
2188
  }
2189
+ parseCaseCondition(allowGroupingCondition) {
2190
+ if (!allowGroupingCondition) return this.parseWhereExpr();
2191
+ this.groupingFieldAllowedDepth++;
2192
+ try {
2193
+ return this.parseWhereExpr();
2194
+ } finally {
2195
+ this.groupingFieldAllowedDepth--;
2196
+ }
2197
+ }
2156
2198
  /** THEN / ELSE の結果値。`||` を含む場合だけ新スカラー文法へ渡す。 */
2157
2199
  parseCaseResult() {
2158
2200
  const tok = this.peek();
@@ -2634,6 +2676,18 @@ var Parser = class {
2634
2676
  // - 集計関数(HAVING のみ): COUNT(*) / SUM(f) ...
2635
2677
  // - 通常フィールド参照: [alias.]field
2636
2678
  parseFieldValue() {
2679
+ if (this.isUnsupportedGroupingIdStart()) {
2680
+ throw new ParseError("B65: GROUPING_ID is not supported in Phase1.", this.peek());
2681
+ }
2682
+ if (this.isGroupingFunctionStart()) {
2683
+ if (this.groupingFieldAllowedDepth === 0) {
2684
+ throw new ParseError(
2685
+ "B65: GROUPING() is only allowed in SELECT, SELECT CASE conditions, and direct ORDER BY.",
2686
+ this.peek()
2687
+ );
2688
+ }
2689
+ return { type: "GROUPING_FIELD", ref: this.parseGroupingRef() };
2690
+ }
2637
2691
  if (this.tryStringFuncName() !== null) {
2638
2692
  const expr = this.parseStringFuncExpr();
2639
2693
  if (this.isArithOp(this.peek().kind)) {
@@ -2787,10 +2841,123 @@ var Parser = class {
2787
2841
  parseGroupByKeys() {
2788
2842
  const keys = [];
2789
2843
  do {
2844
+ if (keys.length > 0 && (this.isRollupStart() || this.isGroupingSetsStart() || this.isCubeStart())) {
2845
+ throw new ParseError("B65: ordinary GROUP BY items cannot be mixed with grouping elements.", this.peek());
2846
+ }
2790
2847
  keys.push(this.parseGroupByKey());
2791
2848
  } while (this.consume("," /* COMMA */));
2792
2849
  return keys;
2793
2850
  }
2851
+ isRollupStart() {
2852
+ return this.isSoftKeyword("ROLLUP") && this.peekAt(1).kind === "(" /* LPAREN */;
2853
+ }
2854
+ isCubeStart() {
2855
+ return this.isSoftKeyword("CUBE") && this.peekAt(1).kind === "(" /* LPAREN */;
2856
+ }
2857
+ isGroupingSetsStart() {
2858
+ return this.isSoftKeyword("GROUPING") && this.peekAt(1).kind === "IDENT" /* IDENT */ && this.peekAt(1).value.toUpperCase() === "SETS" && this.peekAt(2).kind === "(" /* LPAREN */;
2859
+ }
2860
+ isGroupingFunctionStart() {
2861
+ return this.isSoftKeyword("GROUPING") && this.peekAt(1).kind === "(" /* LPAREN */;
2862
+ }
2863
+ isUnsupportedGroupingIdStart() {
2864
+ return this.isSoftKeyword("GROUPING_ID") && this.peekAt(1).kind === "(" /* LPAREN */;
2865
+ }
2866
+ groupingItemSyntaxKey(item) {
2867
+ return `${item.tableAlias ?? ""}\0${item.field}`;
2868
+ }
2869
+ groupingAllItems(sets) {
2870
+ const seen = /* @__PURE__ */ new Set();
2871
+ const allItems = [];
2872
+ for (const set of sets) {
2873
+ for (const item of set.items) {
2874
+ const key = this.groupingItemSyntaxKey(item);
2875
+ if (seen.has(key)) continue;
2876
+ seen.add(key);
2877
+ allItems.push(item);
2878
+ }
2879
+ }
2880
+ return allItems;
2881
+ }
2882
+ parseGroupingFieldItem() {
2883
+ if (this.isRollupStart() || this.isGroupingSetsStart() || this.isCubeStart()) {
2884
+ throw new ParseError("B65: nested grouping elements are not supported in Phase1.", this.peek());
2885
+ }
2886
+ const token = this.peek();
2887
+ const field = this.parseQualifiedIdent();
2888
+ if (this.peek().kind !== "," /* COMMA */ && this.peek().kind !== ")" /* RPAREN */) {
2889
+ throw new ParseError("B65: grouping items must be physical field references only.", token);
2890
+ }
2891
+ return { type: "FIELD", tableAlias: field.tableAlias, field: field.field };
2892
+ }
2893
+ parseGroupingSetsClause() {
2894
+ this.advance();
2895
+ this.advance();
2896
+ this.expect("(" /* LPAREN */);
2897
+ if (this.peek().kind === ")" /* RPAREN */) {
2898
+ throw new ParseError("B65: GROUPING SETS requires at least one grouping set; use (()) for the empty set.", this.peek());
2899
+ }
2900
+ const sets = [];
2901
+ do {
2902
+ if (this.consume("(" /* LPAREN */)) {
2903
+ const items = [];
2904
+ if (this.peek().kind !== ")" /* RPAREN */) {
2905
+ do {
2906
+ items.push(this.parseGroupingFieldItem());
2907
+ } while (this.consume("," /* COMMA */));
2908
+ }
2909
+ this.expect(")" /* RPAREN */);
2910
+ sets.push({ items });
2911
+ } else {
2912
+ sets.push({ items: [this.parseGroupingFieldItem()] });
2913
+ }
2914
+ } while (this.consume("," /* COMMA */));
2915
+ this.expect(")" /* RPAREN */);
2916
+ return {
2917
+ type: "GROUPING_SETS",
2918
+ source: "GROUPING_SETS",
2919
+ allItems: this.groupingAllItems(sets),
2920
+ sets
2921
+ };
2922
+ }
2923
+ parseRollupClause() {
2924
+ this.advance();
2925
+ this.expect("(" /* LPAREN */);
2926
+ if (this.peek().kind === ")" /* RPAREN */) {
2927
+ throw new ParseError("B65: ROLLUP requires at least one field.", this.peek());
2928
+ }
2929
+ const items = [];
2930
+ do {
2931
+ items.push(this.parseGroupingFieldItem());
2932
+ } while (this.consume("," /* COMMA */));
2933
+ this.expect(")" /* RPAREN */);
2934
+ const sets = Array.from(
2935
+ { length: items.length + 1 },
2936
+ (_, index) => ({ items: items.slice(0, items.length - index) })
2937
+ );
2938
+ return {
2939
+ type: "GROUPING_SETS",
2940
+ source: "ROLLUP",
2941
+ allItems: this.groupingAllItems(sets),
2942
+ sets
2943
+ };
2944
+ }
2945
+ parseGroupingRef() {
2946
+ const start = this.advance();
2947
+ this.expect("(" /* LPAREN */);
2948
+ if (this.peek().kind === ")" /* RPAREN */) {
2949
+ throw new ParseError("B65: GROUPING() requires exactly one physical field argument.", this.peek());
2950
+ }
2951
+ const field = this.parseQualifiedIdent();
2952
+ if (this.peek().kind !== ")" /* RPAREN */) {
2953
+ throw new ParseError("B65: GROUPING() requires exactly one physical field argument.", start);
2954
+ }
2955
+ this.expect(")" /* RPAREN */);
2956
+ return {
2957
+ type: "GROUPING_REF",
2958
+ field: { type: "FIELD", tableAlias: field.tableAlias, field: field.field }
2959
+ };
2960
+ }
2794
2961
  /**
2795
2962
  * GROUP BY のキーを解析する。ORDER BY と同じ文法(方向指定なし)。
2796
2963
  * 関数: SUBSTRING(作成日時, 1, 7) / ROUND(金額, -4)
@@ -2816,10 +2983,10 @@ var Parser = class {
2816
2983
  }
2817
2984
  return { type: "FIELD_NAME", name };
2818
2985
  }
2819
- parseOrderBy() {
2986
+ parseOrderBy(allowGrouping = true) {
2820
2987
  const items = [];
2821
2988
  do {
2822
- const key = this.parseOrderByKey();
2989
+ const key = this.parseOrderByKey(allowGrouping);
2823
2990
  let direction = "ASC";
2824
2991
  if (this.consume("DESC" /* DESC */)) direction = "DESC";
2825
2992
  else this.consume("ASC" /* ASC */);
@@ -2834,7 +3001,16 @@ var Parser = class {
2834
3001
  * フィールド + 算術: 金額 * 1.1
2835
3002
  * フィールド名/alias: 名前 / total
2836
3003
  */
2837
- parseOrderByKey() {
3004
+ parseOrderByKey(allowGrouping = true) {
3005
+ if (this.isUnsupportedGroupingIdStart()) {
3006
+ throw new ParseError("B65: GROUPING_ID is not supported in Phase1.", this.peek());
3007
+ }
3008
+ if (this.isGroupingFunctionStart()) {
3009
+ if (!allowGrouping) {
3010
+ throw new ParseError("B65: GROUPING() is not allowed in window ORDER BY.", this.peek());
3011
+ }
3012
+ return { type: "GROUPING_KEY", ref: this.parseGroupingRef() };
3013
+ }
2838
3014
  if (this.tryStringFuncName() !== null) {
2839
3015
  const funcExpr = this.parseStringFuncExpr();
2840
3016
  if (this.isArithOp(this.peek().kind)) {
@@ -3754,6 +3930,67 @@ function extractTableRef(name, tok) {
3754
3930
  return { appId: Number(m[1]), subtableCode: m[2] ?? null };
3755
3931
  }
3756
3932
 
3933
+ // src/core/grouping.ts
3934
+ var B65_MAX_GROUPING_SETS = 64;
3935
+ var B65_MAX_GROUPING_ITEMS = 16;
3936
+ var B65_MAX_GENERATED_ROWS = 5e4;
3937
+ function groupingFieldSyntaxKey(item) {
3938
+ return `${item.tableAlias ?? ""}\0${item.field}`;
3939
+ }
3940
+ function normalizeGroupingSpec(stmt) {
3941
+ if (stmt.grouping !== void 0 && stmt.groupBy.length > 0) {
3942
+ throw new Error("internal error: SELECT cannot contain both groupBy and grouping.");
3943
+ }
3944
+ if (stmt.grouping === void 0) {
3945
+ return stmt.groupBy.length === 0 ? { type: "NONE" } : { type: "PLAIN", allItems: stmt.groupBy, sets: [stmt.groupBy] };
3946
+ }
3947
+ const seen = /* @__PURE__ */ new Set();
3948
+ const allItems = [];
3949
+ for (const set of stmt.grouping.sets) {
3950
+ for (const item of set.items) {
3951
+ const key = groupingFieldSyntaxKey(item);
3952
+ if (seen.has(key)) continue;
3953
+ seen.add(key);
3954
+ allItems.push(item);
3955
+ }
3956
+ }
3957
+ const sets = stmt.grouping.sets.map((set) => ({ items: [...set.items] }));
3958
+ return {
3959
+ type: "GROUPING_SETS",
3960
+ source: stmt.grouping.source,
3961
+ allItems,
3962
+ sets
3963
+ };
3964
+ }
3965
+ function hasGroupingClause(stmt) {
3966
+ return normalizeGroupingSpec(stmt).type !== "NONE";
3967
+ }
3968
+ function resolveGroupingSpec(stmt, resolve2) {
3969
+ const normalized = normalizeGroupingSpec(stmt);
3970
+ if (normalized.type !== "GROUPING_SETS") return null;
3971
+ const byCanonicalId = /* @__PURE__ */ new Map();
3972
+ const resolveItem = (field) => {
3973
+ const resolved = resolve2(field);
3974
+ const existing = byCanonicalId.get(resolved.canonicalId);
3975
+ if (existing) return existing;
3976
+ const item = { ...resolved, field };
3977
+ byCanonicalId.set(item.canonicalId, item);
3978
+ return item;
3979
+ };
3980
+ const allItems = normalized.allItems.map(resolveItem).filter(
3981
+ (item, index, items) => items.findIndex((candidate) => candidate.canonicalId === item.canonicalId) === index
3982
+ );
3983
+ const sets = normalized.sets.map((set) => ({
3984
+ items: set.items.map(resolveItem)
3985
+ }));
3986
+ return {
3987
+ type: "GROUPING_SETS",
3988
+ source: normalized.source,
3989
+ allItems,
3990
+ sets
3991
+ };
3992
+ }
3993
+
3757
3994
  // src/core/dmlGuard.ts
3758
3995
  function getStatementType(stmt) {
3759
3996
  if (!stmt || typeof stmt !== "object") return "UNKNOWN";
@@ -3833,6 +4070,7 @@ function unionCompleteInputReasons(stmt) {
3833
4070
  }
3834
4071
  function selectCompleteInputReasons(stmt) {
3835
4072
  const reasons = /* @__PURE__ */ new Set();
4073
+ if (normalizeGroupingSpec(stmt).type === "GROUPING_SETS") reasons.add("GROUPING_SETS");
3836
4074
  if (stmt.orderBy.length > 0) reasons.add("LOCAL_ORDER");
3837
4075
  for (const column of stmt.columns) {
3838
4076
  if (column.type === "WINDOW_COL" && column.orderBy.length > 0) reasons.add("WINDOW_ORDER");
@@ -4551,6 +4789,9 @@ function convertField(field) {
4551
4789
  if (field.type === "CASE_FIELD") {
4552
4790
  throw new KintoneQueryError("WHERE \u53E5\u306E CASE WHEN \u306F kintone \u30AF\u30A8\u30EA\u306B\u5909\u63DB\u3067\u304D\u307E\u305B\u3093");
4553
4791
  }
4792
+ if (field.type === "GROUPING_FIELD") {
4793
+ throw new KintoneQueryError("GROUPING() \u306F kintone \u30AF\u30A8\u30EA\u306B\u5909\u63DB\u3067\u304D\u307E\u305B\u3093");
4794
+ }
4554
4795
  return quoteIdentifier(field.field);
4555
4796
  }
4556
4797
  function convertValue(value, op) {
@@ -4625,7 +4866,7 @@ function resolveSelectMode(stmt) {
4625
4866
  if (stmt.from.subtableCode) return "FULL_SCAN";
4626
4867
  if (stmt.joins.some((j) => j.table.subtableCode)) return "FULL_SCAN";
4627
4868
  if (stmt.joins.length > 0) return "FULL_SCAN";
4628
- if (stmt.groupBy.length > 0) return "FULL_SCAN";
4869
+ if (normalizeGroupingSpec(stmt).type !== "NONE") return "FULL_SCAN";
4629
4870
  if (stmt.distinct) return "FULL_SCAN";
4630
4871
  if (hasWindowColumns(stmt.columns)) return "FULL_SCAN";
4631
4872
  if (stmt.columns.some(
@@ -5014,6 +5255,9 @@ function collectRequiredFieldsByTable(stmt) {
5014
5255
  walkArith(fv.expr, phase);
5015
5256
  return;
5016
5257
  }
5258
+ if (fv.type === "GROUPING_FIELD") {
5259
+ return;
5260
+ }
5017
5261
  walkCase(fv.expr, phase);
5018
5262
  };
5019
5263
  const walkSqlValue = (v, phase = "select") => {
@@ -5069,6 +5313,9 @@ function collectRequiredFieldsByTable(stmt) {
5069
5313
  walkArith(k.expr, phase);
5070
5314
  return;
5071
5315
  }
5316
+ if (k.type === "GROUPING_KEY") {
5317
+ return;
5318
+ }
5072
5319
  walkStringFunc(k.expr, phase);
5073
5320
  };
5074
5321
  for (const col of stmt.columns) {
@@ -5104,6 +5351,8 @@ function collectRequiredFieldsByTable(stmt) {
5104
5351
  case "SCALAR_VALUE_COL":
5105
5352
  walkScalar(col.expr, "select");
5106
5353
  break;
5354
+ case "GROUPING_COL":
5355
+ break;
5107
5356
  case "SCALAR_SUBQUERY_COL":
5108
5357
  break;
5109
5358
  case "WINDOW_COL":
@@ -5117,7 +5366,14 @@ function collectRequiredFieldsByTable(stmt) {
5117
5366
  addFieldRef(join2.on.right.field, join2.on.right.tableAlias, "where");
5118
5367
  }
5119
5368
  walkWhere(stmt.where, "where");
5120
- for (const gk of stmt.groupBy) walkGroupByKey(gk);
5369
+ const grouping = normalizeGroupingSpec(stmt);
5370
+ if (grouping.type === "PLAIN") {
5371
+ for (const item of grouping.allItems) walkGroupByKey(item);
5372
+ } else if (grouping.type === "GROUPING_SETS") {
5373
+ for (const item of grouping.allItems) {
5374
+ addFieldRef(item.field, item.tableAlias, "select");
5375
+ }
5376
+ }
5121
5377
  walkWhere(stmt.having, "having");
5122
5378
  for (const ob of stmt.orderBy) walkOrderByKey(ob.key);
5123
5379
  return states;
@@ -5158,6 +5414,10 @@ function collectSelectOutputNames(columns) {
5158
5414
  if (col.alias) names.add(col.alias);
5159
5415
  continue;
5160
5416
  }
5417
+ if (col.type === "GROUPING_COL") {
5418
+ names.add(col.alias ?? `GROUPING(${col.ref.field.tableAlias ? `${col.ref.field.tableAlias}.` : ""}${col.ref.field.field})`);
5419
+ continue;
5420
+ }
5161
5421
  if (col.type === "SCALAR_SUBQUERY_COL") {
5162
5422
  names.add(col.alias ?? "(subquery)");
5163
5423
  continue;
@@ -5183,7 +5443,7 @@ function canInlineSingleCte(stmt) {
5183
5443
  const finalQuery = stmt.query;
5184
5444
  if (finalQuery.type !== "SELECT") return false;
5185
5445
  if (finalQuery.from.cteName !== cteDef.name || finalQuery.joins.length > 0) return false;
5186
- if (finalQuery.groupBy.length > 0 || finalQuery.distinct) return false;
5446
+ if (hasGroupingClause(finalQuery) || finalQuery.distinct) return false;
5187
5447
  return !finalQuery.columns.some(
5188
5448
  (column) => column.type === "AGGREGATE" || column.type === "ARITH_AGG_COL"
5189
5449
  );
@@ -6715,6 +6975,39 @@ function resolveFieldRef(row, field) {
6715
6975
  return "";
6716
6976
  }
6717
6977
 
6978
+ // src/engine/groupingRowMeta.ts
6979
+ var groupingRowMetaKey = /* @__PURE__ */ Symbol("ksql.groupingRowMeta");
6980
+ var groupingRefCanonicalIds = /* @__PURE__ */ new WeakMap();
6981
+ function attachGroupingRowMeta(row, includedCanonicalIds) {
6982
+ Object.defineProperty(row, groupingRowMetaKey, {
6983
+ value: { includedCanonicalIds },
6984
+ enumerable: false,
6985
+ configurable: false,
6986
+ writable: false
6987
+ });
6988
+ return row;
6989
+ }
6990
+ function getGroupingRowMeta(row) {
6991
+ return row[groupingRowMetaKey];
6992
+ }
6993
+ function readGroupingMembership(row) {
6994
+ return getGroupingRowMeta(row)?.includedCanonicalIds;
6995
+ }
6996
+ function bindGroupingRefCanonicalId(ref, canonicalId) {
6997
+ groupingRefCanonicalIds.set(ref, canonicalId);
6998
+ }
6999
+ function evalGroupingRef(ref, row) {
7000
+ const membership = readGroupingMembership(row);
7001
+ if (!membership) {
7002
+ throw new Error("internal error: GROUPING() evaluation requires B65 grouping row membership.");
7003
+ }
7004
+ const canonicalId = groupingRefCanonicalIds.get(ref);
7005
+ if (!canonicalId) {
7006
+ throw new Error("internal error: GROUPING() reference was not resolved during B65 planning.");
7007
+ }
7008
+ return membership.has(canonicalId) ? "0" : "1";
7009
+ }
7010
+
6718
7011
  // src/engine/evalWhere.ts
6719
7012
  function evalWhere(expr, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2) {
6720
7013
  switch (expr.type) {
@@ -6821,6 +7114,7 @@ function semanticsForLeft(left, fieldType, resolveSemantics) {
6821
7114
  });
6822
7115
  if (modes.length > 0 && modes.every((mode) => mode.compareMode === modes[0].compareMode)) return modes[0];
6823
7116
  }
7117
+ if (left.type === "GROUPING_FIELD") return syntheticSemantics("number");
6824
7118
  return syntheticSemantics("string");
6825
7119
  }
6826
7120
  var STRING_ARRAY_FIELD_TYPES = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
@@ -6886,6 +7180,7 @@ function resolveField(field, row, resolveFieldType, resolveFieldSemantics2) {
6886
7180
  if (field.type === "FUNC_FIELD") return evalStringFunc(field.expr, row);
6887
7181
  if (field.type === "ARITH_FIELD") return String(evalArithExpr(field.expr, row));
6888
7182
  if (field.type === "CASE_FIELD") return evalCaseWhen(field.expr, row, resolveFieldType, resolveFieldSemantics2);
7183
+ if (field.type === "GROUPING_FIELD") return evalGroupingRef(field.ref, row);
6889
7184
  const key = field.tableAlias ? `${field.tableAlias}.${field.field}` : field.field;
6890
7185
  return resolveFieldRef(row, key);
6891
7186
  }
@@ -9591,6 +9886,18 @@ function validateDeclaredBatchVariables(statements, input) {
9591
9886
  }
9592
9887
 
9593
9888
  // src/core/explainMetadata.ts
9889
+ function buildGroupingExplainMetadata(statement, canonicalItemCount) {
9890
+ const grouping = normalizeGroupingSpec(statement);
9891
+ if (grouping.type !== "GROUPING_SETS") return null;
9892
+ return {
9893
+ source: grouping.source,
9894
+ expandedSetCount: grouping.sets.length,
9895
+ groupingItemCount: canonicalItemCount ?? grouping.allItems.length,
9896
+ setLimit: B65_MAX_GROUPING_SETS,
9897
+ itemLimit: B65_MAX_GROUPING_ITEMS,
9898
+ outputRowLimit: B65_MAX_GENERATED_ROWS
9899
+ };
9900
+ }
9594
9901
  function whereNeedsFieldMetadata(where) {
9595
9902
  if (where === null) return false;
9596
9903
  switch (where.type) {
@@ -9617,7 +9924,7 @@ function valueNeedsFieldMetadata(value) {
9617
9924
  return Object.values(item).some(valueNeedsFieldMetadata);
9618
9925
  }
9619
9926
  function selectNeedsOwnMetadata(statement) {
9620
- return whereNeedsFieldMetadata(statement.where) || statement.orderBy.length > 0 || statement.columns.some(
9927
+ return whereNeedsFieldMetadata(statement.where) || normalizeGroupingSpec(statement).type === "GROUPING_SETS" || statement.orderBy.length > 0 || statement.columns.some(
9621
9928
  (column) => column.type === "WINDOW_COL" && column.orderBy.length > 0
9622
9929
  );
9623
9930
  }
@@ -9642,6 +9949,212 @@ function explainNeedsAppMetadata(statement) {
9642
9949
  return visit(statement);
9643
9950
  }
9644
9951
 
9952
+ // src/core/groupingValidation.ts
9953
+ var enforceGroupingPlanningCandidateLimits = (facts) => {
9954
+ if (facts.expandedSetCount > B65_MAX_GROUPING_SETS) {
9955
+ throw new Error(
9956
+ `ArgumentError: B65 expanded grouping set count ${facts.expandedSetCount} exceeds limit ${B65_MAX_GROUPING_SETS} (reason=GROUPING_SET_LIMIT_EXCEEDED).`
9957
+ );
9958
+ }
9959
+ if (facts.canonicalItemCount > B65_MAX_GROUPING_ITEMS) {
9960
+ throw new Error(
9961
+ `ArgumentError: B65 canonical grouping item count ${facts.canonicalItemCount} exceeds limit ${B65_MAX_GROUPING_ITEMS} (reason=GROUPING_ITEM_LIMIT_EXCEEDED).`
9962
+ );
9963
+ }
9964
+ };
9965
+ function displayField(field) {
9966
+ return field.tableAlias ? `${field.tableAlias}.${field.field}` : field.field;
9967
+ }
9968
+ function refFromName(name) {
9969
+ const dot = name.indexOf(".");
9970
+ return dot > 0 ? { type: "FIELD", tableAlias: name.slice(0, dot), field: name.slice(dot + 1) } : { type: "FIELD", tableAlias: null, field: name };
9971
+ }
9972
+ function collectGroupingRefs(node, out) {
9973
+ if (node === null || typeof node !== "object") return;
9974
+ if (Array.isArray(node)) {
9975
+ node.forEach((item) => collectGroupingRefs(item, out));
9976
+ return;
9977
+ }
9978
+ const value = node;
9979
+ if (value["type"] === "SELECT" || value["type"] === "SCALAR_SUBQUERY") return;
9980
+ if (value["type"] === "GROUPING_REF") {
9981
+ out.push(value);
9982
+ return;
9983
+ }
9984
+ Object.values(value).forEach((item) => collectGroupingRefs(item, out));
9985
+ }
9986
+ function collectNonAggregateFieldRefs(node, out) {
9987
+ if (node === null || typeof node !== "object") return;
9988
+ if (Array.isArray(node)) {
9989
+ node.forEach((item) => collectNonAggregateFieldRefs(item, out));
9990
+ return;
9991
+ }
9992
+ const value = node;
9993
+ const type = value["type"];
9994
+ if (type === "SELECT" || type === "SCALAR_SUBQUERY" || type === "GROUPING_REF" || type === "AGG_REF" || type === "AGG_ARITH") return;
9995
+ if (type === "FIELD" && typeof value["field"] === "string") {
9996
+ out.push({
9997
+ type: "FIELD",
9998
+ tableAlias: typeof value["tableAlias"] === "string" ? value["tableAlias"] : null,
9999
+ field: value["field"]
10000
+ });
10001
+ return;
10002
+ }
10003
+ if (type === "FIELD_REF" && typeof value["field"] === "string") {
10004
+ out.push(refFromName(value["field"]));
10005
+ return;
10006
+ }
10007
+ Object.values(value).forEach((item) => collectNonAggregateFieldRefs(item, out));
10008
+ }
10009
+ function containsAggregate(node) {
10010
+ if (node === null || typeof node !== "object") return false;
10011
+ if (Array.isArray(node)) return node.some(containsAggregate);
10012
+ const value = node;
10013
+ if (value["type"] === "AGG_REF" || value["type"] === "AGG_ARITH") return true;
10014
+ if (value["type"] === "SELECT" || value["type"] === "SCALAR_SUBQUERY") return false;
10015
+ return Object.values(value).some(containsAggregate);
10016
+ }
10017
+ function isAggregateMaterializedAlias(column) {
10018
+ if (!("alias" in column) || column.alias === null) return false;
10019
+ if (column.type === "AGGREGATE" || column.type === "ARITH_AGG_COL") return true;
10020
+ if (column.type === "STRFUNC_COL" || column.type === "SCALAR_VALUE_COL") {
10021
+ return containsAggregate(column);
10022
+ }
10023
+ return false;
10024
+ }
10025
+ function outputAliases(columns) {
10026
+ return new Set(columns.flatMap(
10027
+ (column) => "alias" in column && typeof column.alias === "string" ? [column.alias] : []
10028
+ ));
10029
+ }
10030
+ function isAggregateSyntheticReference(ref) {
10031
+ return ref.tableAlias === null && /^(COUNT|SUM|AVG|MAX|MIN|GROUP_CONCAT|STDDEV_POP|STDDEV_SAMP|VAR_POP|VAR_SAMP|MEDIAN|MODE)\(/.test(ref.field);
10032
+ }
10033
+ function validateGroupingRefMembership(ref, resolve2, canonicalItems) {
10034
+ const resolved = resolve2(ref.field);
10035
+ if (!resolved.physical) {
10036
+ throw new Error(`ArgumentError: B65 grouping reference ${displayField(ref.field)} must resolve to a physical APP field.`);
10037
+ }
10038
+ if (!canonicalItems.has(resolved.canonicalId)) {
10039
+ throw new Error(
10040
+ `ArgumentError: B65 GROUPING argument ${displayField(ref.field)} is not present in grouping allItems (reason=B65_GROUPING_ARG_NOT_ITEM).`
10041
+ );
10042
+ }
10043
+ bindGroupingRefCanonicalId(ref, resolved.canonicalId);
10044
+ }
10045
+ function validateDependency(ref, resolve2, canonicalItems, context) {
10046
+ const resolved = resolve2(ref);
10047
+ if (!resolved.physical || !canonicalItems.has(resolved.canonicalId)) {
10048
+ throw new Error(
10049
+ `ArgumentError: B65 non-aggregate field ${displayField(ref)} in ${context} is not a grouping item (reason=B65_NON_GROUPED_DEPENDENCY).`
10050
+ );
10051
+ }
10052
+ }
10053
+ function keyDependencies(key) {
10054
+ if (key.type === "GROUPING_KEY") return [];
10055
+ if (key.type === "FIELD_NAME") return [refFromName(key.name)];
10056
+ const refs = [];
10057
+ collectNonAggregateFieldRefs(key, refs);
10058
+ return refs;
10059
+ }
10060
+ function validateGroupingPlanning(stmt, resolve2, planningGuardHook = () => void 0) {
10061
+ const normalized = normalizeGroupingSpec(stmt);
10062
+ const groupingRefs = [];
10063
+ for (const column of stmt.columns) {
10064
+ if (column.type !== "WINDOW_COL") collectGroupingRefs(column, groupingRefs);
10065
+ }
10066
+ collectGroupingRefs(stmt.orderBy, groupingRefs);
10067
+ const forbiddenGroupingRefs = [];
10068
+ collectGroupingRefs(stmt.where, forbiddenGroupingRefs);
10069
+ collectGroupingRefs(stmt.having, forbiddenGroupingRefs);
10070
+ for (const column of stmt.columns) {
10071
+ if (column.type === "WINDOW_COL") collectGroupingRefs(column, forbiddenGroupingRefs);
10072
+ }
10073
+ if (forbiddenGroupingRefs.length > 0) {
10074
+ throw new Error(
10075
+ "ArgumentError: B65 GROUPING() is not allowed in WHERE, HAVING, JOIN, window, aggregate arguments, or DML expressions in Phase1."
10076
+ );
10077
+ }
10078
+ if (normalized.type !== "GROUPING_SETS") {
10079
+ if (groupingRefs.length > 0) {
10080
+ throw new Error("ArgumentError: B65 GROUPING() requires GROUP BY ROLLUP or GROUPING SETS.");
10081
+ }
10082
+ return null;
10083
+ }
10084
+ if (stmt.distinct) {
10085
+ throw new Error("ArgumentError: B65 SELECT DISTINCT is not supported in Phase1.");
10086
+ }
10087
+ if (stmt.orderMode === "KINTONE_NATIVE") {
10088
+ throw new Error("ArgumentError: B65 KORDER BY is not supported in Phase1.");
10089
+ }
10090
+ if (stmt.columns.some((column) => column.type === "WINDOW_COL")) {
10091
+ throw new Error("ArgumentError: B65 window functions are not supported in Phase1.");
10092
+ }
10093
+ if (stmt.columns.some(
10094
+ (column) => column.type === "WILDCARD" || column.type === "PARENT_WILDCARD"
10095
+ )) {
10096
+ throw new Error("ArgumentError: B65 wildcard projection is not supported in Phase1.");
10097
+ }
10098
+ const resolvedSpec = resolveGroupingSpec(stmt, resolve2);
10099
+ const canonicalItems = /* @__PURE__ */ new Set();
10100
+ const resolvedItems = [];
10101
+ for (const item of resolvedSpec.allItems) {
10102
+ const resolved = item;
10103
+ if (!resolved.physical) {
10104
+ throw new Error(`ArgumentError: B65 grouping item ${displayField(item.field)} must resolve to a physical APP field.`);
10105
+ }
10106
+ if (!canonicalItems.has(resolved.canonicalId)) {
10107
+ canonicalItems.add(resolved.canonicalId);
10108
+ resolvedItems.push(resolved);
10109
+ }
10110
+ }
10111
+ planningGuardHook({
10112
+ expandedSetCount: normalized.sets.length,
10113
+ canonicalItemCount: canonicalItems.size
10114
+ });
10115
+ for (const ref of groupingRefs) {
10116
+ validateGroupingRefMembership(ref, resolve2, canonicalItems);
10117
+ }
10118
+ const aliases = outputAliases(stmt.columns);
10119
+ for (const column of stmt.columns) {
10120
+ if (column.type === "GROUPING_COL" || column.type === "AGGREGATE" || column.type === "ARITH_AGG_COL" || column.type === "LITERAL_COL" || column.type === "VARIABLE_COL" || column.type === "SCALAR_SUBQUERY_COL") continue;
10121
+ const refs = [];
10122
+ if (column.type === "FIELD") refs.push(refFromName(column.field));
10123
+ else collectNonAggregateFieldRefs(column, refs);
10124
+ for (const ref of refs) validateDependency(ref, resolve2, canonicalItems, "SELECT");
10125
+ }
10126
+ if (stmt.having) {
10127
+ const refs = [];
10128
+ collectNonAggregateFieldRefs(stmt.having, refs);
10129
+ for (const ref of refs) {
10130
+ if (ref.tableAlias === null && aliases.has(ref.field) || isAggregateSyntheticReference(ref)) continue;
10131
+ validateDependency(ref, resolve2, canonicalItems, "HAVING");
10132
+ }
10133
+ }
10134
+ for (const order of stmt.orderBy) {
10135
+ for (const ref of keyDependencies(order.key)) {
10136
+ if (ref.tableAlias === null && aliases.has(ref.field) || isAggregateSyntheticReference(ref)) continue;
10137
+ validateDependency(ref, resolve2, canonicalItems, "ORDER BY");
10138
+ }
10139
+ }
10140
+ const collisionKeys = /* @__PURE__ */ new Set();
10141
+ for (const item of resolvedItems) {
10142
+ collisionKeys.add(item.directKey);
10143
+ if (item.unqualifiedBridgeKey !== null) collisionKeys.add(item.unqualifiedBridgeKey);
10144
+ }
10145
+ for (const column of stmt.columns) {
10146
+ if (!isAggregateMaterializedAlias(column)) continue;
10147
+ const alias = "alias" in column ? column.alias : null;
10148
+ if (alias === null) continue;
10149
+ if (collisionKeys.has(alias)) {
10150
+ throw new Error(
10151
+ `ArgumentError: B65 aggregate alias ${alias} collides with a grouping runtime key (reason=B65_AGGREGATE_ALIAS_COLLISION).`
10152
+ );
10153
+ }
10154
+ }
10155
+ return resolvedSpec;
10156
+ }
10157
+
9645
10158
  // src/api/fetchAll.ts
9646
10159
  async function fetchAll(fetcher, app, query, fields, options = {}) {
9647
10160
  const pageSize = options.pageSize ?? PAGE_SIZE_DEFAULT;
@@ -10177,29 +10690,96 @@ function applyGroupBy(rows, groupByKeys, columns, resolveAggSortKind) {
10177
10690
  outRow[stringFuncDefaultKey(k.expr)] = evalStringFunc(k.expr, groupRows[0]);
10178
10691
  }
10179
10692
  }
10180
- for (const col of columns) {
10181
- if (col.type === "AGGREGATE") {
10182
- const syntheticKey = aggregateSyntheticName(col.func, col.distinct, col.arg);
10183
- const value = String(evalAggregate(col.func, col.distinct, col.arg, col.separator, groupRows, resolveAggSortKind));
10184
- outRow[col.alias ?? syntheticKey] = value;
10185
- if (col.alias) outRow[syntheticKey] = value;
10186
- } else if (col.type === "ARITH_AGG_COL") {
10187
- const outputKey = col.alias ?? aggArithDefaultKey(col.expr);
10188
- outRow[outputKey] = String(evalAggArithExpr(col.expr, groupRows, resolveAggSortKind));
10189
- } else if (col.type === "STRFUNC_COL" && hasAggregateInStringFuncExpr2(col.expr)) {
10190
- const outputKey = col.alias ?? stringFuncDefaultKey(col.expr);
10191
- const resolvedExpr = resolveAggInStringFuncExpr(col.expr, groupRows, resolveAggSortKind);
10192
- outRow[outputKey] = evalStringFunc(resolvedExpr, outRow);
10193
- } else if (col.type === "SCALAR_VALUE_COL" && scalarValueHasAggregate2(col.expr)) {
10194
- const outputKey = col.alias ?? scalarValueDefaultKey(col.expr);
10195
- const resolvedExpr = resolveAggInScalarValue(col.expr, groupRows, resolveAggSortKind);
10196
- outRow[outputKey] = String(evalScalarValueExpr(resolvedExpr, outRow));
10693
+ materializeAggregateColumns(outRow, groupRows, columns, resolveAggSortKind);
10694
+ result.push(outRow);
10695
+ }
10696
+ return result;
10697
+ }
10698
+ function applyGroupingSets(rows, spec, columns, resolveAggSortKind, limits = {}) {
10699
+ const result = [];
10700
+ let generatedRows = 0;
10701
+ const countBucket = () => {
10702
+ generatedRows++;
10703
+ if (limits.maxGeneratedRows !== void 0 && generatedRows > limits.maxGeneratedRows) {
10704
+ throw new Error(
10705
+ `LimitError: B65 generated grouping rows ${generatedRows} exceed limit ${limits.maxGeneratedRows} (reason=GROUPING_OUTPUT_LIMIT_EXCEEDED).`
10706
+ );
10707
+ }
10708
+ };
10709
+ for (const set of spec.sets) {
10710
+ const root = { children: /* @__PURE__ */ new Map() };
10711
+ const buckets = [];
10712
+ for (const row of rows) {
10713
+ let node = root;
10714
+ for (const item of set.items) {
10715
+ const value = groupingItemValue(item, row);
10716
+ let child = node.children.get(value);
10717
+ if (!child) {
10718
+ child = { children: /* @__PURE__ */ new Map() };
10719
+ node.children.set(value, child);
10720
+ }
10721
+ node = child;
10722
+ }
10723
+ if (!node.rows) {
10724
+ countBucket();
10725
+ node.rows = [];
10726
+ buckets.push(node.rows);
10727
+ }
10728
+ node.rows.push(row);
10729
+ }
10730
+ if (rows.length === 0 && set.items.length === 0) {
10731
+ countBucket();
10732
+ root.rows = [];
10733
+ buckets.push(root.rows);
10734
+ }
10735
+ const includedCanonicalIds = new Set(set.items.map((item) => item.canonicalId));
10736
+ for (const groupRows of buckets) {
10737
+ const outRow = { ...groupRows[0] };
10738
+ const includedValues = /* @__PURE__ */ new Map();
10739
+ for (const item of set.items) {
10740
+ if (!includedValues.has(item.canonicalId)) {
10741
+ includedValues.set(item.canonicalId, groupingItemValue(item, groupRows[0]));
10742
+ }
10197
10743
  }
10744
+ for (const item of spec.allItems) {
10745
+ const value = includedValues.get(item.canonicalId) ?? "";
10746
+ outRow[item.directKey] = value;
10747
+ if (item.unqualifiedBridgeKey !== null) {
10748
+ outRow[item.unqualifiedBridgeKey] = value;
10749
+ }
10750
+ }
10751
+ materializeAggregateColumns(outRow, groupRows, columns, resolveAggSortKind);
10752
+ attachGroupingRowMeta(outRow, includedCanonicalIds);
10753
+ result.push(outRow);
10198
10754
  }
10199
- result.push(outRow);
10200
10755
  }
10201
10756
  return result;
10202
10757
  }
10758
+ function groupingItemValue(item, row) {
10759
+ if (!row) return "";
10760
+ return row[item.directKey] ?? (item.unqualifiedBridgeKey === null ? void 0 : row[item.unqualifiedBridgeKey]) ?? "";
10761
+ }
10762
+ function materializeAggregateColumns(outRow, groupRows, columns, resolveAggSortKind) {
10763
+ for (const col of columns) {
10764
+ if (col.type === "AGGREGATE") {
10765
+ const syntheticKey = aggregateSyntheticName(col.func, col.distinct, col.arg);
10766
+ const value = String(evalAggregate(col.func, col.distinct, col.arg, col.separator, groupRows, resolveAggSortKind));
10767
+ outRow[col.alias ?? syntheticKey] = value;
10768
+ if (col.alias) outRow[syntheticKey] = value;
10769
+ } else if (col.type === "ARITH_AGG_COL") {
10770
+ const outputKey = col.alias ?? aggArithDefaultKey(col.expr);
10771
+ outRow[outputKey] = String(evalAggArithExpr(col.expr, groupRows, resolveAggSortKind));
10772
+ } else if (col.type === "STRFUNC_COL" && hasAggregateInStringFuncExpr2(col.expr)) {
10773
+ const outputKey = col.alias ?? stringFuncDefaultKey(col.expr);
10774
+ const resolvedExpr = resolveAggInStringFuncExpr(col.expr, groupRows, resolveAggSortKind);
10775
+ outRow[outputKey] = evalStringFunc(resolvedExpr, outRow);
10776
+ } else if (col.type === "SCALAR_VALUE_COL" && scalarValueHasAggregate2(col.expr)) {
10777
+ const outputKey = col.alias ?? scalarValueDefaultKey(col.expr);
10778
+ const resolvedExpr = resolveAggInScalarValue(col.expr, groupRows, resolveAggSortKind);
10779
+ outRow[outputKey] = String(evalScalarValueExpr(resolvedExpr, outRow));
10780
+ }
10781
+ }
10782
+ }
10203
10783
  function evalGroupByKey(key, row) {
10204
10784
  if (key.type === "FIELD_NAME") return row[key.name] ?? "";
10205
10785
  if (key.type === "FUNC_KEY") return evalStringFunc(key.expr, row);
@@ -10412,6 +10992,7 @@ function sortDecoratedRows(rows, orderBy, optionOrders, sortKinds, fieldSemantic
10412
10992
  if (key.type === "FUNC_KEY") {
10413
10993
  return { semantics: syntheticSemantics(NUMERIC_ORDER_FUNCTIONS.has(key.expr.func) ? "number" : "string") };
10414
10994
  }
10995
+ if (key.type === "GROUPING_KEY") return { semantics: syntheticSemantics("number") };
10415
10996
  const semantics = fieldSemantics2?.get(key.name);
10416
10997
  if (semantics) return { semantics };
10417
10998
  const orderMap = optionOrders?.get(key.name);
@@ -10477,6 +11058,8 @@ function evalOrderKey(key, row, aliasEvaluator) {
10477
11058
  return String(evalArithExpr(key.expr, row));
10478
11059
  case "FUNC_KEY":
10479
11060
  return evalStringFunc(key.expr, row);
11061
+ case "GROUPING_KEY":
11062
+ return evalGroupingRef(key.ref, row);
10480
11063
  }
10481
11064
  }
10482
11065
  function buildOrderByAliasEvaluator(columns, scalarCache, resolveFieldType, resolveFieldSemantics2) {
@@ -10523,6 +11106,9 @@ function buildOrderByAliasEvaluator(columns, scalarCache, resolveFieldType, reso
10523
11106
  case "SCALAR_SUBQUERY_COL":
10524
11107
  evaluators.set(alias, () => scalarCache?.get(columnIndex) ?? "");
10525
11108
  break;
11109
+ case "GROUPING_COL":
11110
+ evaluators.set(alias, (row) => evalGroupingRef(column.ref, row));
11111
+ break;
10526
11112
  case "VARIABLE_COL":
10527
11113
  break;
10528
11114
  }
@@ -10642,6 +11228,12 @@ function project(rows, columns, scalarCache, resolveFieldType, sourceColumns, re
10642
11228
  if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
10643
11229
  break;
10644
11230
  }
11231
+ case "GROUPING_COL": {
11232
+ const key = outputKeys?.[colIdx] ?? col.alias ?? `GROUPING(${col.ref.field.tableAlias ? `${col.ref.field.tableAlias}.` : ""}${col.ref.field.field})`;
11233
+ out[key] = evalGroupingRef(col.ref, row);
11234
+ if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
11235
+ break;
11236
+ }
10645
11237
  case "STRFUNC_COL": {
10646
11238
  const key = outputKeys?.[colIdx] ?? col.alias ?? stringFuncDefaultKey(col.expr);
10647
11239
  if (hasAggregateInStringFuncExpr2(col.expr)) {
@@ -10714,6 +11306,8 @@ function computeOutputKey(col, colIdx, defaultFieldKeys) {
10714
11306
  return col.alias ?? stringFuncDefaultKey(col.expr);
10715
11307
  case "SCALAR_VALUE_COL":
10716
11308
  return col.alias ?? scalarValueDefaultKey(col.expr);
11309
+ case "GROUPING_COL":
11310
+ return col.alias ?? `GROUPING(${col.ref.field.tableAlias ? `${col.ref.field.tableAlias}.` : ""}${col.ref.field.field})`;
10717
11311
  case "SCALAR_SUBQUERY_COL":
10718
11312
  return col.alias ?? "(subquery)";
10719
11313
  case "WINDOW_COL":
@@ -10904,7 +11498,8 @@ function runFullScan(input) {
10904
11498
  appliedKlikes,
10905
11499
  sourceColumns,
10906
11500
  tableColumns,
10907
- hiddenQualifiedAliases
11501
+ hiddenQualifiedAliases,
11502
+ resolvedGroupingSpec
10908
11503
  } = input;
10909
11504
  const effectiveOrderSemantics = deriveOutputOrderSemantics(stmt.columns);
10910
11505
  for (const [key, value] of orderSemantics ?? []) effectiveOrderSemantics.set(key, value);
@@ -10933,8 +11528,25 @@ function runFullScan(input) {
10933
11528
  knownColumns = mergeKnownColumns(knownColumns, rightColumns, rows);
10934
11529
  }
10935
11530
  rows = applyFilter(rows, stmt.where, fieldTypeResolver, appliedKlikes, fieldSemanticsResolver);
10936
- if (stmt.groupBy.length > 0 || hasAggregateColumns(stmt.columns)) {
10937
- rows = applyGroupBy(rows, stmt.groupBy, stmt.columns, aggregateSortKindResolver);
11531
+ const grouping = normalizeGroupingSpec(stmt);
11532
+ if (grouping.type === "GROUPING_SETS") {
11533
+ if (!resolvedGroupingSpec) {
11534
+ throw new Error("internal error: B65 grouping sets require a metadata-resolved grouping spec.");
11535
+ }
11536
+ rows = applyGroupingSets(
11537
+ rows,
11538
+ resolvedGroupingSpec,
11539
+ stmt.columns,
11540
+ aggregateSortKindResolver,
11541
+ { maxGeneratedRows: B65_MAX_GENERATED_ROWS }
11542
+ );
11543
+ } else if (grouping.type === "PLAIN" || hasAggregateColumns(stmt.columns)) {
11544
+ rows = applyGroupBy(
11545
+ rows,
11546
+ grouping.type === "PLAIN" ? grouping.allItems : [],
11547
+ stmt.columns,
11548
+ aggregateSortKindResolver
11549
+ );
10938
11550
  }
10939
11551
  rows = applyHaving(rows, stmt.having, havingFieldTypeResolver, havingFieldSemanticsResolver);
10940
11552
  rows = applyWindow(rows, stmt.columns, optionOrders, sortKinds, effectiveOrderSemantics);
@@ -12319,6 +12931,9 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
12319
12931
  assertApplyScope("phase15b", stmt);
12320
12932
  assertApplyExecutionScope("phase15b", stmt);
12321
12933
  validateKlikeStatement(stmt);
12934
+ if (stmt.type !== "EXPLAIN") {
12935
+ await validateStatementGroupingPlanning(stmt, client, cacheContext);
12936
+ }
12322
12937
  if (stmt.type === "IMPORT") return executeImport(stmt, client, options, cacheContext);
12323
12938
  if (stmt.type === "UPDATE" && stmt.applyBlocks?.length) {
12324
12939
  if (stmt.validationErrorTable) {
@@ -13193,7 +13808,7 @@ async function evaluateScalarSubquery(sourceQuery, client, options, cacheContext
13193
13808
  return result.rows[0]?.[result.columns[0]] ?? "";
13194
13809
  }
13195
13810
  function withScalarProbeLimit(query) {
13196
- const hasAgg = query.groupBy.length > 0 || query.columns.some((c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL");
13811
+ const hasAgg = normalizeGroupingSpec(query).type !== "NONE" || query.columns.some((c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL");
13197
13812
  if (hasAgg || query.distinct || query.limit !== null) return { query, probed: false };
13198
13813
  return { query: { ...query, limit: 2 }, probed: true };
13199
13814
  }
@@ -13385,6 +14000,7 @@ async function assertDmlWhereCapability(stmt, client, cacheContext) {
13385
14000
  }
13386
14001
  async function executeSelect(stmt, client, options, cacheContext, cteCache, captureColumnMeta = false) {
13387
14002
  let result;
14003
+ await validateSelectGroupingPlanning(stmt, client, cacheContext, cteCache);
13388
14004
  if (isNoFromSelect(stmt)) {
13389
14005
  result = executeNoFromSelect(stmt);
13390
14006
  if (captureColumnMeta) {
@@ -13414,18 +14030,15 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
13414
14030
  client,
13415
14031
  cacheContext
13416
14032
  );
13417
- const completeReasons = orderPlan?.kind === "CANONICAL_REST_TOP_N" || orderPlan?.kind === "KORDER_NATIVE" || orderPlan?.kind === "KORDER_CURSOR" ? completeInputReasons({ ...stmt, orderBy: [] }) : completeInputReasons(stmt);
13418
- const completeInputRequired = completeReasons.size > 0;
13419
- const truncateWasDisabled = completeInputRequired && options.onLimitReached === "truncate";
13420
- const effectiveOptions = truncateWasDisabled ? { ...options, onLimitReached: "error" } : options;
14033
+ const completePolicy = buildCompleteInputPolicy(stmt, options, orderPlan);
13421
14034
  try {
13422
14035
  if (mode === "SIMPLE") {
13423
- result = await executeSimpleSelect(stmt, client, effectiveOptions, cacheContext, orderPlan, orderMeta);
14036
+ result = await executeSimpleSelect(stmt, client, completePolicy.effectiveOptions, cacheContext, orderPlan, orderMeta);
13424
14037
  } else {
13425
14038
  result = await executeFullScanSelect(
13426
14039
  stmt,
13427
14040
  client,
13428
- effectiveOptions,
14041
+ completePolicy.effectiveOptions,
13429
14042
  cacheContext,
13430
14043
  cteCache,
13431
14044
  whereCapability.capability === "EXACT_PUSHDOWN",
@@ -13433,23 +14046,145 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
13433
14046
  );
13434
14047
  }
13435
14048
  } catch (error) {
13436
- if (completeInputRequired && error instanceof FetchAllLimitError) {
13437
- throw new FetchAllLimitError(
13438
- completeInputErrorPrefix(completeReasons) + (truncateWasDisabled ? "onLimit=truncate\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002" : "") + error.message
13439
- );
13440
- }
13441
- throw error;
14049
+ throwCompleteInputError(completePolicy, error);
13442
14050
  }
13443
14051
  if (captureColumnMeta) {
13444
14052
  materializedMetaBySelectResult.set(result, await inferSelectColumnMeta(stmt, result.columns, client, cacheContext, cteCache));
13445
14053
  }
13446
14054
  return result;
13447
14055
  }
14056
+ var resolvedGroupingSpecs = /* @__PURE__ */ new WeakMap();
14057
+ async function validateStatementGroupingPlanning(statement, client, cacheContext) {
14058
+ const seen = /* @__PURE__ */ new Set();
14059
+ const visit = async (node) => {
14060
+ if (node === null || typeof node !== "object") return;
14061
+ if (seen.has(node)) return;
14062
+ seen.add(node);
14063
+ if (Array.isArray(node)) {
14064
+ for (const item of node) await visit(item);
14065
+ return;
14066
+ }
14067
+ const value = node;
14068
+ if (value["type"] === "SELECT") {
14069
+ await validateSelectGroupingPlanning(node, client, cacheContext);
14070
+ }
14071
+ for (const child of Object.values(value)) await visit(child);
14072
+ };
14073
+ await visit(statement);
14074
+ }
14075
+ async function buildGroupingFieldResolver(stmt, client, cacheContext, materializedTables) {
14076
+ const tables = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
14077
+ const physicalTables = tables.filter((table) => table.cteName === null);
14078
+ const infosByApp = new Map(
14079
+ await Promise.all([...new Set(physicalTables.map((table) => table.appId))].map(async (appId) => {
14080
+ const infos = await getFieldsCached(appId, client, cacheContext);
14081
+ return [appId, new Map(infos.map((info) => [info.code, info]))];
14082
+ }))
14083
+ );
14084
+ const physicalMatch = (table, field) => {
14085
+ if (field === "$id") return "$id";
14086
+ const code = fieldCodeForTypeLookup(table, field);
14087
+ return infosByApp.get(table.appId)?.has(code) ? code : null;
14088
+ };
14089
+ const materializedHas = (table, field) => {
14090
+ if (table.cteName === null) return false;
14091
+ const materialized = materializedTables?.get(table.cteName);
14092
+ return materialized ? materialized.columns.includes(field) : true;
14093
+ };
14094
+ const resolved = (table, tableIndex, field, code) => {
14095
+ const sameNamePhysical = tables.filter(
14096
+ (candidate) => candidate.cteName === null && physicalMatch(candidate, field.field) !== null
14097
+ ).length;
14098
+ const sameNameMaterialized = tables.some((candidate) => materializedHas(candidate, field.field));
14099
+ const alias = effectiveTableAlias(table);
14100
+ return {
14101
+ canonicalId: `source:${tableIndex}:APP${table.appId}:${code}`,
14102
+ directKey: field.tableAlias && alias ? `${field.tableAlias}.${field.field}` : field.field,
14103
+ unqualifiedBridgeKey: sameNamePhysical === 1 && !sameNameMaterialized ? field.field : null,
14104
+ physical: true
14105
+ };
14106
+ };
14107
+ return (field) => {
14108
+ if (field.tableAlias !== null) {
14109
+ const tableIndex = tables.findIndex((table2) => effectiveTableAlias(table2) === field.tableAlias);
14110
+ if (tableIndex < 0) {
14111
+ throw new Error(`ArgumentError: B65 field ${field.tableAlias}.${field.field} has an unknown table alias.`);
14112
+ }
14113
+ const table = tables[tableIndex];
14114
+ if (table.cteName !== null) {
14115
+ throw new Error(
14116
+ `ArgumentError: B65 field ${field.tableAlias}.${field.field} resolves to materialized source ${table.cteName}; physical APP fields are required.`
14117
+ );
14118
+ }
14119
+ const code = physicalMatch(table, field.field);
14120
+ if (code === null) {
14121
+ throw new Error(`ArgumentError: B65 field ${field.tableAlias}.${field.field} does not exist in APP${table.appId}.`);
14122
+ }
14123
+ return resolved(table, tableIndex, field, code);
14124
+ }
14125
+ const physicalMatches = tables.flatMap((table, tableIndex) => {
14126
+ if (table.cteName !== null) return [];
14127
+ const code = physicalMatch(table, field.field);
14128
+ return code === null ? [] : [{ table, tableIndex, code }];
14129
+ });
14130
+ const materializedMatches = tables.filter((table) => materializedHas(table, field.field));
14131
+ if (physicalMatches.length + materializedMatches.length > 1) {
14132
+ throw new Error(`ArgumentError: B65 field ${field.field} is ambiguous across multiple sources.`);
14133
+ }
14134
+ if (physicalMatches.length === 1 && materializedMatches.length === 0) {
14135
+ const match = physicalMatches[0];
14136
+ return resolved(match.table, match.tableIndex, field, match.code);
14137
+ }
14138
+ if (materializedMatches.length === 1) {
14139
+ throw new Error(
14140
+ `ArgumentError: B65 field ${field.field} resolves to a materialized CTE/temp column; physical APP fields are required.`
14141
+ );
14142
+ }
14143
+ throw new Error(`ArgumentError: B65 field ${field.field} does not exist in a physical APP source.`);
14144
+ };
14145
+ }
14146
+ async function validateSelectGroupingPlanning(stmt, client, cacheContext, materializedTables) {
14147
+ resolvedGroupingSpecs.delete(stmt);
14148
+ const normalized = normalizeGroupingSpec(stmt);
14149
+ const hasGroupingNodes = JSON.stringify(stmt.columns).includes('"GROUPING_') || JSON.stringify(stmt.orderBy).includes('"GROUPING_');
14150
+ if (normalized.type === "NONE" && !hasGroupingNodes) return;
14151
+ const resolver = await buildGroupingFieldResolver(stmt, client, cacheContext, materializedTables);
14152
+ const resolvedSpec = validateGroupingPlanning(
14153
+ stmt,
14154
+ resolver,
14155
+ enforceGroupingPlanningCandidateLimits
14156
+ );
14157
+ if (resolvedSpec) resolvedGroupingSpecs.set(stmt, resolvedSpec);
14158
+ }
13448
14159
  function completeInputErrorPrefix(reasons) {
13449
14160
  const reasonList = [...reasons].join(", ");
13450
- const subject = reasons.size === 1 && reasons.has("STATISTICAL_AGGREGATE") ? "\u7D71\u8A08\u96C6\u7D04\u306E\u6B63\u3057\u3044\u7D50\u679C" : "ORDER BY\u306E\u6B63\u3057\u3044\u7D50\u679C";
14161
+ const subject = reasons.size === 1 && reasons.has("STATISTICAL_AGGREGATE") ? "\u7D71\u8A08\u96C6\u7D04\u306E\u6B63\u3057\u3044\u7D50\u679C" : reasons.size === 1 && reasons.has("GROUPING_SETS") ? "\u5C0F\u8A08\u30FB\u7DCF\u8A08\u306E\u6B63\u3057\u3044\u7D50\u679C" : reasons.has("GROUPING_SETS") ? "\u30AF\u30A8\u30EA\u306E\u6B63\u3057\u3044\u7D50\u679C" : "ORDER BY\u306E\u6B63\u3057\u3044\u7D50\u679C";
13451
14162
  return `${subject}\u306B\u306F\u5B8C\u5168\u306A\u5019\u88DC\u96C6\u5408\u304C\u5FC5\u8981\u3067\u3059\u3002complete input reason: ${reasonList}\u3002`;
13452
14163
  }
14164
+ function buildCompleteInputPolicy(stmt, options, orderPlan) {
14165
+ const reasons = orderPlan?.kind === "CANONICAL_REST_TOP_N" || orderPlan?.kind === "KORDER_NATIVE" || orderPlan?.kind === "KORDER_CURSOR" ? completeInputReasons({ ...stmt, orderBy: [] }) : completeInputReasons(stmt);
14166
+ const truncateWasDisabled = reasons.size > 0 && options.onLimitReached === "truncate";
14167
+ return {
14168
+ reasons,
14169
+ effectiveOptions: truncateWasDisabled ? { ...options, onLimitReached: "error" } : options,
14170
+ truncateWasDisabled
14171
+ };
14172
+ }
14173
+ function throwCompleteInputError(policy, error) {
14174
+ if (policy.reasons.size > 0 && error instanceof FetchAllLimitError) {
14175
+ throw new FetchAllLimitError(
14176
+ completeInputErrorPrefix(policy.reasons) + (policy.truncateWasDisabled ? "onLimit=truncate\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002" : "") + error.message
14177
+ );
14178
+ }
14179
+ throw error;
14180
+ }
14181
+ async function withCompleteInputPolicy(policy, action) {
14182
+ try {
14183
+ return await action();
14184
+ } catch (error) {
14185
+ throwCompleteInputError(policy, error);
14186
+ }
14187
+ }
13453
14188
  function isConstantFalseWhere(where) {
13454
14189
  return where?.type === "BOOLEAN" && where.value === false;
13455
14190
  }
@@ -13514,7 +14249,7 @@ function validateNoFromColumns(stmt) {
13514
14249
  }
13515
14250
  }
13516
14251
  function executeNoFromSelect(stmt) {
13517
- if (stmt.joins.length > 0 || stmt.where || stmt.groupBy.length > 0 || stmt.having || stmt.orderBy.length > 0 || stmt.distinct) {
14252
+ if (stmt.joins.length > 0 || stmt.where || normalizeGroupingSpec(stmt).type !== "NONE" || stmt.having || stmt.orderBy.length > 0 || stmt.distinct) {
13518
14253
  throw new Error("ArgumentError: JOIN/WHERE/GROUP BY/HAVING/ORDER BY/DISTINCT are not supported without FROM.");
13519
14254
  }
13520
14255
  validateNoFromColumns(stmt);
@@ -14155,6 +14890,8 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
14155
14890
  }
14156
14891
  } else if (column.type === "ARITH_AGG_COL" || column.type === "ARITH_COL") {
14157
14892
  meta = syntheticColumnMeta("number");
14893
+ } else if (column.type === "GROUPING_COL") {
14894
+ meta = syntheticColumnMeta("number");
14158
14895
  } else if (column.type === "LITERAL_COL") {
14159
14896
  meta = syntheticColumnMeta("string");
14160
14897
  } else if (column.type === "SCALAR_VALUE_COL") {
@@ -14194,7 +14931,7 @@ function mergeUnionColumnMeta(left, right) {
14194
14931
  function buildSelectFieldTypeResolvers(stmt, fieldTypesByApp) {
14195
14932
  const tables = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
14196
14933
  const physicalTables = tables.filter((table) => table.cteName === null);
14197
- const outputAliases = new Set(
14934
+ const outputAliases2 = new Set(
14198
14935
  stmt.columns.map((column) => "alias" in column ? column.alias : null).filter((alias) => alias !== null)
14199
14936
  );
14200
14937
  const row = (field) => {
@@ -14218,7 +14955,7 @@ function buildSelectFieldTypeResolvers(stmt, fieldTypesByApp) {
14218
14955
  return matches.length === 1 ? matches[0] : void 0;
14219
14956
  };
14220
14957
  const having = (field) => {
14221
- if (field.tableAlias === null && outputAliases.has(field.field)) return void 0;
14958
+ if (field.tableAlias === null && outputAliases2.has(field.field)) return void 0;
14222
14959
  return row(field);
14223
14960
  };
14224
14961
  return { row, having };
@@ -14340,7 +15077,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
14340
15077
  havingFieldTypeResolver: fieldTypeResolvers.having,
14341
15078
  havingFieldSemanticsResolver,
14342
15079
  aggregateSortKindResolver,
14343
- appliedKlikes: pushdownPlan.appliedKlikes
15080
+ appliedKlikes: pushdownPlan.appliedKlikes,
15081
+ resolvedGroupingSpec: resolvedGroupingSpecs.get(stmt)
14344
15082
  });
14345
15083
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
14346
15084
  }
@@ -14427,6 +15165,8 @@ async function executeQueryWithCte(query, client, options, cteCache, cacheContex
14427
15165
  return result;
14428
15166
  }
14429
15167
  async function executeFullScanWithCte(stmt, client, options, cteCache, cacheContext) {
15168
+ await validateSelectGroupingPlanning(stmt, client, cacheContext, cteCache);
15169
+ const resolvedGroupingSpec = resolvedGroupingSpecs.get(stmt);
14430
15170
  const hiddenQualifiedAliases = /* @__PURE__ */ new Set();
14431
15171
  const withEffectiveAlias = (table) => {
14432
15172
  if (table.alias !== null || table.cteName === null) return table;
@@ -14471,16 +15211,16 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
14471
15211
  throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(whereCapability)}).`);
14472
15212
  }
14473
15213
  const orderMeta = await buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
14474
- if (hasCanonicalOrder(stmt)) {
14475
- (stmt.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
14476
- stmt,
14477
- staticMode: "FULL_SCAN",
14478
- whereCapability: whereCapability.capability,
14479
- orderSemantics: orderMeta.semantics,
14480
- maxRecords,
14481
- hasKlike: whereHasKlike(stmt.where)
14482
- });
14483
- }
15214
+ const orderPlan = hasCanonicalOrder(stmt) ? (stmt.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
15215
+ stmt,
15216
+ staticMode: "FULL_SCAN",
15217
+ whereCapability: whereCapability.capability,
15218
+ orderSemantics: orderMeta.semantics,
15219
+ maxRecords,
15220
+ hasKlike: whereHasKlike(stmt.where)
15221
+ }) : null;
15222
+ const completePolicy = buildCompleteInputPolicy(stmt, options, orderPlan);
15223
+ const effectiveOptions = completePolicy.effectiveOptions;
14484
15224
  const [pushdownMeta, typedInFieldTypes, aggregateSortKindResolver] = await Promise.all([
14485
15225
  loadTypedPushdownMeta(stmt, client, cacheContext),
14486
15226
  loadTypedInFieldTypes(stmt, client, cacheContext),
@@ -14497,7 +15237,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
14497
15237
  const havingFieldSemanticsResolver = buildHavingFieldSemanticsResolver(stmt, fieldSemanticsResolver);
14498
15238
  const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
14499
15239
  validateKlikePushdownPlan(pushdownPlan);
14500
- const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
15240
+ const scalarCachePromise = resolveScalarColumns(stmt.columns, client, effectiveOptions, cacheContext, cteCache);
14501
15241
  const orderByMetaPromise = Promise.resolve(orderMeta);
14502
15242
  scalarCachePromise.catch(() => {
14503
15243
  });
@@ -14510,21 +15250,21 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
14510
15250
  tables.set(stmt.from.alias, table.rows.map(processRowToKintoneRecord));
14511
15251
  tableColumns.set(stmt.from.alias, table.columns);
14512
15252
  } else {
14513
- const mainRecords = await fetchTableRecordsForFullScan(
15253
+ const mainRecords = await withCompleteInputPolicy(completePolicy, () => fetchTableRecordsForFullScan(
14514
15254
  stmt,
14515
15255
  stmt.from,
14516
15256
  client,
14517
15257
  maxRecords,
14518
15258
  parallel,
14519
15259
  true,
14520
- options.onLimitReached ?? "error",
15260
+ effectiveOptions.onLimitReached ?? "error",
14521
15261
  warnings,
14522
15262
  pushdownPlan.mainCondition,
14523
15263
  whereCapability.capability === "EXACT_PUSHDOWN"
14524
- );
15264
+ ));
14525
15265
  tables.set(stmt.from.alias, mainRecords);
14526
15266
  }
14527
- const joinFetches = stmt.joins.map(async (join2) => {
15267
+ const joinFetches = stmt.joins.map((join2) => withCompleteInputPolicy(completePolicy, async () => {
14528
15268
  if (join2.table.cteName != null) {
14529
15269
  const table = requireMaterializedTable(join2.table.cteName);
14530
15270
  tables.set(join2.table.alias, table.rows.map(processRowToKintoneRecord));
@@ -14538,7 +15278,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
14538
15278
  client,
14539
15279
  maxRecords,
14540
15280
  parallel,
14541
- options.onLimitReached ?? "error",
15281
+ effectiveOptions.onLimitReached ?? "error",
14542
15282
  warnings,
14543
15283
  pushDownCond
14544
15284
  );
@@ -14549,13 +15289,13 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
14549
15289
  maxRecords,
14550
15290
  parallel,
14551
15291
  false,
14552
- options.onLimitReached ?? "error",
15292
+ effectiveOptions.onLimitReached ?? "error",
14553
15293
  warnings,
14554
15294
  pushDownCond
14555
15295
  );
14556
15296
  tables.set(join2.table.alias, joinRecords);
14557
15297
  }
14558
- });
15298
+ }));
14559
15299
  await Promise.all(joinFetches);
14560
15300
  const scalarCache = await scalarCachePromise;
14561
15301
  const { optionOrders, sortKinds, semantics } = await orderByMetaPromise;
@@ -14575,7 +15315,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
14575
15315
  appliedKlikes: pushdownPlan.appliedKlikes,
14576
15316
  sourceColumns,
14577
15317
  tableColumns,
14578
- hiddenQualifiedAliases
15318
+ hiddenQualifiedAliases,
15319
+ resolvedGroupingSpec
14579
15320
  });
14580
15321
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
14581
15322
  }
@@ -14908,6 +15649,8 @@ async function buildOrderSemanticsForSelect(stmt, client, cacheContext, material
14908
15649
  if (column.type === "FIELD") meta = resolveField2(aggregateFieldRef(column.field));
14909
15650
  else if (column.type === "ARITH_COL" || column.type === "ARITH_AGG_COL" || column.type === "WINDOW_COL") {
14910
15651
  meta = syntheticColumnMeta("number");
15652
+ } else if (column.type === "GROUPING_COL") {
15653
+ meta = syntheticColumnMeta("number");
14911
15654
  } else if (column.type === "LITERAL_COL" || column.type === "SCALAR_VALUE_COL") meta = syntheticColumnMeta("string");
14912
15655
  else if (column.type === "STRFUNC_COL") meta = stringFunctionColumnMeta(column.expr);
14913
15656
  else if (column.type === "SCALAR_SUBQUERY_COL") meta = unknownStringColumnMeta();
@@ -17732,7 +18475,7 @@ function compareByOrder(a, b, orderBy, resolveSemantics) {
17732
18475
  for (const item of orderBy) {
17733
18476
  const av = evalOrderKeyForRow(item.key, a);
17734
18477
  const bv = evalOrderKeyForRow(item.key, b);
17735
- const semantics = item.key.type === "FIELD_NAME" ? resolveSemantics(aggregateFieldRef(item.key.name)) : item.key.type === "ARITH_KEY" ? syntheticSemantics("number") : stringFunctionColumnMeta(item.key.expr).semantics ?? syntheticSemantics("string");
18478
+ const semantics = item.key.type === "FIELD_NAME" ? resolveSemantics(aggregateFieldRef(item.key.name)) : item.key.type === "ARITH_KEY" ? syntheticSemantics("number") : item.key.type === "FUNC_KEY" ? stringFunctionColumnMeta(item.key.expr).semantics ?? syntheticSemantics("string") : syntheticSemantics("number");
17736
18479
  const cmp = compareCanonicalValues(av, bv, semantics ?? syntheticSemantics("string"));
17737
18480
  if (cmp !== 0) return item.direction === "ASC" ? cmp : -cmp;
17738
18481
  }
@@ -17746,6 +18489,8 @@ function evalOrderKeyForRow(key, row) {
17746
18489
  return String(evalArithExpr(key.expr, row));
17747
18490
  case "FUNC_KEY":
17748
18491
  return evalStringFunc(key.expr, row);
18492
+ case "GROUPING_KEY":
18493
+ throw new Error("ArgumentError: GROUPING() is not supported in REORDER BY.");
17749
18494
  }
17750
18495
  }
17751
18496
  async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache) {
@@ -17984,6 +18729,7 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
17984
18729
  const typed = node;
17985
18730
  if (typed["type"] === "SELECT") {
17986
18731
  const select = node;
18732
+ await validateSelectGroupingPlanning(select, tracedClient, cacheContext);
17987
18733
  const physicalApps = [select.from, ...select.joins.map((join2) => join2.table)].filter((table) => table.cteName === null).map((table) => table.appId);
17988
18734
  const needsWhereSchema = whereNeedsFieldMetadata(select.where);
17989
18735
  if (needsWhereSchema || select.orderBy.length > 0 || select.columns.some((column) => column.type === "WINDOW_COL" && column.orderBy.length > 0)) {
@@ -18410,13 +19156,23 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
18410
19156
  reasons.push(...whereCapability.reasons.map((reason) => reason.code));
18411
19157
  }
18412
19158
  const lines = [];
19159
+ const groupingMetadata = buildGroupingExplainMetadata(
19160
+ stmt,
19161
+ resolvedGroupingSpecs.get(stmt)?.allItems.length
19162
+ );
18413
19163
  if (label) lines.push(label);
18414
19164
  lines.push(` mode: ${mode}`);
18415
- if (isConstantFalseWhere(stmt.where)) {
18416
- lines.push(" predicate: constant false");
18417
- lines.push(" records API access: none");
18418
- lines.push(` app: APP${stmt.from.appId} (${stmt.from.appId})`);
18419
- return lines;
19165
+ if (groupingMetadata) {
19166
+ lines.push(` grouping source: ${groupingMetadata.source}`);
19167
+ lines.push(
19168
+ ` grouping sets: ${groupingMetadata.expandedSetCount} (limit: ${groupingMetadata.setLimit})`
19169
+ );
19170
+ lines.push(
19171
+ ` grouping items: ${groupingMetadata.groupingItemCount} (limit: ${groupingMetadata.itemLimit})`
19172
+ );
19173
+ lines.push(
19174
+ ` grouping output rows: runtime checked (limit: ${groupingMetadata.outputRowLimit}, before HAVING/DISTINCT/LIMIT)`
19175
+ );
18420
19176
  }
18421
19177
  if (orderPlan) {
18422
19178
  lines.push(` order plan: ${orderPlan.kind}`);
@@ -18430,15 +19186,24 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
18430
19186
  lines.push(" cursor page size: 500");
18431
19187
  lines.push(` scan rows: ${orderPlan.scanRows}`);
18432
19188
  }
19189
+ } else if (groupingMetadata) {
19190
+ lines.push(" order plan: CANONICAL_LOCAL");
18433
19191
  }
18434
19192
  const explainedStmt = orderPlan && !orderPlan.requiresCompleteInput ? { ...stmt, orderBy: [] } : stmt;
18435
19193
  const completeReasons = completeInputReasons(explainedStmt);
18436
19194
  if (orderPlan?.requiresCompleteInput) completeReasons.add("LOCAL_ORDER");
18437
- if (completeReasons.size > 0) {
19195
+ const constantFalse = isConstantFalseWhere(stmt.where);
19196
+ if (completeReasons.size > 0 && (!constantFalse || groupingMetadata !== null)) {
18438
19197
  lines.push(" complete input: required (onLimit=truncate disabled)");
18439
19198
  lines.push(` complete input reason: ${[...completeReasons].join(", ")}`);
18440
19199
  lines.push(" onLimit=truncate: disabled");
18441
19200
  }
19201
+ if (constantFalse) {
19202
+ lines.push(" predicate: constant false");
19203
+ lines.push(" records API access: none");
19204
+ lines.push(` app: APP${stmt.from.appId} (${stmt.from.appId})`);
19205
+ return lines;
19206
+ }
18442
19207
  if (mode === "FULL_SCAN" && reasons.length > 0) {
18443
19208
  lines.push(` reason: ${reasons.join(", ")}`);
18444
19209
  }
@@ -18522,8 +19287,11 @@ function collectFullScanReasons(stmt) {
18522
19287
  r.push("\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u4EEE\u60F3\u30C6\u30FC\u30D6\u30EB");
18523
19288
  if (stmt.joins.length > 0)
18524
19289
  r.push("JOIN \u3042\u308A");
18525
- if (stmt.groupBy.length > 0)
19290
+ const grouping = normalizeGroupingSpec(stmt);
19291
+ if (grouping.type === "PLAIN")
18526
19292
  r.push("GROUP BY \u3042\u308A");
19293
+ else if (grouping.type === "GROUPING_SETS")
19294
+ r.push(grouping.source === "ROLLUP" ? "ROLLUP \u3042\u308A" : "GROUPING SETS \u3042\u308A");
18527
19295
  if (stmt.distinct)
18528
19296
  r.push("DISTINCT \u3042\u308A");
18529
19297
  if (stmt.columns.some((c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL"))