@rex0220/kintone-sql-tools 3.20.0 → 3.22.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
@@ -11929,7 +11929,8 @@ function runFullScan(input) {
11929
11929
  });
11930
11930
  knownColumns = mergeKnownColumns(knownColumns, rightColumns, rows);
11931
11931
  }
11932
- rows = applyFilter(rows, stmt.where, fieldTypeResolver, appliedKlikes, fieldSemanticsResolver);
11932
+ const filterWhere = input.residualWhere !== void 0 ? input.residualWhere : stmt.where;
11933
+ rows = applyFilter(rows, filterWhere, fieldTypeResolver, appliedKlikes, fieldSemanticsResolver);
11933
11934
  const grouping = normalizeGroupingSpec(stmt);
11934
11935
  if (grouping.type === "GROUPING_SETS") {
11935
11936
  if (!resolvedGroupingSpec) {
@@ -12452,7 +12453,7 @@ function nestedSelects(node, root) {
12452
12453
  visit(node);
12453
12454
  return found;
12454
12455
  }
12455
- function collectSelect(select, path, candidates, forceForbidden) {
12456
+ function collectSelect(select, path, candidates, forceForbidden, allowPhase2 = true) {
12456
12457
  const functionNames = relativeDateFunctionNamesInWhere(select.where);
12457
12458
  if (functionNames.length > 0) {
12458
12459
  candidates.push({
@@ -12460,11 +12461,18 @@ function collectSelect(select, path, candidates, forceForbidden) {
12460
12461
  source: select,
12461
12462
  where: select.where,
12462
12463
  functionNames,
12463
- path
12464
+ path,
12465
+ allowPhase2Prefilter: allowPhase2
12464
12466
  });
12465
12467
  }
12466
12468
  nestedSelects(select, select).forEach(
12467
- (nested, index) => collectSelect(nested, `${path}.select-source[${index}]`, candidates, forceForbidden)
12469
+ (nested, index) => collectSelect(
12470
+ nested,
12471
+ `${path}.select-source[${index}]`,
12472
+ candidates,
12473
+ forceForbidden,
12474
+ allowPhase2
12475
+ )
12468
12476
  );
12469
12477
  }
12470
12478
  function collectUnion(union, path, candidates, forceForbidden) {
@@ -12552,19 +12560,34 @@ function collectStatement(statement, path, candidates, forceForbidden = false) {
12552
12560
  }
12553
12561
  }
12554
12562
  nestedSelects(statement, statement).forEach(
12555
- (select, index) => collectSelect(select, `${path}.select-source[${index}]`, candidates, forceForbidden)
12563
+ (select, index) => collectSelect(
12564
+ select,
12565
+ `${path}.select-source[${index}]`,
12566
+ candidates,
12567
+ forceForbidden,
12568
+ false
12569
+ )
12556
12570
  );
12557
12571
  return;
12558
12572
  }
12559
12573
  default:
12560
12574
  nestedSelects(statement, statement).forEach(
12561
- (select, index) => collectSelect(select, `${path}.select-source[${index}]`, candidates, forceForbidden)
12575
+ (select, index) => collectSelect(
12576
+ select,
12577
+ `${path}.select-source[${index}]`,
12578
+ candidates,
12579
+ forceForbidden,
12580
+ false
12581
+ )
12562
12582
  );
12563
12583
  }
12564
12584
  }
12565
12585
  function serializationContainsFunctions(query, names) {
12566
12586
  return names.every((name) => new RegExp(`\\b${name}\\s*\\(`).test(query));
12567
12587
  }
12588
+ function allowRelativeDatePrefilterPlan(select, decomposition) {
12589
+ return decomposition.eligible === true && resolveSelectMode(select) === "FULL_SCAN" && select.orderMode !== "KINTONE_NATIVE" && select.from.cteName === null && !select.from.subtableCode && select.joins.length === 0;
12590
+ }
12568
12591
  function rejectedNode(candidate) {
12569
12592
  return {
12570
12593
  kind: candidate.kind,
@@ -12621,7 +12644,17 @@ async function buildRelativeDatePushdownPlan(statement, resolver) {
12621
12644
  } catch {
12622
12645
  restQuery2 = "";
12623
12646
  }
12624
- const allowed2 = physicalTopLevel && selectMode === "SIMPLE" && (select.orderBy.length === 0 || select.orderMode === "KINTONE_NATIVE") && capability2.capability === "EXACT_PUSHDOWN" && serializationContainsFunctions(restQuery2, candidate.functionNames);
12647
+ let allowed2 = physicalTopLevel && selectMode === "SIMPLE" && (select.orderBy.length === 0 || select.orderMode === "KINTONE_NATIVE") && capability2.capability === "EXACT_PUSHDOWN" && serializationContainsFunctions(restQuery2, candidate.functionNames);
12648
+ let prefilterPlan;
12649
+ let phase2PrefilterEligible;
12650
+ if (!allowed2 && candidate.allowPhase2Prefilter !== false && capability2.capability === "SUPERSET_PREFILTER" && resolver.prefilterDecomposition) {
12651
+ const decomposition = await resolver.prefilterDecomposition(select);
12652
+ if (decomposition?.eligible === true && allowRelativeDatePrefilterPlan(select, decomposition)) {
12653
+ prefilterPlan = decomposition.plan;
12654
+ phase2PrefilterEligible = true;
12655
+ allowed2 = true;
12656
+ }
12657
+ }
12625
12658
  const node2 = {
12626
12659
  kind: candidate.kind,
12627
12660
  source: candidate.source,
@@ -12630,6 +12663,7 @@ async function buildRelativeDatePushdownPlan(statement, resolver) {
12630
12663
  selectMode,
12631
12664
  capability: capability2,
12632
12665
  restQuery: restQuery2,
12666
+ ...prefilterPlan ? { prefilterPlan, phase2PrefilterEligible } : {},
12633
12667
  clientWhereEvaluation: !allowed2,
12634
12668
  allowed: allowed2
12635
12669
  };
@@ -12690,6 +12724,312 @@ function assertRelativeDatePushdownPlan(plan) {
12690
12724
  }
12691
12725
  }
12692
12726
 
12727
+ // src/core/optimization/relativeDatePrefilterPlan.ts
12728
+ function decomposeRelativeDatePrefilter(stmt, resolveField2, testSeam = {}) {
12729
+ const capability = classifyWhereCapability(stmt.where, resolveField2);
12730
+ const reject = (reasonCodes, disposition = "INELIGIBLE") => ({
12731
+ eligible: false,
12732
+ disposition,
12733
+ reasonCodes,
12734
+ capability: capability.capability,
12735
+ reasons: capability.reasons
12736
+ });
12737
+ if (stmt.where === null) return reject(["NO_WHERE"]);
12738
+ if (stmt.from.subtableCode) return reject(["SUBTABLE_UNSUPPORTED"]);
12739
+ if (stmt.joins.length > 0) return reject(["JOIN_UNSUPPORTED"]);
12740
+ if (stmt.from.cteName !== null || stmt.from.appId <= 0) {
12741
+ return reject(["NOT_DIRECT_PHYSICAL_APP"]);
12742
+ }
12743
+ const occurrences = collectRelativeOccurrences(stmt.where);
12744
+ if (occurrences.length === 0) return reject(["NO_RELATIVE_DATE"]);
12745
+ const spine = collectRelativeLeavesOnAndSpine(stmt.where, resolveField2);
12746
+ if (!spine.ok) return reject(spine.reasonCodes);
12747
+ if (!sameRelativeMultiset(occurrences, spine.leaves)) {
12748
+ return reject(["RELATIVE_DATE_LEAF_COUNT_MISMATCH"]);
12749
+ }
12750
+ if (capability.capability === "EXACT_PUSHDOWN") {
12751
+ return reject(["DEFER_TO_PHASE1"], "DEFER_PHASE1");
12752
+ }
12753
+ if (capability.capability !== "SUPERSET_PREFILTER") {
12754
+ return reject(["CAPABILITY_NOT_SUPERSET_PREFILTER"]);
12755
+ }
12756
+ const fieldMetadata = collectFieldMetadata(stmt.where, resolveField2);
12757
+ const safePlan = buildSingleTableKlikePushdownPlan(stmt.where, {
12758
+ tableAlias: stmt.from.alias ?? void 0,
12759
+ allowUnqualifiedFields: true,
12760
+ allowKlike: true,
12761
+ fieldTypes: fieldMetadata.fieldTypes,
12762
+ fieldOptions: fieldMetadata.fieldOptions
12763
+ });
12764
+ const serialize = testSeam.serialize ?? whereToKintone;
12765
+ const containsFunctions = testSeam.containsFunctions ?? serializationContainsFunctions;
12766
+ const confirmedLeaves = [];
12767
+ for (const leaf of spine.leaves) {
12768
+ const name = relativeNameOf(leaf);
12769
+ if (name === null) return reject(["RELATIVE_DATE_LEAF_COUNT_MISMATCH"]);
12770
+ let query;
12771
+ try {
12772
+ query = serialize(leaf);
12773
+ } catch {
12774
+ return reject(["PREFILTER_SERIALIZATION_FAILED"]);
12775
+ }
12776
+ if (!containsFunctions(query, [name])) {
12777
+ return reject(["PREFILTER_FUNCTION_MISSING"]);
12778
+ }
12779
+ confirmedLeaves.push(leaf);
12780
+ }
12781
+ const safeLeaves = collectBinaryIdentities(safePlan.condition);
12782
+ const adoptedLeaves = new Set(confirmedLeaves);
12783
+ const prefilterWhere = selectPrefilterInOriginalOrder(
12784
+ stmt.where,
12785
+ adoptedLeaves,
12786
+ safeLeaves
12787
+ );
12788
+ if (prefilterWhere === null) {
12789
+ return reject(["RELATIVE_DATE_LEAF_COUNT_MISMATCH"]);
12790
+ }
12791
+ let prefilterQuery;
12792
+ try {
12793
+ prefilterQuery = serialize(prefilterWhere);
12794
+ } catch {
12795
+ return reject(["PREFILTER_SERIALIZATION_FAILED"]);
12796
+ }
12797
+ const expectedNames = confirmedLeaves.map((leaf) => relativeNameOf(leaf));
12798
+ if (!containsFunctions(prefilterQuery, [...new Set(expectedNames)]) || !serializedMultisetContains(prefilterQuery, expectedNames)) {
12799
+ return reject(["PREFILTER_FUNCTION_MISSING"]);
12800
+ }
12801
+ const residualWhere = testSeam.rewriteResidual ? testSeam.rewriteResidual(stmt.where, adoptedLeaves) : replaceAdoptedLeaves(stmt.where, adoptedLeaves);
12802
+ if (residualWhere !== null && collectRelativeOccurrences(residualWhere).length > 0) {
12803
+ return reject(["RESIDUAL_RELATIVE_DATE_REMAINED"]);
12804
+ }
12805
+ if (residualWhere === null) {
12806
+ return reject(["DEFER_TO_PHASE1"], "DEFER_PHASE1");
12807
+ }
12808
+ const relativeFunctionNames = /* @__PURE__ */ new Set();
12809
+ for (const name of expectedNames) relativeFunctionNames.add(name);
12810
+ return {
12811
+ eligible: true,
12812
+ plan: {
12813
+ prefilterWhere,
12814
+ residualWhere,
12815
+ exactRelativeLeaves: confirmedLeaves,
12816
+ relativeFunctionNames,
12817
+ appliedKlikes: safePlan.appliedKlikes,
12818
+ capability: capability.capability,
12819
+ reasons: capability.reasons
12820
+ }
12821
+ };
12822
+ }
12823
+ function collectRelativeLeavesOnAndSpine(where, resolveField2) {
12824
+ const leaves = [];
12825
+ let failure = null;
12826
+ const visit = (node) => {
12827
+ if (failure !== null) return;
12828
+ switch (node.type) {
12829
+ case "GROUP":
12830
+ visit(node.expr);
12831
+ return;
12832
+ case "LOGICAL":
12833
+ if (node.op === "AND") {
12834
+ visit(node.left);
12835
+ visit(node.right);
12836
+ return;
12837
+ }
12838
+ if (collectRelativeOccurrences(node).length > 0) {
12839
+ failure = "RELATIVE_DATE_CONTEXT_UNSUPPORTED";
12840
+ }
12841
+ return;
12842
+ case "NOT":
12843
+ if (collectRelativeOccurrences(node).length > 0) {
12844
+ failure = "RELATIVE_DATE_CONTEXT_UNSUPPORTED";
12845
+ }
12846
+ return;
12847
+ case "BINARY": {
12848
+ const name = relativeNameOf(node);
12849
+ if (name === null) return;
12850
+ const result = classifyRelativeDateBinary(
12851
+ node.op,
12852
+ node.left,
12853
+ node.right,
12854
+ resolveField2
12855
+ );
12856
+ if (result.capability !== "EXACT_PUSHDOWN") {
12857
+ failure = "RELATIVE_DATE_LEAF_NOT_EXACT";
12858
+ return;
12859
+ }
12860
+ leaves.push(node);
12861
+ return;
12862
+ }
12863
+ case "EXISTS":
12864
+ if (collectRelativeOccurrences(node).length > 0) {
12865
+ failure = "RELATIVE_DATE_CONTEXT_UNSUPPORTED";
12866
+ }
12867
+ return;
12868
+ case "NULL_CHECK":
12869
+ case "BOOLEAN":
12870
+ return;
12871
+ }
12872
+ };
12873
+ visit(where);
12874
+ return failure === null ? { ok: true, leaves } : { ok: false, reasonCodes: [failure] };
12875
+ }
12876
+ function relativeNameOf(leaf) {
12877
+ return leaf.right.type === "KINTONE_FUNC" && isRelativeDateFunctionName(leaf.right.name) ? leaf.right.name : null;
12878
+ }
12879
+ function collectRelativeOccurrences(where) {
12880
+ const found = [];
12881
+ const visitWhere = (node) => {
12882
+ if (node.type === "BINARY" && relativeNameOf(node) !== null) {
12883
+ found.push(node);
12884
+ return;
12885
+ }
12886
+ switch (node.type) {
12887
+ case "LOGICAL":
12888
+ visitWhere(node.left);
12889
+ visitWhere(node.right);
12890
+ return;
12891
+ case "NOT":
12892
+ case "GROUP":
12893
+ visitWhere(node.expr);
12894
+ return;
12895
+ case "EXISTS":
12896
+ if (node.query.where !== null) visitWhere(node.query.where);
12897
+ return;
12898
+ case "BINARY":
12899
+ case "NULL_CHECK":
12900
+ case "BOOLEAN":
12901
+ return;
12902
+ }
12903
+ };
12904
+ visitWhere(where);
12905
+ return found;
12906
+ }
12907
+ function sameRelativeMultiset(occurrences, candidates) {
12908
+ if (occurrences.length !== candidates.length) return false;
12909
+ const remaining = new Set(candidates);
12910
+ for (const occurrence of occurrences) {
12911
+ if (!remaining.delete(occurrence)) return false;
12912
+ }
12913
+ return remaining.size === 0;
12914
+ }
12915
+ function collectBinaryIdentities(where) {
12916
+ const found = /* @__PURE__ */ new Set();
12917
+ const visit = (node) => {
12918
+ switch (node.type) {
12919
+ case "BINARY":
12920
+ found.add(node);
12921
+ return;
12922
+ case "LOGICAL":
12923
+ visit(node.left);
12924
+ visit(node.right);
12925
+ return;
12926
+ case "NOT":
12927
+ case "GROUP":
12928
+ visit(node.expr);
12929
+ return;
12930
+ case "NULL_CHECK":
12931
+ case "EXISTS":
12932
+ case "BOOLEAN":
12933
+ return;
12934
+ }
12935
+ };
12936
+ if (where !== null) visit(where);
12937
+ return found;
12938
+ }
12939
+ function selectPrefilterInOriginalOrder(where, relativeLeaves, safeLeaves) {
12940
+ switch (where.type) {
12941
+ case "BINARY":
12942
+ return relativeLeaves.has(where) || safeLeaves.has(where) ? where : null;
12943
+ case "LOGICAL":
12944
+ if (where.op !== "AND") return null;
12945
+ {
12946
+ const left = selectPrefilterInOriginalOrder(where.left, relativeLeaves, safeLeaves);
12947
+ const right = selectPrefilterInOriginalOrder(where.right, relativeLeaves, safeLeaves);
12948
+ if (left !== null && right !== null) return { ...where, left, right };
12949
+ return left ?? right;
12950
+ }
12951
+ case "GROUP": {
12952
+ const expr = selectPrefilterInOriginalOrder(where.expr, relativeLeaves, safeLeaves);
12953
+ return expr === null ? null : { ...where, expr };
12954
+ }
12955
+ case "NULL_CHECK":
12956
+ case "NOT":
12957
+ case "EXISTS":
12958
+ case "BOOLEAN":
12959
+ return null;
12960
+ }
12961
+ }
12962
+ var TRUE_PREDICATE = { type: "BOOLEAN", value: true };
12963
+ function replaceAdoptedLeaves(where, adoptedLeaves) {
12964
+ if (where.type === "BINARY" && adoptedLeaves.has(where)) return TRUE_PREDICATE;
12965
+ switch (where.type) {
12966
+ case "LOGICAL": {
12967
+ if (where.op !== "AND") return where;
12968
+ const left = replaceAdoptedLeaves(where.left, adoptedLeaves) ?? TRUE_PREDICATE;
12969
+ const right = replaceAdoptedLeaves(where.right, adoptedLeaves) ?? TRUE_PREDICATE;
12970
+ if (isTrue(left)) return isTrue(right) ? null : right;
12971
+ if (isTrue(right)) return left;
12972
+ if (left === where.left && right === where.right) return where;
12973
+ return { ...where, left, right };
12974
+ }
12975
+ case "GROUP": {
12976
+ const expr = replaceAdoptedLeaves(where.expr, adoptedLeaves) ?? TRUE_PREDICATE;
12977
+ if (isTrue(expr)) return TRUE_PREDICATE;
12978
+ return expr === where.expr ? where : { ...where, expr };
12979
+ }
12980
+ case "BINARY":
12981
+ case "NULL_CHECK":
12982
+ case "NOT":
12983
+ case "EXISTS":
12984
+ case "BOOLEAN":
12985
+ return where;
12986
+ }
12987
+ }
12988
+ function isTrue(where) {
12989
+ return where.type === "BOOLEAN" && where.value;
12990
+ }
12991
+ function collectFieldMetadata(where, resolveField2) {
12992
+ const fields = /* @__PURE__ */ new Map();
12993
+ const visitValue = (value) => {
12994
+ if (value === null || typeof value !== "object") return;
12995
+ if (Array.isArray(value)) {
12996
+ value.forEach(visitValue);
12997
+ return;
12998
+ }
12999
+ const record = value;
13000
+ if (record["type"] === "SELECT") return;
13001
+ if (record["type"] === "FIELD" && typeof record["field"] === "string" && typeof record["tableAlias"] !== "undefined") {
13002
+ fields.set(record["field"], value);
13003
+ return;
13004
+ }
13005
+ Object.values(record).forEach(visitValue);
13006
+ };
13007
+ visitValue(where);
13008
+ const fieldTypes = /* @__PURE__ */ new Map();
13009
+ const fieldOptions = /* @__PURE__ */ new Map();
13010
+ for (const [fieldCode, field] of fields) {
13011
+ const semantics = resolveField2(field);
13012
+ if (!semantics) continue;
13013
+ fieldTypes.set(fieldCode, semantics.fieldType);
13014
+ if (semantics.optionOrder) {
13015
+ fieldOptions.set(fieldCode, new Set(semantics.optionOrder.keys()));
13016
+ }
13017
+ }
13018
+ return { fieldTypes, fieldOptions };
13019
+ }
13020
+ function serializedMultisetContains(query, expectedNames) {
13021
+ const expected = /* @__PURE__ */ new Map();
13022
+ for (const name of expectedNames) {
13023
+ if (!RELATIVE_DATE_FUNCTION_NAMES.has(name)) return false;
13024
+ expected.set(name, (expected.get(name) ?? 0) + 1);
13025
+ }
13026
+ for (const [name, count] of expected) {
13027
+ const matches = query.match(new RegExp(`\\b${name}\\s*\\(`, "g"));
13028
+ if ((matches?.length ?? 0) < count) return false;
13029
+ }
13030
+ return true;
13031
+ }
13032
+
12693
13033
  // src/import/sourceLoader.ts
12694
13034
  var IMPORT_MAX_BYTES = 10 * 1024 * 1024;
12695
13035
  var ImportSourceError = class extends Error {
@@ -13587,7 +13927,12 @@ async function execute(sql, client, options = {}) {
13587
13927
  cacheContext
13588
13928
  );
13589
13929
  metrics.elapsedMs = Date.now() - startedAt;
13590
- return { ...attachSearchAbortWarning(result, collector), metrics };
13930
+ const finalResult = { ...attachSearchAbortWarning(result, collector), metrics };
13931
+ if (result.type === "SELECT") {
13932
+ const columnMeta = materializedMetaBySelectResult.get(result);
13933
+ if (columnMeta) materializedMetaBySelectResult.set(finalResult, columnMeta);
13934
+ }
13935
+ return finalResult;
13591
13936
  }
13592
13937
  function createEmptyMetrics() {
13593
13938
  return {
@@ -13753,7 +14098,16 @@ async function assertRelativeDateExecutionPlan(stmt, client, cacheContext) {
13753
14098
  async function resolveRelativeDateExecutionPlan(stmt, client, cacheContext) {
13754
14099
  return buildRelativeDatePushdownPlan(stmt, {
13755
14100
  select: (select) => resolveSelectWhereCapability(select, client, cacheContext),
13756
- dml: (dml) => resolveDmlWhereCapability(dml, client, cacheContext)
14101
+ dml: (dml) => resolveDmlWhereCapability(dml, client, cacheContext),
14102
+ prefilterDecomposition: async (select) => {
14103
+ if (select.where === null) return null;
14104
+ const resolver = await buildWhereFieldSemanticsResolver(
14105
+ select,
14106
+ client,
14107
+ cacheContext
14108
+ );
14109
+ return decomposeRelativeDatePrefilter(select, resolver);
14110
+ }
13757
14111
  });
13758
14112
  }
13759
14113
  async function executeParsedStatement(stmt, client, options, cacheContext) {
@@ -13789,11 +14143,26 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
13789
14143
  case "VALIDATE":
13790
14144
  return executeExistingRecordValidation(stmt, client, options, cacheContext);
13791
14145
  case "SELECT":
13792
- return executeSelect(stmt, client, options, cacheContext);
14146
+ return executeSelect(
14147
+ stmt,
14148
+ client,
14149
+ options,
14150
+ cacheContext,
14151
+ void 0,
14152
+ options.captureColumnMeta === true,
14153
+ options.captureColumnMeta === true
14154
+ );
13793
14155
  case "UNION":
13794
- return executeUnion(stmt, client, options, cacheContext);
14156
+ return executeUnion(
14157
+ stmt,
14158
+ client,
14159
+ options,
14160
+ cacheContext,
14161
+ options.captureColumnMeta === true,
14162
+ options.captureColumnMeta === true
14163
+ );
13795
14164
  case "WITH":
13796
- return executeWith(stmt, client, options, cacheContext);
14165
+ return executeWith(stmt, client, options, cacheContext, void 0, options.captureColumnMeta === true);
13797
14166
  case "INSERT":
13798
14167
  return executeInsert(stmt, client, options, cacheContext);
13799
14168
  case "INSERT_SELECT":
@@ -14847,13 +15216,16 @@ async function assertDmlWhereCapability(stmt, client, cacheContext) {
14847
15216
  );
14848
15217
  }
14849
15218
  }
14850
- async function executeSelect(stmt, client, options, cacheContext, cteCache, captureColumnMeta = false) {
15219
+ async function executeSelect(stmt, client, options, cacheContext, cteCache, captureColumnMeta = false, forLibraryCapture = false) {
14851
15220
  let result;
14852
15221
  await validateSelectGroupingPlanning(stmt, client, cacheContext, cteCache);
14853
15222
  if (isNoFromSelect(stmt)) {
14854
15223
  result = executeNoFromSelect(stmt);
14855
15224
  if (captureColumnMeta) {
14856
- materializedMetaBySelectResult.set(result, await inferSelectColumnMeta(stmt, result.columns, client, cacheContext, cteCache));
15225
+ materializedMetaBySelectResult.set(
15226
+ result,
15227
+ await inferSelectColumnMeta(stmt, result.columns, client, cacheContext, cteCache, forLibraryCapture)
15228
+ );
14857
15229
  }
14858
15230
  return result;
14859
15231
  }
@@ -14862,6 +15234,14 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
14862
15234
  if (whereCapability.capability === "UNSUPPORTED") {
14863
15235
  throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(whereCapability)}).`);
14864
15236
  }
15237
+ let prefilterPlan;
15238
+ if (whereCapability.capability === "SUPERSET_PREFILTER") {
15239
+ const resolver = await buildWhereFieldSemanticsResolver(stmt, client, cacheContext, cteCache);
15240
+ const decomposition = decomposeRelativeDatePrefilter(stmt, resolver);
15241
+ if (decomposition.eligible && allowRelativeDatePrefilterPlan(stmt, decomposition)) {
15242
+ prefilterPlan = decomposition.plan;
15243
+ }
15244
+ }
14865
15245
  const staticMode = resolveSelectMode(stmt);
14866
15246
  const mode = whereCapability.capability === "EXACT_PUSHDOWN" ? staticMode : "FULL_SCAN";
14867
15247
  const orderMeta = await buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
@@ -14892,14 +15272,18 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
14892
15272
  cacheContext,
14893
15273
  cteCache,
14894
15274
  whereCapability.capability === "EXACT_PUSHDOWN",
14895
- orderMeta
15275
+ orderMeta,
15276
+ prefilterPlan
14896
15277
  );
14897
15278
  }
14898
15279
  } catch (error) {
14899
15280
  throwCompleteInputError(completePolicy, error);
14900
15281
  }
14901
15282
  if (captureColumnMeta) {
14902
- materializedMetaBySelectResult.set(result, await inferSelectColumnMeta(stmt, result.columns, client, cacheContext, cteCache));
15283
+ materializedMetaBySelectResult.set(
15284
+ result,
15285
+ await inferSelectColumnMeta(stmt, result.columns, client, cacheContext, cteCache, forLibraryCapture)
15286
+ );
14903
15287
  }
14904
15288
  return result;
14905
15289
  }
@@ -15667,7 +16051,7 @@ function selectNeedsSourceColumnMeta(stmt) {
15667
16051
  (column) => column.type === "FIELD" || column.type === "WILDCARD" || column.type === "PARENT_WILDCARD" || column.type === "CASE_COL" || column.type === "AGGREGATE" && (column.func === "MIN" || column.func === "MAX" || column.func === "MODE")
15668
16052
  );
15669
16053
  }
15670
- async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext, materializedTables) {
16054
+ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext, materializedTables, forLibraryCapture = false) {
15671
16055
  const physicalInfos = /* @__PURE__ */ new Map();
15672
16056
  if (selectNeedsSourceColumnMeta(stmt)) {
15673
16057
  await Promise.all(physicalSelectTables(stmt).map(async (table) => {
@@ -15677,6 +16061,7 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
15677
16061
  }));
15678
16062
  }
15679
16063
  const tables = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
16064
+ const canExposePublicSource = forLibraryCapture && materializedTables === void 0 && tables.every((table) => table.cteName === null);
15680
16065
  const resolveField2 = (ref) => {
15681
16066
  if (ref.tableAlias !== null) {
15682
16067
  if (ref.tableAlias === "_p" && stmt.from.subtableCode && stmt.from.cteName === null) {
@@ -15706,18 +16091,33 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
15706
16091
  });
15707
16092
  return matches.length === 1 ? matches[0] : void 0;
15708
16093
  };
16094
+ const resolvePublicSourceApp = (ref) => {
16095
+ if (!canExposePublicSource) return void 0;
16096
+ const matches = tables.filter((table) => {
16097
+ if (ref.tableAlias !== null && effectiveTableAlias(table) !== ref.tableAlias) return false;
16098
+ const fieldCode = fieldCodeForTypeLookup(table, ref.field);
16099
+ return physicalInfos.get(table.appId)?.has(fieldCode) === true || systemColumnMeta(ref.field) !== void 0;
16100
+ });
16101
+ return matches.length === 1 ? matches[0].appId : void 0;
16102
+ };
16103
+ const withPublicSource = (meta, ref) => {
16104
+ const publicSourceApp = resolvePublicSourceApp(ref);
16105
+ return meta && publicSourceApp !== void 0 ? { ...meta, publicSourceApp } : meta;
16106
+ };
15709
16107
  const inferred = /* @__PURE__ */ new Map();
15710
16108
  const hasWildcard = stmt.columns.some((column) => column.type === "WILDCARD" || column.type === "PARENT_WILDCARD");
15711
16109
  if (stmt.columns.length === 1 && (stmt.columns[0].type === "WILDCARD" || stmt.columns[0].type === "PARENT_WILDCARD")) {
15712
16110
  for (const output of outputColumns) {
15713
- const meta = resolveField2(aggregateFieldRef(output));
16111
+ const ref = aggregateFieldRef(output);
16112
+ const meta = withPublicSource(resolveField2(ref), ref);
15714
16113
  if (meta) inferred.set(output, meta);
15715
16114
  }
15716
16115
  return inferred;
15717
16116
  }
15718
16117
  if (hasWildcard) {
15719
16118
  for (const output of outputColumns) {
15720
- const meta = resolveField2(aggregateFieldRef(output));
16119
+ const ref = aggregateFieldRef(output);
16120
+ const meta = withPublicSource(resolveField2(ref), ref);
15721
16121
  if (meta) inferred.set(output, meta);
15722
16122
  }
15723
16123
  }
@@ -15729,7 +16129,8 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
15729
16129
  if (!output) return;
15730
16130
  let meta;
15731
16131
  if (column.type === "FIELD") {
15732
- meta = resolveField2(aggregateFieldRef(column.field));
16132
+ const ref = aggregateFieldRef(column.field);
16133
+ meta = withPublicSource(resolveField2(ref), ref);
15733
16134
  } else if (column.type === "AGGREGATE") {
15734
16135
  if (column.func === "GROUP_CONCAT") {
15735
16136
  meta = syntheticColumnMeta("string");
@@ -15773,8 +16174,15 @@ function mergeUnionColumnMeta(left, right) {
15773
16174
  const a = leftMeta?.get(column);
15774
16175
  const rightColumn = right.columns[index];
15775
16176
  const b = rightColumn === void 0 ? void 0 : rightMeta?.get(rightColumn);
15776
- if (a && b) merged.set(column, mergeExpressionColumnMeta([a, b]));
15777
- else if (a || b) merged.set(column, unknownStringColumnMeta());
16177
+ if (a && b) {
16178
+ const combined = mergeExpressionColumnMeta([a, b]);
16179
+ const publicSourceApp = a.publicSourceApp === b.publicSourceApp ? a.publicSourceApp : void 0;
16180
+ const { publicSourceApp: _discarded, ...withoutPublicSource } = combined;
16181
+ merged.set(
16182
+ column,
16183
+ publicSourceApp === void 0 ? withoutPublicSource : { ...withoutPublicSource, publicSourceApp }
16184
+ );
16185
+ } else if (a || b) merged.set(column, unknownStringColumnMeta());
15778
16186
  });
15779
16187
  return merged;
15780
16188
  }
@@ -15810,7 +16218,7 @@ function buildSelectFieldTypeResolvers(stmt, fieldTypesByApp) {
15810
16218
  };
15811
16219
  return { row, having };
15812
16220
  }
15813
- async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache, allowOriginalWherePushdown = true, preloadedOrderMeta) {
16221
+ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache, allowOriginalWherePushdown = true, preloadedOrderMeta, prefilterPlan) {
15814
16222
  const maxRecords = options.maxRecords ?? 1e4;
15815
16223
  const warnings = /* @__PURE__ */ new Set();
15816
16224
  const parallel = options.fetchParallel ?? 1;
@@ -15836,6 +16244,10 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
15836
16244
  validateKlikePushdownPlan(pushdownPlan);
15837
16245
  const mainPushDown = pushdownPlan.mainCondition;
15838
16246
  const tableConditions = pushdownPlan.joinConditions;
16247
+ if (prefilterPlan && allowOriginalWherePushdown) {
16248
+ throw new Error("internal error: relative-date prefilter must disable original WHERE pushdown.");
16249
+ }
16250
+ const mainFetchCondition = prefilterPlan ? prefilterPlan.prefilterWhere : mainPushDown;
15839
16251
  const constantFalse = isConstantFalseWhere(stmt.where);
15840
16252
  const mainFetch = constantFalse ? Promise.resolve([]) : fetchTableRecordsForFullScan(
15841
16253
  stmt,
@@ -15846,7 +16258,7 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
15846
16258
  true,
15847
16259
  options.onLimitReached ?? "error",
15848
16260
  warnings,
15849
- mainPushDown,
16261
+ mainFetchCondition,
15850
16262
  allowOriginalWherePushdown
15851
16263
  );
15852
16264
  const parallelJoins = [];
@@ -15927,15 +16339,16 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
15927
16339
  havingFieldTypeResolver: fieldTypeResolvers.having,
15928
16340
  havingFieldSemanticsResolver,
15929
16341
  aggregateSortKindResolver,
15930
- appliedKlikes: pushdownPlan.appliedKlikes,
16342
+ appliedKlikes: prefilterPlan?.appliedKlikes ?? pushdownPlan.appliedKlikes,
16343
+ ...prefilterPlan ? { residualWhere: prefilterPlan.residualWhere } : {},
15931
16344
  resolvedGroupingSpec: resolvedGroupingSpecs.get(stmt)
15932
16345
  });
15933
16346
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
15934
16347
  }
15935
- async function executeUnion(stmt, client, options, cacheContext) {
16348
+ async function executeUnion(stmt, client, options, cacheContext, captureColumnMeta = false, forLibraryCapture = false) {
15936
16349
  const [leftResult, rightResult] = await Promise.all([
15937
- stmt.left.type === "UNION" ? executeUnion(stmt.left, client, options, cacheContext) : executeSelect(stmt.left, client, options, cacheContext),
15938
- executeSelect(stmt.right, client, options, cacheContext)
16350
+ stmt.left.type === "UNION" ? executeUnion(stmt.left, client, options, cacheContext, captureColumnMeta, forLibraryCapture) : executeSelect(stmt.left, client, options, cacheContext, void 0, captureColumnMeta, forLibraryCapture),
16351
+ executeSelect(stmt.right, client, options, cacheContext, void 0, captureColumnMeta, forLibraryCapture)
15939
16352
  ]);
15940
16353
  const leftCols = leftResult.columns;
15941
16354
  const rightCols = rightResult.columns;
@@ -15948,7 +16361,11 @@ async function executeUnion(stmt, client, options, cacheContext) {
15948
16361
  });
15949
16362
  const combined = [...leftResult.rows, ...remappedRight];
15950
16363
  const rows = stmt.all ? combined : deduplicateRows(combined, leftCols);
15951
- return { type: "SELECT", rows, columns: leftCols, rowCount: rows.length };
16364
+ const result = { type: "SELECT", rows, columns: leftCols, rowCount: rows.length };
16365
+ if (captureColumnMeta) {
16366
+ materializedMetaBySelectResult.set(result, mergeUnionColumnMeta(leftResult, rightResult));
16367
+ }
16368
+ return result;
15952
16369
  }
15953
16370
  function deduplicateRows(rows, columns) {
15954
16371
  const seen = /* @__PURE__ */ new Set();
@@ -15961,7 +16378,7 @@ function deduplicateRows(rows, columns) {
15961
16378
  }
15962
16379
  async function executeWith(stmt, client, options, cacheContext, seed, captureColumnMeta = false) {
15963
16380
  if ((seed == null || seed.size === 0) && canInlineSingleCte(stmt)) {
15964
- return executeSelect(buildInlinedQuery(stmt), client, options, cacheContext, void 0, captureColumnMeta);
16381
+ return executeSelect(buildInlinedQuery(stmt), client, options, cacheContext, void 0, captureColumnMeta, false);
15965
16382
  }
15966
16383
  const cteCache = new Map(seed ?? []);
15967
16384
  for (const cte of stmt.ctes) {
@@ -16006,7 +16423,7 @@ async function executeQueryWithCte(query, client, options, cteCache, cacheContex
16006
16423
  }
16007
16424
  const hasCteRef = query.from.cteName != null && query.from.cteName !== NO_FROM_CTE_NAME || query.joins.some((j) => j.table.cteName != null);
16008
16425
  if (!hasCteRef) {
16009
- return executeSelect(query, client, options, cacheContext, cteCache, captureColumnMeta);
16426
+ return executeSelect(query, client, options, cacheContext, cteCache, captureColumnMeta, false);
16010
16427
  }
16011
16428
  const result = await executeFullScanWithCte(query, client, options, cteCache, cacheContext);
16012
16429
  if (captureColumnMeta) {
@@ -19732,6 +20149,102 @@ function explainMetadataLines(analysis) {
19732
20149
  ...[...analysis.numberPrecisionApps].sort((a, b) => a - b).map((appId) => ` metadata API: number precision APP${appId}`)
19733
20150
  ];
19734
20151
  }
20152
+ function renderResidualOperator(op) {
20153
+ switch (op) {
20154
+ case "NOT_LIKE":
20155
+ return "NOT LIKE";
20156
+ case "NOT_KLIKE":
20157
+ return "NOT KLIKE";
20158
+ case "NOT_IN":
20159
+ return "NOT IN";
20160
+ case "LIKE":
20161
+ case "KLIKE":
20162
+ case "IN":
20163
+ case "=":
20164
+ case "!=":
20165
+ case "<>":
20166
+ case ">":
20167
+ case "<":
20168
+ case ">=":
20169
+ case "<=":
20170
+ return op;
20171
+ default:
20172
+ return "<op>";
20173
+ }
20174
+ }
20175
+ function relativeReasonOperator(op) {
20176
+ return op === "<>" ? "!=" : op;
20177
+ }
20178
+ function renderResidualValue(node) {
20179
+ if (node === null || typeof node !== "object") return "<expr>";
20180
+ const value = node;
20181
+ switch (value["type"]) {
20182
+ case "FIELD":
20183
+ return typeof value["field"] === "string" ? `${typeof value["tableAlias"] === "string" ? `${value["tableAlias"]}.` : ""}${value["field"]}` : "<expr>";
20184
+ case "FIELD_REF":
20185
+ return typeof value["field"] === "string" ? value["field"] : "<expr>";
20186
+ case "NUMBER":
20187
+ return typeof value["raw"] === "string" ? value["raw"] : typeof value["value"] === "number" ? String(value["value"]) : "<expr>";
20188
+ case "STRING":
20189
+ return typeof value["value"] === "string" ? `'${value["value"].replace(/'/g, "''")}'` : "<expr>";
20190
+ case "VARIABLE":
20191
+ return typeof value["name"] === "string" ? `@${value["name"]}` : "<expr>";
20192
+ case "VARIABLE_IN_LIST":
20193
+ return typeof value["name"] === "string" ? `@${value["name"]}` : "<expr>";
20194
+ case "STRING_FUNC": {
20195
+ if (typeof value["func"] !== "string" || !Array.isArray(value["args"])) return "<expr>";
20196
+ return `${value["func"]}(${value["args"].map(renderResidualValue).join(", ")})`;
20197
+ }
20198
+ case "FUNC_FIELD":
20199
+ case "ARITH_FIELD":
20200
+ case "CASE_FIELD":
20201
+ case "ARITH_VALUE":
20202
+ case "CASE_VALUE":
20203
+ return renderResidualValue(value["expr"]);
20204
+ case "GROUPING_FIELD":
20205
+ return `GROUPING(${renderResidualValue(value["ref"])})`;
20206
+ case "GROUPING_REF":
20207
+ return renderResidualValue(value["field"]);
20208
+ case "ARITH":
20209
+ case "SCALAR_ARITH":
20210
+ case "CONCAT_OP":
20211
+ return `(${renderResidualValue(value["left"])} ${typeof value["op"] === "string" ? value["op"] : value["type"] === "CONCAT_OP" ? "||" : "<op>"} ${renderResidualValue(value["right"])})`;
20212
+ case "KINTONE_FUNC":
20213
+ return typeof value["name"] === "string" ? `${value["name"]}(...)` : "<expr>";
20214
+ case "IN_LIST":
20215
+ return Array.isArray(value["values"]) ? `(${value["values"].map(renderResidualValue).join(", ")})` : "<expr>";
20216
+ case "ARRAY":
20217
+ return Array.isArray(value["elements"]) ? `[${value["elements"].map(renderResidualValue).join(", ")}]` : "<expr>";
20218
+ case "CASE":
20219
+ case "CASE_WHEN":
20220
+ return "CASE ... END";
20221
+ default:
20222
+ return "<expr>";
20223
+ }
20224
+ }
20225
+ function renderRelativeDateResidualWhere(where) {
20226
+ try {
20227
+ const node = where;
20228
+ switch (node["type"]) {
20229
+ case "BINARY":
20230
+ return `${renderResidualValue(node["left"])} ${renderResidualOperator(node["op"])} ${renderResidualValue(node["right"])}`;
20231
+ case "NULL_CHECK":
20232
+ return `${renderResidualValue(node["field"])} IS ${node["not"] === true ? "NOT " : ""}NULL`;
20233
+ case "LOGICAL":
20234
+ return `(${renderRelativeDateResidualWhere(node["left"])} ${node["op"] === "AND" || node["op"] === "OR" ? node["op"] : "<op>"} ${renderRelativeDateResidualWhere(node["right"])})`;
20235
+ case "NOT":
20236
+ return `NOT (${renderRelativeDateResidualWhere(node["expr"])})`;
20237
+ case "GROUP":
20238
+ return `(${renderRelativeDateResidualWhere(node["expr"])})`;
20239
+ case "BOOLEAN":
20240
+ return node["value"] === true ? "TRUE" : node["value"] === false ? "FALSE" : "<expr>";
20241
+ default:
20242
+ return "<expr>";
20243
+ }
20244
+ } catch {
20245
+ return "<expr>";
20246
+ }
20247
+ }
19735
20248
  function relativeDateExplainLines(plan) {
19736
20249
  if (!plan.hasRelativeDate) return [];
19737
20250
  if (!plan.allowed && plan.rejection) {
@@ -19745,6 +20258,32 @@ function relativeDateExplainLines(plan) {
19745
20258
  }
19746
20259
  const lines = [];
19747
20260
  for (const node of plan.nodes) {
20261
+ const prefilterPlan = node.prefilterPlan;
20262
+ if (node.allowed && prefilterPlan?.prefilterWhere && prefilterPlan.residualWhere) {
20263
+ for (const leaf of prefilterPlan.exactRelativeLeaves) {
20264
+ const functionName = leaf.right.type === "KINTONE_FUNC" ? leaf.right.name : "(unknown)";
20265
+ const field = leaf.left.type === "FIELD" ? leaf.left.field : void 0;
20266
+ const operator = relativeReasonOperator(leaf.op);
20267
+ const detail = node.capability?.reasons.find(
20268
+ (reason) => reason.functionName === functionName && (field === void 0 || reason.field === field) && reason.operator === operator
20269
+ );
20270
+ lines.push(
20271
+ ` relative date function: ${functionName}`,
20272
+ " relative date evaluation: kintone server exact prefilter",
20273
+ ` field: ${detail?.field ?? field ?? "(unknown)"} (${detail?.fieldType ?? "unknown"})`,
20274
+ ` operator: ${detail?.operator ?? operator}`
20275
+ );
20276
+ }
20277
+ const serverPrefilter = whereToKintone(prefilterPlan.prefilterWhere);
20278
+ lines.push(
20279
+ " where capability: SUPERSET_PREFILTER",
20280
+ ` server prefilter: ${serverPrefilter}`,
20281
+ ` client residual: ${renderRelativeDateResidualWhere(prefilterPlan.residualWhere)}`,
20282
+ " relative date client evaluations: 0",
20283
+ ` kintone query: ${serverPrefilter}`
20284
+ );
20285
+ continue;
20286
+ }
19748
20287
  for (const functionName of node.functionNames) {
19749
20288
  const detail = node.capability?.reasons.find(
19750
20289
  (reason) => reason.functionName === functionName