@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 +851 -83
- package/dist-mcp/ksql-mcp.js +861 -88
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +2 -1
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
10183
|
-
|
|
10184
|
-
|
|
10185
|
-
|
|
10186
|
-
|
|
10187
|
-
|
|
10188
|
-
|
|
10189
|
-
|
|
10190
|
-
|
|
10191
|
-
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10195
|
-
|
|
10196
|
-
|
|
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
|
-
|
|
10937
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 &&
|
|
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
|
-
|
|
14475
|
-
|
|
14476
|
-
|
|
14477
|
-
|
|
14478
|
-
|
|
14479
|
-
|
|
14480
|
-
|
|
14481
|
-
|
|
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,
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
18416
|
-
lines.push(
|
|
18417
|
-
lines.push(
|
|
18418
|
-
|
|
18419
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"))
|