@rex0220/kintone-sql-tools 3.23.0 → 3.24.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
@@ -12604,6 +12604,133 @@ function requireExactRelativeDatePushdown(result) {
12604
12604
  };
12605
12605
  }
12606
12606
 
12607
+ // src/core/optimization/relativeDateFullScanExactPlan.ts
12608
+ function buildRelativeDateFullScanExactPlan(input) {
12609
+ const {
12610
+ select,
12611
+ selectMode,
12612
+ capability,
12613
+ context,
12614
+ serializedWholeWhere,
12615
+ relativeFunctionNames
12616
+ } = input;
12617
+ if (select.where === null) return null;
12618
+ if (select.from.appId <= 0 || select.from.cteName !== null) return null;
12619
+ if (select.from.subtableCode) return null;
12620
+ if (select.joins.length > 0) return null;
12621
+ if (!context.allowFullScanExact) return null;
12622
+ if (select.orderMode === "KINTONE_NATIVE") return null;
12623
+ const hasCanonicalOrder2 = select.orderMode === "CANONICAL" && select.orderBy.length > 0;
12624
+ if (selectMode !== "FULL_SCAN" && !hasCanonicalOrder2) return null;
12625
+ if (capability.capability !== "EXACT_PUSHDOWN") return null;
12626
+ const occurrences = relativeDateFunctionOccurrencesInWhere(select.where);
12627
+ if (occurrences.length === 0) return null;
12628
+ if (!sameOccurrenceList(occurrences, relativeFunctionNames)) return null;
12629
+ if (serializedWholeWhere === null || !serializedMultisetContains(serializedWholeWhere, occurrences)) {
12630
+ return null;
12631
+ }
12632
+ const prefilterPlan = {
12633
+ prefilterWhere: select.where,
12634
+ residualWhere: null,
12635
+ exactRelativeLeaves: collectExactRelativeLeaves(select.where),
12636
+ relativeFunctionNames: new Set(occurrences),
12637
+ appliedKlikes: /* @__PURE__ */ new Set(),
12638
+ capability: capability.capability,
12639
+ reasons: capability.reasons
12640
+ };
12641
+ const plan = {
12642
+ allowForm: "FULL_SCAN_EXACT",
12643
+ clientWhereEvaluation: false,
12644
+ serializedWholeWhere,
12645
+ prefilterPlan
12646
+ };
12647
+ assertRelativeDateFullScanExactPlan(plan, input, occurrences);
12648
+ return plan;
12649
+ }
12650
+ function assertRelativeDateFullScanExactPlan(plan, input, occurrences) {
12651
+ if (plan.allowForm !== "FULL_SCAN_EXACT") {
12652
+ throw new Error("FULL_SCAN_EXACT invariant: allowForm");
12653
+ }
12654
+ if (plan.clientWhereEvaluation !== false) {
12655
+ throw new Error("FULL_SCAN_EXACT invariant: clientWhereEvaluation");
12656
+ }
12657
+ if (input.capability.capability !== "EXACT_PUSHDOWN") {
12658
+ throw new Error("FULL_SCAN_EXACT invariant: capability");
12659
+ }
12660
+ if (input.select.where === null || plan.prefilterPlan.prefilterWhere !== input.select.where) {
12661
+ throw new Error("FULL_SCAN_EXACT invariant: whole WHERE identity");
12662
+ }
12663
+ if (plan.prefilterPlan.residualWhere !== null) {
12664
+ throw new Error("FULL_SCAN_EXACT invariant: residualWhere");
12665
+ }
12666
+ if (plan.prefilterPlan.capability !== "EXACT_PUSHDOWN") {
12667
+ throw new Error("FULL_SCAN_EXACT invariant: transport capability");
12668
+ }
12669
+ if (!serializedMultisetContains(plan.serializedWholeWhere, occurrences)) {
12670
+ throw new Error("FULL_SCAN_EXACT invariant: relative occurrence serialization");
12671
+ }
12672
+ }
12673
+ function relativeDateFunctionOccurrencesInWhere(where) {
12674
+ const names = [];
12675
+ const visit = (node) => {
12676
+ if (Array.isArray(node)) {
12677
+ node.forEach(visit);
12678
+ return;
12679
+ }
12680
+ if (node === null || typeof node !== "object") return;
12681
+ const value = node;
12682
+ if (value["type"] === "SELECT") return;
12683
+ if (value["type"] === "KINTONE_FUNC" && typeof value["name"] === "string" && isRelativeDateFunctionName(value["name"])) {
12684
+ names.push(value["name"]);
12685
+ return;
12686
+ }
12687
+ Object.values(value).forEach(visit);
12688
+ };
12689
+ visit(where);
12690
+ return names;
12691
+ }
12692
+ function collectExactRelativeLeaves(where) {
12693
+ const leaves = [];
12694
+ const visit = (node) => {
12695
+ switch (node.type) {
12696
+ case "BINARY":
12697
+ if (node.right.type === "KINTONE_FUNC" && isRelativeDateFunctionName(node.right.name)) {
12698
+ leaves.push(node);
12699
+ }
12700
+ return;
12701
+ case "LOGICAL":
12702
+ visit(node.left);
12703
+ visit(node.right);
12704
+ return;
12705
+ case "NOT":
12706
+ case "GROUP":
12707
+ visit(node.expr);
12708
+ return;
12709
+ case "EXISTS":
12710
+ case "NULL_CHECK":
12711
+ case "BOOLEAN":
12712
+ return;
12713
+ }
12714
+ };
12715
+ visit(where);
12716
+ return leaves;
12717
+ }
12718
+ function sameOccurrenceList(actual, expected) {
12719
+ return actual.length === expected.length && actual.every((name, index) => name === expected[index]);
12720
+ }
12721
+ function serializedMultisetContains(query, expectedNames) {
12722
+ const expected = /* @__PURE__ */ new Map();
12723
+ for (const name of expectedNames) {
12724
+ if (!isRelativeDateFunctionName(name)) return false;
12725
+ expected.set(name, (expected.get(name) ?? 0) + 1);
12726
+ }
12727
+ for (const [name, count] of expected) {
12728
+ const matches = query.match(new RegExp(`\\b${name}\\s*\\(`, "g"));
12729
+ if ((matches?.length ?? 0) < count) return false;
12730
+ }
12731
+ return true;
12732
+ }
12733
+
12607
12734
  // src/core/optimization/relativeDatePushdownGuard.ts
12608
12735
  function relativeDateFunctionNamesInNode(node, stopAtNestedSelect) {
12609
12736
  const names = [];
@@ -12649,7 +12776,7 @@ function nestedSelects(node, root) {
12649
12776
  visit(node);
12650
12777
  return found;
12651
12778
  }
12652
- function collectSelect(select, path, candidates, forceForbidden, allowPhase2 = true) {
12779
+ function collectSelect(select, path, candidates, forceForbidden, allowPhase2 = true, allowFullScanExact = true) {
12653
12780
  const functionNames = relativeDateFunctionNamesInWhere(select.where);
12654
12781
  if (functionNames.length > 0) {
12655
12782
  candidates.push({
@@ -12658,7 +12785,9 @@ function collectSelect(select, path, candidates, forceForbidden, allowPhase2 = t
12658
12785
  where: select.where,
12659
12786
  functionNames,
12660
12787
  path,
12661
- allowPhase2Prefilter: allowPhase2
12788
+ allowPhase2Prefilter: allowPhase2,
12789
+ allowFullScanExact,
12790
+ relativeFunctionOccurrences: relativeDateFunctionOccurrencesInWhere(select.where)
12662
12791
  });
12663
12792
  }
12664
12793
  nestedSelects(select, select).forEach(
@@ -12667,31 +12796,35 @@ function collectSelect(select, path, candidates, forceForbidden, allowPhase2 = t
12667
12796
  `${path}.select-source[${index}]`,
12668
12797
  candidates,
12669
12798
  forceForbidden,
12670
- allowPhase2
12799
+ allowPhase2,
12800
+ allowFullScanExact
12671
12801
  )
12672
12802
  );
12673
12803
  }
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);
12804
+ function collectUnion(union, path, candidates, forceForbidden, allowFullScanExact = true) {
12805
+ if (union.left.type === "UNION") {
12806
+ collectUnion(union.left, `${path}.left`, candidates, forceForbidden, allowFullScanExact);
12807
+ } else {
12808
+ collectSelect(union.left, `${path}.left`, candidates, forceForbidden, true, allowFullScanExact);
12809
+ }
12810
+ collectSelect(union.right, `${path}.right`, candidates, forceForbidden, true, allowFullScanExact);
12678
12811
  }
12679
12812
  function collectWith(statement, path, candidates, inheritedForbidden) {
12680
12813
  if (!inheritedForbidden && canInlineSingleCte(statement)) {
12681
- collectSelect(buildInlinedQuery(statement), `${path}.inlined`, candidates, false);
12814
+ collectSelect(buildInlinedQuery(statement), `${path}.inlined`, candidates, false, true, false);
12682
12815
  return;
12683
12816
  }
12684
12817
  statement.ctes.forEach((cte, index) => {
12685
12818
  if (cte.query.type === "SELECT") {
12686
- collectSelect(cte.query, `${path}.cte[${index}]`, candidates, true);
12819
+ collectSelect(cte.query, `${path}.cte[${index}]`, candidates, true, true, false);
12687
12820
  } else if (cte.query.type === "UNION") {
12688
- collectUnion(cte.query, `${path}.cte[${index}]`, candidates, true);
12821
+ collectUnion(cte.query, `${path}.cte[${index}]`, candidates, true, false);
12689
12822
  }
12690
12823
  });
12691
12824
  if (statement.query.type === "SELECT") {
12692
- collectSelect(statement.query, `${path}.main`, candidates, true);
12825
+ collectSelect(statement.query, `${path}.main`, candidates, true, true, false);
12693
12826
  } else {
12694
- collectUnion(statement.query, `${path}.main`, candidates, true);
12827
+ collectUnion(statement.query, `${path}.main`, candidates, true, false);
12695
12828
  }
12696
12829
  }
12697
12830
  function collectStatement(statement, path, candidates, forceForbidden = false) {
@@ -12707,8 +12840,8 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12707
12840
  return;
12708
12841
  case "CREATE_TEMP_TABLE":
12709
12842
  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);
12843
+ else if (statement.query.type === "UNION") collectUnion(statement.query, `${path}.query`, candidates, true, false);
12844
+ else collectSelect(statement.query, `${path}.query`, candidates, true, true, false);
12712
12845
  return;
12713
12846
  case "EXPLAIN":
12714
12847
  collectStatement(statement.query, `${path}.query`, candidates, forceForbidden);
@@ -12722,11 +12855,13 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12722
12855
  source: statement,
12723
12856
  where: statement.where,
12724
12857
  functionNames,
12725
- path
12858
+ path,
12859
+ allowFullScanExact: false,
12860
+ relativeFunctionOccurrences: relativeDateFunctionOccurrencesInWhere(statement.where)
12726
12861
  });
12727
12862
  }
12728
12863
  nestedSelects(statement, statement).forEach(
12729
- (select, index) => collectSelect(select, `${path}.select-source[${index}]`, candidates, true)
12864
+ (select, index) => collectSelect(select, `${path}.select-source[${index}]`, candidates, true, true, false)
12730
12865
  );
12731
12866
  return;
12732
12867
  }
@@ -12740,7 +12875,9 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12740
12875
  source: statement,
12741
12876
  where: statement.where,
12742
12877
  functionNames,
12743
- path
12878
+ path,
12879
+ allowFullScanExact: false,
12880
+ relativeFunctionOccurrences: relativeDateFunctionOccurrencesInWhere(statement.where)
12744
12881
  });
12745
12882
  }
12746
12883
  if (statement.type === "UPDATE" && statement.applyBlocks?.length) {
@@ -12751,7 +12888,9 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12751
12888
  source: statement,
12752
12889
  where: null,
12753
12890
  functionNames: applyFunctions,
12754
- path: `${path}.apply`
12891
+ path: `${path}.apply`,
12892
+ allowFullScanExact: false,
12893
+ relativeFunctionOccurrences: []
12755
12894
  });
12756
12895
  }
12757
12896
  }
@@ -12761,6 +12900,7 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12761
12900
  `${path}.select-source[${index}]`,
12762
12901
  candidates,
12763
12902
  forceForbidden,
12903
+ false,
12764
12904
  false
12765
12905
  )
12766
12906
  );
@@ -12773,6 +12913,7 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12773
12913
  `${path}.select-source[${index}]`,
12774
12914
  candidates,
12775
12915
  forceForbidden,
12916
+ false,
12776
12917
  false
12777
12918
  )
12778
12919
  );
@@ -12851,6 +12992,21 @@ async function buildRelativeDatePushdownPlan(statement, resolver) {
12851
12992
  allowed2 = true;
12852
12993
  }
12853
12994
  }
12995
+ let fullScanExactPlan;
12996
+ if (!allowed2 && capability2.capability === "EXACT_PUSHDOWN") {
12997
+ fullScanExactPlan = buildRelativeDateFullScanExactPlan({
12998
+ select,
12999
+ selectMode,
13000
+ capability: capability2,
13001
+ context: { allowFullScanExact: candidate.allowFullScanExact },
13002
+ serializedWholeWhere: restQuery2 || null,
13003
+ relativeFunctionNames: candidate.relativeFunctionOccurrences
13004
+ }) ?? void 0;
13005
+ if (fullScanExactPlan) {
13006
+ prefilterPlan = fullScanExactPlan.prefilterPlan;
13007
+ allowed2 = true;
13008
+ }
13009
+ }
12854
13010
  const node2 = {
12855
13011
  kind: candidate.kind,
12856
13012
  source: candidate.source,
@@ -12860,6 +13016,7 @@ async function buildRelativeDatePushdownPlan(statement, resolver) {
12860
13016
  capability: capability2,
12861
13017
  restQuery: restQuery2,
12862
13018
  ...prefilterPlan ? { prefilterPlan, phase2PrefilterEligible } : {},
13019
+ ...fullScanExactPlan ? { fullScanExactPlan, allowForm: fullScanExactPlan.allowForm } : {},
12863
13020
  clientWhereEvaluation: !allowed2,
12864
13021
  allowed: allowed2
12865
13022
  };
@@ -12991,7 +13148,7 @@ function decomposeRelativeDatePrefilter(stmt, resolveField2, testSeam = {}) {
12991
13148
  return reject(["PREFILTER_SERIALIZATION_FAILED"]);
12992
13149
  }
12993
13150
  const expectedNames = confirmedLeaves.map((leaf) => relativeNameOf(leaf));
12994
- if (!containsFunctions(prefilterQuery, [...new Set(expectedNames)]) || !serializedMultisetContains(prefilterQuery, expectedNames)) {
13151
+ if (!containsFunctions(prefilterQuery, [...new Set(expectedNames)]) || !serializedMultisetContains2(prefilterQuery, expectedNames)) {
12995
13152
  return reject(["PREFILTER_FUNCTION_MISSING"]);
12996
13153
  }
12997
13154
  const residualWhere = testSeam.rewriteResidual ? testSeam.rewriteResidual(stmt.where, adoptedLeaves) : replaceAdoptedLeaves(stmt.where, adoptedLeaves);
@@ -13213,7 +13370,7 @@ function collectFieldMetadata(where, resolveField2) {
13213
13370
  }
13214
13371
  return { fieldTypes, fieldOptions };
13215
13372
  }
13216
- function serializedMultisetContains(query, expectedNames) {
13373
+ function serializedMultisetContains2(query, expectedNames) {
13217
13374
  const expected = /* @__PURE__ */ new Map();
13218
13375
  for (const name of expectedNames) {
13219
13376
  if (!RELATIVE_DATE_FUNCTION_NAMES.has(name)) return false;
@@ -15436,7 +15593,9 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
15436
15593
  if (whereCapability.capability === "UNSUPPORTED") {
15437
15594
  throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(whereCapability)}).`);
15438
15595
  }
15596
+ const staticMode = resolveSelectMode(stmt);
15439
15597
  let prefilterPlan;
15598
+ let fullScanExactPlan;
15440
15599
  if (whereCapability.capability === "SUPERSET_PREFILTER") {
15441
15600
  const resolver = await buildWhereFieldSemanticsResolver(stmt, client, cacheContext, cteCache);
15442
15601
  const decomposition = decomposeRelativeDatePrefilter(stmt, resolver);
@@ -15444,7 +15603,23 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
15444
15603
  prefilterPlan = decomposition.plan;
15445
15604
  }
15446
15605
  }
15447
- const staticMode = resolveSelectMode(stmt);
15606
+ if (prefilterPlan === void 0 && whereCapability.capability === "EXACT_PUSHDOWN" && stmt.where !== null) {
15607
+ let serializedWholeWhere = null;
15608
+ try {
15609
+ serializedWholeWhere = whereToKintone(stmt.where);
15610
+ } catch {
15611
+ serializedWholeWhere = null;
15612
+ }
15613
+ fullScanExactPlan = buildRelativeDateFullScanExactPlan({
15614
+ select: stmt,
15615
+ selectMode: staticMode,
15616
+ capability: whereCapability,
15617
+ context: { allowFullScanExact: true },
15618
+ serializedWholeWhere,
15619
+ relativeFunctionNames: relativeDateFunctionOccurrencesInWhere(stmt.where)
15620
+ }) ?? void 0;
15621
+ if (fullScanExactPlan) prefilterPlan = fullScanExactPlan.prefilterPlan;
15622
+ }
15448
15623
  const mode = whereCapability.capability === "EXACT_PUSHDOWN" ? staticMode : "FULL_SCAN";
15449
15624
  const orderMeta = await buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
15450
15625
  const orderPlan = hasCanonicalOrder(stmt) ? (stmt.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
@@ -15462,18 +15637,31 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
15462
15637
  client,
15463
15638
  cacheContext
15464
15639
  );
15465
- const completePolicy = buildCompleteInputPolicy(stmt, options, orderPlan);
15640
+ const completePolicy = buildCompleteInputPolicy(
15641
+ stmt,
15642
+ options,
15643
+ orderPlan
15644
+ );
15645
+ const failClosedForB72Local = fullScanExactPlan !== void 0 && (staticMode === "FULL_SCAN" || orderPlan?.kind === "CANONICAL_LOCAL");
15646
+ const executionClient = failClosedForB72Local ? wrapClientWithSearchAbort(client, { aborted: false }, true) : client;
15466
15647
  try {
15467
15648
  if (mode === "SIMPLE") {
15468
- result = await executeSimpleSelect(stmt, client, completePolicy.effectiveOptions, cacheContext, orderPlan, orderMeta);
15649
+ result = await executeSimpleSelect(
15650
+ stmt,
15651
+ executionClient,
15652
+ completePolicy.effectiveOptions,
15653
+ cacheContext,
15654
+ orderPlan,
15655
+ orderMeta
15656
+ );
15469
15657
  } else {
15470
15658
  result = await executeFullScanSelect(
15471
15659
  stmt,
15472
- client,
15660
+ executionClient,
15473
15661
  completePolicy.effectiveOptions,
15474
15662
  cacheContext,
15475
15663
  cteCache,
15476
- whereCapability.capability === "EXACT_PUSHDOWN",
15664
+ whereCapability.capability === "EXACT_PUSHDOWN" && prefilterPlan === void 0,
15477
15665
  orderMeta,
15478
15666
  prefilterPlan,
15479
15667
  plainGroupByPlan
@@ -20558,6 +20746,32 @@ function relativeDateExplainLines(plan) {
20558
20746
  }
20559
20747
  const lines = [];
20560
20748
  for (const node of plan.nodes) {
20749
+ const fullScanExactPlan = node.fullScanExactPlan;
20750
+ if (node.allowed && node.allowForm === "FULL_SCAN_EXACT" && fullScanExactPlan) {
20751
+ for (const leaf of fullScanExactPlan.prefilterPlan.exactRelativeLeaves) {
20752
+ const functionName = leaf.right.type === "KINTONE_FUNC" ? leaf.right.name : "(unknown)";
20753
+ const field = leaf.left.type === "FIELD" ? leaf.left.field : void 0;
20754
+ const operator = relativeReasonOperator(leaf.op);
20755
+ const detail = node.capability?.reasons.find(
20756
+ (reason) => reason.functionName === functionName && (field === void 0 || reason.field === field) && reason.operator === operator
20757
+ );
20758
+ lines.push(
20759
+ ` relative date function: ${functionName}`,
20760
+ " relative date evaluation: kintone server whole-WHERE exact",
20761
+ ` field: ${detail?.field ?? field ?? "(unknown)"} (${detail?.fieldType ?? "unknown"})`,
20762
+ ` operator: ${detail?.operator ?? operator}`
20763
+ );
20764
+ }
20765
+ const wholeWhereQuery = fullScanExactPlan.serializedWholeWhere;
20766
+ lines.push(
20767
+ " where capability: EXACT_PUSHDOWN",
20768
+ ` server predicate: ${wholeWhereQuery}`,
20769
+ " client residual: (none)",
20770
+ " relative date client evaluations: 0",
20771
+ ` kintone query: ${wholeWhereQuery}`
20772
+ );
20773
+ continue;
20774
+ }
20561
20775
  const prefilterPlan = node.prefilterPlan;
20562
20776
  if (node.allowed && prefilterPlan?.prefilterWhere && prefilterPlan.residualWhere) {
20563
20777
  for (const leaf of prefilterPlan.exactRelativeLeaves) {