@rex0220/kintone-sql-tools 3.45.0 → 3.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist-cli/ksql.js CHANGED
@@ -752,7 +752,7 @@ function caseLabel(expr) {
752
752
  }
753
753
  function stringFuncArgLabel(arg) {
754
754
  if (arg.type === "AGG_REF") return aggregateSyntheticName(arg.func, arg.distinct, arg.arg);
755
- if (arg.type === "AGG_ARITH") return aggregateOperandLabel(arg);
755
+ if (arg.type === "AGG_ARITH" || arg.type === "AGG_GROUP_KEY" || arg.type === "VARIABLE") return aggregateOperandLabel(arg);
756
756
  return scalarValueLabel(arg);
757
757
  }
758
758
  function stringFuncLabel(expr) {
@@ -790,6 +790,8 @@ function aggregateSyntheticName(func, distinct, arg) {
790
790
  function aggregateOperandLabel(node) {
791
791
  if (node.type === "NUMBER") return numberLiteralText(node);
792
792
  if (node.type === "AGG_REF") return aggregateSyntheticName(node.func, node.distinct, node.arg);
793
+ if (node.type === "AGG_GROUP_KEY") return node.tableAlias ? `${node.tableAlias}.${node.field}` : node.field;
794
+ if (node.type === "VARIABLE") return `@${node.name}`;
793
795
  return `${aggregateOperandLabel(node.left)}${node.op}${aggregateOperandLabel(node.right)}`;
794
796
  }
795
797
 
@@ -1068,6 +1070,8 @@ var Parser = class {
1068
1070
  this.cteNames = /* @__PURE__ */ new Set();
1069
1071
  /** パース中に出現した一時テーブル参照(#name)のトークン。単文 API での拒否に使う */
1070
1072
  this.tempTableRefs = [];
1073
+ /** GROUP BY を読む前に作る B124 候補 leaf の診断位置。AST 公開型へ位置情報を足さない。 */
1074
+ this.aggregateGroupKeyTokens = /* @__PURE__ */ new WeakMap();
1071
1075
  this.allowSelectArithVariable = false;
1072
1076
  }
1073
1077
  // ----------------------------------------------------------
@@ -1879,6 +1883,7 @@ var Parser = class {
1879
1883
  if (grouping && orderMode === "KINTONE_NATIVE") {
1880
1884
  throw new ParseError("B65: KORDER BY cannot be combined with grouping sets in Phase1.", this.peek());
1881
1885
  }
1886
+ this.validateAggregateGroupKeyRefs(columns, having, groupBy, grouping);
1882
1887
  return {
1883
1888
  type: "SELECT",
1884
1889
  distinct,
@@ -1971,6 +1976,12 @@ var Parser = class {
1971
1976
  this.peek()
1972
1977
  );
1973
1978
  }
1979
+ if (this.isNonAggregateArithmeticStartWithAggregate()) {
1980
+ throw new ParseError(
1981
+ `\u96C6\u8A08\u7B97\u8853\u5F0F\u306F\u96C6\u8A08\u95A2\u6570\u304B\u3089\u59CB\u307E\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\uFF08${this.peek().value}\uFF09\u3002`,
1982
+ this.peek()
1983
+ );
1984
+ }
1974
1985
  if (this.tryAggregateFunc() === null && this.hasTopLevelTokenBeforeValueEnd("||" /* CONCAT_OP */)) {
1975
1986
  const expr = this.parseScalarValueExpr({ allowAggregateArgs: true });
1976
1987
  const parsedAlias2 = this.consume("AS" /* AS */) ? this.parseAliasName() : null;
@@ -2194,6 +2205,7 @@ var Parser = class {
2194
2205
  }
2195
2206
  stringFuncArgHasAggregate(arg) {
2196
2207
  if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") return true;
2208
+ if (arg.type === "AGG_GROUP_KEY" || arg.type === "VARIABLE") return false;
2197
2209
  return this.scalarValueHasAggregate(arg);
2198
2210
  }
2199
2211
  scalarValueHasAggregate(expr) {
@@ -2236,7 +2248,7 @@ var Parser = class {
2236
2248
  parseAggArithMulDiv() {
2237
2249
  return this.continueAggArithMulDiv(this.parseAggPrimary());
2238
2250
  }
2239
- /** 集計算術式の一次式: 集計関数 / 数値リテラル / 括弧 */
2251
+ /** 集計算術式の一次式: 集計関数 / 数値 / 変数 / GROUP BY キー候補 / 括弧 */
2240
2252
  parseAggPrimary() {
2241
2253
  if (this.consume("(" /* LPAREN */)) {
2242
2254
  const inner = this.continueAggArith(this.parseAggPrimary());
@@ -2253,14 +2265,99 @@ var Parser = class {
2253
2265
  const tok = this.advance();
2254
2266
  return makeNumberLiteral(tok.value);
2255
2267
  }
2268
+ if (this.peek().kind === "VARIABLE" /* VARIABLE */) {
2269
+ const tok = this.advance();
2270
+ return { type: "VARIABLE", name: tok.value.slice(1).toLowerCase() };
2271
+ }
2256
2272
  const aggFunc = this.tryAggregateFunc();
2257
2273
  if (aggFunc !== null) {
2258
2274
  const ref = this.parseAggregateRef(aggFunc);
2259
2275
  this.rejectAggregateWindowOutsideSelect();
2260
2276
  return ref;
2261
2277
  }
2278
+ if (this.peek().kind === "IDENT" /* IDENT */ || this.peek().kind === "BIDENT" /* BIDENT */) {
2279
+ const tok = this.peek();
2280
+ const parsed = this.parseQualifiedIdent();
2281
+ const ref = {
2282
+ type: "AGG_GROUP_KEY",
2283
+ field: parsed.field,
2284
+ ...parsed.tableAlias ? { tableAlias: parsed.tableAlias } : {}
2285
+ };
2286
+ this.aggregateGroupKeyTokens.set(ref, tok);
2287
+ return ref;
2288
+ }
2262
2289
  throw new ParseError("\u96C6\u8A08\u7B97\u8853\u5F0F\u306B\u306F\u96C6\u8A08\u95A2\u6570\u307E\u305F\u306F\u6570\u5024\u304C\u5FC5\u8981\u3067\u3059", this.peek());
2263
2290
  }
2291
+ /** B124 Phase 1 はトップレベルの非集計オペランド開始形を明示拒否する。 */
2292
+ isNonAggregateArithmeticStartWithAggregate() {
2293
+ const first = this.peek();
2294
+ if (this.tryAggregateFunc() !== null || first.kind === "CASE" /* CASE */ || first.kind === "IF" /* IF */) return false;
2295
+ if (this.isGroupingFunctionStart()) return false;
2296
+ if (this.tryStringFuncName() !== null) return false;
2297
+ if (!["IDENT" /* IDENT */, "BIDENT" /* BIDENT */, "VARIABLE" /* VARIABLE */, "(" /* LPAREN */].includes(first.kind)) return false;
2298
+ let depth = 0;
2299
+ let sawArithmetic = false;
2300
+ for (let index = this.pos; index < this.tokens.length; index++) {
2301
+ const token = this.tokens[index];
2302
+ if (depth === 0 && index > this.pos && (token.kind === "," /* COMMA */ || token.kind === "AS" /* AS */ || token.kind === "FROM" /* FROM */ || token.kind === ";" /* SEMICOLON */ || token.kind === "EOF" /* EOF */ || token.kind === "=" /* EQ */ || token.kind === "!=" /* NEQ */ || token.kind === "<>" /* LT_GT */ || token.kind === ">" /* GT */ || token.kind === "<" /* LT */ || token.kind === ">=" /* GTE */ || token.kind === "<=" /* LTE */ || token.kind === "AND" /* AND */ || token.kind === "OR" /* OR */)) break;
2303
+ if (this.isArithOp(token.kind)) sawArithmetic = true;
2304
+ if (PARSER_AGGREGATE_FUNCTION_TOKEN_MAP[token.kind] !== void 0 && this.tokens[index + 1]?.kind === "(" /* LPAREN */) return sawArithmetic;
2305
+ if (token.kind === "(" /* LPAREN */) depth++;
2306
+ else if (token.kind === ")" /* RPAREN */) {
2307
+ depth--;
2308
+ if (depth < 0) break;
2309
+ }
2310
+ }
2311
+ return false;
2312
+ }
2313
+ /** SELECT ローカルの B124 leaf だけを ordinary GROUP BY の表記と照合する。 */
2314
+ validateAggregateGroupKeyRefs(columns, having, groupBy, grouping) {
2315
+ const refs = [];
2316
+ const visit = (node) => {
2317
+ if (Array.isArray(node)) {
2318
+ node.forEach(visit);
2319
+ return;
2320
+ }
2321
+ if (node === null || typeof node !== "object") return;
2322
+ const value = node;
2323
+ if (value["type"] === "SELECT" || value["type"] === "SCALAR_SUBQUERY") return;
2324
+ if (value["type"] === "AGG_GROUP_KEY") {
2325
+ refs.push(node);
2326
+ return;
2327
+ }
2328
+ Object.values(value).forEach(visit);
2329
+ };
2330
+ visit(columns);
2331
+ visit(having);
2332
+ if (refs.length === 0) return;
2333
+ const first = refs[0];
2334
+ const firstToken = this.aggregateGroupKeyTokens.get(first) ?? this.peek();
2335
+ if (grouping !== void 0) {
2336
+ throw new ParseError(
2337
+ "ROLLUP / CUBE / GROUPING SETS \u3067\u306F\u96C6\u8A08\u7B97\u8853\u5F0F\u306B\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u66F8\u3051\u307E\u305B\u3093\uFF08\u5C0F\u8A08\u30FB\u7DCF\u8A08\u884C\u3067\u5024\u304C\u5B9A\u307E\u3089\u306A\u3044\u305F\u3081\u3067\u3059\uFF09\u3002",
2338
+ firstToken
2339
+ );
2340
+ }
2341
+ if (groupBy.length === 0) {
2342
+ throw new ParseError(
2343
+ `\u96C6\u8A08\u7B97\u8853\u5F0F\u306B\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u66F8\u304F\u306B\u306F GROUP BY \u304C\u5FC5\u8981\u3067\u3059\uFF08${this.aggregateGroupKeyDisplay(first)}\uFF09\u3002`,
2344
+ firstToken
2345
+ );
2346
+ }
2347
+ const ordinaryNames = new Set(groupBy.filter((key) => key.type === "FIELD_NAME").map((key) => key.name));
2348
+ for (const ref of refs) {
2349
+ const display = this.aggregateGroupKeyDisplay(ref);
2350
+ if (!ordinaryNames.has(display)) {
2351
+ throw new ParseError(
2352
+ `\u96C6\u8A08\u7B97\u8853\u5F0F\u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u306F GROUP BY \u306B\u66F8\u3044\u305F\u8868\u8A18\u3068\u4E00\u81F4\u3059\u308B\u5217\u3060\u3051\u3067\u3059\uFF08${display}\uFF09\u3002\u30B0\u30EB\u30FC\u30D7\u5185\u3067\u5024\u304C\u5B9A\u307E\u3089\u306A\u3044\u305F\u3081\u3067\u3059\u3002`,
2353
+ this.aggregateGroupKeyTokens.get(ref) ?? firstToken
2354
+ );
2355
+ }
2356
+ }
2357
+ }
2358
+ aggregateGroupKeyDisplay(ref) {
2359
+ return ref.tableAlias ? `${ref.tableAlias}.${ref.field}` : ref.field;
2360
+ }
2264
2361
  // ──────────────────────────────────────────────────
2265
2362
  // 汎用スカラー値式パーサー(B38)
2266
2363
  // ──────────────────────────────────────────────────
@@ -3008,6 +3105,12 @@ var Parser = class {
3008
3105
  // - 集計関数(HAVING のみ): COUNT(*) / SUM(f) ...
3009
3106
  // - 通常フィールド参照: [alias.]field
3010
3107
  parseFieldValue() {
3108
+ if (this.groupingFieldContext === "HAVING" && this.isNonAggregateArithmeticStartWithAggregate()) {
3109
+ throw new ParseError(
3110
+ `\u96C6\u8A08\u7B97\u8853\u5F0F\u306F\u96C6\u8A08\u95A2\u6570\u304B\u3089\u59CB\u307E\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\uFF08${this.peek().value}\uFF09\u3002`,
3111
+ this.peek()
3112
+ );
3113
+ }
3011
3114
  if (this.isUnsupportedGroupingIdStart()) {
3012
3115
  throw new ParseError("B65: GROUPING_ID is not supported in Phase1.", this.peek());
3013
3116
  }
@@ -5811,7 +5914,7 @@ function collectStringFuncFields(expr, out) {
5811
5914
  }
5812
5915
  }
5813
5916
  function collectStringFuncArgFields(arg, out) {
5814
- if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") {
5917
+ if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH" || arg.type === "AGG_GROUP_KEY" || arg.type === "VARIABLE") {
5815
5918
  collectAggOperandFields(arg, out);
5816
5919
  return;
5817
5920
  }
@@ -5857,6 +5960,9 @@ function collectAggOperandFields(node, out) {
5857
5960
  collectAggOperandFields(node.left, out);
5858
5961
  collectAggOperandFields(node.right, out);
5859
5962
  }
5963
+ if (node.type === "AGG_GROUP_KEY") {
5964
+ out.push(normalizeSimpleFieldRef(node.tableAlias ? `${node.tableAlias}.${node.field}` : node.field));
5965
+ }
5860
5966
  }
5861
5967
  function collectAggregateArgFields(node, out) {
5862
5968
  if (node.type === "FIELD_REF" || node.type === "ARITH") collectArithNode(node, out);
@@ -5865,6 +5971,7 @@ function collectAggregateArgFields(node, out) {
5865
5971
  function hasAggregateInStringFuncExpr(expr) {
5866
5972
  return expr.args.some((arg) => {
5867
5973
  if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") return true;
5974
+ if (arg.type === "AGG_GROUP_KEY" || arg.type === "VARIABLE") return false;
5868
5975
  return scalarValueHasAggregate(arg);
5869
5976
  });
5870
5977
  }
@@ -6052,9 +6159,12 @@ function collectRequiredFieldsByTable(stmt, plainGroupByPlan, sourceAware) {
6052
6159
  walkAgg(node.left, phase);
6053
6160
  walkAgg(node.right, phase);
6054
6161
  }
6162
+ if (node.type === "AGG_GROUP_KEY") {
6163
+ addFieldRef(node.field, node.tableAlias ?? null, phase);
6164
+ }
6055
6165
  };
6056
6166
  const walkStringArg = (arg, phase = "select") => {
6057
- if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") {
6167
+ if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH" || arg.type === "AGG_GROUP_KEY" || arg.type === "VARIABLE") {
6058
6168
  walkAgg(arg, phase);
6059
6169
  return;
6060
6170
  }
@@ -8155,7 +8265,7 @@ function formatWithComma(num, digits) {
8155
8265
  return decStr ? `${intFmt}.${decStr}` : intFmt;
8156
8266
  }
8157
8267
  function evalStringFuncArg(arg, row, resolveFieldType, resolveFieldSemantics2) {
8158
- if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") {
8268
+ if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH" || arg.type === "AGG_GROUP_KEY") {
8159
8269
  return String(evalMaterializedAggregateOperand(arg, row));
8160
8270
  }
8161
8271
  if (arg.type === "NUMBER") return numberLiteralText(arg);
@@ -8166,6 +8276,13 @@ function evalMaterializedAggregateOperand(node, row) {
8166
8276
  if (node.type === "AGG_REF") {
8167
8277
  return row[aggregateSyntheticName(node.func, node.distinct, node.arg)] ?? "";
8168
8278
  }
8279
+ if (node.type === "AGG_GROUP_KEY") {
8280
+ const field = node.tableAlias ? `${node.tableAlias}.${node.field}` : node.field;
8281
+ return Number(resolveFieldRef(row, field));
8282
+ }
8283
+ if (node.type === "VARIABLE") {
8284
+ throw new Error(`InternalError: unresolved aggregate arithmetic variable @${node.name}.`);
8285
+ }
8169
8286
  const left = Number(evalMaterializedAggregateOperand(node.left, row));
8170
8287
  const right = Number(evalMaterializedAggregateOperand(node.right, row));
8171
8288
  switch (node.op) {
@@ -8667,7 +8784,7 @@ function collectStringFuncFields2(expr, out) {
8667
8784
  for (const arg of expr.args) collectStringFuncArgFields2(arg, out);
8668
8785
  }
8669
8786
  function collectStringFuncArgFields2(arg, out) {
8670
- if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") {
8787
+ if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH" || arg.type === "AGG_GROUP_KEY" || arg.type === "VARIABLE") {
8671
8788
  collectAggOperandFields2(arg, out);
8672
8789
  return;
8673
8790
  }
@@ -8698,6 +8815,7 @@ function collectAggOperandFields2(node, out) {
8698
8815
  collectAggOperandFields2(node.left, out);
8699
8816
  collectAggOperandFields2(node.right, out);
8700
8817
  }
8818
+ if (node.type === "AGG_GROUP_KEY") out.add(node.tableAlias ? `${node.tableAlias}.${node.field}` : node.field);
8701
8819
  }
8702
8820
  function collectAggregateArgFields2(node, out) {
8703
8821
  if (node.type === "FIELD_REF" || node.type === "ARITH") collectArithNode2(node, out);
@@ -11501,6 +11619,42 @@ var LOCAL_COLLECTION_TYPES = /* @__PURE__ */ new Set([
11501
11619
  function nativeWhereOperatorsForType(fieldType) {
11502
11620
  return NATIVE_OPERATORS.get(fieldType) ?? /* @__PURE__ */ new Set();
11503
11621
  }
11622
+ function normalizeChoiceEquality(where, resolveField2) {
11623
+ const rewrites = [];
11624
+ const visit = (node) => {
11625
+ if (node.type === "BINARY") {
11626
+ if ((node.op === "=" || node.op === "!=" || node.op === "<>") && node.left.type === "FIELD" && node.right.type === "STRING" && node.right.value !== "") {
11627
+ const semantics = resolveField2(node.left);
11628
+ if (semantics !== void 0 && semantics.compareMode === "option" && LOCAL_SCALAR_TYPES.has(semantics.fieldType) && nativeWhereOperatorsForType(semantics.fieldType).has("in") && semantics.optionOrder?.has(node.right.value) === true) {
11629
+ const normalizedOperator = node.op === "=" ? "IN" : "NOT_IN";
11630
+ rewrites.push({
11631
+ field: node.left,
11632
+ originalOperator: node.op,
11633
+ normalizedOperator,
11634
+ value: node.right.value
11635
+ });
11636
+ return {
11637
+ ...node,
11638
+ op: normalizedOperator,
11639
+ right: { type: "IN_LIST", values: [node.right] }
11640
+ };
11641
+ }
11642
+ }
11643
+ return node;
11644
+ }
11645
+ if (node.type === "LOGICAL") {
11646
+ const left = visit(node.left);
11647
+ const right = visit(node.right);
11648
+ return left === node.left && right === node.right ? node : { ...node, left, right };
11649
+ }
11650
+ if (node.type === "GROUP" || node.type === "NOT") {
11651
+ const expr = visit(node.expr);
11652
+ return expr === node.expr ? node : { ...node, expr };
11653
+ }
11654
+ return node;
11655
+ };
11656
+ return { normalizedWhere: visit(where), rewrites };
11657
+ }
11504
11658
  function classifyWhereCapability(where, resolveField2) {
11505
11659
  if (where === null) {
11506
11660
  return { capability: "EXACT_PUSHDOWN", reasons: [{ code: "WHERE_EXACT" }] };
@@ -13431,6 +13585,13 @@ function toAggregateFieldRef(field) {
13431
13585
  function evalAggArithExpr(node, rows, resolveAggSortKind) {
13432
13586
  if (node.type === "NUMBER") return node.value;
13433
13587
  if (node.type === "AGG_REF") return Number(evalAggregate(node.func, node.distinct, node.arg, node.separator, rows, resolveAggSortKind));
13588
+ if (node.type === "AGG_GROUP_KEY") {
13589
+ const field = node.tableAlias ? `${node.tableAlias}.${node.field}` : node.field;
13590
+ return Number(resolveFieldRef(rows[0] ?? {}, field));
13591
+ }
13592
+ if (node.type === "VARIABLE") {
13593
+ throw new Error(`InternalError: unresolved aggregate arithmetic variable @${node.name}.`);
13594
+ }
13434
13595
  const l = evalAggArithExpr(node.left, rows, resolveAggSortKind);
13435
13596
  const r = evalAggArithExpr(node.right, rows, resolveAggSortKind);
13436
13597
  switch (node.op) {
@@ -14069,7 +14230,8 @@ function arithColDefaultKey(expr) {
14069
14230
  }
14070
14231
  function stringFuncDefaultKey(expr) {
14071
14232
  const argStrs = expr.args.map((a) => {
14072
- if (a.type === "AGG_REF" || a.type === "AGG_ARITH") return aggArithDefaultKey(a);
14233
+ if (a.type === "AGG_REF" || a.type === "AGG_ARITH" || a.type === "AGG_GROUP_KEY") return aggArithDefaultKey(a);
14234
+ if (a.type === "VARIABLE") return `@${a.name}`;
14073
14235
  return scalarValueDefaultKey(a);
14074
14236
  });
14075
14237
  return `${expr.func}(${argStrs.join(",")})`;
@@ -14096,6 +14258,7 @@ function scalarValueDefaultKey(expr) {
14096
14258
  }
14097
14259
  function hasAggregateInStringFuncArg(arg) {
14098
14260
  if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") return true;
14261
+ if (arg.type === "AGG_GROUP_KEY" || arg.type === "VARIABLE") return false;
14099
14262
  return scalarValueHasAggregate2(arg);
14100
14263
  }
14101
14264
  function scalarValueHasAggregate2(expr) {
@@ -14125,6 +14288,13 @@ function resolveAggInStringFuncArg(arg, rows, resolveAggSortKind) {
14125
14288
  const value = evalAggArithExpr(arg, rows, resolveAggSortKind);
14126
14289
  return { type: "NUMBER", value, raw: String(value) };
14127
14290
  }
14291
+ if (arg.type === "AGG_GROUP_KEY") {
14292
+ const field = arg.tableAlias ? `${arg.tableAlias}.${arg.field}` : arg.field;
14293
+ return { type: "NUMBER", value: Number(resolveFieldRef(rows[0] ?? {}, field)), raw: resolveFieldRef(rows[0] ?? {}, field) };
14294
+ }
14295
+ if (arg.type === "VARIABLE") {
14296
+ throw new Error(`InternalError: unresolved aggregate arithmetic variable @${arg.name}.`);
14297
+ }
14128
14298
  if (arg.type === "STRING_FUNC") {
14129
14299
  return resolveAggInStringFuncExpr(arg, rows, resolveAggSortKind);
14130
14300
  }
@@ -16265,18 +16435,18 @@ async function assertRelativeDateExecutionPlan(stmt, client, cacheContext) {
16265
16435
  }
16266
16436
  async function resolveRelativeDateExecutionPlan(stmt, client, cacheContext) {
16267
16437
  return buildRelativeDatePushdownPlan(stmt, {
16268
- select: (select) => resolveSelectWhereCapability(select, client, cacheContext),
16438
+ select: async (select) => {
16439
+ const { resolver } = await normalizeSelectChoiceEquality(select, client, cacheContext);
16440
+ return classifyWhereCapability(select.where, resolver);
16441
+ },
16269
16442
  dml: (dml) => resolveDmlWhereCapability(dml, client, cacheContext),
16270
16443
  prefilterDecomposition: async (select) => {
16271
16444
  if (select.where === null) return null;
16272
- const resolver = await buildWhereFieldSemanticsResolver(
16273
- select,
16274
- client,
16275
- cacheContext
16276
- );
16445
+ const { resolver } = await normalizeSelectChoiceEquality(select, client, cacheContext);
16277
16446
  return decomposeRelativeDatePrefilter(select, resolver);
16278
16447
  },
16279
16448
  joinServerFunctionPlan: async (select) => {
16449
+ await normalizeSelectChoiceEquality(select, client, cacheContext);
16280
16450
  const metadata = await loadTypedPushdownMeta(select, client, cacheContext);
16281
16451
  const runtimePlan = buildRuntimeJoinPushdownPlan(select, metadata);
16282
16452
  if (runtimePlan === null) return null;
@@ -17117,7 +17287,15 @@ function resolveBatchVariableReferencesInternal(node, variables, numericArithmet
17117
17287
  `ArgumentError: variable @${obj["name"]} is not numeric and cannot be used in arithmetic.`
17118
17288
  );
17119
17289
  }
17120
- return numericArithmeticOperand && value.type === "string" && value.placeholder === true ? { type: "NUMBER", value: 0, raw: value.value } : value.type === "number" ? { type: "NUMBER", value: value.value, raw: value.raw ?? String(value.value) } : { type: "STRING", value: value.value };
17290
+ return numericArithmeticOperand && value.type === "string" && value.placeholder === true ? {
17291
+ type: "NUMBER",
17292
+ value: 0,
17293
+ raw: numericArithmeticOperand === "AGG_ARITH" ? `@${obj["name"]}` : value.value
17294
+ } : value.type === "number" ? {
17295
+ type: "NUMBER",
17296
+ value: value.value,
17297
+ raw: numericArithmeticOperand === "AGG_ARITH" ? `@${obj["name"]}` : value.raw ?? String(value.value)
17298
+ } : { type: "STRING", value: value.value };
17121
17299
  }
17122
17300
  if (obj["type"] === "VARIABLE_COL" && typeof obj["name"] === "string" && typeof obj["alias"] === "string") {
17123
17301
  const value = variables.get(obj["name"]);
@@ -17136,7 +17314,7 @@ function resolveBatchVariableReferencesInternal(node, variables, numericArithmet
17136
17314
  resolveBatchVariableReferencesInternal(
17137
17315
  value,
17138
17316
  variables,
17139
- (obj["type"] === "ARITH" || obj["type"] === "SCALAR_ARITH" || obj["type"] === "AGG_ARITH") && (key === "left" || key === "right")
17317
+ key === "left" || key === "right" ? obj["type"] === "AGG_ARITH" ? "AGG_ARITH" : obj["type"] === "ARITH" || obj["type"] === "SCALAR_ARITH" ? "ARITH" : false : false
17140
17318
  )
17141
17319
  ])
17142
17320
  );
@@ -17338,7 +17516,10 @@ async function buildWhereFieldSemanticsResolver(stmt, client, cacheContext, mate
17338
17516
  if (node === null || typeof node !== "object") return;
17339
17517
  const value = node;
17340
17518
  if (value["type"] === "SELECT") return;
17341
- if (value["type"] === "BINARY" && [">", "<", ">=", "<="].includes(String(value["op"]))) {
17519
+ const operator = String(value["op"]);
17520
+ const right = value["right"];
17521
+ const needsOptionExistence = ["=", "!=", "<>"].includes(operator) && right?.["type"] === "STRING" && right["value"] !== "";
17522
+ if (value["type"] === "BINARY" && ([">", "<", ">=", "<="].includes(operator) || needsOptionExistence)) {
17342
17523
  const left = value["left"];
17343
17524
  if (left?.["type"] === "FIELD" && typeof left["field"] === "string") {
17344
17525
  orderedFields.add(left["field"]);
@@ -17405,6 +17586,49 @@ async function buildWhereFieldSemanticsResolver(stmt, client, cacheContext, mate
17405
17586
  return matches.length > 1 ? syntheticSemantics("string") : void 0;
17406
17587
  };
17407
17588
  }
17589
+ var choiceEqualityRewritesBySelect = /* @__PURE__ */ new WeakMap();
17590
+ async function normalizeSelectChoiceEquality(stmt, client, cacheContext, materializedTables, forcePhysicalMetadata = false) {
17591
+ const resolver = await buildWhereFieldSemanticsResolver(
17592
+ stmt,
17593
+ client,
17594
+ cacheContext,
17595
+ materializedTables,
17596
+ forcePhysicalMetadata
17597
+ );
17598
+ if (stmt.where === null) return { resolver, rewrites: [] };
17599
+ const normalization = normalizeChoiceEquality(stmt.where, resolver);
17600
+ stmt.where = normalization.normalizedWhere;
17601
+ if (normalization.rewrites.length > 0) {
17602
+ choiceEqualityRewritesBySelect.set(stmt, normalization.rewrites);
17603
+ }
17604
+ return { resolver, rewrites: normalization.rewrites };
17605
+ }
17606
+ function hasDefaultRangeAggregateWindow(stmt) {
17607
+ return stmt.columns.some(
17608
+ (column) => column.type === "WINDOW_COL" && column.windowKind === "AGGREGATE" && column.orderBy.length > 0 && column.frame?.source === "DEFAULT"
17609
+ );
17610
+ }
17611
+ function collectDefaultRangeWindowWarnings(stmt, resolveField2, context) {
17612
+ const canProveSinglePhysicalInput = context === "DIRECT" && stmt.joins.length === 0 && stmt.from.cteName === null && stmt.from.subtableCode == null;
17613
+ const warnings = [];
17614
+ for (const column of stmt.columns) {
17615
+ if (column.type !== "WINDOW_COL" || column.windowKind !== "AGGREGATE" || column.orderBy.length === 0 || column.frame?.source !== "DEFAULT") continue;
17616
+ const hasTotalOrderKey = canProveSinglePhysicalInput && column.orderBy.some((item) => {
17617
+ if (item.key.type !== "FIELD_NAME") return false;
17618
+ const ref = aggregateFieldRef(item.key.name);
17619
+ return ref.field === "$id" || resolveField2(ref)?.fieldType === "RECORD_NUMBER";
17620
+ });
17621
+ if (hasTotalOrderKey) continue;
17622
+ warnings.push(
17623
+ `${column.alias} \u306F\u65E2\u5B9A\u30D5\u30EC\u30FC\u30E0\uFF08RANGE\uFF09\u3067\u8A55\u4FA1\u3055\u308C\u307E\u3059\u3002ORDER BY \u306E\u5024\u304C\u540C\u3058\u884C\u306F\u3059\u3079\u3066\u540C\u3058\u5024\u306B\u306A\u308A\u307E\u3059\u3002\u884C\u3054\u3068\u306E\u5024\u304C\u5FC5\u8981\u306A\u3089 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW \u3092\u660E\u793A\u3059\u308B\u304B\u3001ORDER BY \u306B\u30EC\u30B3\u30FC\u30C9\u756A\u53F7\u306A\u3069\u306E\u30BF\u30A4\u30D6\u30EC\u30FC\u30AF\u30AD\u30FC\u3092\u8DB3\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
17624
+ );
17625
+ }
17626
+ return warnings;
17627
+ }
17628
+ function mergeSelectWarnings(result, additional) {
17629
+ if (additional.length === 0) return result;
17630
+ return { ...result, warnings: [.../* @__PURE__ */ new Set([...result.warnings ?? [], ...additional])] };
17631
+ }
17408
17632
  function selectCaseConditionsNeedFieldMetadata(stmt) {
17409
17633
  const visit = (value) => {
17410
17634
  if (value === null || typeof value !== "object") return false;
@@ -17461,11 +17685,6 @@ function buildHavingFieldSemanticsResolver(stmt, rowResolver) {
17461
17685
  }
17462
17686
  return (field) => field.tableAlias === null && aliases.has(field.field) ? aliases.get(field.field) : rowResolver(field);
17463
17687
  }
17464
- async function resolveSelectWhereCapability(stmt, client, cacheContext, materializedTables) {
17465
- if (stmt.where === null) return classifyWhereCapability(null, () => void 0);
17466
- const resolver = await buildWhereFieldSemanticsResolver(stmt, client, cacheContext, materializedTables);
17467
- return classifyWhereCapability(stmt.where, resolver);
17468
- }
17469
17688
  function formatWhereCapabilityFailure(result) {
17470
17689
  const reason = result.reasons.find(
17471
17690
  (candidate) => candidate.code === "WHERE_FIELD_UNRESOLVED" || candidate.code === "WHERE_OPERATOR_UNSUPPORTED" || candidate.code === "WHERE_OPERATOR_INVALID_FOR_FIELD_TYPE"
@@ -17510,7 +17729,7 @@ async function assertDmlWhereCapability(stmt, client, cacheContext) {
17510
17729
  );
17511
17730
  }
17512
17731
  }
17513
- async function executeSelect(stmt, client, options, cacheContext, cteCache, captureColumnMeta = false, forLibraryCapture = false) {
17732
+ async function executeSelect(stmt, client, options, cacheContext, cteCache, captureColumnMeta = false, forLibraryCapture = false, windowWarningContext = "DIRECT") {
17514
17733
  let result;
17515
17734
  await validateSelectGroupingPlanning(stmt, client, cacheContext, cteCache);
17516
17735
  if (isNoFromSelect(stmt)) {
@@ -17523,6 +17742,18 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
17523
17742
  }
17524
17743
  return result;
17525
17744
  }
17745
+ const { resolver: fieldSemanticsResolver } = await normalizeSelectChoiceEquality(
17746
+ stmt,
17747
+ client,
17748
+ cacheContext,
17749
+ cteCache,
17750
+ hasDefaultRangeAggregateWindow(stmt)
17751
+ );
17752
+ const defaultRangeWarnings = collectDefaultRangeWindowWarnings(
17753
+ stmt,
17754
+ fieldSemanticsResolver,
17755
+ windowWarningContext
17756
+ );
17526
17757
  const plainGroupByPlan = await buildRuntimePlainGroupByPlan(
17527
17758
  stmt,
17528
17759
  client,
@@ -17530,7 +17761,10 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
17530
17761
  cteCache
17531
17762
  );
17532
17763
  await resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache);
17533
- const whereCapability = rememberSelectWhereCapability(stmt, await resolveSelectWhereCapability(stmt, client, cacheContext, cteCache));
17764
+ const whereCapability = rememberSelectWhereCapability(
17765
+ stmt,
17766
+ classifyWhereCapability(stmt.where, fieldSemanticsResolver)
17767
+ );
17534
17768
  if (whereCapability.capability === "UNSUPPORTED") {
17535
17769
  throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(whereCapability)}).`);
17536
17770
  }
@@ -17617,7 +17851,7 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
17617
17851
  await inferSelectColumnMeta(stmt, result.columns, client, cacheContext, cteCache, forLibraryCapture)
17618
17852
  );
17619
17853
  }
17620
- return result;
17854
+ return mergeSelectWarnings(result, defaultRangeWarnings);
17621
17855
  }
17622
17856
  async function buildRuntimePlainGroupByPlan(stmt, client, cacheContext, materializedTables) {
17623
17857
  if (stmt.groupBy.length === 0) return void 0;
@@ -17853,6 +18087,8 @@ function arithHasFieldRef(node) {
17853
18087
  return false;
17854
18088
  }
17855
18089
  function stringFuncArgHasFieldRef(arg) {
18090
+ if (arg.type === "AGG_GROUP_KEY") return true;
18091
+ if (arg.type === "VARIABLE") return false;
17856
18092
  if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") return true;
17857
18093
  return scalarValueHasFieldRef(arg);
17858
18094
  }
@@ -18410,7 +18646,11 @@ function collectAggregateArgFieldRefs(arg, out) {
18410
18646
  }
18411
18647
  if (arg.type === "STRING_FUNC") {
18412
18648
  for (const child of arg.args) {
18413
- if (child.type !== "AGG_REF" && child.type !== "AGG_ARITH") collectAggregateArgFieldRefs(child, out);
18649
+ if (child.type === "AGG_GROUP_KEY") {
18650
+ out.push({ type: "FIELD", tableAlias: child.tableAlias ?? null, field: child.field });
18651
+ } else if (child.type !== "AGG_REF" && child.type !== "AGG_ARITH" && child.type !== "VARIABLE") {
18652
+ collectAggregateArgFieldRefs(child, out);
18653
+ }
18414
18654
  }
18415
18655
  return;
18416
18656
  }
@@ -18430,11 +18670,16 @@ function collectAggregateOperandRefs(node, out) {
18430
18670
  collectAggregateOperandRefs(node.left, out);
18431
18671
  collectAggregateOperandRefs(node.right, out);
18432
18672
  }
18673
+ if (node.type === "AGG_GROUP_KEY") out.push({ type: "FIELD", tableAlias: node.tableAlias ?? null, field: node.field });
18433
18674
  }
18434
18675
  function collectStringFuncAggregateRefs(expr, out) {
18435
18676
  for (const arg of expr.args) {
18436
18677
  if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") {
18437
18678
  collectAggregateOperandRefs(arg, out);
18679
+ } else if (arg.type === "AGG_GROUP_KEY") {
18680
+ out.push({ type: "FIELD", tableAlias: arg.tableAlias ?? null, field: arg.field });
18681
+ } else if (arg.type === "VARIABLE") {
18682
+ continue;
18438
18683
  } else {
18439
18684
  collectScalarAggregateRefs(arg, out);
18440
18685
  }
@@ -19117,7 +19362,8 @@ async function executeUnion(stmt, client, options, cacheContext, captureColumnMe
19117
19362
  cacheContext,
19118
19363
  void 0,
19119
19364
  captureColumnMeta,
19120
- forLibraryCapture
19365
+ forLibraryCapture,
19366
+ "DERIVED"
19121
19367
  ),
19122
19368
  executeSelect(
19123
19369
  markCountTotalCountRoot(stmt.right),
@@ -19126,7 +19372,8 @@ async function executeUnion(stmt, client, options, cacheContext, captureColumnMe
19126
19372
  cacheContext,
19127
19373
  void 0,
19128
19374
  captureColumnMeta,
19129
- forLibraryCapture
19375
+ forLibraryCapture,
19376
+ "DERIVED"
19130
19377
  )
19131
19378
  ]);
19132
19379
  const leftCols = leftResult.columns;
@@ -19140,7 +19387,17 @@ async function executeUnion(stmt, client, options, cacheContext, captureColumnMe
19140
19387
  });
19141
19388
  const combined = [...leftResult.rows, ...remappedRight];
19142
19389
  const rows = stmt.all ? combined : deduplicateRows(combined, leftCols);
19143
- const result = { type: "SELECT", rows, columns: leftCols, rowCount: rows.length };
19390
+ const warnings = [.../* @__PURE__ */ new Set([
19391
+ ...leftResult.warnings ?? [],
19392
+ ...rightResult.warnings ?? []
19393
+ ])];
19394
+ const result = {
19395
+ type: "SELECT",
19396
+ rows,
19397
+ columns: leftCols,
19398
+ rowCount: rows.length,
19399
+ warnings
19400
+ };
19144
19401
  if (captureColumnMeta) {
19145
19402
  materializedMetaBySelectResult.set(result, mergeUnionColumnMeta(leftResult, rightResult));
19146
19403
  }
@@ -19158,25 +19415,44 @@ function deduplicateRows(rows, columns) {
19158
19415
  }
19159
19416
  async function executeWith(stmt, client, options, cacheContext, seed, captureColumnMeta = false) {
19160
19417
  if ((seed == null || seed.size === 0) && canInlineSingleCte(stmt)) {
19161
- return executeSelect(buildInlinedQuery(stmt), client, options, cacheContext, void 0, captureColumnMeta, false);
19418
+ return executeSelect(
19419
+ buildInlinedQuery(stmt),
19420
+ client,
19421
+ options,
19422
+ cacheContext,
19423
+ void 0,
19424
+ captureColumnMeta,
19425
+ false,
19426
+ "DERIVED"
19427
+ );
19162
19428
  }
19163
19429
  const cteCache = new Map(seed ?? []);
19430
+ const warnings = /* @__PURE__ */ new Set();
19164
19431
  for (const cte of stmt.ctes) {
19165
- let result;
19432
+ let result2;
19166
19433
  if (cte.query.type === "SHOW_APPS") {
19167
- result = await executeShowApps(client);
19434
+ result2 = await executeShowApps(client);
19168
19435
  } else if (cte.query.type === "DESCRIBE") {
19169
- result = await executeDescribe(cte.query, client, cacheContext);
19436
+ result2 = await executeDescribe(cte.query, client, cacheContext);
19170
19437
  } else {
19171
- result = await executeQueryWithCte(cte.query, client, options, cteCache, cacheContext, true);
19438
+ result2 = await executeQueryWithCte(cte.query, client, options, cteCache, cacheContext, true);
19172
19439
  }
19440
+ for (const warning of result2.warnings ?? []) warnings.add(warning);
19173
19441
  cteCache.set(cte.name, {
19174
- rows: result.rows,
19175
- columns: result.columns,
19176
- columnMeta: materializedMetaBySelectResult.get(result)
19442
+ rows: result2.rows,
19443
+ columns: result2.columns,
19444
+ columnMeta: materializedMetaBySelectResult.get(result2)
19177
19445
  });
19178
19446
  }
19179
- return executeQueryWithCte(stmt.query, client, options, cteCache, cacheContext, captureColumnMeta);
19447
+ const result = await executeQueryWithCte(
19448
+ stmt.query,
19449
+ client,
19450
+ options,
19451
+ cteCache,
19452
+ cacheContext,
19453
+ captureColumnMeta
19454
+ );
19455
+ return mergeSelectWarnings(result, [...warnings]);
19180
19456
  }
19181
19457
  async function executeQueryWithCte(query, client, options, cteCache, cacheContext, captureColumnMeta = false, b86PreflightComplete = false) {
19182
19458
  if (!b86PreflightComplete) {
@@ -19198,7 +19474,17 @@ async function executeQueryWithCte(query, client, options, cteCache, cacheContex
19198
19474
  });
19199
19475
  const combined = [...leftResult.rows, ...remapped];
19200
19476
  const rows = query.all ? combined : deduplicateRows(combined, leftCols);
19201
- const result2 = { type: "SELECT", rows, columns: leftCols, rowCount: rows.length };
19477
+ const warnings = [.../* @__PURE__ */ new Set([
19478
+ ...leftResult.warnings ?? [],
19479
+ ...rightResult.warnings ?? []
19480
+ ])];
19481
+ const result2 = {
19482
+ type: "SELECT",
19483
+ rows,
19484
+ columns: leftCols,
19485
+ rowCount: rows.length,
19486
+ warnings
19487
+ };
19202
19488
  if (captureColumnMeta) {
19203
19489
  materializedMetaBySelectResult.set(result2, mergeUnionColumnMeta(leftResult, rightResult));
19204
19490
  }
@@ -19206,7 +19492,16 @@ async function executeQueryWithCte(query, client, options, cteCache, cacheContex
19206
19492
  }
19207
19493
  const hasCteRef = query.from.cteName != null && query.from.cteName !== NO_FROM_CTE_NAME || query.joins.some((j) => j.table.cteName != null);
19208
19494
  if (!hasCteRef) {
19209
- return executeSelect(query, client, options, cacheContext, cteCache, captureColumnMeta, false);
19495
+ return executeSelect(
19496
+ query,
19497
+ client,
19498
+ options,
19499
+ cacheContext,
19500
+ cteCache,
19501
+ captureColumnMeta,
19502
+ false,
19503
+ "DERIVED"
19504
+ );
19210
19505
  }
19211
19506
  const result = await executeFullScanWithCte(query, client, options, cteCache, cacheContext);
19212
19507
  if (captureColumnMeta) {
@@ -19254,6 +19549,18 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
19254
19549
  for (const table of [stmt.from, ...stmt.joins.map((join2) => join2.table)]) {
19255
19550
  if (table.cteName !== null) requireMaterializedTable(table.cteName);
19256
19551
  }
19552
+ const { resolver: choiceAndWindowResolver } = await normalizeSelectChoiceEquality(
19553
+ stmt,
19554
+ client,
19555
+ cacheContext,
19556
+ cteCache,
19557
+ hasDefaultRangeAggregateWindow(stmt)
19558
+ );
19559
+ const defaultRangeWarnings = collectDefaultRangeWindowWarnings(
19560
+ stmt,
19561
+ choiceAndWindowResolver,
19562
+ "DERIVED"
19563
+ );
19257
19564
  const maxRecords = options.maxRecords ?? 1e4;
19258
19565
  const warnings = /* @__PURE__ */ new Set();
19259
19566
  const parallel = options.fetchParallel ?? 1;
@@ -19262,7 +19569,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
19262
19569
  resolveSubqueries(stmt.having, client, options, cacheContext, cteCache),
19263
19570
  resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache)
19264
19571
  ]);
19265
- const whereCapability = await resolveSelectWhereCapability(stmt, client, cacheContext, cteCache);
19572
+ const whereCapability = classifyWhereCapability(stmt.where, choiceAndWindowResolver);
19266
19573
  if (whereCapability.capability === "UNSUPPORTED") {
19267
19574
  throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(whereCapability)}).`);
19268
19575
  }
@@ -19391,7 +19698,10 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
19391
19698
  client,
19392
19699
  cacheContext
19393
19700
  );
19394
- return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
19701
+ return mergeSelectWarnings(
19702
+ { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] },
19703
+ defaultRangeWarnings
19704
+ );
19395
19705
  }
19396
19706
  async function restoreEmptyWildcardColumns(stmt, rows, columns, client, cacheContext) {
19397
19707
  if (rows.length !== 0 || columns.length !== 0 || stmt.columns.length !== 1 || stmt.columns[0].type !== "WILDCARD" || stmt.joins.length !== 0 || stmt.from.cteName !== null) {
@@ -22805,6 +23115,7 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
22805
23115
  return cache;
22806
23116
  }
22807
23117
  var explainJoinPushdownPlans = /* @__PURE__ */ new WeakMap();
23118
+ var explainChoiceEqualityRewrites = /* @__PURE__ */ new WeakMap();
22808
23119
  var validateExplainInfo = /* @__PURE__ */ new WeakMap();
22809
23120
  var applyParentExplainPlan = /* @__PURE__ */ new WeakMap();
22810
23121
  async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords = 1e4, relativeDatePlan) {
@@ -22860,7 +23171,13 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
22860
23171
  if (needsWhereSchema || select.orderBy.length > 0 || select.columns.some((column) => column.type === "WINDOW_COL" && column.orderBy.length > 0)) {
22861
23172
  physicalApps.forEach((appId) => fieldApps.add(appId));
22862
23173
  }
22863
- const capability = await resolveSelectWhereCapability(select, tracedClient, cacheContext);
23174
+ const { resolver, rewrites } = await normalizeSelectChoiceEquality(
23175
+ select,
23176
+ tracedClient,
23177
+ cacheContext
23178
+ );
23179
+ if (rewrites.length > 0) explainChoiceEqualityRewrites.set(select, rewrites);
23180
+ const capability = classifyWhereCapability(select.where, resolver);
22864
23181
  const relativeNode = relativeNodeFor(select);
22865
23182
  if (capability.capability === "UNSUPPORTED" && !relativeNode) {
22866
23183
  throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
@@ -22972,7 +23289,13 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
22972
23289
  await visit(query);
22973
23290
  if (typeof query === "object" && query !== null && query.type === "WITH" && canInlineSingleCte(query)) {
22974
23291
  const inlined = buildInlinedQuery(query);
22975
- const capability = await resolveSelectWhereCapability(inlined, tracedClient, cacheContext);
23292
+ const { resolver, rewrites } = await normalizeSelectChoiceEquality(
23293
+ inlined,
23294
+ tracedClient,
23295
+ cacheContext
23296
+ );
23297
+ if (rewrites.length > 0) explainChoiceEqualityRewrites.set(inlined, rewrites);
23298
+ const capability = classifyWhereCapability(inlined.where, resolver);
22976
23299
  const relativeNode = relativeNodeFor(inlined);
22977
23300
  if (capability.capability === "UNSUPPORTED" && !relativeNode) {
22978
23301
  throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
@@ -23744,6 +24067,13 @@ function buildValidatePlan(stmt, label) {
23744
24067
  lines.push(" records/mutation API during EXPLAIN: none; violation count unavailable");
23745
24068
  return lines;
23746
24069
  }
24070
+ function formatChoiceEqualityRewrite(rewrite) {
24071
+ const field = rewrite.field.tableAlias ? `${rewrite.field.tableAlias}.${rewrite.field.field}` : rewrite.field.field;
24072
+ const originalValue = rewrite.value.replace(/'/g, "''");
24073
+ const normalizedValue = rewrite.value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
24074
+ const normalizedOperator = rewrite.normalizedOperator === "IN" ? "in" : "not in";
24075
+ return ` pushdown normalized: ${field} ${rewrite.originalOperator} '${originalValue}' -> ${field} ${normalizedOperator} ("${normalizedValue}")`;
24076
+ }
23747
24077
  function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlans, allowTotalCountPlan = true, emitFetch = true, collector = { sources: [] }, sourceRole = "main") {
23748
24078
  const whereCapability = capabilities?.get(stmt) ?? (capabilities ? [...capabilities].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
23749
24079
  const orderPlan = orderPlans?.get(stmt) ?? (orderPlans ? [...orderPlans].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
@@ -23761,6 +24091,9 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlan
23761
24091
  );
23762
24092
  if (label) lines.push(label);
23763
24093
  lines.push(` mode: ${mode}`);
24094
+ for (const rewrite of explainChoiceEqualityRewrites.get(stmt) ?? choiceEqualityRewritesBySelect.get(stmt) ?? []) {
24095
+ lines.push(formatChoiceEqualityRewrite(rewrite));
24096
+ }
23764
24097
  if (groupingMetadata) {
23765
24098
  lines.push(` grouping source: ${groupingMetadata.source}`);
23766
24099
  lines.push(
@@ -24416,7 +24749,8 @@ function collectArithNodeRefs(node, out) {
24416
24749
  }
24417
24750
  if (node.type === "STRING_FUNC") {
24418
24751
  for (const arg of node.args) {
24419
- if (arg.type !== "AGG_REF" && arg.type !== "AGG_ARITH") collectScalarNodeRefs(arg, out);
24752
+ if (arg.type === "AGG_GROUP_KEY") out.add(arg.tableAlias ? `${arg.tableAlias}.${arg.field}` : arg.field);
24753
+ else if (arg.type !== "AGG_REF" && arg.type !== "AGG_ARITH" && arg.type !== "VARIABLE") collectScalarNodeRefs(arg, out);
24420
24754
  }
24421
24755
  }
24422
24756
  }
@@ -24478,7 +24812,10 @@ function collectScalarNodeRefs(node, out) {
24478
24812
  return;
24479
24813
  }
24480
24814
  if (node.type === "STRING_FUNC") {
24481
- for (const arg of node.args) if (arg.type !== "AGG_REF" && arg.type !== "AGG_ARITH") collectScalarNodeRefs(arg, out);
24815
+ for (const arg of node.args) {
24816
+ if (arg.type === "AGG_GROUP_KEY") out.add(arg.tableAlias ? `${arg.tableAlias}.${arg.field}` : arg.field);
24817
+ else if (arg.type !== "AGG_REF" && arg.type !== "AGG_ARITH" && arg.type !== "VARIABLE") collectScalarNodeRefs(arg, out);
24818
+ }
24482
24819
  return;
24483
24820
  }
24484
24821
  if (node.type === "SCALAR_ARITH" || node.type === "CONCAT_OP") {