@rex0220/kintone-sql-tools 3.23.0 → 3.25.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
@@ -798,10 +798,26 @@ var RELATIVE_DATE_FUNCTION_NAMES = /* @__PURE__ */ new Set([
798
798
  "LAST_YEAR",
799
799
  "NEXT_YEAR"
800
800
  ]);
801
+ var LEGACY_KINTONE_FUNCTION_NAMES = /* @__PURE__ */ new Set([
802
+ "TODAY",
803
+ "NOW",
804
+ "LOGINUSER"
805
+ ]);
806
+ var SERVER_ONLY_WHERE_FUNCTION_NAMES = /* @__PURE__ */ new Set([
807
+ ...LEGACY_KINTONE_FUNCTION_NAMES,
808
+ ...RELATIVE_DATE_FUNCTION_NAMES
809
+ ]);
801
810
  var WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN = "WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN";
811
+ var WHERE_KINTONE_FUNCTION_REQUIRES_EXACT_PUSHDOWN = "WHERE_KINTONE_FUNCTION_REQUIRES_EXACT_PUSHDOWN";
802
812
  function isRelativeDateFunctionName(name) {
803
813
  return RELATIVE_DATE_FUNCTION_NAMES.has(name);
804
814
  }
815
+ function isLegacyKintoneFunctionName(name) {
816
+ return LEGACY_KINTONE_FUNCTION_NAMES.has(name);
817
+ }
818
+ function isServerOnlyWhereFunctionName(name) {
819
+ return SERVER_ONLY_WHERE_FUNCTION_NAMES.has(name);
820
+ }
805
821
 
806
822
  // src/parser/parser.ts
807
823
  var MAX_BATCH_STATEMENTS = 20;
@@ -3034,7 +3050,8 @@ var Parser = class {
3034
3050
  // IN リストの値
3035
3051
  parseInValues() {
3036
3052
  const values = [];
3037
- const invalidValueMessage = "IN \u30EA\u30B9\u30C8\u306B\u306F\u6587\u5B57\u5217\u3001\u6570\u5024\u3001\u307E\u305F\u306F\u30D0\u30C3\u30C1\u5909\u6570\u304C\u5FC5\u8981\u3067\u3059";
3053
+ const invalidValueMessage = "IN \u30EA\u30B9\u30C8\u306B\u306F\u6587\u5B57\u5217\u3001\u6570\u5024\u3001\u30D0\u30C3\u30C1\u5909\u6570\u3001\u307E\u305F\u306F\u5358\u72EC\u306E LOGINUSER() \u304C\u5FC5\u8981\u3067\u3059";
3054
+ const mixedLoginUserMessage = "LOGINUSER() \u306F IN / NOT IN \u30EA\u30B9\u30C8\u306E\u5358\u72EC\u8981\u7D20\u3068\u3057\u3066\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059";
3038
3055
  do {
3039
3056
  const tok = this.advance();
3040
3057
  if (tok.kind === "STRING" /* STRING */) {
@@ -3051,6 +3068,16 @@ var Parser = class {
3051
3068
  values.push(makeNumberLiteral(`${sign}${number.value}`));
3052
3069
  } else if (tok.kind === "VARIABLE" /* VARIABLE */) {
3053
3070
  values.push({ type: "VARIABLE", name: tok.value.slice(1).toLowerCase() });
3071
+ } else if (tok.kind === "LOGINUSER" /* LOGINUSER */) {
3072
+ if (values.length > 0) {
3073
+ throw new ParseError(mixedLoginUserMessage, tok);
3074
+ }
3075
+ this.expect("(" /* LPAREN */, "LOGINUSER \u306E\u76F4\u5F8C\u306B\u306F\u7A7A\u5F15\u6570\u306E () \u304C\u5FC5\u8981\u3067\u3059");
3076
+ this.expect(")" /* RPAREN */, "LOGINUSER \u306E\u76F4\u5F8C\u306B\u306F\u7A7A\u5F15\u6570\u306E () \u304C\u5FC5\u8981\u3067\u3059");
3077
+ values.push({ type: "KINTONE_FUNC", name: "LOGINUSER" });
3078
+ if (this.peek().kind === "," /* COMMA */) {
3079
+ throw new ParseError(mixedLoginUserMessage, this.peek());
3080
+ }
3054
3081
  } else {
3055
3082
  throw new ParseError(invalidValueMessage, tok);
3056
3083
  }
@@ -5082,9 +5109,11 @@ function convertInList(v, op) {
5082
5109
  throw new KintoneQueryError("IN_LIST \u306F IN / NOT IN \u6F14\u7B97\u5B50\u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059");
5083
5110
  }
5084
5111
  assertResolvedInListValues(v.values);
5085
- const values = v.values.map(
5086
- (item) => item.type === "STRING" ? convertString(item) : numberLiteralText(item)
5087
- ).join(",");
5112
+ const values = v.values.map((item) => {
5113
+ if (item.type === "STRING") return convertString(item);
5114
+ if (item.type === "NUMBER") return numberLiteralText(item);
5115
+ return convertKintoneFunc(item);
5116
+ }).join(",");
5088
5117
  return `(${values})`;
5089
5118
  }
5090
5119
  function assertResolvedInListValues(values) {
@@ -7682,6 +7711,12 @@ function assertResolvedInListValues2(values) {
7682
7711
  if (unresolved?.type === "VARIABLE") {
7683
7712
  throw new Error(`ParseError: unresolved batch variable @${unresolved.name}.`);
7684
7713
  }
7714
+ const serverOnlyFunction = values.find((item) => item.type === "KINTONE_FUNC");
7715
+ if (serverOnlyFunction?.type === "KINTONE_FUNC") {
7716
+ throw new Error(
7717
+ `${serverOnlyFunction.name}: ${WHERE_KINTONE_FUNCTION_REQUIRES_EXACT_PUSHDOWN}`
7718
+ );
7719
+ }
7685
7720
  }
7686
7721
  function evalNullCheck(expr, row) {
7687
7722
  const val = resolveField(expr.field, row);
@@ -7787,11 +7822,12 @@ function resolveKintoneFuncValue(name) {
7787
7822
  `${name}: ${WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN}`
7788
7823
  );
7789
7824
  }
7825
+ if (isLegacyKintoneFunctionName(name)) {
7826
+ throw new Error(
7827
+ `${name}: ${WHERE_KINTONE_FUNCTION_REQUIRES_EXACT_PUSHDOWN}`
7828
+ );
7829
+ }
7790
7830
  switch (name) {
7791
- case "TODAY":
7792
- case "NOW":
7793
- case "LOGINUSER":
7794
- return resolveKintoneFunc(name);
7795
7831
  default:
7796
7832
  throw new Error(`InternalError: unexpected KINTONE_FUNC name: ${name}`);
7797
7833
  }
@@ -12294,6 +12330,16 @@ var RELATIVE_DATE_FIELD_TYPES = /* @__PURE__ */ new Set([
12294
12330
  "UPDATED_TIME"
12295
12331
  ]);
12296
12332
  var RELATIVE_DATE_OPERATORS = new Set(RANGE_AND_EQUALITY);
12333
+ var LEGACY_KINTONE_FUNCTION_FIELD_TYPES = /* @__PURE__ */ new Map([
12334
+ ["TODAY", /* @__PURE__ */ new Set(["DATE", "DATETIME", "CREATED_TIME", "UPDATED_TIME"])],
12335
+ ["NOW", /* @__PURE__ */ new Set(["DATETIME", "CREATED_TIME", "UPDATED_TIME"])],
12336
+ ["LOGINUSER", /* @__PURE__ */ new Set(["CREATOR", "MODIFIER", "USER_SELECT"])]
12337
+ ]);
12338
+ var LEGACY_KINTONE_FUNCTION_OPERATORS = /* @__PURE__ */ new Map([
12339
+ ["TODAY", new Set(RANGE_AND_EQUALITY)],
12340
+ ["NOW", new Set(RANGE_AND_EQUALITY)],
12341
+ ["LOGINUSER", /* @__PURE__ */ new Set(["in", "not in"])]
12342
+ ]);
12297
12343
  var EQUALITY_IN = ["=", "!=", "in", "not in"];
12298
12344
  var NATIVE_OPERATORS = /* @__PURE__ */ new Map([
12299
12345
  ["RECORD_NUMBER", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
@@ -12321,6 +12367,12 @@ var NATIVE_OPERATORS = /* @__PURE__ */ new Map([
12321
12367
  ["GROUP_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
12322
12368
  ["STATUS", new Set(EQUALITY_IN)]
12323
12369
  ]);
12370
+ var LOCAL_VALID_OPERATORS = /* @__PURE__ */ new Map([
12371
+ ["CREATOR", /* @__PURE__ */ new Set(["in", "not in"])],
12372
+ ["MODIFIER", /* @__PURE__ */ new Set(["in", "not in"])],
12373
+ ["CHECK_BOX", /* @__PURE__ */ new Set(["in", "not in"])],
12374
+ ["MULTI_SELECT", /* @__PURE__ */ new Set(["in", "not in"])]
12375
+ ]);
12324
12376
  var LOCAL_SCALAR_TYPES = /* @__PURE__ */ new Set([
12325
12377
  "RECORD_NUMBER",
12326
12378
  "__ID__",
@@ -12383,7 +12435,7 @@ function classifyNode(where, resolveField2) {
12383
12435
  if (!hasRelativeDateReason(inner.reasons)) {
12384
12436
  return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
12385
12437
  }
12386
- return requireExactRelativeDatePushdown({
12438
+ return requireExactFunctionPushdown({
12387
12439
  capability: "LOCAL_ONLY",
12388
12440
  reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }, ...inner.reasons]
12389
12441
  });
@@ -12399,15 +12451,43 @@ function classifyBinary(op, left, right, resolveField2) {
12399
12451
  if (right.type === "KINTONE_FUNC" && isRelativeDateFunctionName(right.name)) {
12400
12452
  return classifyRelativeDateBinary(op, left, right, resolveField2);
12401
12453
  }
12454
+ if (right.type === "KINTONE_FUNC" && isLegacyKintoneFunction(right)) {
12455
+ return classifyLegacyKintoneFunctionBinary(op, left, right, resolveField2);
12456
+ }
12457
+ if (right.type === "IN_LIST") {
12458
+ const functions = right.values.filter(
12459
+ (value) => value.type === "KINTONE_FUNC"
12460
+ );
12461
+ if (functions.length > 0) {
12462
+ if (right.values.length === 1 && functions.length === 1) {
12463
+ return classifyLegacyKintoneFunctionBinary(op, left, functions[0], resolveField2);
12464
+ }
12465
+ return legacyKintoneFunctionUnsupported(
12466
+ "WHERE_KINTONE_FUNCTION_CONTEXT_UNSUPPORTED",
12467
+ functions[0].name,
12468
+ left.type === "FIELD" ? left.field : void 0,
12469
+ left.type === "FIELD" ? resolveField2(left)?.fieldType : void 0,
12470
+ normalizeOperator(op)
12471
+ );
12472
+ }
12473
+ }
12402
12474
  if (left.type !== "FIELD") return localExpression();
12403
12475
  const semantics = resolveField2(left);
12404
12476
  if (!semantics) {
12405
12477
  return unsupported2("WHERE_FIELD_UNRESOLVED", left.field, void 0, normalizeOperator(op));
12406
12478
  }
12479
+ const nativeOp = normalizeOperator(op);
12480
+ if (!isLocallyValidOperator(semantics.fieldType, nativeOp)) {
12481
+ return unsupported2(
12482
+ "WHERE_OPERATOR_INVALID_FOR_FIELD_TYPE",
12483
+ left.field,
12484
+ semantics.fieldType,
12485
+ nativeOp
12486
+ );
12487
+ }
12407
12488
  if (!hasLocalContract(semantics.fieldType, op)) {
12408
- return unsupported2("WHERE_OPERATOR_UNSUPPORTED", left.field, semantics.fieldType, normalizeOperator(op));
12489
+ return unsupported2("WHERE_OPERATOR_UNSUPPORTED", left.field, semantics.fieldType, nativeOp);
12409
12490
  }
12410
- const nativeOp = normalizeOperator(op);
12411
12491
  const native = nativeWhereOperatorsForType(semantics.fieldType);
12412
12492
  const rightCanPush = right.type === "STRING" || right.type === "NUMBER" || right.type === "IN_LIST" || isLegacyKintoneFunction(right);
12413
12493
  const structureAllows = !semantics.requiresCollectionOperators || nativeOp !== "=" && nativeOp !== "!=";
@@ -12434,7 +12514,51 @@ function classifyBinary(op, left, right, resolveField2) {
12434
12514
  };
12435
12515
  }
12436
12516
  function isLegacyKintoneFunction(value) {
12437
- return value.type === "KINTONE_FUNC" && (value.name === "TODAY" || value.name === "NOW" || value.name === "LOGINUSER");
12517
+ return value.type === "KINTONE_FUNC" && LEGACY_KINTONE_FUNCTION_NAMES.has(value.name);
12518
+ }
12519
+ function classifyLegacyKintoneFunctionBinary(op, left, right, resolveField2) {
12520
+ const operator = normalizeOperator(op);
12521
+ const functionName = right.name;
12522
+ if (!LEGACY_KINTONE_FUNCTION_NAMES.has(functionName) || left.type !== "FIELD") {
12523
+ return legacyKintoneFunctionUnsupported(
12524
+ "WHERE_KINTONE_FUNCTION_CONTEXT_UNSUPPORTED",
12525
+ functionName,
12526
+ void 0,
12527
+ void 0,
12528
+ operator
12529
+ );
12530
+ }
12531
+ const semantics = resolveField2(left);
12532
+ const validFieldTypes = LEGACY_KINTONE_FUNCTION_FIELD_TYPES.get(functionName);
12533
+ if (!semantics || !validFieldTypes.has(semantics.fieldType) || semantics.inSubtable || semantics.requiresCollectionOperators) {
12534
+ return legacyKintoneFunctionUnsupported(
12535
+ "WHERE_KINTONE_FUNCTION_FIELD_TYPE_UNSUPPORTED",
12536
+ functionName,
12537
+ left.field,
12538
+ semantics?.fieldType,
12539
+ operator
12540
+ );
12541
+ }
12542
+ const validOperators = LEGACY_KINTONE_FUNCTION_OPERATORS.get(functionName);
12543
+ if (!validOperators.has(operator)) {
12544
+ return legacyKintoneFunctionUnsupported(
12545
+ "WHERE_KINTONE_FUNCTION_OPERATOR_UNSUPPORTED",
12546
+ functionName,
12547
+ left.field,
12548
+ semantics.fieldType,
12549
+ operator
12550
+ );
12551
+ }
12552
+ return {
12553
+ capability: "EXACT_PUSHDOWN",
12554
+ reasons: [{
12555
+ code: "WHERE_EXACT",
12556
+ functionName,
12557
+ field: left.field,
12558
+ fieldType: semantics.fieldType,
12559
+ operator
12560
+ }]
12561
+ };
12438
12562
  }
12439
12563
  function classifyRelativeDateBinary(op, left, right, resolveField2) {
12440
12564
  const operator = normalizeOperator(op);
@@ -12522,6 +12646,14 @@ function hasValidRelativeDateArguments(value) {
12522
12646
  function classifyLocalOnlyField(field, operator, resolveField2) {
12523
12647
  const semantics = resolveField2(field);
12524
12648
  if (!semantics) return unsupported2("WHERE_FIELD_UNRESOLVED", field.field, void 0, operator);
12649
+ if (!isLocallyValidOperator(semantics.fieldType, operator)) {
12650
+ return unsupported2(
12651
+ "WHERE_OPERATOR_INVALID_FOR_FIELD_TYPE",
12652
+ field.field,
12653
+ semantics.fieldType,
12654
+ operator
12655
+ );
12656
+ }
12525
12657
  if (!LOCAL_SCALAR_TYPES.has(semantics.fieldType) && !LOCAL_COLLECTION_TYPES.has(semantics.fieldType)) {
12526
12658
  return unsupported2("WHERE_OPERATOR_UNSUPPORTED", field.field, semantics.fieldType, operator);
12527
12659
  }
@@ -12530,6 +12662,10 @@ function classifyLocalOnlyField(field, operator, resolveField2) {
12530
12662
  reasons: [{ code: "WHERE_RESIDUAL", field: field.field, fieldType: semantics.fieldType, operator }]
12531
12663
  };
12532
12664
  }
12665
+ function isLocallyValidOperator(fieldType, operator) {
12666
+ const policy = LOCAL_VALID_OPERATORS.get(fieldType);
12667
+ return policy === void 0 || policy.has(operator);
12668
+ }
12533
12669
  function hasLocalContract(fieldType, op) {
12534
12670
  if (LOCAL_SCALAR_TYPES.has(fieldType)) return true;
12535
12671
  if (!LOCAL_COLLECTION_TYPES.has(fieldType)) return false;
@@ -12556,18 +12692,18 @@ function normalizeOperator(op) {
12556
12692
  function combineLogical(op, left, right) {
12557
12693
  const reasons = [...left.reasons, ...right.reasons];
12558
12694
  if (left.capability === "UNSUPPORTED" || right.capability === "UNSUPPORTED") {
12559
- return requireExactRelativeDatePushdown({ capability: "UNSUPPORTED", reasons });
12695
+ return requireExactFunctionPushdown({ capability: "UNSUPPORTED", reasons });
12560
12696
  }
12561
12697
  if (left.capability === "EXACT_PUSHDOWN" && right.capability === "EXACT_PUSHDOWN") {
12562
12698
  return { capability: "EXACT_PUSHDOWN", reasons };
12563
12699
  }
12564
12700
  if (op === "AND" && (left.capability === "EXACT_PUSHDOWN" || right.capability === "EXACT_PUSHDOWN" || left.capability === "SUPERSET_PREFILTER" || right.capability === "SUPERSET_PREFILTER")) {
12565
- return requireExactRelativeDatePushdown({
12701
+ return requireExactFunctionPushdown({
12566
12702
  capability: "SUPERSET_PREFILTER",
12567
12703
  reasons: [{ code: "WHERE_SUPERSET_PREFILTER" }, ...reasons]
12568
12704
  });
12569
12705
  }
12570
- return requireExactRelativeDatePushdown({ capability: "LOCAL_ONLY", reasons });
12706
+ return requireExactFunctionPushdown({ capability: "LOCAL_ONLY", reasons });
12571
12707
  }
12572
12708
  function localExpression() {
12573
12709
  return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
@@ -12575,6 +12711,12 @@ function localExpression() {
12575
12711
  function unsupported2(code, field, fieldType, operator) {
12576
12712
  return { capability: "UNSUPPORTED", reasons: [{ code, field, fieldType, operator }] };
12577
12713
  }
12714
+ function legacyKintoneFunctionUnsupported(code, functionName, field, fieldType, operator) {
12715
+ return requireExactFunctionPushdown({
12716
+ capability: "UNSUPPORTED",
12717
+ reasons: [{ code, functionName, field, fieldType, operator }]
12718
+ });
12719
+ }
12578
12720
  function relativeDateUnsupported(code, functionName, field, fieldType, operator) {
12579
12721
  return requireExactRelativeDatePushdown({
12580
12722
  capability: "UNSUPPORTED",
@@ -12582,13 +12724,22 @@ function relativeDateUnsupported(code, functionName, field, fieldType, operator)
12582
12724
  });
12583
12725
  }
12584
12726
  function hasRelativeDateReason(reasons) {
12585
- return reasons.some((reason) => reason.functionName !== void 0);
12727
+ return reasons.some(
12728
+ (reason) => reason.code.startsWith("WHERE_RELATIVE_DATE_") || reason.functionName !== void 0 && isRelativeDateFunctionName(reason.functionName)
12729
+ );
12730
+ }
12731
+ function hasLegacyKintoneFunctionReason(reasons) {
12732
+ return reasons.some(
12733
+ (reason) => reason.code.startsWith("WHERE_KINTONE_FUNCTION_") || reason.functionName !== void 0 && LEGACY_KINTONE_FUNCTION_NAMES.has(reason.functionName)
12734
+ );
12586
12735
  }
12587
12736
  function requireExactRelativeDatePushdown(result) {
12588
12737
  if (result.capability === "EXACT_PUSHDOWN" || !hasRelativeDateReason(result.reasons) || result.reasons.some((reason) => reason.code === "WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN")) {
12589
12738
  return result;
12590
12739
  }
12591
- const relative = result.reasons.find((reason) => reason.functionName !== void 0);
12740
+ const relative = result.reasons.find(
12741
+ (reason) => reason.code.startsWith("WHERE_RELATIVE_DATE_") || reason.functionName !== void 0 && isRelativeDateFunctionName(reason.functionName)
12742
+ );
12592
12743
  return {
12593
12744
  capability: result.capability,
12594
12745
  reasons: [
@@ -12603,6 +12754,161 @@ function requireExactRelativeDatePushdown(result) {
12603
12754
  ]
12604
12755
  };
12605
12756
  }
12757
+ function requireExactLegacyKintoneFunctionPushdown(result) {
12758
+ if (result.capability === "EXACT_PUSHDOWN" || !hasLegacyKintoneFunctionReason(result.reasons) || result.reasons.some(
12759
+ (reason) => reason.code === "WHERE_KINTONE_FUNCTION_REQUIRES_EXACT_PUSHDOWN"
12760
+ )) {
12761
+ return result;
12762
+ }
12763
+ const legacy = result.reasons.find(
12764
+ (reason) => reason.code.startsWith("WHERE_KINTONE_FUNCTION_") || reason.functionName !== void 0 && LEGACY_KINTONE_FUNCTION_NAMES.has(reason.functionName)
12765
+ );
12766
+ return {
12767
+ capability: result.capability,
12768
+ reasons: [
12769
+ ...result.reasons,
12770
+ {
12771
+ code: "WHERE_KINTONE_FUNCTION_REQUIRES_EXACT_PUSHDOWN",
12772
+ functionName: legacy.functionName,
12773
+ field: legacy.field,
12774
+ fieldType: legacy.fieldType,
12775
+ operator: legacy.operator
12776
+ }
12777
+ ]
12778
+ };
12779
+ }
12780
+ function requireExactFunctionPushdown(result) {
12781
+ return requireExactLegacyKintoneFunctionPushdown(
12782
+ requireExactRelativeDatePushdown(result)
12783
+ );
12784
+ }
12785
+
12786
+ // src/core/optimization/relativeDateFullScanExactPlan.ts
12787
+ function buildRelativeDateFullScanExactPlan(input) {
12788
+ const {
12789
+ select,
12790
+ selectMode,
12791
+ capability,
12792
+ context,
12793
+ serializedWholeWhere,
12794
+ relativeFunctionNames
12795
+ } = input;
12796
+ if (select.where === null) return null;
12797
+ if (select.from.appId <= 0 || select.from.cteName !== null) return null;
12798
+ if (select.from.subtableCode) return null;
12799
+ if (select.joins.length > 0) return null;
12800
+ if (!context.allowFullScanExact) return null;
12801
+ if (select.orderMode === "KINTONE_NATIVE") return null;
12802
+ const hasCanonicalOrder2 = select.orderMode === "CANONICAL" && select.orderBy.length > 0;
12803
+ if (selectMode !== "FULL_SCAN" && !hasCanonicalOrder2) return null;
12804
+ if (capability.capability !== "EXACT_PUSHDOWN") return null;
12805
+ const occurrences = serverOnlyFunctionOccurrencesInWhere(select.where);
12806
+ if (occurrences.length === 0) return null;
12807
+ if (!sameOccurrenceList(occurrences, relativeFunctionNames)) return null;
12808
+ if (serializedWholeWhere === null || !serializedMultisetContains(serializedWholeWhere, occurrences)) {
12809
+ return null;
12810
+ }
12811
+ const prefilterPlan = {
12812
+ prefilterWhere: select.where,
12813
+ residualWhere: null,
12814
+ exactRelativeLeaves: collectExactServerFunctionLeaves(select.where),
12815
+ relativeFunctionNames: new Set(occurrences),
12816
+ appliedKlikes: /* @__PURE__ */ new Set(),
12817
+ capability: capability.capability,
12818
+ reasons: capability.reasons
12819
+ };
12820
+ const plan = {
12821
+ allowForm: "FULL_SCAN_EXACT",
12822
+ clientWhereEvaluation: false,
12823
+ serializedWholeWhere,
12824
+ prefilterPlan
12825
+ };
12826
+ assertRelativeDateFullScanExactPlan(plan, input, occurrences);
12827
+ return plan;
12828
+ }
12829
+ function assertRelativeDateFullScanExactPlan(plan, input, occurrences) {
12830
+ if (plan.allowForm !== "FULL_SCAN_EXACT") {
12831
+ throw new Error("FULL_SCAN_EXACT invariant: allowForm");
12832
+ }
12833
+ if (plan.clientWhereEvaluation !== false) {
12834
+ throw new Error("FULL_SCAN_EXACT invariant: clientWhereEvaluation");
12835
+ }
12836
+ if (input.capability.capability !== "EXACT_PUSHDOWN") {
12837
+ throw new Error("FULL_SCAN_EXACT invariant: capability");
12838
+ }
12839
+ if (input.select.where === null || plan.prefilterPlan.prefilterWhere !== input.select.where) {
12840
+ throw new Error("FULL_SCAN_EXACT invariant: whole WHERE identity");
12841
+ }
12842
+ if (plan.prefilterPlan.residualWhere !== null) {
12843
+ throw new Error("FULL_SCAN_EXACT invariant: residualWhere");
12844
+ }
12845
+ if (plan.prefilterPlan.capability !== "EXACT_PUSHDOWN") {
12846
+ throw new Error("FULL_SCAN_EXACT invariant: transport capability");
12847
+ }
12848
+ if (!serializedMultisetContains(plan.serializedWholeWhere, occurrences)) {
12849
+ throw new Error("FULL_SCAN_EXACT invariant: relative occurrence serialization");
12850
+ }
12851
+ }
12852
+ function serverOnlyFunctionOccurrencesInWhere(where) {
12853
+ const names = [];
12854
+ const visit = (node) => {
12855
+ if (Array.isArray(node)) {
12856
+ node.forEach(visit);
12857
+ return;
12858
+ }
12859
+ if (node === null || typeof node !== "object") return;
12860
+ const value = node;
12861
+ if (value["type"] === "SELECT") return;
12862
+ if (value["type"] === "KINTONE_FUNC" && typeof value["name"] === "string" && isServerOnlyWhereFunctionName(value["name"])) {
12863
+ names.push(value["name"]);
12864
+ return;
12865
+ }
12866
+ Object.values(value).forEach(visit);
12867
+ };
12868
+ visit(where);
12869
+ return names;
12870
+ }
12871
+ function collectExactServerFunctionLeaves(where) {
12872
+ const leaves = [];
12873
+ const visit = (node) => {
12874
+ switch (node.type) {
12875
+ case "BINARY":
12876
+ if (node.right.type === "KINTONE_FUNC" && isServerOnlyWhereFunctionName(node.right.name) || node.right.type === "IN_LIST" && node.right.values.length === 1 && node.right.values[0].type === "KINTONE_FUNC" && isServerOnlyWhereFunctionName(node.right.values[0].name)) {
12877
+ leaves.push(node);
12878
+ }
12879
+ return;
12880
+ case "LOGICAL":
12881
+ visit(node.left);
12882
+ visit(node.right);
12883
+ return;
12884
+ case "NOT":
12885
+ case "GROUP":
12886
+ visit(node.expr);
12887
+ return;
12888
+ case "EXISTS":
12889
+ case "NULL_CHECK":
12890
+ case "BOOLEAN":
12891
+ return;
12892
+ }
12893
+ };
12894
+ visit(where);
12895
+ return leaves;
12896
+ }
12897
+ function sameOccurrenceList(actual, expected) {
12898
+ return actual.length === expected.length && actual.every((name, index) => name === expected[index]);
12899
+ }
12900
+ function serializedMultisetContains(query, expectedNames) {
12901
+ const expected = /* @__PURE__ */ new Map();
12902
+ for (const name of expectedNames) {
12903
+ if (!isServerOnlyWhereFunctionName(name)) return false;
12904
+ expected.set(name, (expected.get(name) ?? 0) + 1);
12905
+ }
12906
+ for (const [name, count] of expected) {
12907
+ const matches = query.match(new RegExp(`\\b${name}\\s*\\(`, "g"));
12908
+ if ((matches?.length ?? 0) < count) return false;
12909
+ }
12910
+ return true;
12911
+ }
12606
12912
 
12607
12913
  // src/core/optimization/relativeDatePushdownGuard.ts
12608
12914
  function relativeDateFunctionNamesInNode(node, stopAtNestedSelect) {
@@ -12615,7 +12921,7 @@ function relativeDateFunctionNamesInNode(node, stopAtNestedSelect) {
12615
12921
  }
12616
12922
  if (valueNode === null || typeof valueNode !== "object") return;
12617
12923
  const value = valueNode;
12618
- if (value["type"] === "KINTONE_FUNC" && typeof value["name"] === "string" && isRelativeDateFunctionName(value["name"]) && !seen.has(value["name"])) {
12924
+ if (value["type"] === "KINTONE_FUNC" && typeof value["name"] === "string" && isServerOnlyWhereFunctionName(value["name"]) && !seen.has(value["name"])) {
12619
12925
  seen.add(value["name"]);
12620
12926
  names.push(value["name"]);
12621
12927
  return;
@@ -12649,7 +12955,7 @@ function nestedSelects(node, root) {
12649
12955
  visit(node);
12650
12956
  return found;
12651
12957
  }
12652
- function collectSelect(select, path, candidates, forceForbidden, allowPhase2 = true) {
12958
+ function collectSelect(select, path, candidates, forceForbidden, allowPhase2 = true, allowFullScanExact = true, forceNestedForbidden = forceForbidden) {
12653
12959
  const functionNames = relativeDateFunctionNamesInWhere(select.where);
12654
12960
  if (functionNames.length > 0) {
12655
12961
  candidates.push({
@@ -12658,7 +12964,9 @@ function collectSelect(select, path, candidates, forceForbidden, allowPhase2 = t
12658
12964
  where: select.where,
12659
12965
  functionNames,
12660
12966
  path,
12661
- allowPhase2Prefilter: allowPhase2
12967
+ allowPhase2Prefilter: allowPhase2,
12968
+ allowFullScanExact,
12969
+ relativeFunctionOccurrences: serverOnlyFunctionOccurrencesInWhere(select.where)
12662
12970
  });
12663
12971
  }
12664
12972
  nestedSelects(select, select).forEach(
@@ -12666,32 +12974,53 @@ function collectSelect(select, path, candidates, forceForbidden, allowPhase2 = t
12666
12974
  nested,
12667
12975
  `${path}.select-source[${index}]`,
12668
12976
  candidates,
12669
- forceForbidden,
12670
- allowPhase2
12977
+ forceNestedForbidden,
12978
+ forceNestedForbidden ? false : allowPhase2,
12979
+ forceNestedForbidden ? false : allowFullScanExact,
12980
+ forceNestedForbidden
12671
12981
  )
12672
12982
  );
12673
12983
  }
12674
- function collectUnion(union, path, candidates, forceForbidden) {
12675
- if (union.left.type === "UNION") collectUnion(union.left, `${path}.left`, candidates, forceForbidden);
12676
- else collectSelect(union.left, `${path}.left`, candidates, forceForbidden);
12677
- collectSelect(union.right, `${path}.right`, candidates, forceForbidden);
12984
+ function collectUnion(union, path, candidates, forceForbidden, allowFullScanExact = true) {
12985
+ if (union.left.type === "UNION") {
12986
+ collectUnion(union.left, `${path}.left`, candidates, forceForbidden, allowFullScanExact);
12987
+ } else {
12988
+ collectSelect(union.left, `${path}.left`, candidates, forceForbidden, true, allowFullScanExact);
12989
+ }
12990
+ collectSelect(union.right, `${path}.right`, candidates, forceForbidden, true, allowFullScanExact);
12678
12991
  }
12679
12992
  function collectWith(statement, path, candidates, inheritedForbidden) {
12680
12993
  if (!inheritedForbidden && canInlineSingleCte(statement)) {
12681
- collectSelect(buildInlinedQuery(statement), `${path}.inlined`, candidates, false);
12994
+ collectSelect(buildInlinedQuery(statement), `${path}.inlined`, candidates, false, true, true);
12682
12995
  return;
12683
12996
  }
12684
12997
  statement.ctes.forEach((cte, index) => {
12685
12998
  if (cte.query.type === "SELECT") {
12686
- collectSelect(cte.query, `${path}.cte[${index}]`, candidates, true);
12999
+ collectSelect(
13000
+ cte.query,
13001
+ `${path}.cte[${index}]`,
13002
+ candidates,
13003
+ inheritedForbidden,
13004
+ false,
13005
+ !inheritedForbidden,
13006
+ true
13007
+ );
12687
13008
  } else if (cte.query.type === "UNION") {
12688
- collectUnion(cte.query, `${path}.cte[${index}]`, candidates, true);
13009
+ collectUnion(cte.query, `${path}.cte[${index}]`, candidates, true, false);
12689
13010
  }
12690
13011
  });
12691
13012
  if (statement.query.type === "SELECT") {
12692
- collectSelect(statement.query, `${path}.main`, candidates, true);
13013
+ collectSelect(
13014
+ statement.query,
13015
+ `${path}.main`,
13016
+ candidates,
13017
+ inheritedForbidden,
13018
+ false,
13019
+ !inheritedForbidden,
13020
+ true
13021
+ );
12693
13022
  } else {
12694
- collectUnion(statement.query, `${path}.main`, candidates, true);
13023
+ collectUnion(statement.query, `${path}.main`, candidates, true, false);
12695
13024
  }
12696
13025
  }
12697
13026
  function collectStatement(statement, path, candidates, forceForbidden = false) {
@@ -12706,9 +13035,9 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12706
13035
  collectWith(statement, path, candidates, forceForbidden);
12707
13036
  return;
12708
13037
  case "CREATE_TEMP_TABLE":
12709
- if (statement.query.type === "WITH") collectWith(statement.query, `${path}.query`, candidates, true);
12710
- else if (statement.query.type === "UNION") collectUnion(statement.query, `${path}.query`, candidates, true);
12711
- else collectSelect(statement.query, `${path}.query`, candidates, true);
13038
+ if (statement.query.type === "WITH") collectWith(statement.query, `${path}.query`, candidates, false);
13039
+ else if (statement.query.type === "UNION") collectUnion(statement.query, `${path}.query`, candidates, true, false);
13040
+ else collectSelect(statement.query, `${path}.query`, candidates, false, false, true, true);
12712
13041
  return;
12713
13042
  case "EXPLAIN":
12714
13043
  collectStatement(statement.query, `${path}.query`, candidates, forceForbidden);
@@ -12722,11 +13051,13 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12722
13051
  source: statement,
12723
13052
  where: statement.where,
12724
13053
  functionNames,
12725
- path
13054
+ path,
13055
+ allowFullScanExact: false,
13056
+ relativeFunctionOccurrences: serverOnlyFunctionOccurrencesInWhere(statement.where)
12726
13057
  });
12727
13058
  }
12728
13059
  nestedSelects(statement, statement).forEach(
12729
- (select, index) => collectSelect(select, `${path}.select-source[${index}]`, candidates, true)
13060
+ (select, index) => collectSelect(select, `${path}.select-source[${index}]`, candidates, true, true, false)
12730
13061
  );
12731
13062
  return;
12732
13063
  }
@@ -12740,7 +13071,9 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12740
13071
  source: statement,
12741
13072
  where: statement.where,
12742
13073
  functionNames,
12743
- path
13074
+ path,
13075
+ allowFullScanExact: false,
13076
+ relativeFunctionOccurrences: serverOnlyFunctionOccurrencesInWhere(statement.where)
12744
13077
  });
12745
13078
  }
12746
13079
  if (statement.type === "UPDATE" && statement.applyBlocks?.length) {
@@ -12751,7 +13084,9 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12751
13084
  source: statement,
12752
13085
  where: null,
12753
13086
  functionNames: applyFunctions,
12754
- path: `${path}.apply`
13087
+ path: `${path}.apply`,
13088
+ allowFullScanExact: false,
13089
+ relativeFunctionOccurrences: []
12755
13090
  });
12756
13091
  }
12757
13092
  }
@@ -12761,6 +13096,7 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12761
13096
  `${path}.select-source[${index}]`,
12762
13097
  candidates,
12763
13098
  forceForbidden,
13099
+ false,
12764
13100
  false
12765
13101
  )
12766
13102
  );
@@ -12773,13 +13109,23 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12773
13109
  `${path}.select-source[${index}]`,
12774
13110
  candidates,
12775
13111
  forceForbidden,
13112
+ false,
12776
13113
  false
12777
13114
  )
12778
13115
  );
12779
13116
  }
12780
13117
  }
12781
13118
  function serializationContainsFunctions(query, names) {
12782
- return names.every((name) => new RegExp(`\\b${name}\\s*\\(`).test(query));
13119
+ const expected = /* @__PURE__ */ new Map();
13120
+ for (const name of names) {
13121
+ if (!isServerOnlyWhereFunctionName(name)) return false;
13122
+ expected.set(name, (expected.get(name) ?? 0) + 1);
13123
+ }
13124
+ for (const [name, count] of expected) {
13125
+ const matches = query.match(new RegExp(`\\b${name}\\s*\\(`, "g"));
13126
+ if ((matches?.length ?? 0) < count) return false;
13127
+ }
13128
+ return true;
12783
13129
  }
12784
13130
  function allowRelativeDatePrefilterPlan(select, decomposition) {
12785
13131
  return decomposition.eligible === true && resolveSelectMode(select) === "FULL_SCAN" && select.orderMode !== "KINTONE_NATIVE" && select.from.cteName === null && !select.from.subtableCode && select.joins.length === 0;
@@ -12794,23 +13140,36 @@ function rejectedNode(candidate) {
12794
13140
  allowed: false
12795
13141
  };
12796
13142
  }
12797
- function relativeDateReasonCodes(capability) {
13143
+ function serverFunctionReasonCodes(functionName, capability) {
12798
13144
  const codes = (capability?.reasons ?? []).filter(
12799
- (reason) => reason.functionName !== void 0
12800
- ).map((reason) => reason.code).filter((code) => code.startsWith("WHERE_RELATIVE_DATE_"));
12801
- if (!codes.includes(WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN)) {
13145
+ (reason) => reason.functionName === functionName
13146
+ ).map((reason) => reason.code).filter(
13147
+ (code) => code.startsWith("WHERE_RELATIVE_DATE_") || code.startsWith("WHERE_KINTONE_FUNCTION_")
13148
+ );
13149
+ if (isLegacyKintoneFunctionName(functionName)) {
13150
+ if (!codes.some(
13151
+ (code) => code !== WHERE_KINTONE_FUNCTION_REQUIRES_EXACT_PUSHDOWN
13152
+ )) {
13153
+ codes.unshift("WHERE_KINTONE_FUNCTION_CONTEXT_UNSUPPORTED");
13154
+ }
13155
+ if (!codes.includes(WHERE_KINTONE_FUNCTION_REQUIRES_EXACT_PUSHDOWN)) {
13156
+ codes.push(WHERE_KINTONE_FUNCTION_REQUIRES_EXACT_PUSHDOWN);
13157
+ }
13158
+ } else if (!codes.includes(WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN)) {
12802
13159
  codes.push(WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN);
12803
13160
  }
12804
13161
  return [...new Set(codes)];
12805
13162
  }
12806
13163
  function rejectionFor(candidate, capability) {
12807
- const reasonCodes = relativeDateReasonCodes(capability);
13164
+ const functionName = candidate.functionNames[0];
13165
+ const requiresExact = isLegacyKintoneFunctionName(functionName) ? WHERE_KINTONE_FUNCTION_REQUIRES_EXACT_PUSHDOWN : WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN;
13166
+ const reasonCodes = serverFunctionReasonCodes(functionName, capability);
12808
13167
  return {
12809
- functionName: candidate.functionNames[0],
13168
+ functionName,
12810
13169
  path: candidate.path,
12811
13170
  code: reasonCodes.find(
12812
- (code) => code !== WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN
12813
- ) ?? WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN,
13171
+ (code) => code !== requiresExact
13172
+ ) ?? requiresExact,
12814
13173
  reasonCodes
12815
13174
  };
12816
13175
  }
@@ -12823,7 +13182,8 @@ async function buildRelativeDatePushdownPlan(statement, resolver) {
12823
13182
  const node2 = rejectedNode(candidate);
12824
13183
  nodes.push(node2);
12825
13184
  return {
12826
- hasRelativeDate: true,
13185
+ hasRelativeDate: candidate.functionNames.some(isRelativeDateFunctionName),
13186
+ hasServerOnlyWhereFunction: true,
12827
13187
  nodes,
12828
13188
  allowed: false,
12829
13189
  rejection: rejectionFor(candidate)
@@ -12840,7 +13200,10 @@ async function buildRelativeDatePushdownPlan(statement, resolver) {
12840
13200
  } catch {
12841
13201
  restQuery2 = "";
12842
13202
  }
12843
- let allowed2 = physicalTopLevel && selectMode === "SIMPLE" && (select.orderBy.length === 0 || select.orderMode === "KINTONE_NATIVE") && capability2.capability === "EXACT_PUSHDOWN" && serializationContainsFunctions(restQuery2, candidate.functionNames);
13203
+ let allowed2 = physicalTopLevel && selectMode === "SIMPLE" && (select.orderBy.length === 0 || select.orderMode === "KINTONE_NATIVE") && capability2.capability === "EXACT_PUSHDOWN" && serializationContainsFunctions(
13204
+ restQuery2,
13205
+ candidate.relativeFunctionOccurrences
13206
+ );
12844
13207
  let prefilterPlan;
12845
13208
  let phase2PrefilterEligible;
12846
13209
  if (!allowed2 && candidate.allowPhase2Prefilter !== false && capability2.capability === "SUPERSET_PREFILTER" && resolver.prefilterDecomposition) {
@@ -12851,6 +13214,21 @@ async function buildRelativeDatePushdownPlan(statement, resolver) {
12851
13214
  allowed2 = true;
12852
13215
  }
12853
13216
  }
13217
+ let fullScanExactPlan;
13218
+ if (!allowed2 && capability2.capability === "EXACT_PUSHDOWN") {
13219
+ fullScanExactPlan = buildRelativeDateFullScanExactPlan({
13220
+ select,
13221
+ selectMode,
13222
+ capability: capability2,
13223
+ context: { allowFullScanExact: candidate.allowFullScanExact },
13224
+ serializedWholeWhere: restQuery2 || null,
13225
+ relativeFunctionNames: candidate.relativeFunctionOccurrences
13226
+ }) ?? void 0;
13227
+ if (fullScanExactPlan) {
13228
+ prefilterPlan = fullScanExactPlan.prefilterPlan;
13229
+ allowed2 = true;
13230
+ }
13231
+ }
12854
13232
  const node2 = {
12855
13233
  kind: candidate.kind,
12856
13234
  source: candidate.source,
@@ -12860,13 +13238,17 @@ async function buildRelativeDatePushdownPlan(statement, resolver) {
12860
13238
  capability: capability2,
12861
13239
  restQuery: restQuery2,
12862
13240
  ...prefilterPlan ? { prefilterPlan, phase2PrefilterEligible } : {},
13241
+ ...fullScanExactPlan ? { fullScanExactPlan, allowForm: fullScanExactPlan.allowForm } : {},
12863
13242
  clientWhereEvaluation: !allowed2,
12864
13243
  allowed: allowed2
12865
13244
  };
12866
13245
  nodes.push(node2);
12867
13246
  if (!allowed2) {
12868
13247
  return {
12869
- hasRelativeDate: true,
13248
+ hasRelativeDate: candidates.some(
13249
+ (entry) => entry.functionNames.some(isRelativeDateFunctionName)
13250
+ ),
13251
+ hasServerOnlyWhereFunction: true,
12870
13252
  nodes,
12871
13253
  allowed: false,
12872
13254
  rejection: rejectionFor(candidate, capability2)
@@ -12882,7 +13264,10 @@ async function buildRelativeDatePushdownPlan(statement, resolver) {
12882
13264
  } catch {
12883
13265
  restQuery = "";
12884
13266
  }
12885
- const allowed = capability.capability === "EXACT_PUSHDOWN" && serializationContainsFunctions(restQuery, candidate.functionNames);
13267
+ const allowed = capability.capability === "EXACT_PUSHDOWN" && serializationContainsFunctions(
13268
+ restQuery,
13269
+ candidate.relativeFunctionOccurrences
13270
+ );
12886
13271
  const node = {
12887
13272
  kind: candidate.kind,
12888
13273
  source: candidate.source,
@@ -12896,7 +13281,10 @@ async function buildRelativeDatePushdownPlan(statement, resolver) {
12896
13281
  nodes.push(node);
12897
13282
  if (!allowed) {
12898
13283
  return {
12899
- hasRelativeDate: true,
13284
+ hasRelativeDate: candidates.some(
13285
+ (entry) => entry.functionNames.some(isRelativeDateFunctionName)
13286
+ ),
13287
+ hasServerOnlyWhereFunction: true,
12900
13288
  nodes,
12901
13289
  allowed: false,
12902
13290
  rejection: rejectionFor(candidate, capability)
@@ -12904,18 +13292,22 @@ async function buildRelativeDatePushdownPlan(statement, resolver) {
12904
13292
  }
12905
13293
  }
12906
13294
  return {
12907
- hasRelativeDate: candidates.length > 0,
13295
+ hasRelativeDate: candidates.some(
13296
+ (entry) => entry.functionNames.some(isRelativeDateFunctionName)
13297
+ ),
13298
+ hasServerOnlyWhereFunction: candidates.length > 0,
12908
13299
  nodes,
12909
13300
  allowed: true
12910
13301
  };
12911
13302
  }
12912
13303
  function assertRelativeDatePushdownPlan(plan) {
12913
13304
  if (!plan.allowed && plan.rejection) {
13305
+ const requiresExact = isLegacyKintoneFunctionName(plan.rejection.functionName) ? WHERE_KINTONE_FUNCTION_REQUIRES_EXACT_PUSHDOWN : WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN;
12914
13306
  const details = plan.rejection.reasonCodes.filter(
12915
- (code) => code !== WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN
13307
+ (code) => code !== requiresExact
12916
13308
  );
12917
13309
  throw new Error(
12918
- `${plan.rejection.functionName}: ${WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN}${details.length > 0 ? ` (reason=${details.join(", ")})` : ""} (path=${plan.rejection.path})`
13310
+ `${plan.rejection.functionName}: ${requiresExact}${details.length > 0 ? ` (reason=${details.join(", ")})` : ""} (path=${plan.rejection.path})`
12919
13311
  );
12920
13312
  }
12921
13313
  }
@@ -12936,9 +13328,9 @@ function decomposeRelativeDatePrefilter(stmt, resolveField2, testSeam = {}) {
12936
13328
  if (stmt.from.cteName !== null || stmt.from.appId <= 0) {
12937
13329
  return reject(["NOT_DIRECT_PHYSICAL_APP"]);
12938
13330
  }
12939
- const occurrences = collectRelativeOccurrences(stmt.where);
13331
+ const occurrences = collectServerFunctionOccurrences(stmt.where);
12940
13332
  if (occurrences.length === 0) return reject(["NO_RELATIVE_DATE"]);
12941
- const spine = collectRelativeLeavesOnAndSpine(stmt.where, resolveField2);
13333
+ const spine = collectServerFunctionLeavesOnAndSpine(stmt.where, resolveField2);
12942
13334
  if (!spine.ok) return reject(spine.reasonCodes);
12943
13335
  if (!sameRelativeMultiset(occurrences, spine.leaves)) {
12944
13336
  return reject(["RELATIVE_DATE_LEAF_COUNT_MISMATCH"]);
@@ -12961,7 +13353,7 @@ function decomposeRelativeDatePrefilter(stmt, resolveField2, testSeam = {}) {
12961
13353
  const containsFunctions = testSeam.containsFunctions ?? serializationContainsFunctions;
12962
13354
  const confirmedLeaves = [];
12963
13355
  for (const leaf of spine.leaves) {
12964
- const name = relativeNameOf(leaf);
13356
+ const name = serverFunctionNameOf(leaf);
12965
13357
  if (name === null) return reject(["RELATIVE_DATE_LEAF_COUNT_MISMATCH"]);
12966
13358
  let query;
12967
13359
  try {
@@ -12990,12 +13382,12 @@ function decomposeRelativeDatePrefilter(stmt, resolveField2, testSeam = {}) {
12990
13382
  } catch {
12991
13383
  return reject(["PREFILTER_SERIALIZATION_FAILED"]);
12992
13384
  }
12993
- const expectedNames = confirmedLeaves.map((leaf) => relativeNameOf(leaf));
12994
- if (!containsFunctions(prefilterQuery, [...new Set(expectedNames)]) || !serializedMultisetContains(prefilterQuery, expectedNames)) {
13385
+ const expectedNames = confirmedLeaves.map((leaf) => serverFunctionNameOf(leaf));
13386
+ if (!containsFunctions(prefilterQuery, [...new Set(expectedNames)]) || !serializedMultisetContains2(prefilterQuery, expectedNames)) {
12995
13387
  return reject(["PREFILTER_FUNCTION_MISSING"]);
12996
13388
  }
12997
13389
  const residualWhere = testSeam.rewriteResidual ? testSeam.rewriteResidual(stmt.where, adoptedLeaves) : replaceAdoptedLeaves(stmt.where, adoptedLeaves);
12998
- if (residualWhere !== null && collectRelativeOccurrences(residualWhere).length > 0) {
13390
+ if (residualWhere !== null && collectServerFunctionOccurrences(residualWhere).length > 0) {
12999
13391
  return reject(["RESIDUAL_RELATIVE_DATE_REMAINED"]);
13000
13392
  }
13001
13393
  if (residualWhere === null) {
@@ -13016,7 +13408,7 @@ function decomposeRelativeDatePrefilter(stmt, resolveField2, testSeam = {}) {
13016
13408
  }
13017
13409
  };
13018
13410
  }
13019
- function collectRelativeLeavesOnAndSpine(where, resolveField2) {
13411
+ function collectServerFunctionLeavesOnAndSpine(where, resolveField2) {
13020
13412
  const leaves = [];
13021
13413
  let failure = null;
13022
13414
  const visit = (node) => {
@@ -13031,24 +13423,19 @@ function collectRelativeLeavesOnAndSpine(where, resolveField2) {
13031
13423
  visit(node.right);
13032
13424
  return;
13033
13425
  }
13034
- if (collectRelativeOccurrences(node).length > 0) {
13426
+ if (collectServerFunctionOccurrences(node).length > 0) {
13035
13427
  failure = "RELATIVE_DATE_CONTEXT_UNSUPPORTED";
13036
13428
  }
13037
13429
  return;
13038
13430
  case "NOT":
13039
- if (collectRelativeOccurrences(node).length > 0) {
13431
+ if (collectServerFunctionOccurrences(node).length > 0) {
13040
13432
  failure = "RELATIVE_DATE_CONTEXT_UNSUPPORTED";
13041
13433
  }
13042
13434
  return;
13043
13435
  case "BINARY": {
13044
- const name = relativeNameOf(node);
13436
+ const name = serverFunctionNameOf(node);
13045
13437
  if (name === null) return;
13046
- const result = classifyRelativeDateBinary(
13047
- node.op,
13048
- node.left,
13049
- node.right,
13050
- resolveField2
13051
- );
13438
+ const result = classifyWhereCapability(node, resolveField2);
13052
13439
  if (result.capability !== "EXACT_PUSHDOWN") {
13053
13440
  failure = "RELATIVE_DATE_LEAF_NOT_EXACT";
13054
13441
  return;
@@ -13057,7 +13444,7 @@ function collectRelativeLeavesOnAndSpine(where, resolveField2) {
13057
13444
  return;
13058
13445
  }
13059
13446
  case "EXISTS":
13060
- if (collectRelativeOccurrences(node).length > 0) {
13447
+ if (collectServerFunctionOccurrences(node).length > 0) {
13061
13448
  failure = "RELATIVE_DATE_CONTEXT_UNSUPPORTED";
13062
13449
  }
13063
13450
  return;
@@ -13069,13 +13456,19 @@ function collectRelativeLeavesOnAndSpine(where, resolveField2) {
13069
13456
  visit(where);
13070
13457
  return failure === null ? { ok: true, leaves } : { ok: false, reasonCodes: [failure] };
13071
13458
  }
13072
- function relativeNameOf(leaf) {
13073
- return leaf.right.type === "KINTONE_FUNC" && isRelativeDateFunctionName(leaf.right.name) ? leaf.right.name : null;
13459
+ function serverFunctionNameOf(leaf) {
13460
+ if (leaf.right.type === "KINTONE_FUNC" && isServerOnlyWhereFunctionName(leaf.right.name)) {
13461
+ return leaf.right.name;
13462
+ }
13463
+ if (leaf.right.type === "IN_LIST" && leaf.right.values.length === 1 && leaf.right.values[0].type === "KINTONE_FUNC" && isServerOnlyWhereFunctionName(leaf.right.values[0].name)) {
13464
+ return leaf.right.values[0].name;
13465
+ }
13466
+ return null;
13074
13467
  }
13075
- function collectRelativeOccurrences(where) {
13468
+ function collectServerFunctionOccurrences(where) {
13076
13469
  const found = [];
13077
13470
  const visitWhere = (node) => {
13078
- if (node.type === "BINARY" && relativeNameOf(node) !== null) {
13471
+ if (node.type === "BINARY" && serverFunctionNameOf(node) !== null) {
13079
13472
  found.push(node);
13080
13473
  return;
13081
13474
  }
@@ -13213,10 +13606,10 @@ function collectFieldMetadata(where, resolveField2) {
13213
13606
  }
13214
13607
  return { fieldTypes, fieldOptions };
13215
13608
  }
13216
- function serializedMultisetContains(query, expectedNames) {
13609
+ function serializedMultisetContains2(query, expectedNames) {
13217
13610
  const expected = /* @__PURE__ */ new Map();
13218
13611
  for (const name of expectedNames) {
13219
- if (!RELATIVE_DATE_FUNCTION_NAMES.has(name)) return false;
13612
+ if (!SERVER_ONLY_WHERE_FUNCTION_NAMES.has(name)) return false;
13220
13613
  expected.set(name, (expected.get(name) ?? 0) + 1);
13221
13614
  }
13222
13615
  for (const [name, count] of expected) {
@@ -15370,7 +15763,7 @@ async function resolveSelectWhereCapability(stmt, client, cacheContext, material
15370
15763
  }
15371
15764
  function formatWhereCapabilityFailure(result) {
15372
15765
  const reason = result.reasons.find(
15373
- (candidate) => candidate.code === "WHERE_FIELD_UNRESOLVED" || candidate.code === "WHERE_OPERATOR_UNSUPPORTED"
15766
+ (candidate) => candidate.code === "WHERE_FIELD_UNRESOLVED" || candidate.code === "WHERE_OPERATOR_UNSUPPORTED" || candidate.code === "WHERE_OPERATOR_INVALID_FOR_FIELD_TYPE"
15374
15767
  ) ?? result.reasons[0];
15375
15768
  const details = [
15376
15769
  reason?.field ? `field=${reason.field}` : null,
@@ -15436,7 +15829,9 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
15436
15829
  if (whereCapability.capability === "UNSUPPORTED") {
15437
15830
  throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(whereCapability)}).`);
15438
15831
  }
15832
+ const staticMode = resolveSelectMode(stmt);
15439
15833
  let prefilterPlan;
15834
+ let fullScanExactPlan;
15440
15835
  if (whereCapability.capability === "SUPERSET_PREFILTER") {
15441
15836
  const resolver = await buildWhereFieldSemanticsResolver(stmt, client, cacheContext, cteCache);
15442
15837
  const decomposition = decomposeRelativeDatePrefilter(stmt, resolver);
@@ -15444,7 +15839,23 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
15444
15839
  prefilterPlan = decomposition.plan;
15445
15840
  }
15446
15841
  }
15447
- const staticMode = resolveSelectMode(stmt);
15842
+ if (prefilterPlan === void 0 && whereCapability.capability === "EXACT_PUSHDOWN" && stmt.where !== null) {
15843
+ let serializedWholeWhere = null;
15844
+ try {
15845
+ serializedWholeWhere = whereToKintone(stmt.where);
15846
+ } catch {
15847
+ serializedWholeWhere = null;
15848
+ }
15849
+ fullScanExactPlan = buildRelativeDateFullScanExactPlan({
15850
+ select: stmt,
15851
+ selectMode: staticMode,
15852
+ capability: whereCapability,
15853
+ context: { allowFullScanExact: true },
15854
+ serializedWholeWhere,
15855
+ relativeFunctionNames: serverOnlyFunctionOccurrencesInWhere(stmt.where)
15856
+ }) ?? void 0;
15857
+ if (fullScanExactPlan) prefilterPlan = fullScanExactPlan.prefilterPlan;
15858
+ }
15448
15859
  const mode = whereCapability.capability === "EXACT_PUSHDOWN" ? staticMode : "FULL_SCAN";
15449
15860
  const orderMeta = await buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
15450
15861
  const orderPlan = hasCanonicalOrder(stmt) ? (stmt.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
@@ -15462,18 +15873,31 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
15462
15873
  client,
15463
15874
  cacheContext
15464
15875
  );
15465
- const completePolicy = buildCompleteInputPolicy(stmt, options, orderPlan);
15876
+ const completePolicy = buildCompleteInputPolicy(
15877
+ stmt,
15878
+ options,
15879
+ orderPlan
15880
+ );
15881
+ const failClosedForB72Local = fullScanExactPlan !== void 0 && (staticMode === "FULL_SCAN" || orderPlan?.kind === "CANONICAL_LOCAL");
15882
+ const executionClient = failClosedForB72Local ? wrapClientWithSearchAbort(client, { aborted: false }, true) : client;
15466
15883
  try {
15467
15884
  if (mode === "SIMPLE") {
15468
- result = await executeSimpleSelect(stmt, client, completePolicy.effectiveOptions, cacheContext, orderPlan, orderMeta);
15885
+ result = await executeSimpleSelect(
15886
+ stmt,
15887
+ executionClient,
15888
+ completePolicy.effectiveOptions,
15889
+ cacheContext,
15890
+ orderPlan,
15891
+ orderMeta
15892
+ );
15469
15893
  } else {
15470
15894
  result = await executeFullScanSelect(
15471
15895
  stmt,
15472
- client,
15896
+ executionClient,
15473
15897
  completePolicy.effectiveOptions,
15474
15898
  cacheContext,
15475
15899
  cteCache,
15476
- whereCapability.capability === "EXACT_PUSHDOWN",
15900
+ whereCapability.capability === "EXACT_PUSHDOWN" && prefilterPlan === void 0,
15477
15901
  orderMeta,
15478
15902
  prefilterPlan,
15479
15903
  plainGroupByPlan
@@ -20510,7 +20934,7 @@ function renderResidualValue(node) {
20510
20934
  case "CONCAT_OP":
20511
20935
  return `(${renderResidualValue(value["left"])} ${typeof value["op"] === "string" ? value["op"] : value["type"] === "CONCAT_OP" ? "||" : "<op>"} ${renderResidualValue(value["right"])})`;
20512
20936
  case "KINTONE_FUNC":
20513
- return typeof value["name"] === "string" ? `${value["name"]}(...)` : "<expr>";
20937
+ return typeof value["name"] === "string" ? value["name"] === "LOGINUSER" ? "LOGINUSER()" : `${value["name"]}(...)` : "<expr>";
20514
20938
  case "IN_LIST":
20515
20939
  return Array.isArray(value["values"]) ? `(${value["values"].map(renderResidualValue).join(", ")})` : "<expr>";
20516
20940
  case "ARRAY":
@@ -20546,10 +20970,11 @@ function renderRelativeDateResidualWhere(where) {
20546
20970
  }
20547
20971
  }
20548
20972
  function relativeDateExplainLines(plan) {
20549
- if (!plan.hasRelativeDate) return [];
20973
+ if (!plan.hasServerOnlyWhereFunction) return [];
20550
20974
  if (!plan.allowed && plan.rejection) {
20975
+ const label = isRelativeDateFunctionName(plan.rejection.functionName) ? "relative date function" : "kintone function";
20551
20976
  return [
20552
- ` relative date function: ${plan.rejection.functionName}`,
20977
+ ` ${label}: ${plan.rejection.functionName}`,
20553
20978
  " plan status: rejected",
20554
20979
  ` reason: ${plan.rejection.reasonCodes.join(", ")}`,
20555
20980
  " client evaluation: forbidden",
@@ -20558,18 +20983,46 @@ function relativeDateExplainLines(plan) {
20558
20983
  }
20559
20984
  const lines = [];
20560
20985
  for (const node of plan.nodes) {
20986
+ const fullScanExactPlan = node.fullScanExactPlan;
20987
+ if (node.allowed && node.allowForm === "FULL_SCAN_EXACT" && fullScanExactPlan) {
20988
+ for (const leaf of fullScanExactPlan.prefilterPlan.exactRelativeLeaves) {
20989
+ const functionName = serverFunctionNameOfExplainLeaf(leaf);
20990
+ const field = leaf.left.type === "FIELD" ? leaf.left.field : void 0;
20991
+ const operator = relativeReasonOperator(leaf.op);
20992
+ const detail = node.capability?.reasons.find(
20993
+ (reason) => reason.functionName === functionName && (field === void 0 || reason.field === field) && reason.operator === operator
20994
+ );
20995
+ lines.push(
20996
+ ` ${serverFunctionLabel(functionName)}: ${functionName}`,
20997
+ ` ${serverFunctionEvaluationLabel(functionName)}: kintone server whole-WHERE exact`,
20998
+ ` field: ${detail?.field ?? field ?? "(unknown)"} (${detail?.fieldType ?? "unknown"})`,
20999
+ ` operator: ${detail?.operator ?? operator}`
21000
+ );
21001
+ }
21002
+ const wholeWhereQuery = fullScanExactPlan.serializedWholeWhere;
21003
+ lines.push(
21004
+ " where capability: EXACT_PUSHDOWN",
21005
+ ` server predicate: ${wholeWhereQuery}`,
21006
+ " client residual: (none)",
21007
+ ` ${serverFunctionClientEvaluationLabel(
21008
+ fullScanExactPlan.prefilterPlan.exactRelativeLeaves
21009
+ )}: 0`,
21010
+ ` kintone query: ${wholeWhereQuery}`
21011
+ );
21012
+ continue;
21013
+ }
20561
21014
  const prefilterPlan = node.prefilterPlan;
20562
21015
  if (node.allowed && prefilterPlan?.prefilterWhere && prefilterPlan.residualWhere) {
20563
21016
  for (const leaf of prefilterPlan.exactRelativeLeaves) {
20564
- const functionName = leaf.right.type === "KINTONE_FUNC" ? leaf.right.name : "(unknown)";
21017
+ const functionName = serverFunctionNameOfExplainLeaf(leaf);
20565
21018
  const field = leaf.left.type === "FIELD" ? leaf.left.field : void 0;
20566
21019
  const operator = relativeReasonOperator(leaf.op);
20567
21020
  const detail = node.capability?.reasons.find(
20568
21021
  (reason) => reason.functionName === functionName && (field === void 0 || reason.field === field) && reason.operator === operator
20569
21022
  );
20570
21023
  lines.push(
20571
- ` relative date function: ${functionName}`,
20572
- " relative date evaluation: kintone server exact prefilter",
21024
+ ` ${serverFunctionLabel(functionName)}: ${functionName}`,
21025
+ ` ${serverFunctionEvaluationLabel(functionName)}: kintone server exact prefilter`,
20573
21026
  ` field: ${detail?.field ?? field ?? "(unknown)"} (${detail?.fieldType ?? "unknown"})`,
20574
21027
  ` operator: ${detail?.operator ?? operator}`
20575
21028
  );
@@ -20579,7 +21032,7 @@ function relativeDateExplainLines(plan) {
20579
21032
  " where capability: SUPERSET_PREFILTER",
20580
21033
  ` server prefilter: ${serverPrefilter}`,
20581
21034
  ` client residual: ${renderRelativeDateResidualWhere(prefilterPlan.residualWhere)}`,
20582
- " relative date client evaluations: 0",
21035
+ ` ${serverFunctionClientEvaluationLabel(prefilterPlan.exactRelativeLeaves)}: 0`,
20583
21036
  ` kintone query: ${serverPrefilter}`
20584
21037
  );
20585
21038
  continue;
@@ -20588,6 +21041,19 @@ function relativeDateExplainLines(plan) {
20588
21041
  const detail = node.capability?.reasons.find(
20589
21042
  (reason) => reason.functionName === functionName
20590
21043
  );
21044
+ if (!isRelativeDateFunctionName(functionName)) {
21045
+ lines.push(
21046
+ ` kintone function: ${functionName}`,
21047
+ " kintone function evaluation: kintone server",
21048
+ ` field: ${detail?.field ?? "(unknown)"} (${detail?.fieldType ?? "unknown"})`,
21049
+ ` operator: ${detail?.operator ?? "(unknown)"}`,
21050
+ ` where capability: ${node.capability?.capability ?? "(unknown)"}`,
21051
+ " client residual: (none)",
21052
+ " kintone function client evaluations: 0",
21053
+ ` kintone query: ${node.restQuery || "(\u306A\u3057)"}`
21054
+ );
21055
+ continue;
21056
+ }
20591
21057
  lines.push(
20592
21058
  ` relative date function: ${functionName}`,
20593
21059
  " evaluation: kintone server",
@@ -20601,6 +21067,24 @@ function relativeDateExplainLines(plan) {
20601
21067
  }
20602
21068
  return lines;
20603
21069
  }
21070
+ function serverFunctionLabel(functionName) {
21071
+ return isRelativeDateFunctionName(functionName) ? "relative date function" : "kintone function";
21072
+ }
21073
+ function serverFunctionNameOfExplainLeaf(leaf) {
21074
+ if (leaf.right.type === "KINTONE_FUNC") return leaf.right.name;
21075
+ if (leaf.right.type === "IN_LIST" && leaf.right.values.length === 1 && leaf.right.values[0].type === "KINTONE_FUNC") {
21076
+ return leaf.right.values[0].name;
21077
+ }
21078
+ return "(unknown)";
21079
+ }
21080
+ function serverFunctionEvaluationLabel(functionName) {
21081
+ return isRelativeDateFunctionName(functionName) ? "relative date evaluation" : "kintone function evaluation";
21082
+ }
21083
+ function serverFunctionClientEvaluationLabel(leaves) {
21084
+ return leaves.every(
21085
+ (leaf) => leaf.right.type === "KINTONE_FUNC" && isRelativeDateFunctionName(leaf.right.name)
21086
+ ) ? "relative date client evaluations" : "kintone function client evaluations";
21087
+ }
20604
21088
  async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords = 1e4, cursorMaxActive = 2, enableImport = false, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS) {
20605
21089
  const statements = parseSqlBatch(sql, enableImport);
20606
21090
  const analysis = analyzeBatch(statements);
@@ -20619,7 +21103,7 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
20619
21103
  maxRecords,
20620
21104
  relativeDatePlan
20621
21105
  );
20622
- const statementPlan = relativeDatePlan.hasRelativeDate && !relativeDatePlan.allowed ? relativeDateExplainLines(relativeDatePlan) : [
21106
+ const statementPlan = relativeDatePlan.hasServerOnlyWhereFunction && !relativeDatePlan.allowed ? relativeDateExplainLines(relativeDatePlan) : [
20623
21107
  ...relativeDateExplainLines(relativeDatePlan),
20624
21108
  ...addCursorConcurrency(buildBatchStatementPlan(
20625
21109
  planStmt,
@@ -20748,7 +21232,7 @@ async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxA
20748
21232
  sharedPlan
20749
21233
  );
20750
21234
  const relativeLines = relativeDateExplainLines(sharedPlan);
20751
- const lines = sharedPlan.hasRelativeDate && !sharedPlan.allowed ? [...explainMetadataLines(analysis), ...relativeLines] : [
21235
+ const lines = sharedPlan.hasServerOnlyWhereFunction && !sharedPlan.allowed ? [...explainMetadataLines(analysis), ...relativeLines] : [
20752
21236
  ...explainMetadataLines(analysis),
20753
21237
  ...relativeLines,
20754
21238
  ...addCursorConcurrency(