@rex0220/kintone-sql-tools 3.25.0 → 3.27.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
@@ -10453,6 +10453,28 @@ function validateDeclaredBatchVariables(statements, input) {
10453
10453
  return normalized;
10454
10454
  }
10455
10455
 
10456
+ // src/core/outerJoinSearchAbortGuard.ts
10457
+ function isOuterJoinSelect(value) {
10458
+ if (value["type"] !== "SELECT" || !Array.isArray(value["joins"])) return false;
10459
+ return value["joins"].some((join2) => {
10460
+ if (join2 === null || typeof join2 !== "object") return false;
10461
+ const type = join2["type"];
10462
+ return type === "LEFT" || type === "RIGHT";
10463
+ });
10464
+ }
10465
+ function statementContainsOuterJoin(statement) {
10466
+ const seen = /* @__PURE__ */ new Set();
10467
+ const visit = (value) => {
10468
+ if (Array.isArray(value)) return value.some(visit);
10469
+ if (value === null || typeof value !== "object" || seen.has(value)) return false;
10470
+ seen.add(value);
10471
+ const object = value;
10472
+ if (isOuterJoinSelect(object)) return true;
10473
+ return Object.values(object).some(visit);
10474
+ };
10475
+ return visit(statement);
10476
+ }
10477
+
10456
10478
  // src/core/explainMetadata.ts
10457
10479
  function buildGroupingExplainMetadata(statement, canonicalItemCount) {
10458
10480
  const grouping = normalizeGroupingSpec(statement);
@@ -10699,2088 +10721,2421 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
10699
10721
  };
10700
10722
  }
10701
10723
 
10702
- // src/core/optimization/applyParentSelectionPlan.ts
10703
- function buildApplyParentSelectionPlan(where, metadata) {
10704
- const plan = buildSingleTableKlikePushdownPlan(where, {
10705
- allowUnqualifiedFields: true,
10706
- allowKlike: true,
10707
- fieldTypes: metadata.fieldTypes,
10708
- fieldOptions: metadata.fieldOptions
10709
- });
10710
- return {
10711
- prefilter: plan.condition,
10712
- appliedKlikes: plan.appliedKlikes,
10713
- unappliedKlikes: plan.allKlikes.filter((expr) => !plan.appliedKlikes.has(expr))
10714
- };
10715
- }
10716
-
10717
- // src/core/optimization/canonicalOrderPlanner.ts
10718
- var REST_OFFSET_MAX = 1e4;
10719
- var REST_LIMIT_MAX = 500;
10720
- function fieldSemantics(item, semantics) {
10721
- return item.key.type === "FIELD_NAME" ? semantics.get(item.key.name) : void 0;
10724
+ // src/core/optimization/whereCapability.ts
10725
+ var RANGE_AND_EQUALITY = ["=", "!=", ">", "<", ">=", "<="];
10726
+ var RELATIVE_DATE_FIELD_TYPES = /* @__PURE__ */ new Set([
10727
+ "DATE",
10728
+ "DATETIME",
10729
+ "CREATED_TIME",
10730
+ "UPDATED_TIME"
10731
+ ]);
10732
+ var RELATIVE_DATE_OPERATORS = new Set(RANGE_AND_EQUALITY);
10733
+ var LEGACY_KINTONE_FUNCTION_FIELD_TYPES = /* @__PURE__ */ new Map([
10734
+ ["TODAY", /* @__PURE__ */ new Set(["DATE", "DATETIME", "CREATED_TIME", "UPDATED_TIME"])],
10735
+ ["NOW", /* @__PURE__ */ new Set(["DATETIME", "CREATED_TIME", "UPDATED_TIME"])],
10736
+ ["LOGINUSER", /* @__PURE__ */ new Set(["CREATOR", "MODIFIER", "USER_SELECT"])]
10737
+ ]);
10738
+ var LEGACY_KINTONE_FUNCTION_OPERATORS = /* @__PURE__ */ new Map([
10739
+ ["TODAY", new Set(RANGE_AND_EQUALITY)],
10740
+ ["NOW", new Set(RANGE_AND_EQUALITY)],
10741
+ ["LOGINUSER", /* @__PURE__ */ new Set(["in", "not in"])]
10742
+ ]);
10743
+ var EQUALITY_IN = ["=", "!=", "in", "not in"];
10744
+ var NATIVE_OPERATORS = /* @__PURE__ */ new Map([
10745
+ ["RECORD_NUMBER", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
10746
+ ["__ID__", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
10747
+ ["CREATOR", /* @__PURE__ */ new Set(["in", "not in"])],
10748
+ ["MODIFIER", /* @__PURE__ */ new Set(["in", "not in"])],
10749
+ ["CREATED_TIME", new Set(RANGE_AND_EQUALITY)],
10750
+ ["UPDATED_TIME", new Set(RANGE_AND_EQUALITY)],
10751
+ ["DATE", new Set(RANGE_AND_EQUALITY)],
10752
+ ["TIME", new Set(RANGE_AND_EQUALITY)],
10753
+ ["DATETIME", new Set(RANGE_AND_EQUALITY)],
10754
+ ["SINGLE_LINE_TEXT", /* @__PURE__ */ new Set(["=", "!=", "in", "not in", "like", "not like"])],
10755
+ ["LINK", /* @__PURE__ */ new Set(["=", "!=", "in", "not in", "like", "not like"])],
10756
+ ["NUMBER", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
10757
+ ["CALC", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
10758
+ ["MULTI_LINE_TEXT", /* @__PURE__ */ new Set(["like", "not like"])],
10759
+ ["RICH_TEXT", /* @__PURE__ */ new Set(["like", "not like"])],
10760
+ ["CHECK_BOX", /* @__PURE__ */ new Set(["in", "not in"])],
10761
+ ["RADIO_BUTTON", /* @__PURE__ */ new Set(["in", "not in"])],
10762
+ ["DROP_DOWN", /* @__PURE__ */ new Set(["in", "not in"])],
10763
+ ["MULTI_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
10764
+ ["FILE", /* @__PURE__ */ new Set(["like", "not like"])],
10765
+ ["USER_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
10766
+ ["ORGANIZATION_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
10767
+ ["GROUP_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
10768
+ ["STATUS", new Set(EQUALITY_IN)]
10769
+ ]);
10770
+ var LOCAL_VALID_OPERATORS = /* @__PURE__ */ new Map([
10771
+ ["CREATOR", /* @__PURE__ */ new Set(["in", "not in"])],
10772
+ ["MODIFIER", /* @__PURE__ */ new Set(["in", "not in"])],
10773
+ ["CHECK_BOX", /* @__PURE__ */ new Set(["in", "not in"])],
10774
+ ["MULTI_SELECT", /* @__PURE__ */ new Set(["in", "not in"])]
10775
+ ]);
10776
+ var LOCAL_SCALAR_TYPES = /* @__PURE__ */ new Set([
10777
+ "RECORD_NUMBER",
10778
+ "__ID__",
10779
+ "CREATOR",
10780
+ "MODIFIER",
10781
+ "CREATED_TIME",
10782
+ "UPDATED_TIME",
10783
+ "DATE",
10784
+ "TIME",
10785
+ "DATETIME",
10786
+ "SINGLE_LINE_TEXT",
10787
+ "LINK",
10788
+ "NUMBER",
10789
+ "CALC",
10790
+ "MULTI_LINE_TEXT",
10791
+ "RICH_TEXT",
10792
+ "RADIO_BUTTON",
10793
+ "DROP_DOWN",
10794
+ "STATUS",
10795
+ // 一時表・CTE・式列は kintone REST へは送らず、共有ローカル評価器で扱う。
10796
+ "KSQL_STRING",
10797
+ "KSQL_NUMBER",
10798
+ "KSQL_BOOLEAN"
10799
+ ]);
10800
+ var LOCAL_COLLECTION_TYPES = /* @__PURE__ */ new Set([
10801
+ "CHECK_BOX",
10802
+ "MULTI_SELECT",
10803
+ "FILE",
10804
+ "USER_SELECT",
10805
+ "ORGANIZATION_SELECT",
10806
+ "GROUP_SELECT",
10807
+ "STATUS_ASSIGNEE",
10808
+ "CATEGORY"
10809
+ ]);
10810
+ function nativeWhereOperatorsForType(fieldType) {
10811
+ return NATIVE_OPERATORS.get(fieldType) ?? /* @__PURE__ */ new Set();
10722
10812
  }
10723
- function hasSelectOutputAlias(stmt, name) {
10724
- return stmt.columns.some((column) => "alias" in column && column.alias === name);
10813
+ function classifyWhereCapability(where, resolveField2) {
10814
+ if (where === null) {
10815
+ return { capability: "EXACT_PUSHDOWN", reasons: [{ code: "WHERE_EXACT" }] };
10816
+ }
10817
+ return classifyNode(where, resolveField2);
10725
10818
  }
10726
- function planCanonicalOrder(input) {
10727
- const { stmt } = input;
10728
- const reasons = [];
10729
- const windowOrderBy = stmt.columns.flatMap(
10730
- (column) => column.type === "WINDOW_COL" ? column.orderBy : []
10731
- );
10732
- const allOrderBy = [...stmt.orderBy, ...windowOrderBy];
10733
- for (const item of allOrderBy) {
10734
- if (item.key.type !== "FIELD_NAME") continue;
10735
- const semantics = fieldSemantics(item, input.orderSemantics);
10736
- if (!semantics) {
10737
- reasons.push("ORDER_KEY_UNRESOLVED");
10738
- continue;
10819
+ function classifyNode(where, resolveField2) {
10820
+ switch (where.type) {
10821
+ case "BOOLEAN":
10822
+ return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
10823
+ case "BINARY":
10824
+ return classifyBinary(where.op, where.left, where.right, resolveField2);
10825
+ case "NULL_CHECK":
10826
+ if (where.field.type !== "FIELD") return localExpression();
10827
+ return classifyLocalOnlyField(where.field, where.not ? "IS NOT NULL" : "IS NULL", resolveField2);
10828
+ case "EXISTS":
10829
+ return localExpression();
10830
+ case "GROUP":
10831
+ return classifyNode(where.expr, resolveField2);
10832
+ case "NOT": {
10833
+ const inner = classifyNode(where.expr, resolveField2);
10834
+ if (inner.capability !== "SUPERSET_PREFILTER") return inner;
10835
+ if (!hasRelativeDateReason(inner.reasons)) {
10836
+ return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
10837
+ }
10838
+ return requireExactFunctionPushdown({
10839
+ capability: "LOCAL_ONLY",
10840
+ reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }, ...inner.reasons]
10841
+ });
10842
+ }
10843
+ case "LOGICAL": {
10844
+ const left = classifyNode(where.left, resolveField2);
10845
+ const right = classifyNode(where.right, resolveField2);
10846
+ return combineLogical(where.op, left, right);
10739
10847
  }
10740
- if (semantics.fieldType === "KSQL_AMBIGUOUS") reasons.push("ORDER_KEY_AMBIGUOUS");
10741
- else if (semantics.compareMode === "unsupported") reasons.push("ORDER_KEY_UNSUPPORTED");
10742
10848
  }
10743
- if (reasons.includes("ORDER_KEY_AMBIGUOUS")) {
10744
- throw new Error(
10745
- "ArgumentError: ORDER BY key is an ambiguous column reference (reason=ORDER_KEY_AMBIGUOUS). Qualify the key with its table alias."
10849
+ }
10850
+ function classifyBinary(op, left, right, resolveField2) {
10851
+ if (right.type === "KINTONE_FUNC" && isRelativeDateFunctionName(right.name)) {
10852
+ return classifyRelativeDateBinary(op, left, right, resolveField2);
10853
+ }
10854
+ if (right.type === "KINTONE_FUNC" && isLegacyKintoneFunction(right)) {
10855
+ return classifyLegacyKintoneFunctionBinary(op, left, right, resolveField2);
10856
+ }
10857
+ if (right.type === "IN_LIST") {
10858
+ const functions = right.values.filter(
10859
+ (value) => value.type === "KINTONE_FUNC"
10746
10860
  );
10861
+ if (functions.length > 0) {
10862
+ if (right.values.length === 1 && functions.length === 1) {
10863
+ return classifyLegacyKintoneFunctionBinary(op, left, functions[0], resolveField2);
10864
+ }
10865
+ return legacyKintoneFunctionUnsupported(
10866
+ "WHERE_KINTONE_FUNCTION_CONTEXT_UNSUPPORTED",
10867
+ functions[0].name,
10868
+ left.type === "FIELD" ? left.field : void 0,
10869
+ left.type === "FIELD" ? resolveField2(left)?.fieldType : void 0,
10870
+ normalizeOperator(op)
10871
+ );
10872
+ }
10747
10873
  }
10748
- if (reasons.includes("ORDER_KEY_UNSUPPORTED") || reasons.includes("ORDER_KEY_UNRESOLVED")) {
10749
- const reason = reasons.includes("ORDER_KEY_UNSUPPORTED") ? "ORDER_KEY_UNSUPPORTED" : "ORDER_KEY_UNRESOLVED";
10750
- throw new Error(`ArgumentError: ORDER BY key has no canonical comparison contract (reason=${reason}).`);
10874
+ if (left.type !== "FIELD") return localExpression();
10875
+ const semantics = resolveField2(left);
10876
+ if (!semantics) {
10877
+ return unsupported2("WHERE_FIELD_UNRESOLVED", left.field, void 0, normalizeOperator(op));
10751
10878
  }
10752
- const allRestEquivalent = stmt.orderBy.length > 0 && windowOrderBy.length === 0 && stmt.orderBy.every(
10753
- (item) => item.key.type === "FIELD_NAME" && item.key.name === "$id" && !hasSelectOutputAlias(stmt, item.key.name)
10754
- );
10755
- if (!allRestEquivalent) reasons.push("ORDER_KEY_NOT_REST_EQUIVALENT");
10756
- if (input.whereCapability !== "EXACT_PUSHDOWN") reasons.push("WHERE_NOT_EXACT");
10757
- if (input.staticMode !== "SIMPLE") reasons.push("QUERY_SHAPE_LOCAL");
10758
- if (stmt.limit === null || stmt.limit < 0 || stmt.limit > REST_LIMIT_MAX) {
10759
- reasons.push("LIMIT_NOT_REST_WINDOW");
10879
+ const nativeOp = normalizeOperator(op);
10880
+ if (!isLocallyValidOperator(semantics.fieldType, nativeOp)) {
10881
+ return unsupported2(
10882
+ "WHERE_OPERATOR_INVALID_FOR_FIELD_TYPE",
10883
+ left.field,
10884
+ semantics.fieldType,
10885
+ nativeOp
10886
+ );
10760
10887
  }
10761
- if ((stmt.offset ?? 0) < 0 || (stmt.offset ?? 0) > REST_OFFSET_MAX) {
10762
- reasons.push("OFFSET_NOT_REST_WINDOW");
10888
+ if (!hasLocalContract(semantics.fieldType, op)) {
10889
+ return unsupported2("WHERE_OPERATOR_UNSUPPORTED", left.field, semantics.fieldType, nativeOp);
10763
10890
  }
10764
- if (stmt.limit !== null && stmt.limit > input.maxRecords) reasons.push("MAX_RECORDS_WINDOW");
10765
- if (input.hasKlike) reasons.push("KLIKE_NOT_REST_WINDOW");
10766
- if (reasons.length === 0) {
10891
+ const native = nativeWhereOperatorsForType(semantics.fieldType);
10892
+ const rightCanPush = right.type === "STRING" || right.type === "NUMBER" || right.type === "IN_LIST" || isLegacyKintoneFunction(right);
10893
+ const structureAllows = !semantics.requiresCollectionOperators || nativeOp !== "=" && nativeOp !== "!=";
10894
+ const sqlLikeIsResidual = op === "LIKE" || op === "NOT_LIKE";
10895
+ if (rightCanPush && structureAllows && native.has(nativeOp) && !sqlLikeIsResidual) {
10767
10896
  return {
10768
- kind: "CANONICAL_REST_TOP_N",
10769
- requiresCompleteInput: false,
10770
- localOrderBy: false,
10771
- applyLocalOffsetLimit: false,
10772
- reasonCodes: []
10897
+ capability: "EXACT_PUSHDOWN",
10898
+ reasons: [{
10899
+ code: "WHERE_EXACT",
10900
+ field: left.field,
10901
+ fieldType: semantics.fieldType,
10902
+ operator: nativeOp
10903
+ }]
10773
10904
  };
10774
10905
  }
10775
10906
  return {
10776
- kind: "CANONICAL_LOCAL",
10777
- requiresCompleteInput: allOrderBy.length > 0,
10778
- localOrderBy: stmt.orderBy.length > 0,
10779
- applyLocalOffsetLimit: stmt.orderBy.length > 0,
10780
- reasonCodes: [...new Set(reasons)]
10907
+ capability: "LOCAL_ONLY",
10908
+ reasons: [{
10909
+ code: "WHERE_RESIDUAL",
10910
+ field: left.field,
10911
+ fieldType: semantics.fieldType,
10912
+ operator: nativeOp
10913
+ }]
10781
10914
  };
10782
10915
  }
10783
-
10784
- // src/core/optimization/korderPlanner.ts
10785
- var KORDER_NATIVE_FIELD_TYPES = /* @__PURE__ */ new Set([
10786
- "RECORD_NUMBER",
10787
- "SINGLE_LINE_TEXT",
10788
- "NUMBER",
10789
- "CALC",
10790
- "DATE",
10791
- "DATETIME",
10792
- "TIME",
10793
- "CREATED_TIME",
10794
- "UPDATED_TIME",
10795
- "DROP_DOWN",
10796
- "RADIO_BUTTON",
10797
- "STATUS",
10798
- "LINK",
10799
- "CREATOR",
10800
- "MODIFIER"
10801
- ]);
10802
- function hasSelectOutputAlias2(stmt, name) {
10803
- return stmt.columns.some((column) => "alias" in column && column.alias === name);
10916
+ function isLegacyKintoneFunction(value) {
10917
+ return value.type === "KINTONE_FUNC" && LEGACY_KINTONE_FUNCTION_NAMES.has(value.name);
10804
10918
  }
10805
- function planKorder(input) {
10806
- const { stmt } = input;
10807
- const reasons = [];
10808
- if (stmt.orderMode !== "KINTONE_NATIVE") reasons.push("KORDER_MODE_REQUIRED");
10809
- if (stmt.from.cteName !== null || stmt.from.subtableCode || input.staticMode !== "SIMPLE") {
10810
- reasons.push("KORDER_QUERY_SHAPE_UNSUPPORTED");
10919
+ function classifyLegacyKintoneFunctionBinary(op, left, right, resolveField2) {
10920
+ const operator = normalizeOperator(op);
10921
+ const functionName = right.name;
10922
+ if (!LEGACY_KINTONE_FUNCTION_NAMES.has(functionName) || left.type !== "FIELD") {
10923
+ return legacyKintoneFunctionUnsupported(
10924
+ "WHERE_KINTONE_FUNCTION_CONTEXT_UNSUPPORTED",
10925
+ functionName,
10926
+ void 0,
10927
+ void 0,
10928
+ operator
10929
+ );
10811
10930
  }
10812
- if (input.whereCapability !== "EXACT_PUSHDOWN") {
10813
- reasons.push(...(input.whereReasons ?? []).filter((reason) => reason.functionName !== void 0).map((reason) => reason.code));
10814
- reasons.push("KORDER_WHERE_NOT_EXACT");
10931
+ const semantics = resolveField2(left);
10932
+ const validFieldTypes = LEGACY_KINTONE_FUNCTION_FIELD_TYPES.get(functionName);
10933
+ if (!semantics || !validFieldTypes.has(semantics.fieldType) || semantics.inSubtable || semantics.requiresCollectionOperators) {
10934
+ return legacyKintoneFunctionUnsupported(
10935
+ "WHERE_KINTONE_FUNCTION_FIELD_TYPE_UNSUPPORTED",
10936
+ functionName,
10937
+ left.field,
10938
+ semantics?.fieldType,
10939
+ operator
10940
+ );
10815
10941
  }
10816
- if (input.hasKlike) reasons.push("KORDER_KLIKE_UNSUPPORTED");
10817
- if (stmt.orderBy.length === 0) reasons.push("KORDER_KEY_REQUIRED");
10818
- for (const item of stmt.orderBy) {
10819
- if (item.key.type !== "FIELD_NAME") {
10820
- reasons.push(`KORDER_KEY_NOT_DIRECT_FIELD(key=${item.key.type})`);
10821
- continue;
10822
- }
10823
- const name = item.key.name;
10824
- const semantics = input.orderSemantics.get(name);
10825
- if (!semantics) {
10826
- reasons.push(`KORDER_KEY_UNRESOLVED(field=${name})`);
10827
- continue;
10828
- }
10829
- if (hasSelectOutputAlias2(stmt, name)) {
10830
- reasons.push(`KORDER_KEY_NOT_DIRECT_FIELD(field=${name})`);
10831
- continue;
10832
- }
10833
- if (name === "$id") continue;
10834
- if (!semantics.source || semantics.source.fieldCode !== name) {
10835
- reasons.push(`KORDER_KEY_NOT_DIRECT_FIELD(field=${name})`);
10836
- continue;
10837
- }
10838
- if (!KORDER_NATIVE_FIELD_TYPES.has(semantics.fieldType)) {
10839
- reasons.push(`KORDER_TYPE_UNSUPPORTED(field=${name}, type=${semantics.fieldType})`);
10840
- }
10942
+ const validOperators = LEGACY_KINTONE_FUNCTION_OPERATORS.get(functionName);
10943
+ if (!validOperators.has(operator)) {
10944
+ return legacyKintoneFunctionUnsupported(
10945
+ "WHERE_KINTONE_FUNCTION_OPERATOR_UNSUPPORTED",
10946
+ functionName,
10947
+ left.field,
10948
+ semantics.fieldType,
10949
+ operator
10950
+ );
10841
10951
  }
10842
- if (stmt.limit === null || !Number.isSafeInteger(stmt.limit) || stmt.limit < 0) {
10843
- reasons.push(`KORDER_LIMIT_INVALID(limit=${String(stmt.limit)})`);
10952
+ return {
10953
+ capability: "EXACT_PUSHDOWN",
10954
+ reasons: [{
10955
+ code: "WHERE_EXACT",
10956
+ functionName,
10957
+ field: left.field,
10958
+ fieldType: semantics.fieldType,
10959
+ operator
10960
+ }]
10961
+ };
10962
+ }
10963
+ function classifyRelativeDateBinary(op, left, right, resolveField2) {
10964
+ const operator = normalizeOperator(op);
10965
+ const functionName = right.name;
10966
+ if (left.type !== "FIELD") {
10967
+ return relativeDateUnsupported(
10968
+ "WHERE_RELATIVE_DATE_CONTEXT_UNSUPPORTED",
10969
+ functionName,
10970
+ void 0,
10971
+ void 0,
10972
+ operator
10973
+ );
10844
10974
  }
10845
- const offset = stmt.offset ?? 0;
10846
- if (!Number.isSafeInteger(offset) || offset < 0) {
10847
- reasons.push(`KORDER_OFFSET_INVALID(offset=${offset})`);
10975
+ const semantics = resolveField2(left);
10976
+ if (!semantics) {
10977
+ return relativeDateUnsupported(
10978
+ "WHERE_RELATIVE_DATE_FIELD_TYPE_UNSUPPORTED",
10979
+ functionName,
10980
+ left.field,
10981
+ void 0,
10982
+ operator
10983
+ );
10848
10984
  }
10849
- const scanRows = stmt.limit === null ? Number.NaN : offset + stmt.limit;
10850
- if (stmt.limit !== null && !Number.isSafeInteger(scanRows)) {
10851
- reasons.push(`KORDER_SCAN_ROWS_INVALID(offset=${offset}, limit=${stmt.limit})`);
10985
+ if (!hasValidRelativeDateArguments(right)) {
10986
+ return relativeDateUnsupported(
10987
+ "WHERE_RELATIVE_DATE_ARGUMENT_INVALID",
10988
+ functionName,
10989
+ left.field,
10990
+ semantics.fieldType,
10991
+ operator
10992
+ );
10852
10993
  }
10853
- const unique2 = [...new Set(reasons)];
10854
- if (unique2.length > 0) {
10855
- throw new Error(
10856
- `ArgumentError: KORDER BY cannot be executed (mode=KINTONE_NATIVE; ${unique2.join(", ")}). Use ORDER BY for canonical local ordering or simplify the query.`
10994
+ if (!RELATIVE_DATE_OPERATORS.has(operator)) {
10995
+ return relativeDateUnsupported(
10996
+ "WHERE_RELATIVE_DATE_OPERATOR_UNSUPPORTED",
10997
+ functionName,
10998
+ left.field,
10999
+ semantics.fieldType,
11000
+ operator
10857
11001
  );
10858
11002
  }
10859
- const native = stmt.limit <= 500 && offset <= 1e4 && stmt.limit <= input.maxRecords;
10860
- if (!native && scanRows > input.maxRecords) {
10861
- throw new Error(
10862
- `ArgumentError: KORDER BY cannot be executed (mode=KINTONE_NATIVE; KORDER_SCAN_ROWS_EXCEEDS_MAX_RECORDS(scanRows=${scanRows}, maxRecords=${input.maxRecords})). Use ORDER BY for canonical local ordering, raise maxRecords, or reduce LIMIT/OFFSET.`
11003
+ if (!RELATIVE_DATE_FIELD_TYPES.has(semantics.fieldType) || semantics.inSubtable || semantics.requiresCollectionOperators) {
11004
+ return relativeDateUnsupported(
11005
+ "WHERE_RELATIVE_DATE_FIELD_TYPE_UNSUPPORTED",
11006
+ functionName,
11007
+ left.field,
11008
+ semantics.fieldType,
11009
+ operator
10863
11010
  );
10864
11011
  }
10865
11012
  return {
10866
- kind: native ? "KORDER_NATIVE" : "KORDER_CURSOR",
10867
- requiresCompleteInput: false,
10868
- localOrderBy: false,
10869
- applyLocalOffsetLimit: false,
10870
- reasonCodes: [],
10871
- scanRows
11013
+ capability: "EXACT_PUSHDOWN",
11014
+ reasons: [{
11015
+ code: "WHERE_EXACT",
11016
+ functionName,
11017
+ field: left.field,
11018
+ fieldType: semantics.fieldType,
11019
+ operator
11020
+ }]
10872
11021
  };
10873
11022
  }
10874
-
10875
- // src/core/errors/cursorErrors.ts
10876
- var CursorCapacityError = class extends Error {
10877
- constructor(host, limit, waitMs) {
10878
- super(`CursorCapacityError: host=${host} \u306E active cursor \u4E0A\u9650 ${limit} \u306B ${waitMs}ms \u4EE5\u5185\u3067\u7A7A\u304D\u304C\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002`);
10879
- this.name = "CursorCapacityError";
11023
+ function hasValidRelativeDateArguments(value) {
11024
+ if (!("args" in value) || !value.args) return false;
11025
+ switch (value.name) {
11026
+ case "YESTERDAY":
11027
+ case "TOMORROW":
11028
+ case "THIS_YEAR":
11029
+ case "LAST_YEAR":
11030
+ case "NEXT_YEAR":
11031
+ return value.args.kind === "NONE";
11032
+ case "FROM_TODAY":
11033
+ return value.args.kind === "FROM_TODAY" && Number.isSafeInteger(value.args.offset) && value.args.offsetText === String(value.args.offset === 0 ? 0 : value.args.offset) && (value.args.unit === "DAYS" || value.args.unit === "WEEKS" || value.args.unit === "MONTHS" || value.args.unit === "YEARS");
11034
+ case "THIS_WEEK":
11035
+ case "LAST_WEEK":
11036
+ case "NEXT_WEEK":
11037
+ return value.args.kind === "WEEK" && (value.args.weekday === null || value.args.weekday === "SUNDAY" || value.args.weekday === "MONDAY" || value.args.weekday === "TUESDAY" || value.args.weekday === "WEDNESDAY" || value.args.weekday === "THURSDAY" || value.args.weekday === "FRIDAY" || value.args.weekday === "SATURDAY");
11038
+ case "THIS_MONTH":
11039
+ case "LAST_MONTH":
11040
+ case "NEXT_MONTH":
11041
+ return value.args.kind === "MONTH" && (value.args.day === null || value.args.day === "LAST" || Number.isInteger(value.args.day) && value.args.day >= 1 && value.args.day <= 31);
11042
+ default:
11043
+ return false;
10880
11044
  }
10881
- };
10882
- var CursorCreateOutcomeUnknownError = class extends Error {
10883
- constructor(cause) {
10884
- super("CursorCreateOutcomeUnknownError: Create Cursor \u306E\u6210\u5426\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002\u81EA\u52D5\u518D\u8A66\u884C\u305B\u305A\u3001\u6700\u592710\u5206+\u5B89\u5168\u4F59\u88D5\u306E\u9593\u306F\u67A0\u3092\u9694\u96E2\u3057\u307E\u3059\u3002");
10885
- this.name = "CursorCreateOutcomeUnknownError";
10886
- this.cause = cause;
11045
+ }
11046
+ function classifyLocalOnlyField(field, operator, resolveField2) {
11047
+ const semantics = resolveField2(field);
11048
+ if (!semantics) return unsupported2("WHERE_FIELD_UNRESOLVED", field.field, void 0, operator);
11049
+ if (!isLocallyValidOperator(semantics.fieldType, operator)) {
11050
+ return unsupported2(
11051
+ "WHERE_OPERATOR_INVALID_FOR_FIELD_TYPE",
11052
+ field.field,
11053
+ semantics.fieldType,
11054
+ operator
11055
+ );
10887
11056
  }
10888
- };
10889
- var CursorCleanupWarning = class extends Error {
10890
- constructor(cause) {
10891
- const detail = cause instanceof Error ? cause.message : String(cause);
10892
- super(`CursorCleanupWarning: Cursor \u306E\u89E3\u653E\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002\u7D50\u679C\u306F\u6709\u52B9\u3067\u3059\u304C\u3001\u6700\u592710\u5206+\u5B89\u5168\u4F59\u88D5\u306E\u9593\u306F\u67A0\u3092\u9694\u96E2\u3057\u307E\u3059\u3002\u8A73\u7D30: ${detail}`);
10893
- this.name = "CursorCleanupWarning";
10894
- this.cause = cause;
11057
+ if (!LOCAL_SCALAR_TYPES.has(semantics.fieldType) && !LOCAL_COLLECTION_TYPES.has(semantics.fieldType)) {
11058
+ return unsupported2("WHERE_OPERATOR_UNSUPPORTED", field.field, semantics.fieldType, operator);
10895
11059
  }
10896
- };
10897
-
10898
- // src/core/optimization/korderCursorExecutor.ts
10899
- async function executeKorderCursor(input) {
10900
- const handle = await input.client.openCursor({
10901
- app: input.app,
10902
- fields: input.fields.length > 0 ? input.fields : void 0,
10903
- query: input.query,
10904
- size: 500
10905
- });
10906
- const records = [];
10907
- let seen = 0;
10908
- let primaryError;
10909
- let cleanupWarning;
10910
- try {
10911
- if (handle.totalCount > input.offset) {
10912
- while (records.length < input.limit) {
10913
- const page = await handle.nextPage();
10914
- for (const record of page.records) {
10915
- if (seen < input.offset) seen += 1;
10916
- else if (records.length < input.limit) records.push(record);
10917
- else break;
10918
- }
10919
- if (!page.next) break;
10920
- }
10921
- }
10922
- } catch (error) {
10923
- primaryError = error;
10924
- throw error;
10925
- } finally {
10926
- try {
10927
- await handle.close();
10928
- } catch (cleanupError) {
10929
- if (primaryError && primaryError instanceof Error) {
10930
- Object.defineProperty(primaryError, "cursorCleanupError", {
10931
- value: cleanupError,
10932
- configurable: true
10933
- });
10934
- } else {
10935
- cleanupWarning = new CursorCleanupWarning(cleanupError).message;
10936
- }
10937
- }
10938
- }
10939
- return { records, cleanupWarning };
11060
+ return {
11061
+ capability: "LOCAL_ONLY",
11062
+ reasons: [{ code: "WHERE_RESIDUAL", field: field.field, fieldType: semantics.fieldType, operator }]
11063
+ };
10940
11064
  }
10941
-
10942
- // src/converter/korderCursorQuery.ts
10943
- function buildKorderCursorQuery(stmt) {
10944
- const parts = [];
10945
- if (stmt.where) parts.push(whereToKintone(stmt.where));
10946
- const order = stmt.orderBy.map((item) => {
10947
- if (item.key.type !== "FIELD_NAME") {
10948
- throw new Error("ArgumentError: KORDER cursor key must be a direct field.");
10949
- }
10950
- return `${item.key.name} ${item.direction === "ASC" ? "asc" : "desc"}`;
10951
- });
10952
- parts.push(`order by ${order.join(", ")}`);
10953
- return parts.join(" ");
11065
+ function isLocallyValidOperator(fieldType, operator) {
11066
+ const policy = LOCAL_VALID_OPERATORS.get(fieldType);
11067
+ return policy === void 0 || policy.has(operator);
10954
11068
  }
10955
-
10956
- // src/core/systemFields.ts
10957
- var APP_SYSTEM_FIELD_CODES = [
10958
- "$id",
10959
- "$revision",
10960
- "\u30EC\u30B3\u30FC\u30C9\u756A\u53F7",
10961
- "\u4F5C\u6210\u8005",
10962
- "\u4F5C\u6210\u65E5\u6642",
10963
- "\u66F4\u65B0\u8005",
10964
- "\u66F4\u65B0\u65E5\u6642",
10965
- "\u30B9\u30C6\u30FC\u30BF\u30B9",
10966
- "\u4F5C\u696D\u8005"
10967
- ];
10968
- function isSystemLikeFieldCode(code) {
10969
- return code.startsWith("_") || code.startsWith("$");
11069
+ function hasLocalContract(fieldType, op) {
11070
+ if (LOCAL_SCALAR_TYPES.has(fieldType)) return true;
11071
+ if (!LOCAL_COLLECTION_TYPES.has(fieldType)) return false;
11072
+ return op === "=" || op === "!=" || op === "<>" || op === "IN" || op === "NOT_IN" || op === "LIKE" || op === "NOT_LIKE" || op === "KLIKE" || op === "NOT_KLIKE";
10970
11073
  }
10971
-
10972
- // src/core/optimization/plainGroupByPlan.ts
10973
- var AGGREGATE_REFERENCE_PREFIX = /^(COUNT|SUM|AVG|MAX|MIN|GROUP_CONCAT|STDDEV_POP|STDDEV_SAMP|VAR_POP|VAR_SAMP|MEDIAN|MODE)\(/;
10974
- function containsAggregateColumnNode(node) {
10975
- if (node === null || typeof node !== "object") return false;
10976
- if (Array.isArray(node)) return node.some(containsAggregateColumnNode);
10977
- const value = node;
10978
- if (value["type"] === "AGGREGATE" || value["type"] === "ARITH_AGG_COL") return true;
10979
- if (value["type"] === "SELECT" || value["type"] === "SCALAR_SUBQUERY") return false;
10980
- if (value["type"] === "FIELD" && typeof value["field"] === "string") {
10981
- return AGGREGATE_REFERENCE_PREFIX.test(value["field"]);
11074
+ function normalizeOperator(op) {
11075
+ switch (op) {
11076
+ case "<>":
11077
+ return "!=";
11078
+ case "IN":
11079
+ return "in";
11080
+ case "NOT_IN":
11081
+ return "not in";
11082
+ case "LIKE":
11083
+ case "KLIKE":
11084
+ return "like";
11085
+ case "NOT_LIKE":
11086
+ case "NOT_KLIKE":
11087
+ return "not like";
11088
+ default:
11089
+ return op;
10982
11090
  }
10983
- return Object.values(value).some(containsAggregateColumnNode);
10984
11091
  }
10985
- function classifyPreGroupAlias(column) {
10986
- switch (column.type) {
10987
- case "AGGREGATE":
10988
- case "ARITH_AGG_COL":
10989
- return "AGGREGATE_DEPENDENT";
10990
- case "GROUPING_COL":
10991
- case "WINDOW_COL":
10992
- return "POST_GROUP_ONLY";
10993
- case "VARIABLE_COL":
10994
- throw new Error("InternalError: unresolved VARIABLE_COL reached GROUP BY alias planning.");
10995
- case "WILDCARD":
10996
- case "PARENT_WILDCARD":
10997
- case "FIELD":
10998
- case "LITERAL_COL":
10999
- case "ARITH_COL":
11000
- case "CASE_COL":
11001
- case "STRFUNC_COL":
11002
- case "SCALAR_VALUE_COL":
11003
- case "SCALAR_SUBQUERY_COL":
11004
- return containsAggregate(column) || containsAggregateColumnNode(column) ? "AGGREGATE_DEPENDENT" : "SAFE";
11092
+ function combineLogical(op, left, right) {
11093
+ const reasons = [...left.reasons, ...right.reasons];
11094
+ if (left.capability === "UNSUPPORTED" || right.capability === "UNSUPPORTED") {
11095
+ return requireExactFunctionPushdown({ capability: "UNSUPPORTED", reasons });
11096
+ }
11097
+ if (left.capability === "EXACT_PUSHDOWN" && right.capability === "EXACT_PUSHDOWN") {
11098
+ return { capability: "EXACT_PUSHDOWN", reasons };
11099
+ }
11100
+ if (op === "AND" && (left.capability === "EXACT_PUSHDOWN" || right.capability === "EXACT_PUSHDOWN" || left.capability === "SUPERSET_PREFILTER" || right.capability === "SUPERSET_PREFILTER")) {
11101
+ return requireExactFunctionPushdown({
11102
+ capability: "SUPERSET_PREFILTER",
11103
+ reasons: [{ code: "WHERE_SUPERSET_PREFILTER" }, ...reasons]
11104
+ });
11005
11105
  }
11106
+ return requireExactFunctionPushdown({ capability: "LOCAL_ONLY", reasons });
11006
11107
  }
11007
- var SUBTABLE_SYSTEM_COLUMNS = ["_pid", "_rid", "_idx"];
11008
- function unique(values) {
11009
- return [...new Set(values)];
11108
+ function localExpression() {
11109
+ return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
11010
11110
  }
11011
- function sourceColumns(input) {
11012
- switch (input.kind) {
11013
- case "APP":
11014
- return unique([
11015
- ...input.fieldCodes,
11016
- ...APP_SYSTEM_FIELD_CODES
11017
- ]);
11018
- case "SUBTABLE":
11019
- return unique([
11020
- ...input.childFieldCodes,
11021
- ...SUBTABLE_SYSTEM_COLUMNS,
11022
- ...input.parentFieldCodes.map((field) => `_p.${field}`)
11023
- ]);
11024
- case "MATERIALIZED":
11025
- return unique(input.columns);
11026
- }
11111
+ function unsupported2(code, field, fieldType, operator) {
11112
+ return { capability: "UNSUPPORTED", reasons: [{ code, field, fieldType, operator }] };
11027
11113
  }
11028
- function resolvePlainGroupBySourceSchemas(stmt, lookup) {
11029
- const sources = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
11030
- return sources.map((source, sourceIndex) => ({
11031
- sourceIndex,
11032
- qualifier: source.alias ?? source.cteName,
11033
- columns: sourceColumns(lookup(source, sourceIndex))
11034
- }));
11114
+ function legacyKintoneFunctionUnsupported(code, functionName, field, fieldType, operator) {
11115
+ return requireExactFunctionPushdown({
11116
+ capability: "UNSUPPORTED",
11117
+ reasons: [{ code, functionName, field, fieldType, operator }]
11118
+ });
11035
11119
  }
11036
- function parseGroupName(name) {
11037
- if (name.startsWith("_p.")) return { qualifier: null, fieldCode: name };
11038
- const dot = name.indexOf(".");
11039
- if (dot <= 0 || dot === name.length - 1) {
11040
- return { qualifier: null, fieldCode: name };
11041
- }
11042
- return { qualifier: name.slice(0, dot), fieldCode: name.slice(dot + 1) };
11120
+ function relativeDateUnsupported(code, functionName, field, fieldType, operator) {
11121
+ return requireExactRelativeDatePushdown({
11122
+ capability: "UNSUPPORTED",
11123
+ reasons: [{ code, functionName, field, fieldType, operator }]
11124
+ });
11043
11125
  }
11044
- function runtimeKey(source, fieldCode) {
11045
- return source.qualifier === null ? fieldCode : `${source.qualifier}.${fieldCode}`;
11126
+ function hasRelativeDateReason(reasons) {
11127
+ return reasons.some(
11128
+ (reason) => reason.code.startsWith("WHERE_RELATIVE_DATE_") || reason.functionName !== void 0 && isRelativeDateFunctionName(reason.functionName)
11129
+ );
11046
11130
  }
11047
- function explicitAlias(column) {
11048
- return "alias" in column && typeof column.alias === "string" ? column.alias : null;
11131
+ function hasLegacyKintoneFunctionReason(reasons) {
11132
+ return reasons.some(
11133
+ (reason) => reason.code.startsWith("WHERE_KINTONE_FUNCTION_") || reason.functionName !== void 0 && LEGACY_KINTONE_FUNCTION_NAMES.has(reason.functionName)
11134
+ );
11049
11135
  }
11050
- function resolveFieldName(name, columns, schemas) {
11051
- const parsed = parseGroupName(name);
11052
- const candidateSources = parsed.qualifier === null ? schemas : schemas.filter((source) => source.qualifier === parsed.qualifier);
11053
- const physical = candidateSources.filter((source) => source.columns.includes(parsed.fieldCode));
11054
- if (physical.length > 1) {
11055
- throw new Error(
11056
- `ArgumentError: GROUP BY field ${name} is ambiguous across multiple sources (reason=GROUP_BY_FIELD_AMBIGUOUS).`
11057
- );
11058
- }
11059
- if (physical.length === 1) {
11060
- const source = physical[0];
11061
- return {
11062
- kind: "PHYSICAL",
11063
- sourceIndex: source.sourceIndex,
11064
- fieldCode: parsed.fieldCode,
11065
- runtimeKey: runtimeKey(source, parsed.fieldCode)
11066
- };
11136
+ function requireExactRelativeDatePushdown(result) {
11137
+ if (result.capability === "EXACT_PUSHDOWN" || !hasRelativeDateReason(result.reasons) || result.reasons.some((reason) => reason.code === "WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN")) {
11138
+ return result;
11067
11139
  }
11068
- if (parsed.qualifier !== null) return { kind: "UNKNOWN", name };
11069
- const aliases = columns.flatMap(
11070
- (column, columnIndex) => explicitAlias(column) === name ? [{ column, columnIndex }] : []
11140
+ const relative = result.reasons.find(
11141
+ (reason) => reason.code.startsWith("WHERE_RELATIVE_DATE_") || reason.functionName !== void 0 && isRelativeDateFunctionName(reason.functionName)
11071
11142
  );
11072
- if (aliases.length > 1) return { kind: "ALIAS_REJECT", reason: "DUPLICATE" };
11073
- if (aliases.length === 1) {
11074
- const candidate = aliases[0];
11075
- const classification = classifyPreGroupAlias(candidate.column);
11076
- if (classification === "SAFE") {
11077
- return { kind: "ALIAS_SAFE", columnIndex: candidate.columnIndex };
11078
- }
11079
- return {
11080
- kind: "ALIAS_REJECT",
11081
- reason: classification === "AGGREGATE_DEPENDENT" ? "AGGREGATE" : "POST_GROUP_ONLY"
11082
- };
11143
+ return {
11144
+ capability: result.capability,
11145
+ reasons: [
11146
+ ...result.reasons,
11147
+ {
11148
+ code: "WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN",
11149
+ functionName: relative.functionName,
11150
+ field: relative.field,
11151
+ fieldType: relative.fieldType,
11152
+ operator: relative.operator
11153
+ }
11154
+ ]
11155
+ };
11156
+ }
11157
+ function requireExactLegacyKintoneFunctionPushdown(result) {
11158
+ if (result.capability === "EXACT_PUSHDOWN" || !hasLegacyKintoneFunctionReason(result.reasons) || result.reasons.some(
11159
+ (reason) => reason.code === "WHERE_KINTONE_FUNCTION_REQUIRES_EXACT_PUSHDOWN"
11160
+ )) {
11161
+ return result;
11083
11162
  }
11084
- const aggregateSyntheticMatch = columns.some(
11085
- (column) => column.type === "AGGREGATE" && column.alias === null && aggregateSyntheticName(column.func, column.distinct, column.arg) === name
11163
+ const legacy = result.reasons.find(
11164
+ (reason) => reason.code.startsWith("WHERE_KINTONE_FUNCTION_") || reason.functionName !== void 0 && LEGACY_KINTONE_FUNCTION_NAMES.has(reason.functionName)
11086
11165
  );
11087
- if (aggregateSyntheticMatch) return { kind: "ALIAS_REJECT", reason: "AGGREGATE" };
11088
- return { kind: "UNKNOWN", name };
11089
- }
11090
- function planPlainGroupByResolution(groupBy, columns, schemas) {
11091
11166
  return {
11092
- items: groupBy.map(
11093
- (key) => key.type === "FIELD_NAME" ? resolveFieldName(key.name, columns, schemas) : { kind: "EXPRESSION" }
11094
- )
11167
+ capability: result.capability,
11168
+ reasons: [
11169
+ ...result.reasons,
11170
+ {
11171
+ code: "WHERE_KINTONE_FUNCTION_REQUIRES_EXACT_PUSHDOWN",
11172
+ functionName: legacy.functionName,
11173
+ field: legacy.field,
11174
+ fieldType: legacy.fieldType,
11175
+ operator: legacy.operator
11176
+ }
11177
+ ]
11095
11178
  };
11096
11179
  }
11180
+ function requireExactFunctionPushdown(result) {
11181
+ return requireExactLegacyKintoneFunctionPushdown(
11182
+ requireExactRelativeDatePushdown(result)
11183
+ );
11184
+ }
11097
11185
 
11098
- // src/engine/process.ts
11099
- function flatten(record, alias) {
11100
- const row = {};
11101
- for (const [field, fv] of Object.entries(record)) {
11102
- const val = fv.value;
11103
- const strVal = val == null ? "" : typeof val === "string" ? val : JSON.stringify(val);
11104
- if (alias) {
11105
- row[`${alias}.${field}`] = strVal;
11106
- row[field] = strVal;
11107
- } else {
11108
- row[field] = strVal;
11109
- }
11110
- }
11111
- return row;
11186
+ // src/core/optimization/joinPredicatePushdown.ts
11187
+ var SELECTION_TYPES = /* @__PURE__ */ new Set([
11188
+ "DROP_DOWN",
11189
+ "RADIO_BUTTON",
11190
+ "CHECK_BOX",
11191
+ "MULTI_SELECT",
11192
+ "STATUS"
11193
+ ]);
11194
+ var KLIKE_TYPES = /* @__PURE__ */ new Set([
11195
+ "SINGLE_LINE_TEXT",
11196
+ "LINK",
11197
+ "MULTI_LINE_TEXT",
11198
+ "RICH_TEXT",
11199
+ "FILE"
11200
+ ]);
11201
+ var DATETIME_EQUALITY_TYPES = /* @__PURE__ */ new Set([
11202
+ "DATETIME",
11203
+ "CREATED_TIME",
11204
+ "UPDATED_TIME"
11205
+ ]);
11206
+ function resolveJoinFieldOwner(field, sources) {
11207
+ if (field.tableAlias !== null) {
11208
+ const aliases = sources.filter((source2) => source2.alias === field.tableAlias);
11209
+ if (aliases.length > 1) return Object.freeze({ status: "AMBIGUOUS" });
11210
+ if (aliases.length === 0) return Object.freeze({ status: "UNKNOWN" });
11211
+ const source = aliases[0];
11212
+ if (!source.fieldTypes.has(field.field)) return Object.freeze({ status: "UNKNOWN" });
11213
+ return owned(source, field.field);
11214
+ }
11215
+ const matches = sources.filter((source) => source.fieldTypes.has(field.field));
11216
+ if (matches.length > 1) return Object.freeze({ status: "AMBIGUOUS" });
11217
+ if (matches.length === 0) return Object.freeze({ status: "UNKNOWN" });
11218
+ return owned(matches[0], field.field);
11219
+ }
11220
+ function classifyJoinPushdownLeaf(predicate, sources) {
11221
+ if (predicate.left.type !== "FIELD") return unsafe();
11222
+ const owner = resolveJoinFieldOwner(predicate.left, sources);
11223
+ if (owner.status !== "OWNED" || owner.source.sourceKind !== "APP") return unsafe();
11224
+ const fieldType = owner.source.fieldTypes.get(owner.fieldCode);
11225
+ if (fieldType === void 0 || fieldType.startsWith("KSQL_")) return unsafe();
11226
+ const capability = classifyWhereCapability(predicate, (field) => {
11227
+ const resolved = resolveJoinFieldOwner(field, sources);
11228
+ if (resolved.status !== "OWNED" || resolved.source !== owner.source) return void 0;
11229
+ const type = resolved.source.fieldTypes.get(resolved.fieldCode);
11230
+ return type === void 0 ? void 0 : resolveFieldSemantics({ fieldType: type });
11231
+ });
11232
+ if (capability.capability !== "EXACT_PUSHDOWN") return unsafe();
11233
+ const relation = classifySupportedLeaf(predicate, owner, fieldType);
11234
+ return relation === "unsafe" ? unsafe() : Object.freeze({ relation, owner });
11112
11235
  }
11113
- function applyJoin(leftRows, rightRows, join2, columns = {}) {
11114
- const { on, type: joinType } = join2;
11115
- const leftKey = on.left.tableAlias ? `${on.left.tableAlias}.${on.left.field}` : on.left.field;
11116
- const rightKey = on.right.tableAlias ? `${on.right.tableAlias}.${on.right.field}` : on.right.field;
11117
- assertJoinKeyAvailable(leftRows, leftKey, columns.leftColumns);
11118
- assertJoinKeyAvailable(rightRows, rightKey, columns.rightColumns);
11119
- if (joinType === "RIGHT") {
11120
- const leftIndex = /* @__PURE__ */ new Map();
11121
- for (const lRow of leftRows) {
11122
- const k = lRow[leftKey] ?? "";
11123
- const bucket = leftIndex.get(k);
11124
- if (bucket) bucket.push(lRow);
11125
- else leftIndex.set(k, [lRow]);
11126
- }
11127
- const emptyLeft = {};
11128
- for (const key of columns.leftColumns ?? Object.keys(leftRows[0] ?? {})) emptyLeft[key] = "";
11129
- const result2 = [];
11130
- for (const rRow of rightRows) {
11131
- const k = rRow[rightKey] ?? "";
11132
- const matched = leftIndex.get(k) ?? [];
11133
- if (matched.length > 0) {
11134
- for (const lRow of matched) result2.push({ ...lRow, ...rRow });
11135
- } else {
11136
- result2.push({ ...emptyLeft, ...rRow });
11236
+ function buildJoinPushdownPlan(where, sources) {
11237
+ const allKlikes = /* @__PURE__ */ new Set();
11238
+ collectKlikes2(where, allKlikes);
11239
+ const fragments = where === null ? [] : mergeAndFragments(classifyTree(where, sources));
11240
+ const items = fragments.map((fragment) => Object.freeze({
11241
+ targetAlias: fragment.owner.alias,
11242
+ appId: fragment.owner.appId,
11243
+ predicate: fragment.predicate,
11244
+ relation: fragment.relation
11245
+ }));
11246
+ const appliedKlikes = /* @__PURE__ */ new Set();
11247
+ for (const item of items) collectKlikes2(item.predicate, appliedKlikes);
11248
+ return Object.freeze({
11249
+ items: Object.freeze(items),
11250
+ appliedKlikes,
11251
+ allKlikes: Object.freeze([...allKlikes]),
11252
+ rejections: Object.freeze(collectRejections(where, sources))
11253
+ });
11254
+ }
11255
+ function collectRejections(where, sources) {
11256
+ if (where === null) return [];
11257
+ const reasons = /* @__PURE__ */ new Set();
11258
+ const visit = (expr) => {
11259
+ if (expr.type === "BINARY") {
11260
+ if (expr.left.type !== "FIELD") {
11261
+ reasons.add("UNSAFE_RELATION");
11262
+ return;
11137
11263
  }
11138
- }
11139
- return result2;
11140
- }
11141
- const rightIndex = /* @__PURE__ */ new Map();
11142
- for (const rRow of rightRows) {
11143
- const k = rRow[rightKey] ?? "";
11144
- const bucket = rightIndex.get(k);
11145
- if (bucket) bucket.push(rRow);
11146
- else rightIndex.set(k, [rRow]);
11147
- }
11148
- const result = [];
11149
- const emptyRight = {};
11150
- for (const key of columns.rightColumns ?? Object.keys(rightRows[0] ?? {})) emptyRight[key] = "";
11151
- for (const lRow of leftRows) {
11152
- const k = lRow[leftKey] ?? "";
11153
- const matched = rightIndex.get(k) ?? [];
11154
- if (matched.length > 0) {
11155
- for (const rRow of matched) {
11156
- result.push({ ...lRow, ...rRow });
11264
+ const owner = resolveJoinFieldOwner(expr.left, sources);
11265
+ if (owner.status === "AMBIGUOUS") {
11266
+ reasons.add("AMBIGUOUS_FIELD");
11267
+ return;
11157
11268
  }
11158
- } else if (joinType === "LEFT") {
11159
- result.push({ ...lRow, ...emptyRight });
11160
- }
11161
- }
11162
- return result;
11163
- }
11164
- function assertJoinKeyAvailable(rows, key, savedColumns) {
11165
- const missing = rows.length > 0 ? rows.some((row) => !Object.prototype.hasOwnProperty.call(row, key)) : savedColumns !== void 0 && !savedColumns.includes(key);
11166
- if (missing) {
11167
- throw new Error(`ArgumentError: JOIN key ${key} is not available in the materialized table.`);
11168
- }
11169
- }
11170
- function applyFilter(rows, where, resolveFieldType, appliedKlikes, resolveFieldSemantics2) {
11171
- if (where === null) return rows;
11172
- return rows.filter((row) => evalWhere(where, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2));
11173
- }
11174
- function hasAggregateColumns(columns) {
11175
- return columns.some(
11176
- (c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL" || c.type === "STRFUNC_COL" && hasAggregateInStringFuncExpr2(c.expr) || c.type === "SCALAR_VALUE_COL" && scalarValueHasAggregate2(c.expr)
11177
- );
11178
- }
11179
- function applyGroupBy(rows, groupByKeys, columns, resolveAggSortKind, resolutionPlan, aliasEvaluationContext = {}) {
11180
- if (resolutionPlan && resolutionPlan.items.length !== groupByKeys.length) {
11181
- throw new Error("InternalError: plain GROUP BY resolution plan length does not match group keys.");
11182
- }
11183
- const groups = /* @__PURE__ */ new Map();
11184
- for (const row of rows) {
11185
- const key = groupByKeys.map(
11186
- (groupKey, index) => evalGroupByKey(
11187
- groupKey,
11188
- row,
11189
- resolutionPlan?.items[index],
11190
- columns,
11191
- aliasEvaluationContext
11192
- )
11193
- ).join("\0");
11194
- const bucket = groups.get(key);
11195
- if (bucket) bucket.push(row);
11196
- else groups.set(key, [row]);
11197
- }
11198
- if (groups.size === 0 && groupByKeys.length === 0 && hasAggregateColumns(columns)) {
11199
- groups.set("", []);
11200
- }
11201
- const result = [];
11202
- for (const groupRows of groups.values()) {
11203
- const outRow = { ...groupRows[0] };
11204
- for (const k of groupByKeys) {
11205
- if (k.type === "ARITH_KEY") {
11206
- outRow[arithColDefaultKey(k.expr)] = String(evalArithExpr(k.expr, groupRows[0]));
11207
- } else if (k.type === "FUNC_KEY") {
11208
- outRow[stringFuncDefaultKey(k.expr)] = evalStringFunc(k.expr, groupRows[0]);
11269
+ if (owner.status === "UNKNOWN") {
11270
+ reasons.add("UNKNOWN_FIELD_OR_METADATA");
11271
+ return;
11209
11272
  }
11273
+ if (containsFieldReference(expr.right)) {
11274
+ reasons.add("CROSS_TABLE_BINARY");
11275
+ return;
11276
+ }
11277
+ if (classifyJoinPushdownLeaf(expr, sources).relation === "unsafe") {
11278
+ reasons.add("UNSAFE_RELATION");
11279
+ }
11280
+ return;
11210
11281
  }
11211
- materializeAggregateColumns(outRow, groupRows, columns, resolveAggSortKind);
11212
- result.push(outRow);
11213
- }
11214
- return result;
11215
- }
11216
- function applyGroupingSets(rows, spec, columns, resolveAggSortKind, limits = {}) {
11217
- const result = [];
11218
- let generatedRows = 0;
11219
- const countBucket = () => {
11220
- generatedRows++;
11221
- if (limits.maxGeneratedRows !== void 0 && generatedRows > limits.maxGeneratedRows) {
11222
- throw new Error(
11223
- `LimitError: B65 generated grouping rows ${generatedRows} exceed limit ${limits.maxGeneratedRows} (reason=GROUPING_OUTPUT_LIMIT_EXCEEDED).`
11224
- );
11225
- }
11226
- };
11227
- for (const set of spec.sets) {
11228
- const root = { children: /* @__PURE__ */ new Map() };
11229
- const buckets = [];
11230
- for (const row of rows) {
11231
- let node = root;
11232
- for (const item of set.items) {
11233
- const value = groupingItemValue(item, row);
11234
- let child = node.children.get(value);
11235
- if (!child) {
11236
- child = { children: /* @__PURE__ */ new Map() };
11237
- node.children.set(value, child);
11282
+ if (expr.type === "LOGICAL") {
11283
+ if (expr.op === "OR") {
11284
+ if (containsKlike2(expr)) {
11285
+ reasons.add("KLIKE_OR");
11286
+ return;
11287
+ }
11288
+ const left = classifyTree(expr.left, sources);
11289
+ const right = classifyTree(expr.right, sources);
11290
+ if (left.length !== 1 || right.length !== 1 || !sameOwner(left[0].owner, right[0].owner)) {
11291
+ reasons.add("CROSS_ALIAS_OR");
11292
+ return;
11238
11293
  }
11239
- node = child;
11240
- }
11241
- if (!node.rows) {
11242
- countBucket();
11243
- node.rows = [];
11244
- buckets.push(node.rows);
11245
11294
  }
11246
- node.rows.push(row);
11295
+ visit(expr.left);
11296
+ visit(expr.right);
11297
+ return;
11247
11298
  }
11248
- if (rows.length === 0 && set.items.length === 0) {
11249
- countBucket();
11250
- root.rows = [];
11251
- buckets.push(root.rows);
11299
+ if (expr.type === "GROUP") {
11300
+ visit(expr.expr);
11301
+ return;
11252
11302
  }
11253
- const includedCanonicalIds = new Set(set.items.map((item) => item.canonicalId));
11254
- for (const groupRows of buckets) {
11255
- const outRow = { ...groupRows[0] };
11256
- const includedValues = /* @__PURE__ */ new Map();
11257
- for (const item of set.items) {
11258
- if (!includedValues.has(item.canonicalId)) {
11259
- includedValues.set(item.canonicalId, groupingItemValue(item, groupRows[0]));
11260
- }
11303
+ if (expr.type === "NOT") {
11304
+ reasons.add("NOT");
11305
+ return;
11306
+ }
11307
+ reasons.add("UNSAFE_RELATION");
11308
+ };
11309
+ visit(where);
11310
+ return [...reasons].map((reason) => Object.freeze({ reason }));
11311
+ }
11312
+ function serializeJoinPushdownItem(item, sources) {
11313
+ assertPredicateOwnership(item.predicate, item, sources);
11314
+ return whereToKintone(item.predicate);
11315
+ }
11316
+ function assertPredicateOwnership(predicate, item, sources) {
11317
+ switch (predicate.type) {
11318
+ case "LOGICAL":
11319
+ assertPredicateOwnership(predicate.left, item, sources);
11320
+ assertPredicateOwnership(predicate.right, item, sources);
11321
+ return;
11322
+ case "GROUP":
11323
+ assertPredicateOwnership(predicate.expr, item, sources);
11324
+ return;
11325
+ case "BINARY": {
11326
+ if (predicate.left.type !== "FIELD" || containsFieldReference(predicate.right)) {
11327
+ throw joinOwnershipError(item, "predicate contains a non-target or RHS field reference");
11261
11328
  }
11262
- for (const item of spec.allItems) {
11263
- const value = includedValues.get(item.canonicalId) ?? "";
11264
- outRow[item.directKey] = value;
11265
- if (item.unqualifiedBridgeKey !== null) {
11266
- outRow[item.unqualifiedBridgeKey] = value;
11267
- }
11329
+ const owner = resolveJoinFieldOwner(predicate.left, sources);
11330
+ if (owner.status !== "OWNED" || owner.alias !== item.targetAlias || owner.appId !== item.appId || owner.source.sourceKind !== "APP") {
11331
+ throw joinOwnershipError(item, `field ${predicate.left.field} is not uniquely owned by the target`);
11268
11332
  }
11269
- materializeAggregateColumns(outRow, groupRows, columns, resolveAggSortKind);
11270
- attachGroupingRowMeta(outRow, includedCanonicalIds);
11271
- result.push(outRow);
11333
+ return;
11272
11334
  }
11335
+ case "NOT":
11336
+ case "NULL_CHECK":
11337
+ case "EXISTS":
11338
+ case "BOOLEAN":
11339
+ throw joinOwnershipError(item, "predicate shape is outside the Phase A serializer contract");
11273
11340
  }
11274
- return result;
11275
11341
  }
11276
- function groupingItemValue(item, row) {
11277
- if (!row) return "";
11278
- return row[item.directKey] ?? (item.unqualifiedBridgeKey === null ? void 0 : row[item.unqualifiedBridgeKey]) ?? "";
11342
+ function joinOwnershipError(item, reason) {
11343
+ return new Error(
11344
+ `InternalError: JOIN pushdown ownership guard failed for ${item.targetAlias}/APP${item.appId}: ${reason}.`
11345
+ );
11279
11346
  }
11280
- function materializeAggregateColumns(outRow, groupRows, columns, resolveAggSortKind) {
11281
- for (const col of columns) {
11282
- if (col.type === "AGGREGATE") {
11283
- const syntheticKey = aggregateSyntheticName(col.func, col.distinct, col.arg);
11284
- const value = String(evalAggregate(col.func, col.distinct, col.arg, col.separator, groupRows, resolveAggSortKind));
11285
- outRow[col.alias ?? syntheticKey] = value;
11286
- if (col.alias) outRow[syntheticKey] = value;
11287
- } else if (col.type === "ARITH_AGG_COL") {
11288
- const outputKey = col.alias ?? aggArithDefaultKey(col.expr);
11289
- outRow[outputKey] = String(evalAggArithExpr(col.expr, groupRows, resolveAggSortKind));
11290
- } else if (col.type === "STRFUNC_COL" && hasAggregateInStringFuncExpr2(col.expr)) {
11291
- const outputKey = col.alias ?? stringFuncDefaultKey(col.expr);
11292
- const resolvedExpr = resolveAggInStringFuncExpr(col.expr, groupRows, resolveAggSortKind);
11293
- outRow[outputKey] = evalStringFunc(resolvedExpr, outRow);
11294
- } else if (col.type === "SCALAR_VALUE_COL" && scalarValueHasAggregate2(col.expr)) {
11295
- const outputKey = col.alias ?? scalarValueDefaultKey(col.expr);
11296
- const resolvedExpr = resolveAggInScalarValue(col.expr, groupRows, resolveAggSortKind);
11297
- outRow[outputKey] = String(evalScalarValueExpr(resolvedExpr, outRow));
11347
+ function classifyTree(where, sources) {
11348
+ switch (where.type) {
11349
+ case "BINARY": {
11350
+ const classification = classifyJoinPushdownLeaf(where, sources);
11351
+ if (classification.relation === "unsafe" || classification.owner === void 0) return [];
11352
+ return [{
11353
+ owner: classification.owner,
11354
+ predicate: where,
11355
+ relation: classification.relation
11356
+ }];
11298
11357
  }
11358
+ case "LOGICAL": {
11359
+ const left = classifyTree(where.left, sources);
11360
+ const right = classifyTree(where.right, sources);
11361
+ if (where.op === "AND") return mergeAndFragments([...left, ...right]);
11362
+ if (containsKlike2(where)) return [];
11363
+ if (left.length !== 1 || right.length !== 1) return [];
11364
+ const a = left[0];
11365
+ const b = right[0];
11366
+ if (!sameOwner(a.owner, b.owner)) return [];
11367
+ return [{
11368
+ owner: a.owner,
11369
+ predicate: { type: "LOGICAL", op: "OR", left: a.predicate, right: b.predicate },
11370
+ relation: combineRelation(a.relation, b.relation)
11371
+ }];
11372
+ }
11373
+ case "GROUP":
11374
+ return classifyTree(where.expr, sources).map((fragment) => ({
11375
+ ...fragment,
11376
+ predicate: { type: "GROUP", expr: fragment.predicate }
11377
+ }));
11378
+ case "NOT":
11379
+ case "NULL_CHECK":
11380
+ case "EXISTS":
11381
+ case "BOOLEAN":
11382
+ return [];
11299
11383
  }
11300
11384
  }
11301
- function evalGroupByKey(key, row, resolution, columns, aliasEvaluationContext) {
11302
- if (key.type === "FIELD_NAME") {
11303
- if (!resolution) return row[key.name] ?? "";
11304
- if (resolution.kind === "PHYSICAL") return row[resolution.runtimeKey] ?? "";
11305
- if (resolution.kind === "ALIAS_SAFE") {
11306
- const column = columns[resolution.columnIndex];
11307
- if (!column) {
11308
- throw new Error(
11309
- `InternalError: GROUP BY alias column index ${resolution.columnIndex} is out of range.`
11310
- );
11311
- }
11312
- const value = evaluateSelectColumnValue(
11313
- column,
11314
- row,
11315
- resolution.columnIndex,
11316
- aliasEvaluationContext
11317
- );
11318
- if (typeof value !== "string") {
11319
- throw new Error("InternalError: GROUP BY alias resolved to an expanded SELECT column.");
11320
- }
11321
- return value;
11385
+ function mergeAndFragments(fragments) {
11386
+ const merged = [];
11387
+ for (const fragment of fragments) {
11388
+ const index = merged.findIndex((candidate) => sameOwner(candidate.owner, fragment.owner));
11389
+ if (index < 0) {
11390
+ merged.push(fragment);
11391
+ continue;
11322
11392
  }
11323
- throw new Error(
11324
- `InternalError: unresolved plain GROUP BY item ${resolution.kind} reached evaluation.`
11325
- );
11393
+ const current = merged[index];
11394
+ merged[index] = {
11395
+ owner: current.owner,
11396
+ predicate: {
11397
+ type: "LOGICAL",
11398
+ op: "AND",
11399
+ left: current.predicate,
11400
+ right: fragment.predicate
11401
+ },
11402
+ relation: combineRelation(current.relation, fragment.relation)
11403
+ };
11326
11404
  }
11327
- if (key.type === "FUNC_KEY") return evalStringFunc(key.expr, row);
11328
- return String(evalArithExpr(key.expr, row));
11405
+ return merged;
11329
11406
  }
11330
- function evalAggregate(func, distinct, arg, separator, rows, resolveAggSortKind) {
11331
- if (arg.type === "WILDCARD") {
11332
- return func === "COUNT" ? rows.length : 0;
11333
- }
11334
- const strValues = [];
11335
- for (const row of rows) {
11336
- let strVal;
11337
- if (arg.type === "FIELD_REF") {
11338
- const raw = row[arg.field];
11339
- if (raw === void 0 || raw === "" && func !== "MIN" && func !== "MAX") continue;
11340
- strVal = raw;
11341
- } else if (arg.type === "ARITH" || arg.type === "NUMBER" || arg.type === "STRING_FUNC") {
11342
- const n = evalArithExpr(arg, row);
11343
- if (isNaN(n)) continue;
11344
- strVal = String(n);
11345
- } else {
11346
- const value = evalScalarValueExprNullable(arg, row);
11347
- if (value === null) continue;
11348
- if (value === "" && func !== "MIN" && func !== "MAX") continue;
11349
- if (typeof value === "number" && Number.isNaN(value)) continue;
11350
- strVal = String(value);
11351
- }
11352
- strValues.push(strVal);
11353
- }
11354
- const statistical = func === "STDDEV_POP" || func === "STDDEV_SAMP" || func === "VAR_POP" || func === "VAR_SAMP" || func === "MEDIAN";
11355
- const numericValues = statistical ? strValues.map((value) => {
11356
- const numeric = Number(value);
11357
- if (!Number.isFinite(numeric)) {
11358
- throw new Error(`ArgumentError: ${func} \u306E\u5F15\u6570\u306B\u975E\u6570\u5024\u307E\u305F\u306F\u975E\u6709\u9650\u306E\u5024\u304C\u3042\u308A\u307E\u3059: ${value}`);
11359
- }
11360
- return numeric;
11361
- }) : null;
11362
- const eff = distinct ? statistical ? [...new Set(numericValues)] : [...new Set(strValues)] : statistical ? numericValues : strValues;
11363
- if (func === "COUNT") return eff.length;
11364
- if (func === "GROUP_CONCAT") return eff.join(separator ?? ",");
11365
- const comparison = func === "MIN" || func === "MAX" || func === "MODE" ? resolveAggregateArgSemantics(arg, resolveAggSortKind) : void 0;
11366
- const semantics = typeof comparison === "string" ? syntheticSemantics(comparison) : comparison ?? (arg.type === "FIELD_REF" || arg.type === "FIELD" || arg.type === "STRING" || arg.type === "CONCAT_OP" || arg.type === "CASE_WHEN" ? syntheticSemantics("string") : syntheticSemantics("number"));
11367
- if (func === "MODE") {
11368
- if (strValues.length === 0) return "";
11369
- const frequencies = /* @__PURE__ */ new Map();
11370
- for (const value of strValues) frequencies.set(value, (frequencies.get(value) ?? 0) + 1);
11371
- let result = "";
11372
- let maxFrequency = 0;
11373
- for (const [candidate, frequency] of frequencies) {
11374
- if (frequency > maxFrequency) {
11375
- result = candidate;
11376
- maxFrequency = frequency;
11377
- continue;
11378
- }
11379
- if (frequency !== maxFrequency) continue;
11380
- const canonical = compareCanonicalValues(candidate, result, semantics);
11381
- if (canonical < 0 || canonical === 0 && compareCodePointStrings(candidate, result) < 0) {
11382
- result = candidate;
11383
- }
11384
- }
11385
- return result;
11407
+ function classifySupportedLeaf(predicate, owner, fieldType) {
11408
+ if (predicate.op === "LIKE" || predicate.op === "NOT_LIKE") return "unsafe";
11409
+ if (predicate.op === "KLIKE" || predicate.op === "NOT_KLIKE") {
11410
+ return KLIKE_TYPES.has(fieldType) && predicate.right.type === "STRING" && predicate.right.value !== "" ? "exact" : "unsafe";
11386
11411
  }
11387
- if (func === "MIN" || func === "MAX") {
11388
- const comparableValues = eff;
11389
- if (comparableValues.length === 0) return 0;
11390
- let result = comparableValues[0];
11391
- for (const candidate of comparableValues.slice(1)) {
11392
- const cmp = compareCanonicalValues(candidate, result, semantics);
11393
- if (func === "MAX" && cmp > 0 || func === "MIN" && cmp < 0) result = candidate;
11394
- }
11395
- return result;
11412
+ if (fieldType === "__ID__" || owner.fieldCode === "$id") {
11413
+ return isPositiveSafeInteger(predicate.right) && (predicate.op === "=" || predicate.op === "<" || predicate.op === ">" || predicate.op === "<=" || predicate.op === ">=") ? "exact" : "unsafe";
11396
11414
  }
11397
- const nums = statistical ? eff : eff.map(Number);
11398
- switch (func) {
11399
- case "SUM":
11400
- return nums.reduce((a, b) => a + b, 0);
11401
- case "AVG":
11402
- return nums.length === 0 ? 0 : nums.reduce((a, b) => a + b, 0) / nums.length;
11403
- case "MEDIAN": {
11404
- if (nums.length === 0) return "";
11405
- const sorted = [...nums].sort((a, b) => a - b);
11406
- const middle = Math.floor(sorted.length / 2);
11407
- return sorted.length % 2 === 1 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
11408
- }
11409
- case "VAR_POP":
11410
- case "VAR_SAMP":
11411
- case "STDDEV_POP":
11412
- case "STDDEV_SAMP": {
11413
- const sample = func === "VAR_SAMP" || func === "STDDEV_SAMP";
11414
- if (nums.length === 0 || sample && nums.length < 2) return "";
11415
- const origin = nums[0];
11416
- let count = 0;
11417
- let mean = 0;
11418
- let m2 = 0;
11419
- for (const rawValue of nums) {
11420
- const value = rawValue - origin;
11421
- count++;
11422
- const delta = value - mean;
11423
- mean += delta / count;
11424
- m2 += delta * (value - mean);
11425
- }
11426
- const variance = Math.max(0, m2 / (sample ? count - 1 : count));
11427
- return func === "STDDEV_POP" || func === "STDDEV_SAMP" ? Math.sqrt(variance) : variance;
11428
- }
11415
+ if (fieldType === "RECORD_NUMBER") {
11416
+ return "unsafe";
11429
11417
  }
11418
+ if (fieldType === "NUMBER") {
11419
+ if (predicate.right.type !== "NUMBER") return "unsafe";
11420
+ if (predicate.op === "=") return "superset";
11421
+ return (predicate.op === "<" || predicate.op === ">") && isSafeIntegerLiteral(predicate.right) ? "superset" : "unsafe";
11422
+ }
11423
+ if (fieldType === "SINGLE_LINE_TEXT") {
11424
+ return predicate.op === "=" && predicate.right.type === "STRING" && predicate.right.value !== "" ? "superset" : "unsafe";
11425
+ }
11426
+ if (fieldType === "DATE" || fieldType === "TIME" || DATETIME_EQUALITY_TYPES.has(fieldType)) {
11427
+ if (predicate.op !== "=" || predicate.right.type !== "STRING") return "unsafe";
11428
+ if (fieldType === "DATE") return isCanonicalDate(predicate.right.value) ? "superset" : "unsafe";
11429
+ if (fieldType === "TIME") return isCanonicalTime(predicate.right.value) ? "superset" : "unsafe";
11430
+ return isCanonicalDateTime(predicate.right.value) ? "superset" : "unsafe";
11431
+ }
11432
+ if (SELECTION_TYPES.has(fieldType)) {
11433
+ if (predicate.op !== "IN" && predicate.op !== "NOT_IN" || predicate.right.type !== "IN_LIST" || predicate.right.values.length === 0) return "unsafe";
11434
+ const options = owner.source.fieldOptions?.get(owner.fieldCode);
11435
+ if (options === void 0) return "unsafe";
11436
+ return predicate.right.values.every(
11437
+ (value) => value.type === "STRING" && value.value !== "" && options.has(value.value)
11438
+ ) ? "exact" : "unsafe";
11439
+ }
11440
+ return "unsafe";
11441
+ }
11442
+ function owned(source, fieldCode) {
11443
+ return Object.freeze({
11444
+ status: "OWNED",
11445
+ alias: source.alias,
11446
+ appId: source.appId,
11447
+ fieldCode,
11448
+ source
11449
+ });
11430
11450
  }
11431
- function toAggregateFieldRef(field) {
11432
- const dot = field.indexOf(".");
11433
- return dot > 0 ? { type: "FIELD", tableAlias: field.slice(0, dot), field: field.slice(dot + 1) } : { type: "FIELD", tableAlias: null, field };
11451
+ function unsafe() {
11452
+ return Object.freeze({ relation: "unsafe" });
11434
11453
  }
11435
- function evalAggArithExpr(node, rows, resolveAggSortKind) {
11436
- if (node.type === "NUMBER") return node.value;
11437
- if (node.type === "AGG_REF") return Number(evalAggregate(node.func, node.distinct, node.arg, node.separator, rows, resolveAggSortKind));
11438
- const l = evalAggArithExpr(node.left, rows, resolveAggSortKind);
11439
- const r = evalAggArithExpr(node.right, rows, resolveAggSortKind);
11440
- switch (node.op) {
11441
- case "+":
11442
- return l + r;
11443
- case "-":
11444
- return l - r;
11445
- case "*":
11446
- return l * r;
11447
- case "/":
11448
- return r !== 0 ? l / r : NaN;
11449
- case "%":
11450
- return r !== 0 ? l % r : NaN;
11451
- }
11454
+ function sameOwner(left, right) {
11455
+ return left.alias === right.alias && left.appId === right.appId && left.source === right.source;
11452
11456
  }
11453
- function aggArithDefaultKey(node) {
11454
- return aggregateOperandLabel(node);
11457
+ function combineRelation(left, right) {
11458
+ return left === "exact" && right === "exact" ? "exact" : "superset";
11455
11459
  }
11456
- function resolveAggregateArgSemantics(arg, resolver) {
11457
- if (arg.type === "FIELD_REF") return resolver?.(toAggregateFieldRef(arg.field)) ?? "string";
11458
- if (arg.type === "FIELD") return resolver?.(arg) ?? "string";
11459
- if (arg.type === "NUMBER" || arg.type === "ARITH" || arg.type === "SCALAR_ARITH") return "number";
11460
- if (arg.type === "STRING" || arg.type === "CONCAT_OP" || arg.type === "VARIABLE") return "string";
11461
- if (arg.type === "STRING_FUNC") {
11462
- const numeric = /* @__PURE__ */ new Set(["LENGTH", "LENGTH_CHAR", "INSTR", "ROUND", "FLOOR", "CEIL", "TRUNCATE", "YEAR", "MONTH", "DAY", "DATEDIFF", "ABS", "MOD", "POWER", "SQRT", "DAYOFWEEK", "QUARTER", "WEEK"]);
11463
- if (arg.func === "CAST") return arg.args[1]?.type === "STRING" && arg.args[1].value === "NUMBER" ? "number" : "string";
11464
- return numeric.has(arg.func) ? "number" : "string";
11465
- }
11466
- const results = [...arg.branches.map((branch) => branch.result), ...arg.elseResult === null ? [] : [arg.elseResult]].filter((result) => result.type !== "ARRAY").map((result) => resolveAggregateArgSemantics(result, resolver));
11467
- if (results.length === 0 || results.some((result) => result === void 0)) return "string";
11468
- const kinds = results.map((result) => typeof result === "string" ? result : result.compareMode === "number" || result.compareMode === "recordNumber" ? "number" : "string");
11469
- if (!kinds.every((kind) => kind === kinds[0])) return "string";
11470
- const first = results[0];
11471
- return results.every((result) => JSON.stringify(result) === JSON.stringify(first)) ? first : kinds[0];
11460
+ function isPositiveSafeInteger(value) {
11461
+ return value.type === "NUMBER" && isSafeIntegerLiteral(value) && value.value > 0;
11472
11462
  }
11473
- function applyHaving(rows, having, resolveFieldType, resolveFieldSemantics2) {
11474
- if (having === null) return rows;
11475
- return rows.filter((row) => evalWhere(having, row, resolveFieldType, void 0, resolveFieldSemantics2));
11463
+ function isSafeIntegerLiteral(value) {
11464
+ return /^-?\d+$/.test(numberLiteralText(value)) && Number.isSafeInteger(value.value);
11476
11465
  }
11477
- function applyDistinct(rows, columns, scalarCache, resolveFieldType, resolveFieldSemantics2) {
11478
- if (rows.length === 0) return rows;
11479
- const keyFor = buildDistinctKeyBuilder(rows, columns, {
11480
- scalarCache,
11481
- resolveFieldType,
11482
- resolveFieldSemantics: resolveFieldSemantics2
11483
- });
11484
- const seen = /* @__PURE__ */ new Set();
11485
- return rows.filter((row) => {
11486
- const key = keyFor(row);
11487
- if (seen.has(key)) return false;
11488
- seen.add(key);
11489
- return true;
11490
- });
11466
+ function isCanonicalDate(value) {
11467
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
11468
+ if (!match) return false;
11469
+ const year = Number(match[1]);
11470
+ const month = Number(match[2]);
11471
+ const day = Number(match[3]);
11472
+ const date = new Date(Date.UTC(year, month - 1, day));
11473
+ return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
11491
11474
  }
11492
- function buildDistinctKeyBuilder(rows, columns, context) {
11493
- let sortedWildcardKeys = [];
11494
- if (columns.some((c) => c.type === "WILDCARD")) {
11495
- const allKeys = /* @__PURE__ */ new Set();
11496
- for (const row of rows) {
11497
- for (const k of Object.keys(row)) allKeys.add(k);
11498
- }
11499
- sortedWildcardKeys = [...allKeys].sort();
11475
+ function isCanonicalTime(value) {
11476
+ const match = /^(\d{2}):(\d{2})$/.exec(value);
11477
+ return match !== null && Number(match[1]) <= 23 && Number(match[2]) <= 59;
11478
+ }
11479
+ function isCanonicalDateTime(value) {
11480
+ const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/.exec(value);
11481
+ if (!match || !isCanonicalDate(match[1])) return false;
11482
+ return Number(match[2]) <= 23 && Number(match[3]) <= 59 && Number(match[4]) <= 59;
11483
+ }
11484
+ function collectKlikes2(where, out) {
11485
+ if (where === null) return;
11486
+ if (isKlike(where)) {
11487
+ out.add(where);
11488
+ return;
11500
11489
  }
11501
- let sortedParentKeys = [];
11502
- if (columns.some((c) => c.type === "PARENT_WILDCARD")) {
11503
- const parentKeys = /* @__PURE__ */ new Set();
11504
- for (const row of rows) {
11505
- for (const k of Object.keys(row)) {
11506
- if (k.startsWith("_p.")) parentKeys.add(k);
11507
- }
11508
- }
11509
- sortedParentKeys = [...parentKeys].sort();
11490
+ switch (where.type) {
11491
+ case "LOGICAL":
11492
+ collectKlikes2(where.left, out);
11493
+ collectKlikes2(where.right, out);
11494
+ return;
11495
+ case "NOT":
11496
+ case "GROUP":
11497
+ collectKlikes2(where.expr, out);
11498
+ return;
11499
+ case "BINARY":
11500
+ case "NULL_CHECK":
11501
+ case "EXISTS":
11502
+ case "BOOLEAN":
11503
+ return;
11510
11504
  }
11511
- const distinctContext = {
11512
- ...context,
11513
- wildcardKeys: sortedWildcardKeys,
11514
- parentWildcardKeys: sortedParentKeys
11505
+ }
11506
+ function containsKlike2(where) {
11507
+ const found = /* @__PURE__ */ new Set();
11508
+ collectKlikes2(where, found);
11509
+ return found.size > 0;
11510
+ }
11511
+ function containsFieldReference(value) {
11512
+ if (value === null || typeof value !== "object") return false;
11513
+ if (Array.isArray(value)) return value.some(containsFieldReference);
11514
+ const record = value;
11515
+ if (record.type === "FIELD" || record.type === "FIELD_REF") return true;
11516
+ return Object.values(record).some(containsFieldReference);
11517
+ }
11518
+
11519
+ // src/core/optimization/applyParentSelectionPlan.ts
11520
+ function buildApplyParentSelectionPlan(where, metadata) {
11521
+ const plan = buildSingleTableKlikePushdownPlan(where, {
11522
+ allowUnqualifiedFields: true,
11523
+ allowKlike: true,
11524
+ fieldTypes: metadata.fieldTypes,
11525
+ fieldOptions: metadata.fieldOptions
11526
+ });
11527
+ return {
11528
+ prefilter: plan.condition,
11529
+ appliedKlikes: plan.appliedKlikes,
11530
+ unappliedKlikes: plan.allKlikes.filter((expr) => !plan.appliedKlikes.has(expr))
11515
11531
  };
11516
- return (row) => JSON.stringify(buildDistinctTuple(columns, row, distinctContext));
11517
11532
  }
11518
- function applyOrderBy(rows, orderBy, optionOrders, sortKinds, fieldSemantics2, aliasEvaluator) {
11519
- if (orderBy.length === 0) return rows;
11520
- return sortDecoratedRows(rows, orderBy, optionOrders, sortKinds, fieldSemantics2, aliasEvaluator).rows.map((item) => item.row);
11533
+
11534
+ // src/core/optimization/canonicalOrderPlanner.ts
11535
+ var REST_OFFSET_MAX = 1e4;
11536
+ var REST_LIMIT_MAX = 500;
11537
+ function fieldSemantics(item, semantics) {
11538
+ return item.key.type === "FIELD_NAME" ? semantics.get(item.key.name) : void 0;
11521
11539
  }
11522
- function sortDecoratedRows(rows, orderBy, optionOrders, sortKinds, fieldSemantics2, aliasEvaluator) {
11523
- const keyMeta = orderBy.map(({ key }) => {
11524
- if (key.type === "ARITH_KEY") return { semantics: syntheticSemantics("number") };
11525
- if (key.type === "FUNC_KEY") {
11526
- return { semantics: syntheticSemantics(NUMERIC_ORDER_FUNCTIONS.has(key.expr.func) ? "number" : "string") };
11527
- }
11528
- if (key.type === "GROUPING_KEY") return { semantics: syntheticSemantics("number") };
11529
- const semantics = fieldSemantics2?.get(key.name);
11530
- if (semantics) return { semantics };
11531
- const orderMap = optionOrders?.get(key.name);
11532
- if (orderMap) {
11533
- return {
11534
- semantics: {
11535
- fieldType: "MULTI_SELECT",
11536
- compareMode: "option",
11537
- inSubtable: false,
11538
- requiresCollectionOperators: false,
11539
- optionOrder: orderMap
11540
- }
11541
- };
11542
- }
11543
- return { semantics: syntheticSemantics(sortKinds?.get(key.name) ?? "string") };
11544
- });
11545
- const decorated = rows.map((row) => ({
11546
- row,
11547
- keys: orderBy.map(({ key }, i) => {
11548
- const s = evalOrderKey(key, row, aliasEvaluator);
11549
- return { s };
11550
- })
11551
- }));
11552
- const compare = (a, b) => compareDecoratedRows(a, b, orderBy, keyMeta);
11553
- decorated.sort(compare);
11554
- return { rows: decorated, compare };
11540
+ function hasSelectOutputAlias(stmt, name) {
11541
+ return stmt.columns.some((column) => "alias" in column && column.alias === name);
11555
11542
  }
11556
- function compareDecoratedRows(a, b, orderBy, keyMeta) {
11557
- for (let i = 0; i < orderBy.length; i++) {
11558
- const cmp = compareSortKeys(a.keys[i], b.keys[i], keyMeta[i]);
11559
- if (cmp !== 0) return orderBy[i].direction === "ASC" ? cmp : -cmp;
11543
+ function planCanonicalOrder(input) {
11544
+ const { stmt } = input;
11545
+ const reasons = [];
11546
+ const windowOrderBy = stmt.columns.flatMap(
11547
+ (column) => column.type === "WINDOW_COL" ? column.orderBy : []
11548
+ );
11549
+ const allOrderBy = [...stmt.orderBy, ...windowOrderBy];
11550
+ for (const item of allOrderBy) {
11551
+ if (item.key.type !== "FIELD_NAME") continue;
11552
+ const semantics = fieldSemantics(item, input.orderSemantics);
11553
+ if (!semantics) {
11554
+ reasons.push("ORDER_KEY_UNRESOLVED");
11555
+ continue;
11556
+ }
11557
+ if (semantics.fieldType === "KSQL_AMBIGUOUS") reasons.push("ORDER_KEY_AMBIGUOUS");
11558
+ else if (semantics.compareMode === "unsupported") reasons.push("ORDER_KEY_UNSUPPORTED");
11560
11559
  }
11561
- return 0;
11562
- }
11563
- function compareSortKeys(a, b, meta) {
11564
- return compareCanonicalValues(a.s, b.s, meta.semantics);
11560
+ if (reasons.includes("ORDER_KEY_AMBIGUOUS")) {
11561
+ throw new Error(
11562
+ "ArgumentError: ORDER BY key is an ambiguous column reference (reason=ORDER_KEY_AMBIGUOUS). Qualify the key with its table alias."
11563
+ );
11564
+ }
11565
+ if (reasons.includes("ORDER_KEY_UNSUPPORTED") || reasons.includes("ORDER_KEY_UNRESOLVED")) {
11566
+ const reason = reasons.includes("ORDER_KEY_UNSUPPORTED") ? "ORDER_KEY_UNSUPPORTED" : "ORDER_KEY_UNRESOLVED";
11567
+ throw new Error(`ArgumentError: ORDER BY key has no canonical comparison contract (reason=${reason}).`);
11568
+ }
11569
+ const allRestEquivalent = stmt.orderBy.length > 0 && windowOrderBy.length === 0 && stmt.orderBy.every(
11570
+ (item) => item.key.type === "FIELD_NAME" && item.key.name === "$id" && !hasSelectOutputAlias(stmt, item.key.name)
11571
+ );
11572
+ if (!allRestEquivalent) reasons.push("ORDER_KEY_NOT_REST_EQUIVALENT");
11573
+ if (input.whereCapability !== "EXACT_PUSHDOWN") reasons.push("WHERE_NOT_EXACT");
11574
+ if (input.staticMode !== "SIMPLE") reasons.push("QUERY_SHAPE_LOCAL");
11575
+ if (stmt.limit === null || stmt.limit < 0 || stmt.limit > REST_LIMIT_MAX) {
11576
+ reasons.push("LIMIT_NOT_REST_WINDOW");
11577
+ }
11578
+ if ((stmt.offset ?? 0) < 0 || (stmt.offset ?? 0) > REST_OFFSET_MAX) {
11579
+ reasons.push("OFFSET_NOT_REST_WINDOW");
11580
+ }
11581
+ if (stmt.limit !== null && stmt.limit > input.maxRecords) reasons.push("MAX_RECORDS_WINDOW");
11582
+ if (input.hasKlike) reasons.push("KLIKE_NOT_REST_WINDOW");
11583
+ if (reasons.length === 0) {
11584
+ return {
11585
+ kind: "CANONICAL_REST_TOP_N",
11586
+ requiresCompleteInput: false,
11587
+ localOrderBy: false,
11588
+ applyLocalOffsetLimit: false,
11589
+ reasonCodes: []
11590
+ };
11591
+ }
11592
+ return {
11593
+ kind: "CANONICAL_LOCAL",
11594
+ requiresCompleteInput: allOrderBy.length > 0,
11595
+ localOrderBy: stmt.orderBy.length > 0,
11596
+ applyLocalOffsetLimit: stmt.orderBy.length > 0,
11597
+ reasonCodes: [...new Set(reasons)]
11598
+ };
11565
11599
  }
11566
- var NUMERIC_ORDER_FUNCTIONS = /* @__PURE__ */ new Set([
11567
- "LENGTH",
11568
- "LENGTH_CHAR",
11569
- "INSTR",
11570
- "ROUND",
11571
- "FLOOR",
11572
- "CEIL",
11573
- "TRUNCATE",
11574
- "YEAR",
11575
- "MONTH",
11576
- "DAY",
11577
- "DATEDIFF",
11578
- "ABS",
11579
- "MOD",
11580
- "POWER",
11581
- "SQRT",
11582
- "DAYOFWEEK",
11583
- "QUARTER",
11584
- "WEEK"
11600
+
11601
+ // src/core/optimization/korderPlanner.ts
11602
+ var KORDER_NATIVE_FIELD_TYPES = /* @__PURE__ */ new Set([
11603
+ "RECORD_NUMBER",
11604
+ "SINGLE_LINE_TEXT",
11605
+ "NUMBER",
11606
+ "CALC",
11607
+ "DATE",
11608
+ "DATETIME",
11609
+ "TIME",
11610
+ "CREATED_TIME",
11611
+ "UPDATED_TIME",
11612
+ "DROP_DOWN",
11613
+ "RADIO_BUTTON",
11614
+ "STATUS",
11615
+ "LINK",
11616
+ "CREATOR",
11617
+ "MODIFIER"
11585
11618
  ]);
11586
- function evalOrderKey(key, row, aliasEvaluator) {
11587
- switch (key.type) {
11588
- case "FIELD_NAME":
11589
- return aliasEvaluator?.(key.name, row) ?? row[key.name] ?? "";
11590
- case "ARITH_KEY":
11591
- return String(evalArithExpr(key.expr, row));
11592
- case "FUNC_KEY":
11593
- return evalStringFunc(key.expr, row);
11594
- case "GROUPING_KEY":
11595
- return evalGroupingRef(key.ref, row);
11596
- }
11619
+ function hasSelectOutputAlias2(stmt, name) {
11620
+ return stmt.columns.some((column) => "alias" in column && column.alias === name);
11597
11621
  }
11598
- function buildOrderByAliasEvaluator(columns, scalarCache, resolveFieldType, resolveFieldSemantics2) {
11599
- const evaluators = /* @__PURE__ */ new Map();
11600
- for (const [columnIndex, column] of columns.entries()) {
11601
- if (!("alias" in column) || column.alias === null) continue;
11602
- const alias = column.alias;
11603
- switch (column.type) {
11604
- case "FIELD":
11605
- evaluators.set(alias, (row) => resolveFieldRef(row, column.field));
11606
- break;
11607
- case "LITERAL_COL":
11608
- evaluators.set(alias, () => column.value);
11609
- break;
11610
- case "AGGREGATE": {
11611
- const source = aggregateSyntheticName(column.func, column.distinct, column.arg);
11612
- evaluators.set(alias, (row) => row[alias] ?? row[source] ?? "0");
11613
- break;
11614
- }
11615
- case "ARITH_AGG_COL": {
11616
- const source = aggArithDefaultKey(column.expr);
11617
- evaluators.set(alias, (row) => row[alias] ?? row[source] ?? "0");
11618
- break;
11619
- }
11620
- case "WINDOW_COL":
11621
- evaluators.set(alias, (row) => row[alias] ?? "");
11622
- break;
11623
- case "ARITH_COL":
11624
- evaluators.set(alias, (row) => String(evalArithExpr(column.expr, row)));
11625
- break;
11626
- case "STRFUNC_COL": {
11627
- const source = stringFuncDefaultKey(column.expr);
11628
- evaluators.set(alias, (row) => hasAggregateInStringFuncExpr2(column.expr) ? row[alias] ?? row[source] ?? evalStringFunc(column.expr, row, resolveFieldType, resolveFieldSemantics2) : evalStringFunc(column.expr, row, resolveFieldType, resolveFieldSemantics2));
11629
- break;
11630
- }
11631
- case "CASE_COL":
11632
- evaluators.set(alias, (row) => evalCaseWhen(column.expr, row, resolveFieldType, resolveFieldSemantics2));
11633
- break;
11634
- case "SCALAR_VALUE_COL": {
11635
- const source = scalarValueDefaultKey(column.expr);
11636
- evaluators.set(alias, (row) => scalarValueHasAggregate2(column.expr) ? row[alias] ?? row[source] ?? "" : String(evalScalarValueExpr(column.expr, row, resolveFieldType, resolveFieldSemantics2)));
11637
- break;
11638
- }
11639
- case "SCALAR_SUBQUERY_COL":
11640
- evaluators.set(alias, () => scalarCache?.get(columnIndex) ?? "");
11641
- break;
11642
- case "GROUPING_COL":
11643
- evaluators.set(alias, (row) => evalGroupingRef(column.ref, row));
11644
- break;
11645
- case "VARIABLE_COL":
11646
- break;
11647
- }
11622
+ function planKorder(input) {
11623
+ const { stmt } = input;
11624
+ const reasons = [];
11625
+ if (stmt.orderMode !== "KINTONE_NATIVE") reasons.push("KORDER_MODE_REQUIRED");
11626
+ if (stmt.from.cteName !== null || stmt.from.subtableCode || input.staticMode !== "SIMPLE") {
11627
+ reasons.push("KORDER_QUERY_SHAPE_UNSUPPORTED");
11648
11628
  }
11649
- return (name, row) => evaluators.get(name)?.(row);
11650
- }
11651
- function applyWindow(rows, columns, optionOrders, sortKinds, fieldSemantics2) {
11652
- const windows = columns.filter((column) => column.type === "WINDOW_COL");
11653
- if (rows.length === 0 || windows.length === 0) return rows;
11654
- for (const window of windows) {
11655
- const partitions = /* @__PURE__ */ new Map();
11656
- for (const row of rows) {
11657
- const key = JSON.stringify(window.partitionBy.map((ref) => resolveWindowField(row, ref)));
11658
- const partition = partitions.get(key);
11659
- if (partition) partition.push(row);
11660
- else partitions.set(key, [row]);
11629
+ if (input.whereCapability !== "EXACT_PUSHDOWN") {
11630
+ reasons.push(...(input.whereReasons ?? []).filter((reason) => reason.functionName !== void 0).map((reason) => reason.code));
11631
+ reasons.push("KORDER_WHERE_NOT_EXACT");
11632
+ }
11633
+ if (input.hasKlike) reasons.push("KORDER_KLIKE_UNSUPPORTED");
11634
+ if (stmt.orderBy.length === 0) reasons.push("KORDER_KEY_REQUIRED");
11635
+ for (const item of stmt.orderBy) {
11636
+ if (item.key.type !== "FIELD_NAME") {
11637
+ reasons.push(`KORDER_KEY_NOT_DIRECT_FIELD(key=${item.key.type})`);
11638
+ continue;
11661
11639
  }
11662
- for (const partition of partitions.values()) {
11663
- const sortedResult = sortDecoratedRows(partition, window.orderBy, optionOrders, sortKinds, fieldSemantics2);
11664
- const sorted = sortedResult.rows;
11665
- let rank = 1;
11666
- let denseRank = 1;
11667
- for (let index = 0; index < sorted.length; index++) {
11668
- if (index > 0 && sortedResult.compare(sorted[index - 1], sorted[index]) !== 0) {
11669
- rank = index + 1;
11670
- denseRank++;
11671
- }
11672
- const value = window.func === "ROW_NUMBER" ? index + 1 : window.func === "RANK" ? rank : denseRank;
11673
- sorted[index].row[window.alias] = String(value);
11674
- }
11640
+ const name = item.key.name;
11641
+ const semantics = input.orderSemantics.get(name);
11642
+ if (!semantics) {
11643
+ reasons.push(`KORDER_KEY_UNRESOLVED(field=${name})`);
11644
+ continue;
11645
+ }
11646
+ if (hasSelectOutputAlias2(stmt, name)) {
11647
+ reasons.push(`KORDER_KEY_NOT_DIRECT_FIELD(field=${name})`);
11648
+ continue;
11649
+ }
11650
+ if (name === "$id") continue;
11651
+ if (!semantics.source || semantics.source.fieldCode !== name) {
11652
+ reasons.push(`KORDER_KEY_NOT_DIRECT_FIELD(field=${name})`);
11653
+ continue;
11654
+ }
11655
+ if (!KORDER_NATIVE_FIELD_TYPES.has(semantics.fieldType)) {
11656
+ reasons.push(`KORDER_TYPE_UNSUPPORTED(field=${name}, type=${semantics.fieldType})`);
11675
11657
  }
11676
11658
  }
11677
- return rows;
11678
- }
11679
- function resolveWindowField(row, ref) {
11680
- const name = ref.tableAlias ? `${ref.tableAlias}.${ref.field}` : ref.field;
11681
- return resolveFieldRef(row, name);
11682
- }
11683
- function applyLimit(rows, limit, offset) {
11684
- const start = offset ?? 0;
11685
- if (limit === null) return rows.slice(start);
11686
- return rows.slice(start, start + limit);
11659
+ if (stmt.limit === null || !Number.isSafeInteger(stmt.limit) || stmt.limit < 0) {
11660
+ reasons.push(`KORDER_LIMIT_INVALID(limit=${String(stmt.limit)})`);
11661
+ }
11662
+ const offset = stmt.offset ?? 0;
11663
+ if (!Number.isSafeInteger(offset) || offset < 0) {
11664
+ reasons.push(`KORDER_OFFSET_INVALID(offset=${offset})`);
11665
+ }
11666
+ const scanRows = stmt.limit === null ? Number.NaN : offset + stmt.limit;
11667
+ if (stmt.limit !== null && !Number.isSafeInteger(scanRows)) {
11668
+ reasons.push(`KORDER_SCAN_ROWS_INVALID(offset=${offset}, limit=${stmt.limit})`);
11669
+ }
11670
+ const unique2 = [...new Set(reasons)];
11671
+ if (unique2.length > 0) {
11672
+ throw new Error(
11673
+ `ArgumentError: KORDER BY cannot be executed (mode=KINTONE_NATIVE; ${unique2.join(", ")}). Use ORDER BY for canonical local ordering or simplify the query.`
11674
+ );
11675
+ }
11676
+ const native = stmt.limit <= 500 && offset <= 1e4 && stmt.limit <= input.maxRecords;
11677
+ if (!native && scanRows > input.maxRecords) {
11678
+ throw new Error(
11679
+ `ArgumentError: KORDER BY cannot be executed (mode=KINTONE_NATIVE; KORDER_SCAN_ROWS_EXCEEDS_MAX_RECORDS(scanRows=${scanRows}, maxRecords=${input.maxRecords})). Use ORDER BY for canonical local ordering, raise maxRecords, or reduce LIMIT/OFFSET.`
11680
+ );
11681
+ }
11682
+ return {
11683
+ kind: native ? "KORDER_NATIVE" : "KORDER_CURSOR",
11684
+ requiresCompleteInput: false,
11685
+ localOrderBy: false,
11686
+ applyLocalOffsetLimit: false,
11687
+ reasonCodes: [],
11688
+ scanRows
11689
+ };
11687
11690
  }
11688
- function evaluateSelectColumnValue(column, row, columnIndex, context = {}) {
11689
- switch (column.type) {
11690
- case "VARIABLE_COL":
11691
- throw new Error(`internal error: unresolved SELECT variable @${column.name}`);
11692
- case "WILDCARD": {
11693
- const keys = context.wildcardKeys ?? Object.keys(row);
11694
- return {
11695
- kind: "EXPANDED",
11696
- entries: keys.map((key) => [key, row[key] !== void 0 ? row[key] : null])
11697
- };
11691
+
11692
+ // src/core/errors/cursorErrors.ts
11693
+ var CursorCapacityError = class extends Error {
11694
+ constructor(host, limit, waitMs) {
11695
+ super(`CursorCapacityError: host=${host} \u306E active cursor \u4E0A\u9650 ${limit} \u306B ${waitMs}ms \u4EE5\u5185\u3067\u7A7A\u304D\u304C\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002`);
11696
+ this.name = "CursorCapacityError";
11697
+ }
11698
+ };
11699
+ var CursorCreateOutcomeUnknownError = class extends Error {
11700
+ constructor(cause) {
11701
+ super("CursorCreateOutcomeUnknownError: Create Cursor \u306E\u6210\u5426\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002\u81EA\u52D5\u518D\u8A66\u884C\u305B\u305A\u3001\u6700\u592710\u5206+\u5B89\u5168\u4F59\u88D5\u306E\u9593\u306F\u67A0\u3092\u9694\u96E2\u3057\u307E\u3059\u3002");
11702
+ this.name = "CursorCreateOutcomeUnknownError";
11703
+ this.cause = cause;
11704
+ }
11705
+ };
11706
+ var CursorCleanupWarning = class extends Error {
11707
+ constructor(cause) {
11708
+ const detail = cause instanceof Error ? cause.message : String(cause);
11709
+ super(`CursorCleanupWarning: Cursor \u306E\u89E3\u653E\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002\u7D50\u679C\u306F\u6709\u52B9\u3067\u3059\u304C\u3001\u6700\u592710\u5206+\u5B89\u5168\u4F59\u88D5\u306E\u9593\u306F\u67A0\u3092\u9694\u96E2\u3057\u307E\u3059\u3002\u8A73\u7D30: ${detail}`);
11710
+ this.name = "CursorCleanupWarning";
11711
+ this.cause = cause;
11712
+ }
11713
+ };
11714
+
11715
+ // src/core/optimization/korderCursorExecutor.ts
11716
+ async function executeKorderCursor(input) {
11717
+ const handle = await input.client.openCursor({
11718
+ app: input.app,
11719
+ fields: input.fields.length > 0 ? input.fields : void 0,
11720
+ query: input.query,
11721
+ size: 500
11722
+ });
11723
+ const records = [];
11724
+ let seen = 0;
11725
+ let primaryError;
11726
+ let cleanupWarning;
11727
+ try {
11728
+ if (handle.totalCount > input.offset) {
11729
+ while (records.length < input.limit) {
11730
+ const page = await handle.nextPage();
11731
+ for (const record of page.records) {
11732
+ if (seen < input.offset) seen += 1;
11733
+ else if (records.length < input.limit) records.push(record);
11734
+ else break;
11735
+ }
11736
+ if (!page.next) break;
11737
+ }
11698
11738
  }
11699
- case "PARENT_WILDCARD": {
11700
- const keys = context.parentWildcardKeys ?? Object.keys(row).filter((key) => key.startsWith("_p.")).sort();
11701
- return {
11702
- kind: "EXPANDED",
11703
- entries: keys.map((key) => [key, row[key] !== void 0 ? row[key] : null])
11704
- };
11739
+ } catch (error) {
11740
+ primaryError = error;
11741
+ throw error;
11742
+ } finally {
11743
+ try {
11744
+ await handle.close();
11745
+ } catch (cleanupError) {
11746
+ if (primaryError && primaryError instanceof Error) {
11747
+ Object.defineProperty(primaryError, "cursorCleanupError", {
11748
+ value: cleanupError,
11749
+ configurable: true
11750
+ });
11751
+ } else {
11752
+ cleanupWarning = new CursorCleanupWarning(cleanupError).message;
11753
+ }
11754
+ }
11755
+ }
11756
+ return { records, cleanupWarning };
11757
+ }
11758
+
11759
+ // src/converter/korderCursorQuery.ts
11760
+ function buildKorderCursorQuery(stmt) {
11761
+ const parts = [];
11762
+ if (stmt.where) parts.push(whereToKintone(stmt.where));
11763
+ const order = stmt.orderBy.map((item) => {
11764
+ if (item.key.type !== "FIELD_NAME") {
11765
+ throw new Error("ArgumentError: KORDER cursor key must be a direct field.");
11705
11766
  }
11767
+ return `${item.key.name} ${item.direction === "ASC" ? "asc" : "desc"}`;
11768
+ });
11769
+ parts.push(`order by ${order.join(", ")}`);
11770
+ return parts.join(" ");
11771
+ }
11772
+
11773
+ // src/core/systemFields.ts
11774
+ var APP_SYSTEM_FIELD_CODES = [
11775
+ "$id",
11776
+ "$revision",
11777
+ "\u30EC\u30B3\u30FC\u30C9\u756A\u53F7",
11778
+ "\u4F5C\u6210\u8005",
11779
+ "\u4F5C\u6210\u65E5\u6642",
11780
+ "\u66F4\u65B0\u8005",
11781
+ "\u66F4\u65B0\u65E5\u6642",
11782
+ "\u30B9\u30C6\u30FC\u30BF\u30B9",
11783
+ "\u4F5C\u696D\u8005"
11784
+ ];
11785
+ function isSystemLikeFieldCode(code) {
11786
+ return code.startsWith("_") || code.startsWith("$");
11787
+ }
11788
+
11789
+ // src/core/optimization/plainGroupByPlan.ts
11790
+ var AGGREGATE_REFERENCE_PREFIX = /^(COUNT|SUM|AVG|MAX|MIN|GROUP_CONCAT|STDDEV_POP|STDDEV_SAMP|VAR_POP|VAR_SAMP|MEDIAN|MODE)\(/;
11791
+ function containsAggregateColumnNode(node) {
11792
+ if (node === null || typeof node !== "object") return false;
11793
+ if (Array.isArray(node)) return node.some(containsAggregateColumnNode);
11794
+ const value = node;
11795
+ if (value["type"] === "AGGREGATE" || value["type"] === "ARITH_AGG_COL") return true;
11796
+ if (value["type"] === "SELECT" || value["type"] === "SCALAR_SUBQUERY") return false;
11797
+ if (value["type"] === "FIELD" && typeof value["field"] === "string") {
11798
+ return AGGREGATE_REFERENCE_PREFIX.test(value["field"]);
11799
+ }
11800
+ return Object.values(value).some(containsAggregateColumnNode);
11801
+ }
11802
+ function classifyPreGroupAlias(column) {
11803
+ switch (column.type) {
11804
+ case "AGGREGATE":
11805
+ case "ARITH_AGG_COL":
11806
+ return "AGGREGATE_DEPENDENT";
11807
+ case "GROUPING_COL":
11808
+ case "WINDOW_COL":
11809
+ return "POST_GROUP_ONLY";
11810
+ case "VARIABLE_COL":
11811
+ throw new Error("InternalError: unresolved VARIABLE_COL reached GROUP BY alias planning.");
11812
+ case "WILDCARD":
11813
+ case "PARENT_WILDCARD":
11706
11814
  case "FIELD":
11707
- return resolveFieldRef(row, column.field);
11708
11815
  case "LITERAL_COL":
11709
- return column.value;
11710
- case "AGGREGATE": {
11711
- const source = aggregateSyntheticName(column.func, column.distinct, column.arg);
11712
- return row[column.alias ?? source] ?? row[source] ?? "0";
11713
- }
11714
- case "ARITH_AGG_COL": {
11715
- const source = column.alias ?? aggArithDefaultKey(column.expr);
11716
- return row[source] ?? "0";
11717
- }
11718
11816
  case "ARITH_COL":
11719
- return String(evalArithExpr(column.expr, row));
11720
11817
  case "CASE_COL":
11721
- return evalCaseWhen(
11722
- column.expr,
11723
- row,
11724
- context.resolveFieldType,
11725
- context.resolveFieldSemantics
11726
- );
11727
- case "GROUPING_COL":
11728
- return evalGroupingRef(column.ref, row);
11729
- case "STRFUNC_COL": {
11730
- const source = stringFuncDefaultKey(column.expr);
11731
- return hasAggregateInStringFuncExpr2(column.expr) ? row[column.alias ?? source] ?? row[source] ?? evalStringFunc(
11732
- column.expr,
11733
- row,
11734
- context.resolveFieldType,
11735
- context.resolveFieldSemantics
11736
- ) : evalStringFunc(
11737
- column.expr,
11738
- row,
11739
- context.resolveFieldType,
11740
- context.resolveFieldSemantics
11741
- );
11742
- }
11743
- case "SCALAR_VALUE_COL": {
11744
- const source = scalarValueDefaultKey(column.expr);
11745
- return scalarValueHasAggregate2(column.expr) ? row[column.alias ?? source] ?? row[source] ?? "" : String(evalScalarValueExpr(
11746
- column.expr,
11747
- row,
11748
- context.resolveFieldType,
11749
- context.resolveFieldSemantics
11750
- ));
11751
- }
11818
+ case "STRFUNC_COL":
11819
+ case "SCALAR_VALUE_COL":
11752
11820
  case "SCALAR_SUBQUERY_COL":
11753
- return context.scalarCache?.get(columnIndex) ?? "";
11754
- case "WINDOW_COL":
11755
- return row[column.alias] ?? "";
11821
+ return containsAggregate(column) || containsAggregateColumnNode(column) ? "AGGREGATE_DEPENDENT" : "SAFE";
11756
11822
  }
11757
11823
  }
11758
- function buildDistinctTuple(columns, row, context = {}) {
11759
- return columns.map((column, columnIndex) => {
11760
- const value = evaluateSelectColumnValue(column, row, columnIndex, context);
11761
- return typeof value === "string" ? value : value.entries.map(([, entryValue]) => entryValue);
11762
- });
11824
+ var SUBTABLE_SYSTEM_COLUMNS = ["_pid", "_rid", "_idx"];
11825
+ function unique(values) {
11826
+ return [...new Set(values)];
11763
11827
  }
11764
- function project(rows, columns, scalarCache, resolveFieldType, sourceColumns2, resolveFieldSemantics2, hiddenQualifiedAliases) {
11765
- if (columns.length === 1 && columns[0].type === "WILDCARD") {
11766
- const projected2 = rows.map((row) => {
11767
- const visible = stripHiddenQualifiedColumns(
11768
- stripParentShortcutColumns(row),
11769
- hiddenQualifiedAliases
11770
- );
11771
- const value = evaluateSelectColumnValue(columns[0], row, 0, {
11772
- wildcardKeys: Object.keys(visible)
11773
- });
11774
- const out = {};
11775
- if (typeof value !== "string") {
11776
- for (const [key, entryValue] of value.entries) {
11777
- if (entryValue !== null) out[key] = entryValue;
11778
- }
11779
- }
11780
- return out;
11781
- });
11782
- const cols = projected2.length > 0 ? Object.keys(projected2[0]) : [...sourceColumns2 ?? []];
11783
- return { rows: projected2, columns: cols };
11784
- }
11785
- const defaultFieldKeys = buildDefaultFieldOutputKeys(columns);
11786
- const hasWildcard = columns.some(
11787
- (col) => col.type === "WILDCARD" || col.type === "PARENT_WILDCARD"
11788
- );
11789
- const outputKeys = hasWildcard ? null : computeOutputKeys(columns, defaultFieldKeys);
11790
- const orderedKeys = outputKeys ?? [];
11791
- if (hasWildcard && rows.length === 0) {
11792
- return { rows: [], columns: computeExplicitOutputKeys(columns, defaultFieldKeys) };
11793
- }
11794
- const projected = rows.map((row, rowIdx) => {
11795
- const out = {};
11796
- const evaluationContext = {
11797
- scalarCache,
11798
- resolveFieldType,
11799
- resolveFieldSemantics: resolveFieldSemantics2,
11800
- wildcardKeys: Object.keys(stripHiddenQualifiedColumns(
11801
- stripParentShortcutColumns(row),
11802
- hiddenQualifiedAliases
11803
- )),
11804
- parentWildcardKeys: Object.keys(row).filter((key) => key.startsWith("_p.")).sort()
11805
- };
11806
- for (const [colIdx, col] of columns.entries()) {
11807
- const value = evaluateSelectColumnValue(col, row, colIdx, evaluationContext);
11808
- switch (col.type) {
11809
- case "VARIABLE_COL":
11810
- break;
11811
- case "WILDCARD":
11812
- if (typeof value !== "string") {
11813
- for (const [key, entryValue] of value.entries) {
11814
- if (entryValue !== null) out[key] = entryValue;
11815
- }
11816
- }
11817
- break;
11818
- case "PARENT_WILDCARD": {
11819
- if (typeof value !== "string") {
11820
- for (const [key, entryValue] of value.entries) {
11821
- if (entryValue !== null) out[key] = entryValue;
11822
- if (rowIdx === 0) orderedKeys.push(key);
11823
- }
11824
- }
11825
- break;
11826
- }
11827
- case "FIELD": {
11828
- const key = outputKeys?.[colIdx] ?? col.alias ?? defaultFieldKeys.get(colIdx) ?? col.field;
11829
- out[key] = value;
11830
- if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
11831
- break;
11832
- }
11833
- case "LITERAL_COL": {
11834
- const key = outputKeys?.[colIdx] ?? col.alias ?? `'${col.value}'`;
11835
- out[key] = value;
11836
- if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
11837
- break;
11838
- }
11839
- case "AGGREGATE": {
11840
- const srcKey = aggregateSyntheticName(col.func, col.distinct, col.arg);
11841
- const dstKey = outputKeys?.[colIdx] ?? col.alias ?? srcKey;
11842
- out[dstKey] = value;
11843
- if (outputKeys === null && rowIdx === 0) orderedKeys.push(dstKey);
11844
- break;
11845
- }
11846
- case "ARITH_AGG_COL": {
11847
- const key = outputKeys?.[colIdx] ?? col.alias ?? aggArithDefaultKey(col.expr);
11848
- out[key] = value;
11849
- if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
11850
- break;
11851
- }
11852
- case "ARITH_COL": {
11853
- const key = outputKeys?.[colIdx] ?? col.alias ?? arithColDefaultKey(col.expr);
11854
- out[key] = value;
11855
- if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
11856
- break;
11857
- }
11858
- case "CASE_COL": {
11859
- const key = outputKeys?.[colIdx] ?? col.alias ?? "case";
11860
- out[key] = value;
11861
- if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
11862
- break;
11863
- }
11864
- case "GROUPING_COL": {
11865
- const key = outputKeys?.[colIdx] ?? col.alias ?? `GROUPING(${col.ref.field.tableAlias ? `${col.ref.field.tableAlias}.` : ""}${col.ref.field.field})`;
11866
- out[key] = value;
11867
- if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
11868
- break;
11869
- }
11870
- case "STRFUNC_COL": {
11871
- const key = outputKeys?.[colIdx] ?? col.alias ?? stringFuncDefaultKey(col.expr);
11872
- out[key] = value;
11873
- if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
11874
- break;
11875
- }
11876
- case "SCALAR_VALUE_COL": {
11877
- const key = outputKeys?.[colIdx] ?? col.alias ?? scalarValueDefaultKey(col.expr);
11878
- out[key] = value;
11879
- if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
11880
- break;
11881
- }
11882
- case "SCALAR_SUBQUERY_COL": {
11883
- const key = outputKeys?.[colIdx] ?? col.alias ?? "(subquery)";
11884
- out[key] = value;
11885
- if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
11886
- break;
11887
- }
11888
- case "WINDOW_COL": {
11889
- const key = outputKeys?.[colIdx] ?? col.alias;
11890
- out[key] = value;
11891
- if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
11892
- break;
11893
- }
11894
- }
11895
- }
11896
- return out;
11897
- });
11898
- return { rows: projected, columns: orderedKeys };
11828
+ function sourceColumns(input) {
11829
+ switch (input.kind) {
11830
+ case "APP":
11831
+ return unique([
11832
+ ...input.fieldCodes,
11833
+ ...APP_SYSTEM_FIELD_CODES
11834
+ ]);
11835
+ case "SUBTABLE":
11836
+ return unique([
11837
+ ...input.childFieldCodes,
11838
+ ...SUBTABLE_SYSTEM_COLUMNS,
11839
+ ...input.parentFieldCodes.map((field) => `_p.${field}`)
11840
+ ]);
11841
+ case "MATERIALIZED":
11842
+ return unique(input.columns);
11843
+ }
11899
11844
  }
11900
- function computeOutputKeys(columns, defaultFieldKeys) {
11901
- return columns.map((col, colIdx) => computeOutputKey(col, colIdx, defaultFieldKeys));
11845
+ function resolvePlainGroupBySourceSchemas(stmt, lookup) {
11846
+ const sources = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
11847
+ return sources.map((source, sourceIndex) => ({
11848
+ sourceIndex,
11849
+ qualifier: source.alias ?? source.cteName,
11850
+ columns: sourceColumns(lookup(source, sourceIndex))
11851
+ }));
11902
11852
  }
11903
- function computeExplicitOutputKeys(columns, defaultFieldKeys) {
11904
- const keys = [];
11905
- const seen = /* @__PURE__ */ new Set();
11906
- for (const [colIdx, col] of columns.entries()) {
11907
- if (col.type === "WILDCARD" || col.type === "PARENT_WILDCARD") continue;
11908
- const key = computeOutputKey(col, colIdx, defaultFieldKeys);
11909
- if (!seen.has(key)) {
11910
- seen.add(key);
11911
- keys.push(key);
11912
- }
11853
+ function parseGroupName(name) {
11854
+ if (name.startsWith("_p.")) return { qualifier: null, fieldCode: name };
11855
+ const dot = name.indexOf(".");
11856
+ if (dot <= 0 || dot === name.length - 1) {
11857
+ return { qualifier: null, fieldCode: name };
11913
11858
  }
11914
- return keys;
11859
+ return { qualifier: name.slice(0, dot), fieldCode: name.slice(dot + 1) };
11915
11860
  }
11916
- function computeOutputKey(col, colIdx, defaultFieldKeys) {
11917
- switch (col.type) {
11918
- case "VARIABLE_COL":
11919
- throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
11920
- case "FIELD":
11921
- return col.alias ?? defaultFieldKeys.get(colIdx) ?? col.field;
11922
- case "LITERAL_COL":
11923
- return col.alias ?? `'${col.value}'`;
11924
- case "AGGREGATE":
11925
- return col.alias ?? aggregateSyntheticName(col.func, col.distinct, col.arg);
11926
- case "ARITH_AGG_COL":
11927
- return col.alias ?? aggArithDefaultKey(col.expr);
11928
- case "ARITH_COL":
11929
- return col.alias ?? arithColDefaultKey(col.expr);
11930
- case "CASE_COL":
11931
- return col.alias ?? "case";
11932
- case "STRFUNC_COL":
11933
- return col.alias ?? stringFuncDefaultKey(col.expr);
11934
- case "SCALAR_VALUE_COL":
11935
- return col.alias ?? scalarValueDefaultKey(col.expr);
11936
- case "GROUPING_COL":
11937
- return col.alias ?? `GROUPING(${col.ref.field.tableAlias ? `${col.ref.field.tableAlias}.` : ""}${col.ref.field.field})`;
11938
- case "SCALAR_SUBQUERY_COL":
11939
- return col.alias ?? "(subquery)";
11940
- case "WINDOW_COL":
11941
- return col.alias;
11942
- case "WILDCARD":
11943
- case "PARENT_WILDCARD":
11944
- throw new Error("internal: computeOutputKey received a wildcard column");
11945
- }
11861
+ function runtimeKey(source, fieldCode) {
11862
+ return source.qualifier === null ? fieldCode : `${source.qualifier}.${fieldCode}`;
11946
11863
  }
11947
- function buildDefaultFieldOutputKeys(columns) {
11948
- const qualifierCollisionCount = /* @__PURE__ */ new Map();
11949
- for (const col of columns) {
11950
- if (col.type !== "FIELD" || col.alias) continue;
11951
- const unqualified = stripTableQualifier(col.field);
11952
- qualifierCollisionCount.set(unqualified, (qualifierCollisionCount.get(unqualified) ?? 0) + 1);
11864
+ function explicitAlias(column) {
11865
+ return "alias" in column && typeof column.alias === "string" ? column.alias : null;
11866
+ }
11867
+ function resolveFieldName(name, columns, schemas) {
11868
+ const parsed = parseGroupName(name);
11869
+ const candidateSources = parsed.qualifier === null ? schemas : schemas.filter((source) => source.qualifier === parsed.qualifier);
11870
+ const physical = candidateSources.filter((source) => source.columns.includes(parsed.fieldCode));
11871
+ if (physical.length > 1) {
11872
+ throw new Error(
11873
+ `ArgumentError: GROUP BY field ${name} is ambiguous across multiple sources (reason=GROUP_BY_FIELD_AMBIGUOUS).`
11874
+ );
11953
11875
  }
11954
- const keys = /* @__PURE__ */ new Map();
11955
- for (const [idx, col] of columns.entries()) {
11956
- if (col.type !== "FIELD" || col.alias) continue;
11957
- const unqualified = stripTableQualifier(col.field);
11958
- const duplicate = (qualifierCollisionCount.get(unqualified) ?? 0) > 1;
11959
- const hasTableQualifier = col.field.includes(".") && !col.field.startsWith("_p.");
11960
- keys.set(idx, duplicate && hasTableQualifier ? col.field : unqualified);
11876
+ if (physical.length === 1) {
11877
+ const source = physical[0];
11878
+ return {
11879
+ kind: "PHYSICAL",
11880
+ sourceIndex: source.sourceIndex,
11881
+ fieldCode: parsed.fieldCode,
11882
+ runtimeKey: runtimeKey(source, parsed.fieldCode)
11883
+ };
11961
11884
  }
11962
- return keys;
11963
- }
11964
- function stripTableQualifier(field) {
11965
- if (field.startsWith("_p.")) return field;
11966
- const dot = field.indexOf(".");
11967
- if (dot <= 0) return field;
11968
- const unqualified = field.slice(dot + 1);
11969
- return unqualified || field;
11970
- }
11971
- function stripParentShortcutColumns(row) {
11972
- const out = {};
11973
- for (const [k, v] of Object.entries(row)) {
11974
- if (k.startsWith("_p.")) continue;
11975
- out[k] = v;
11885
+ if (parsed.qualifier !== null) return { kind: "UNKNOWN", name };
11886
+ const aliases = columns.flatMap(
11887
+ (column, columnIndex) => explicitAlias(column) === name ? [{ column, columnIndex }] : []
11888
+ );
11889
+ if (aliases.length > 1) return { kind: "ALIAS_REJECT", reason: "DUPLICATE" };
11890
+ if (aliases.length === 1) {
11891
+ const candidate = aliases[0];
11892
+ const classification = classifyPreGroupAlias(candidate.column);
11893
+ if (classification === "SAFE") {
11894
+ return { kind: "ALIAS_SAFE", columnIndex: candidate.columnIndex };
11895
+ }
11896
+ return {
11897
+ kind: "ALIAS_REJECT",
11898
+ reason: classification === "AGGREGATE_DEPENDENT" ? "AGGREGATE" : "POST_GROUP_ONLY"
11899
+ };
11976
11900
  }
11977
- return out;
11901
+ const aggregateSyntheticMatch = columns.some(
11902
+ (column) => column.type === "AGGREGATE" && column.alias === null && aggregateSyntheticName(column.func, column.distinct, column.arg) === name
11903
+ );
11904
+ if (aggregateSyntheticMatch) return { kind: "ALIAS_REJECT", reason: "AGGREGATE" };
11905
+ return { kind: "UNKNOWN", name };
11978
11906
  }
11979
- function arithColDefaultKey(expr) {
11980
- const nodeLabel = (n) => {
11981
- if (n.type === "FIELD_REF") return n.field;
11982
- if (n.type === "NUMBER") return numberLiteralText(n);
11983
- if (n.type === "STRING_FUNC") return stringFuncDefaultKey(n);
11984
- return `(${nodeLabel(n.left)}${n.op}${nodeLabel(n.right)})`;
11907
+ function planPlainGroupByResolution(groupBy, columns, schemas) {
11908
+ return {
11909
+ items: groupBy.map(
11910
+ (key) => key.type === "FIELD_NAME" ? resolveFieldName(key.name, columns, schemas) : { kind: "EXPRESSION" }
11911
+ )
11985
11912
  };
11986
- if (expr.type === "ARITH") {
11987
- return `${nodeLabel(expr.left)}${expr.op}${nodeLabel(expr.right)}`;
11988
- }
11989
- return nodeLabel(expr);
11990
- }
11991
- function stringFuncDefaultKey(expr) {
11992
- const argStrs = expr.args.map((a) => {
11993
- if (a.type === "AGG_REF" || a.type === "AGG_ARITH") return aggArithDefaultKey(a);
11994
- return scalarValueDefaultKey(a);
11995
- });
11996
- return `${expr.func}(${argStrs.join(",")})`;
11997
11913
  }
11998
- function scalarValueDefaultKey(expr) {
11999
- switch (expr.type) {
12000
- case "STRING":
12001
- return `'${expr.value}'`;
12002
- case "NUMBER":
12003
- return numberLiteralText(expr);
12004
- case "VARIABLE":
12005
- return `@${expr.name}`;
12006
- case "FIELD":
12007
- return expr.tableAlias ? `${expr.tableAlias}.${expr.field}` : expr.field;
12008
- case "STRING_FUNC":
12009
- return stringFuncDefaultKey(expr);
12010
- case "CASE_WHEN":
12011
- return "case";
12012
- case "SCALAR_ARITH":
12013
- return `${scalarValueDefaultKey(expr.left)}${expr.op}${scalarValueDefaultKey(expr.right)}`;
12014
- case "CONCAT_OP":
12015
- return `${scalarValueDefaultKey(expr.left)}||${scalarValueDefaultKey(expr.right)}`;
11914
+
11915
+ // src/engine/process.ts
11916
+ function flatten(record, alias) {
11917
+ const row = {};
11918
+ for (const [field, fv] of Object.entries(record)) {
11919
+ const val = fv.value;
11920
+ const strVal = val == null ? "" : typeof val === "string" ? val : JSON.stringify(val);
11921
+ if (alias) {
11922
+ row[`${alias}.${field}`] = strVal;
11923
+ row[field] = strVal;
11924
+ } else {
11925
+ row[field] = strVal;
11926
+ }
12016
11927
  }
11928
+ return row;
12017
11929
  }
12018
- function hasAggregateInStringFuncArg(arg) {
12019
- if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") return true;
12020
- return scalarValueHasAggregate2(arg);
12021
- }
12022
- function scalarValueHasAggregate2(expr) {
12023
- if (expr.type === "STRING_FUNC") return hasAggregateInStringFuncExpr2(expr);
12024
- if (expr.type === "SCALAR_ARITH" || expr.type === "CONCAT_OP") {
12025
- return scalarValueHasAggregate2(expr.left) || scalarValueHasAggregate2(expr.right);
11930
+ function applyJoin(leftRows, rightRows, join2, columns = {}) {
11931
+ const { on, type: joinType } = join2;
11932
+ const leftKey = on.left.tableAlias ? `${on.left.tableAlias}.${on.left.field}` : on.left.field;
11933
+ const rightKey = on.right.tableAlias ? `${on.right.tableAlias}.${on.right.field}` : on.right.field;
11934
+ assertJoinKeyAvailable(leftRows, leftKey, columns.leftColumns);
11935
+ assertJoinKeyAvailable(rightRows, rightKey, columns.rightColumns);
11936
+ if (joinType === "RIGHT") {
11937
+ const leftIndex = /* @__PURE__ */ new Map();
11938
+ for (const lRow of leftRows) {
11939
+ const k = lRow[leftKey] ?? "";
11940
+ const bucket = leftIndex.get(k);
11941
+ if (bucket) bucket.push(lRow);
11942
+ else leftIndex.set(k, [lRow]);
11943
+ }
11944
+ const emptyLeft = {};
11945
+ for (const key of columns.leftColumns ?? Object.keys(leftRows[0] ?? {})) emptyLeft[key] = "";
11946
+ const result2 = [];
11947
+ for (const rRow of rightRows) {
11948
+ const k = rRow[rightKey] ?? "";
11949
+ const matched = leftIndex.get(k) ?? [];
11950
+ if (matched.length > 0) {
11951
+ for (const lRow of matched) result2.push({ ...lRow, ...rRow });
11952
+ } else {
11953
+ result2.push({ ...emptyLeft, ...rRow });
11954
+ }
11955
+ }
11956
+ return result2;
12026
11957
  }
12027
- if (expr.type === "CASE_WHEN") {
12028
- return expr.branches.some((branch) => caseResultHasAggregate2(branch.result)) || expr.elseResult !== null && caseResultHasAggregate2(expr.elseResult);
11958
+ const rightIndex = /* @__PURE__ */ new Map();
11959
+ for (const rRow of rightRows) {
11960
+ const k = rRow[rightKey] ?? "";
11961
+ const bucket = rightIndex.get(k);
11962
+ if (bucket) bucket.push(rRow);
11963
+ else rightIndex.set(k, [rRow]);
12029
11964
  }
12030
- return false;
11965
+ const result = [];
11966
+ const emptyRight = {};
11967
+ for (const key of columns.rightColumns ?? Object.keys(rightRows[0] ?? {})) emptyRight[key] = "";
11968
+ for (const lRow of leftRows) {
11969
+ const k = lRow[leftKey] ?? "";
11970
+ const matched = rightIndex.get(k) ?? [];
11971
+ if (matched.length > 0) {
11972
+ for (const rRow of matched) {
11973
+ result.push({ ...lRow, ...rRow });
11974
+ }
11975
+ } else if (joinType === "LEFT") {
11976
+ result.push({ ...lRow, ...emptyRight });
11977
+ }
11978
+ }
11979
+ return result;
12031
11980
  }
12032
- function caseResultHasAggregate2(result) {
12033
- if (result.type === "ARRAY" || result.type === "FIELD_REF" || result.type === "ARITH") return false;
12034
- return scalarValueHasAggregate2(result);
11981
+ function assertJoinKeyAvailable(rows, key, savedColumns) {
11982
+ const missing = rows.length > 0 ? rows.some((row) => !Object.prototype.hasOwnProperty.call(row, key)) : savedColumns !== void 0 && !savedColumns.includes(key);
11983
+ if (missing) {
11984
+ throw new Error(`ArgumentError: JOIN key ${key} is not available in the materialized table.`);
11985
+ }
12035
11986
  }
12036
- function hasAggregateInStringFuncExpr2(expr) {
12037
- return expr.args.some((arg) => hasAggregateInStringFuncArg(arg));
11987
+ function applyFilter(rows, where, resolveFieldType, appliedKlikes, resolveFieldSemantics2) {
11988
+ if (where === null) return rows;
11989
+ return rows.filter((row) => evalWhere(where, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2));
12038
11990
  }
12039
- function resolveAggInStringFuncArg(arg, rows, resolveAggSortKind) {
12040
- if (arg.type === "AGG_REF") {
12041
- const value = evalAggregate(arg.func, arg.distinct, arg.arg, arg.separator, rows, resolveAggSortKind);
12042
- return typeof value === "number" ? { type: "NUMBER", value, raw: String(value) } : { type: "STRING", value };
11991
+ function hasAggregateColumns(columns) {
11992
+ return columns.some(
11993
+ (c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL" || c.type === "STRFUNC_COL" && hasAggregateInStringFuncExpr2(c.expr) || c.type === "SCALAR_VALUE_COL" && scalarValueHasAggregate2(c.expr)
11994
+ );
11995
+ }
11996
+ function applyGroupBy(rows, groupByKeys, columns, resolveAggSortKind, resolutionPlan, aliasEvaluationContext = {}) {
11997
+ if (resolutionPlan && resolutionPlan.items.length !== groupByKeys.length) {
11998
+ throw new Error("InternalError: plain GROUP BY resolution plan length does not match group keys.");
12043
11999
  }
12044
- if (arg.type === "AGG_ARITH") {
12045
- const value = evalAggArithExpr(arg, rows, resolveAggSortKind);
12046
- return { type: "NUMBER", value, raw: String(value) };
12000
+ const groups = /* @__PURE__ */ new Map();
12001
+ for (const row of rows) {
12002
+ const key = groupByKeys.map(
12003
+ (groupKey, index) => evalGroupByKey(
12004
+ groupKey,
12005
+ row,
12006
+ resolutionPlan?.items[index],
12007
+ columns,
12008
+ aliasEvaluationContext
12009
+ )
12010
+ ).join("\0");
12011
+ const bucket = groups.get(key);
12012
+ if (bucket) bucket.push(row);
12013
+ else groups.set(key, [row]);
12047
12014
  }
12048
- if (arg.type === "STRING_FUNC") {
12049
- return resolveAggInStringFuncExpr(arg, rows, resolveAggSortKind);
12015
+ if (groups.size === 0 && groupByKeys.length === 0 && hasAggregateColumns(columns)) {
12016
+ groups.set("", []);
12050
12017
  }
12051
- return resolveAggInScalarValue(arg, rows, resolveAggSortKind);
12052
- }
12053
- function resolveAggInScalarValue(expr, rows, resolveAggSortKind) {
12054
- if (expr.type === "STRING_FUNC") return resolveAggInStringFuncExpr(expr, rows, resolveAggSortKind);
12055
- if (expr.type === "SCALAR_ARITH" || expr.type === "CONCAT_OP") {
12056
- return {
12057
- ...expr,
12058
- left: resolveAggInScalarValue(expr.left, rows, resolveAggSortKind),
12059
- right: resolveAggInScalarValue(expr.right, rows, resolveAggSortKind)
12060
- };
12018
+ const result = [];
12019
+ for (const groupRows of groups.values()) {
12020
+ const outRow = { ...groupRows[0] };
12021
+ for (const k of groupByKeys) {
12022
+ if (k.type === "ARITH_KEY") {
12023
+ outRow[arithColDefaultKey(k.expr)] = String(evalArithExpr(k.expr, groupRows[0]));
12024
+ } else if (k.type === "FUNC_KEY") {
12025
+ outRow[stringFuncDefaultKey(k.expr)] = evalStringFunc(k.expr, groupRows[0]);
12026
+ }
12027
+ }
12028
+ materializeAggregateColumns(outRow, groupRows, columns, resolveAggSortKind);
12029
+ result.push(outRow);
12061
12030
  }
12062
- return expr;
12031
+ return result;
12063
12032
  }
12064
- function resolveAggInStringFuncExpr(expr, rows, resolveAggSortKind) {
12065
- return {
12066
- type: "STRING_FUNC",
12067
- func: expr.func,
12068
- args: expr.args.map((arg) => resolveAggInStringFuncArg(arg, rows, resolveAggSortKind))
12033
+ function applyGroupingSets(rows, spec, columns, resolveAggSortKind, limits = {}) {
12034
+ const result = [];
12035
+ let generatedRows = 0;
12036
+ const countBucket = () => {
12037
+ generatedRows++;
12038
+ if (limits.maxGeneratedRows !== void 0 && generatedRows > limits.maxGeneratedRows) {
12039
+ throw new Error(
12040
+ `LimitError: B65 generated grouping rows ${generatedRows} exceed limit ${limits.maxGeneratedRows} (reason=GROUPING_OUTPUT_LIMIT_EXCEEDED).`
12041
+ );
12042
+ }
12069
12043
  };
12070
- }
12071
- function stripHiddenQualifiedColumns(row, hiddenQualifiedAliases) {
12072
- if (!hiddenQualifiedAliases || hiddenQualifiedAliases.size === 0) return row;
12073
- const out = {};
12074
- for (const [key, value] of Object.entries(row)) {
12075
- if ([...hiddenQualifiedAliases].some((alias) => key.startsWith(`${alias}.`))) continue;
12076
- out[key] = value;
12044
+ for (const set of spec.sets) {
12045
+ const root = { children: /* @__PURE__ */ new Map() };
12046
+ const buckets = [];
12047
+ for (const row of rows) {
12048
+ let node = root;
12049
+ for (const item of set.items) {
12050
+ const value = groupingItemValue(item, row);
12051
+ let child = node.children.get(value);
12052
+ if (!child) {
12053
+ child = { children: /* @__PURE__ */ new Map() };
12054
+ node.children.set(value, child);
12055
+ }
12056
+ node = child;
12057
+ }
12058
+ if (!node.rows) {
12059
+ countBucket();
12060
+ node.rows = [];
12061
+ buckets.push(node.rows);
12062
+ }
12063
+ node.rows.push(row);
12064
+ }
12065
+ if (rows.length === 0 && set.items.length === 0) {
12066
+ countBucket();
12067
+ root.rows = [];
12068
+ buckets.push(root.rows);
12069
+ }
12070
+ const includedCanonicalIds = new Set(set.items.map((item) => item.canonicalId));
12071
+ for (const groupRows of buckets) {
12072
+ const outRow = { ...groupRows[0] };
12073
+ const includedValues = /* @__PURE__ */ new Map();
12074
+ for (const item of set.items) {
12075
+ if (!includedValues.has(item.canonicalId)) {
12076
+ includedValues.set(item.canonicalId, groupingItemValue(item, groupRows[0]));
12077
+ }
12078
+ }
12079
+ for (const item of spec.allItems) {
12080
+ const value = includedValues.get(item.canonicalId) ?? "";
12081
+ outRow[item.directKey] = value;
12082
+ if (item.unqualifiedBridgeKey !== null) {
12083
+ outRow[item.unqualifiedBridgeKey] = value;
12084
+ }
12085
+ }
12086
+ materializeAggregateColumns(outRow, groupRows, columns, resolveAggSortKind);
12087
+ attachGroupingRowMeta(outRow, includedCanonicalIds);
12088
+ result.push(outRow);
12089
+ }
12077
12090
  }
12078
- return out;
12079
- }
12080
- function flattenedColumns(columns, alias) {
12081
- if (columns === void 0) return void 0;
12082
- return alias ? columns.flatMap((column) => [`${alias}.${column}`, column]) : [...columns];
12091
+ return result;
12083
12092
  }
12084
- function mergeKnownColumns(left, right, rows) {
12085
- if (left === void 0 && right === void 0 && rows.length === 0) return void 0;
12086
- return [.../* @__PURE__ */ new Set([
12087
- ...left ?? [],
12088
- ...right ?? [],
12089
- ...Object.keys(rows[0] ?? {})
12090
- ])];
12093
+ function groupingItemValue(item, row) {
12094
+ if (!row) return "";
12095
+ return row[item.directKey] ?? (item.unqualifiedBridgeKey === null ? void 0 : row[item.unqualifiedBridgeKey]) ?? "";
12091
12096
  }
12092
- function deriveOutputOrderSemantics(columns) {
12093
- const result = /* @__PURE__ */ new Map();
12094
- for (const column of columns) {
12095
- if (!("alias" in column) || !column.alias) continue;
12096
- if (column.type === "ARITH_COL" || column.type === "ARITH_AGG_COL" || column.type === "WINDOW_COL") {
12097
- result.set(column.alias, syntheticSemantics("number"));
12098
- } else if (column.type === "AGGREGATE") {
12099
- if (column.func === "COUNT" || column.func === "SUM" || column.func === "AVG" || column.func === "STDDEV_POP" || column.func === "STDDEV_SAMP" || column.func === "VAR_POP" || column.func === "VAR_SAMP" || column.func === "MEDIAN") {
12100
- result.set(column.alias, syntheticSemantics("number"));
12101
- } else if (column.func === "GROUP_CONCAT") {
12102
- result.set(column.alias, syntheticSemantics("string"));
12103
- }
12104
- } else if (column.type === "LITERAL_COL" || column.type === "CASE_COL" || column.type === "SCALAR_SUBQUERY_COL" || column.type === "SCALAR_VALUE_COL") {
12105
- result.set(column.alias, syntheticSemantics("string"));
12106
- } else if (column.type === "STRFUNC_COL") {
12107
- result.set(column.alias, syntheticSemantics(NUMERIC_ORDER_FUNCTIONS.has(column.expr.func) ? "number" : "string"));
12108
- }
12109
- }
12110
- return result;
12111
- }
12112
- function runFullScan(input) {
12113
- const {
12114
- stmt,
12115
- tables,
12116
- scalarCache,
12117
- optionOrders,
12118
- sortKinds,
12119
- orderSemantics,
12120
- fieldTypeResolver,
12121
- fieldSemanticsResolver,
12122
- havingFieldTypeResolver,
12123
- havingFieldSemanticsResolver,
12124
- aggregateSortKindResolver,
12125
- appliedKlikes,
12126
- sourceColumns: sourceColumns2,
12127
- tableColumns,
12128
- hiddenQualifiedAliases,
12129
- resolvedGroupingSpec,
12130
- plainGroupByPlan
12131
- } = input;
12132
- const effectiveOrderSemantics = deriveOutputOrderSemantics(stmt.columns);
12133
- for (const [key, value] of orderSemantics ?? []) effectiveOrderSemantics.set(key, value);
12134
- let rows = [];
12135
- const mainAlias = stmt.from.alias;
12136
- const mainRecords = tables.get(mainAlias) ?? tables.get(null) ?? [];
12137
- rows = mainRecords.map((r) => flatten(r, mainAlias));
12138
- let knownColumns = mergeKnownColumns(
12139
- flattenedColumns(tableColumns?.get(mainAlias), mainAlias),
12140
- void 0,
12141
- rows
12142
- );
12143
- for (const join2 of stmt.joins) {
12144
- const rightAlias = join2.table.alias;
12145
- const rightRecords = tables.get(rightAlias) ?? [];
12146
- const rightRows = rightRecords.map((r) => flatten(r, rightAlias));
12147
- const rightColumns = mergeKnownColumns(
12148
- flattenedColumns(tableColumns?.get(rightAlias), rightAlias),
12149
- void 0,
12150
- rightRows
12151
- );
12152
- rows = applyJoin(rows, rightRows, join2, {
12153
- leftColumns: knownColumns,
12154
- rightColumns
12155
- });
12156
- knownColumns = mergeKnownColumns(knownColumns, rightColumns, rows);
12157
- }
12158
- const filterWhere = input.residualWhere !== void 0 ? input.residualWhere : stmt.where;
12159
- rows = applyFilter(rows, filterWhere, fieldTypeResolver, appliedKlikes, fieldSemanticsResolver);
12160
- const grouping = normalizeGroupingSpec(stmt);
12161
- if (grouping.type === "GROUPING_SETS") {
12162
- if (!resolvedGroupingSpec) {
12163
- throw new Error("internal error: B65 grouping sets require a metadata-resolved grouping spec.");
12097
+ function materializeAggregateColumns(outRow, groupRows, columns, resolveAggSortKind) {
12098
+ for (const col of columns) {
12099
+ if (col.type === "AGGREGATE") {
12100
+ const syntheticKey = aggregateSyntheticName(col.func, col.distinct, col.arg);
12101
+ const value = String(evalAggregate(col.func, col.distinct, col.arg, col.separator, groupRows, resolveAggSortKind));
12102
+ outRow[col.alias ?? syntheticKey] = value;
12103
+ if (col.alias) outRow[syntheticKey] = value;
12104
+ } else if (col.type === "ARITH_AGG_COL") {
12105
+ const outputKey = col.alias ?? aggArithDefaultKey(col.expr);
12106
+ outRow[outputKey] = String(evalAggArithExpr(col.expr, groupRows, resolveAggSortKind));
12107
+ } else if (col.type === "STRFUNC_COL" && hasAggregateInStringFuncExpr2(col.expr)) {
12108
+ const outputKey = col.alias ?? stringFuncDefaultKey(col.expr);
12109
+ const resolvedExpr = resolveAggInStringFuncExpr(col.expr, groupRows, resolveAggSortKind);
12110
+ outRow[outputKey] = evalStringFunc(resolvedExpr, outRow);
12111
+ } else if (col.type === "SCALAR_VALUE_COL" && scalarValueHasAggregate2(col.expr)) {
12112
+ const outputKey = col.alias ?? scalarValueDefaultKey(col.expr);
12113
+ const resolvedExpr = resolveAggInScalarValue(col.expr, groupRows, resolveAggSortKind);
12114
+ outRow[outputKey] = String(evalScalarValueExpr(resolvedExpr, outRow));
12164
12115
  }
12165
- rows = applyGroupingSets(
12166
- rows,
12167
- resolvedGroupingSpec,
12168
- stmt.columns,
12169
- aggregateSortKindResolver,
12170
- { maxGeneratedRows: B65_MAX_GENERATED_ROWS }
12171
- );
12172
- } else if (grouping.type === "PLAIN" || hasAggregateColumns(stmt.columns)) {
12173
- rows = applyGroupBy(
12174
- rows,
12175
- grouping.type === "PLAIN" ? grouping.allItems : [],
12176
- stmt.columns,
12177
- aggregateSortKindResolver,
12178
- grouping.type === "PLAIN" ? plainGroupByPlan : void 0,
12179
- {
12180
- scalarCache,
12181
- resolveFieldType: fieldTypeResolver,
12182
- resolveFieldSemantics: fieldSemanticsResolver
12183
- }
12184
- );
12185
- }
12186
- rows = applyHaving(rows, stmt.having, havingFieldTypeResolver, havingFieldSemanticsResolver);
12187
- rows = applyWindow(rows, stmt.columns, optionOrders, sortKinds, effectiveOrderSemantics);
12188
- if (stmt.distinct) {
12189
- rows = applyDistinct(
12190
- rows,
12191
- stmt.columns,
12192
- scalarCache,
12193
- fieldTypeResolver,
12194
- fieldSemanticsResolver
12195
- );
12196
12116
  }
12197
- rows = applyOrderBy(
12198
- rows,
12199
- stmt.orderBy,
12200
- optionOrders,
12201
- sortKinds,
12202
- effectiveOrderSemantics,
12203
- buildOrderByAliasEvaluator(stmt.columns, scalarCache, fieldTypeResolver, fieldSemanticsResolver)
12204
- );
12205
- rows = applyLimit(rows, stmt.limit, stmt.offset);
12206
- return project(
12207
- rows,
12208
- stmt.columns,
12209
- scalarCache,
12210
- fieldTypeResolver,
12211
- sourceColumns2,
12212
- fieldSemanticsResolver,
12213
- hiddenQualifiedAliases
12214
- );
12215
12117
  }
12216
-
12217
- // src/converter/subtableAdapter.ts
12218
- function expandSubtableRecords(parents, subtableCode) {
12219
- const rows = [];
12220
- for (const parent of parents) {
12221
- const parentId = toFlatString(parent["$id"]?.value);
12222
- const tableValue = readTableRows(parent[subtableCode]?.value);
12223
- if (tableValue.length === 0) continue;
12224
- for (let i = 0; i < tableValue.length; i++) {
12225
- const row = tableValue[i];
12226
- const out = {
12227
- _pid: { value: parentId },
12228
- _rid: { value: row.id ?? "" },
12229
- _idx: { value: String(i) }
12230
- };
12231
- for (const [field, fieldValue] of Object.entries(parent)) {
12232
- if (field === subtableCode) continue;
12233
- const flat = toFlatString(fieldValue?.value);
12234
- out[`_p.${field}`] = { value: flat };
12118
+ function evalGroupByKey(key, row, resolution, columns, aliasEvaluationContext) {
12119
+ if (key.type === "FIELD_NAME") {
12120
+ if (!resolution) return row[key.name] ?? "";
12121
+ if (resolution.kind === "PHYSICAL") return row[resolution.runtimeKey] ?? "";
12122
+ if (resolution.kind === "ALIAS_SAFE") {
12123
+ const column = columns[resolution.columnIndex];
12124
+ if (!column) {
12125
+ throw new Error(
12126
+ `InternalError: GROUP BY alias column index ${resolution.columnIndex} is out of range.`
12127
+ );
12235
12128
  }
12236
- for (const [field, cell] of Object.entries(row.value ?? {})) {
12237
- out[field] = { value: toFlatString(cell?.value) };
12129
+ const value = evaluateSelectColumnValue(
12130
+ column,
12131
+ row,
12132
+ resolution.columnIndex,
12133
+ aliasEvaluationContext
12134
+ );
12135
+ if (typeof value !== "string") {
12136
+ throw new Error("InternalError: GROUP BY alias resolved to an expanded SELECT column.");
12238
12137
  }
12239
- rows.push(out);
12138
+ return value;
12240
12139
  }
12140
+ throw new Error(
12141
+ `InternalError: unresolved plain GROUP BY item ${resolution.kind} reached evaluation.`
12142
+ );
12241
12143
  }
12242
- return rows;
12243
- }
12244
- function readTableRows(value) {
12245
- if (!Array.isArray(value)) return [];
12246
- return value.filter((item) => !!item && typeof item === "object");
12247
- }
12248
- function toFlatString(value) {
12249
- if (value === null || value === void 0) return "";
12250
- if (typeof value === "string") return value;
12251
- if (typeof value === "number" || typeof value === "boolean") return String(value);
12252
- try {
12253
- return JSON.stringify(value);
12254
- } catch {
12255
- return String(value);
12256
- }
12257
- }
12258
-
12259
- // src/core/dmlPrevalidation.ts
12260
- function collectDmlPrevalidationSnapshotFields(fieldIndex) {
12261
- return [
12262
- "$id",
12263
- .../* @__PURE__ */ new Set([
12264
- ...fieldIndex.topLevel.map((field) => field.code).filter((code) => code !== "$id" && code !== "$revision"),
12265
- ...fieldIndex.subtables.keys()
12266
- ])
12267
- ];
12144
+ if (key.type === "FUNC_KEY") return evalStringFunc(key.expr, row);
12145
+ return String(evalArithExpr(key.expr, row));
12268
12146
  }
12269
- function buildDmlValidationPostImage(snapshot, sparseRecord) {
12270
- const postImage = deepClone(snapshot);
12271
- for (const [field, cell] of Object.entries(sparseRecord)) {
12272
- postImage[field] = deepClone(cell);
12147
+ function evalAggregate(func, distinct, arg, separator, rows, resolveAggSortKind) {
12148
+ if (arg.type === "WILDCARD") {
12149
+ return func === "COUNT" ? rows.length : 0;
12273
12150
  }
12274
- return postImage;
12275
- }
12276
- function mergeDmlCandidateValidation(input) {
12277
- const errors = [
12278
- ...input.preErrors.map((row) => normalizePlainError(row)),
12279
- ...input.postImageErrors.map((row) => normalizePlainError(row, row["$err_subtable"] !== "")),
12280
- ...input.checkErrors.map((row) => normalizePlainError(row))
12281
- ];
12282
- const invalidRowNumbers = /* @__PURE__ */ new Set();
12283
- if (errors.length > 0) invalidRowNumbers.add(input.rowNumber);
12284
- const writeRecord = {};
12285
- for (const field of new Set(input.setFields)) {
12286
- if (!Object.prototype.hasOwnProperty.call(input.normalizedPostImage, field)) {
12287
- throw new Error(`InternalError: normalized post-image is missing SET field: ${field}`);
12151
+ const strValues = [];
12152
+ for (const row of rows) {
12153
+ let strVal;
12154
+ if (arg.type === "FIELD_REF") {
12155
+ const raw = row[arg.field];
12156
+ if (raw === void 0 || raw === "" && func !== "MIN" && func !== "MAX") continue;
12157
+ strVal = raw;
12158
+ } else if (arg.type === "ARITH" || arg.type === "NUMBER" || arg.type === "STRING_FUNC") {
12159
+ const n = evalArithExpr(arg, row);
12160
+ if (isNaN(n)) continue;
12161
+ strVal = String(n);
12162
+ } else {
12163
+ const value = evalScalarValueExprNullable(arg, row);
12164
+ if (value === null) continue;
12165
+ if (value === "" && func !== "MIN" && func !== "MAX") continue;
12166
+ if (typeof value === "number" && Number.isNaN(value)) continue;
12167
+ strVal = String(value);
12168
+ }
12169
+ strValues.push(strVal);
12170
+ }
12171
+ const statistical = func === "STDDEV_POP" || func === "STDDEV_SAMP" || func === "VAR_POP" || func === "VAR_SAMP" || func === "MEDIAN";
12172
+ const numericValues = statistical ? strValues.map((value) => {
12173
+ const numeric = Number(value);
12174
+ if (!Number.isFinite(numeric)) {
12175
+ throw new Error(`ArgumentError: ${func} \u306E\u5F15\u6570\u306B\u975E\u6570\u5024\u307E\u305F\u306F\u975E\u6709\u9650\u306E\u5024\u304C\u3042\u308A\u307E\u3059: ${value}`);
12176
+ }
12177
+ return numeric;
12178
+ }) : null;
12179
+ const eff = distinct ? statistical ? [...new Set(numericValues)] : [...new Set(strValues)] : statistical ? numericValues : strValues;
12180
+ if (func === "COUNT") return eff.length;
12181
+ if (func === "GROUP_CONCAT") return eff.join(separator ?? ",");
12182
+ const comparison = func === "MIN" || func === "MAX" || func === "MODE" ? resolveAggregateArgSemantics(arg, resolveAggSortKind) : void 0;
12183
+ const semantics = typeof comparison === "string" ? syntheticSemantics(comparison) : comparison ?? (arg.type === "FIELD_REF" || arg.type === "FIELD" || arg.type === "STRING" || arg.type === "CONCAT_OP" || arg.type === "CASE_WHEN" ? syntheticSemantics("string") : syntheticSemantics("number"));
12184
+ if (func === "MODE") {
12185
+ if (strValues.length === 0) return "";
12186
+ const frequencies = /* @__PURE__ */ new Map();
12187
+ for (const value of strValues) frequencies.set(value, (frequencies.get(value) ?? 0) + 1);
12188
+ let result = "";
12189
+ let maxFrequency = 0;
12190
+ for (const [candidate, frequency] of frequencies) {
12191
+ if (frequency > maxFrequency) {
12192
+ result = candidate;
12193
+ maxFrequency = frequency;
12194
+ continue;
12195
+ }
12196
+ if (frequency !== maxFrequency) continue;
12197
+ const canonical = compareCanonicalValues(candidate, result, semantics);
12198
+ if (canonical < 0 || canonical === 0 && compareCodePointStrings(candidate, result) < 0) {
12199
+ result = candidate;
12200
+ }
12201
+ }
12202
+ return result;
12203
+ }
12204
+ if (func === "MIN" || func === "MAX") {
12205
+ const comparableValues = eff;
12206
+ if (comparableValues.length === 0) return 0;
12207
+ let result = comparableValues[0];
12208
+ for (const candidate of comparableValues.slice(1)) {
12209
+ const cmp = compareCanonicalValues(candidate, result, semantics);
12210
+ if (func === "MAX" && cmp > 0 || func === "MIN" && cmp < 0) result = candidate;
12211
+ }
12212
+ return result;
12213
+ }
12214
+ const nums = statistical ? eff : eff.map(Number);
12215
+ switch (func) {
12216
+ case "SUM":
12217
+ return nums.reduce((a, b) => a + b, 0);
12218
+ case "AVG":
12219
+ return nums.length === 0 ? 0 : nums.reduce((a, b) => a + b, 0) / nums.length;
12220
+ case "MEDIAN": {
12221
+ if (nums.length === 0) return "";
12222
+ const sorted = [...nums].sort((a, b) => a - b);
12223
+ const middle = Math.floor(sorted.length / 2);
12224
+ return sorted.length % 2 === 1 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
12225
+ }
12226
+ case "VAR_POP":
12227
+ case "VAR_SAMP":
12228
+ case "STDDEV_POP":
12229
+ case "STDDEV_SAMP": {
12230
+ const sample = func === "VAR_SAMP" || func === "STDDEV_SAMP";
12231
+ if (nums.length === 0 || sample && nums.length < 2) return "";
12232
+ const origin = nums[0];
12233
+ let count = 0;
12234
+ let mean = 0;
12235
+ let m2 = 0;
12236
+ for (const rawValue of nums) {
12237
+ const value = rawValue - origin;
12238
+ count++;
12239
+ const delta = value - mean;
12240
+ mean += delta / count;
12241
+ m2 += delta * (value - mean);
12242
+ }
12243
+ const variance = Math.max(0, m2 / (sample ? count - 1 : count));
12244
+ return func === "STDDEV_POP" || func === "STDDEV_SAMP" ? Math.sqrt(variance) : variance;
12288
12245
  }
12289
- writeRecord[field] = deepClone(input.normalizedPostImage[field]);
12290
12246
  }
12291
- return {
12292
- errors,
12293
- invalidRows: invalidRowNumbers.size,
12294
- invalidRowNumbers,
12295
- writeRecord
12296
- };
12297
12247
  }
12298
- function normalizePlainError(row, childCell = false) {
12299
- return {
12300
- ...row,
12301
- $err_value: childCell ? row["$err_value"] ?? "" : "",
12302
- $err_subtable: childCell ? row["$err_subtable"] ?? "" : "",
12303
- $err_subrow: childCell ? row["$err_subrow"] ?? "" : "",
12304
- $err_subrow_id: childCell ? row["$err_subrow_id"] ?? "" : ""
12305
- };
12248
+ function toAggregateFieldRef(field) {
12249
+ const dot = field.indexOf(".");
12250
+ return dot > 0 ? { type: "FIELD", tableAlias: field.slice(0, dot), field: field.slice(dot + 1) } : { type: "FIELD", tableAlias: null, field };
12306
12251
  }
12307
- function deepClone(value, seen = /* @__PURE__ */ new Map()) {
12308
- if (value === null || typeof value !== "object") return value;
12309
- const object = value;
12310
- const existing = seen.get(object);
12311
- if (existing !== void 0) return existing;
12312
- if (Array.isArray(value)) {
12313
- const clone2 = [];
12314
- seen.set(object, clone2);
12315
- for (const item of value) clone2.push(deepClone(item, seen));
12316
- return clone2;
12252
+ function evalAggArithExpr(node, rows, resolveAggSortKind) {
12253
+ if (node.type === "NUMBER") return node.value;
12254
+ if (node.type === "AGG_REF") return Number(evalAggregate(node.func, node.distinct, node.arg, node.separator, rows, resolveAggSortKind));
12255
+ const l = evalAggArithExpr(node.left, rows, resolveAggSortKind);
12256
+ const r = evalAggArithExpr(node.right, rows, resolveAggSortKind);
12257
+ switch (node.op) {
12258
+ case "+":
12259
+ return l + r;
12260
+ case "-":
12261
+ return l - r;
12262
+ case "*":
12263
+ return l * r;
12264
+ case "/":
12265
+ return r !== 0 ? l / r : NaN;
12266
+ case "%":
12267
+ return r !== 0 ? l % r : NaN;
12317
12268
  }
12318
- const clone = {};
12319
- seen.set(object, clone);
12320
- for (const [key, child] of Object.entries(value)) clone[key] = deepClone(child, seen);
12321
- return clone;
12322
12269
  }
12323
-
12324
- // src/core/optimization/whereCapability.ts
12325
- var RANGE_AND_EQUALITY = ["=", "!=", ">", "<", ">=", "<="];
12326
- var RELATIVE_DATE_FIELD_TYPES = /* @__PURE__ */ new Set([
12327
- "DATE",
12328
- "DATETIME",
12329
- "CREATED_TIME",
12330
- "UPDATED_TIME"
12331
- ]);
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
- ]);
12343
- var EQUALITY_IN = ["=", "!=", "in", "not in"];
12344
- var NATIVE_OPERATORS = /* @__PURE__ */ new Map([
12345
- ["RECORD_NUMBER", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
12346
- ["__ID__", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
12347
- ["CREATOR", /* @__PURE__ */ new Set(["in", "not in"])],
12348
- ["MODIFIER", /* @__PURE__ */ new Set(["in", "not in"])],
12349
- ["CREATED_TIME", new Set(RANGE_AND_EQUALITY)],
12350
- ["UPDATED_TIME", new Set(RANGE_AND_EQUALITY)],
12351
- ["DATE", new Set(RANGE_AND_EQUALITY)],
12352
- ["TIME", new Set(RANGE_AND_EQUALITY)],
12353
- ["DATETIME", new Set(RANGE_AND_EQUALITY)],
12354
- ["SINGLE_LINE_TEXT", /* @__PURE__ */ new Set(["=", "!=", "in", "not in", "like", "not like"])],
12355
- ["LINK", /* @__PURE__ */ new Set(["=", "!=", "in", "not in", "like", "not like"])],
12356
- ["NUMBER", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
12357
- ["CALC", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
12358
- ["MULTI_LINE_TEXT", /* @__PURE__ */ new Set(["like", "not like"])],
12359
- ["RICH_TEXT", /* @__PURE__ */ new Set(["like", "not like"])],
12360
- ["CHECK_BOX", /* @__PURE__ */ new Set(["in", "not in"])],
12361
- ["RADIO_BUTTON", /* @__PURE__ */ new Set(["in", "not in"])],
12362
- ["DROP_DOWN", /* @__PURE__ */ new Set(["in", "not in"])],
12363
- ["MULTI_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
12364
- ["FILE", /* @__PURE__ */ new Set(["like", "not like"])],
12365
- ["USER_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
12366
- ["ORGANIZATION_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
12367
- ["GROUP_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
12368
- ["STATUS", new Set(EQUALITY_IN)]
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
- ]);
12376
- var LOCAL_SCALAR_TYPES = /* @__PURE__ */ new Set([
12377
- "RECORD_NUMBER",
12378
- "__ID__",
12379
- "CREATOR",
12380
- "MODIFIER",
12381
- "CREATED_TIME",
12382
- "UPDATED_TIME",
12383
- "DATE",
12384
- "TIME",
12385
- "DATETIME",
12386
- "SINGLE_LINE_TEXT",
12387
- "LINK",
12388
- "NUMBER",
12389
- "CALC",
12390
- "MULTI_LINE_TEXT",
12391
- "RICH_TEXT",
12392
- "RADIO_BUTTON",
12393
- "DROP_DOWN",
12394
- "STATUS",
12395
- // 一時表・CTE・式列は kintone REST へは送らず、共有ローカル評価器で扱う。
12396
- "KSQL_STRING",
12397
- "KSQL_NUMBER",
12398
- "KSQL_BOOLEAN"
12399
- ]);
12400
- var LOCAL_COLLECTION_TYPES = /* @__PURE__ */ new Set([
12401
- "CHECK_BOX",
12402
- "MULTI_SELECT",
12403
- "FILE",
12404
- "USER_SELECT",
12405
- "ORGANIZATION_SELECT",
12406
- "GROUP_SELECT",
12407
- "STATUS_ASSIGNEE",
12408
- "CATEGORY"
12409
- ]);
12410
- function nativeWhereOperatorsForType(fieldType) {
12411
- return NATIVE_OPERATORS.get(fieldType) ?? /* @__PURE__ */ new Set();
12270
+ function aggArithDefaultKey(node) {
12271
+ return aggregateOperandLabel(node);
12412
12272
  }
12413
- function classifyWhereCapability(where, resolveField2) {
12414
- if (where === null) {
12415
- return { capability: "EXACT_PUSHDOWN", reasons: [{ code: "WHERE_EXACT" }] };
12273
+ function resolveAggregateArgSemantics(arg, resolver) {
12274
+ if (arg.type === "FIELD_REF") return resolver?.(toAggregateFieldRef(arg.field)) ?? "string";
12275
+ if (arg.type === "FIELD") return resolver?.(arg) ?? "string";
12276
+ if (arg.type === "NUMBER" || arg.type === "ARITH" || arg.type === "SCALAR_ARITH") return "number";
12277
+ if (arg.type === "STRING" || arg.type === "CONCAT_OP" || arg.type === "VARIABLE") return "string";
12278
+ if (arg.type === "STRING_FUNC") {
12279
+ const numeric = /* @__PURE__ */ new Set(["LENGTH", "LENGTH_CHAR", "INSTR", "ROUND", "FLOOR", "CEIL", "TRUNCATE", "YEAR", "MONTH", "DAY", "DATEDIFF", "ABS", "MOD", "POWER", "SQRT", "DAYOFWEEK", "QUARTER", "WEEK"]);
12280
+ if (arg.func === "CAST") return arg.args[1]?.type === "STRING" && arg.args[1].value === "NUMBER" ? "number" : "string";
12281
+ return numeric.has(arg.func) ? "number" : "string";
12416
12282
  }
12417
- return classifyNode(where, resolveField2);
12283
+ const results = [...arg.branches.map((branch) => branch.result), ...arg.elseResult === null ? [] : [arg.elseResult]].filter((result) => result.type !== "ARRAY").map((result) => resolveAggregateArgSemantics(result, resolver));
12284
+ if (results.length === 0 || results.some((result) => result === void 0)) return "string";
12285
+ const kinds = results.map((result) => typeof result === "string" ? result : result.compareMode === "number" || result.compareMode === "recordNumber" ? "number" : "string");
12286
+ if (!kinds.every((kind) => kind === kinds[0])) return "string";
12287
+ const first = results[0];
12288
+ return results.every((result) => JSON.stringify(result) === JSON.stringify(first)) ? first : kinds[0];
12418
12289
  }
12419
- function classifyNode(where, resolveField2) {
12420
- switch (where.type) {
12421
- case "BOOLEAN":
12422
- return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
12423
- case "BINARY":
12424
- return classifyBinary(where.op, where.left, where.right, resolveField2);
12425
- case "NULL_CHECK":
12426
- if (where.field.type !== "FIELD") return localExpression();
12427
- return classifyLocalOnlyField(where.field, where.not ? "IS NOT NULL" : "IS NULL", resolveField2);
12428
- case "EXISTS":
12429
- return localExpression();
12430
- case "GROUP":
12431
- return classifyNode(where.expr, resolveField2);
12432
- case "NOT": {
12433
- const inner = classifyNode(where.expr, resolveField2);
12434
- if (inner.capability !== "SUPERSET_PREFILTER") return inner;
12435
- if (!hasRelativeDateReason(inner.reasons)) {
12436
- return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
12290
+ function applyHaving(rows, having, resolveFieldType, resolveFieldSemantics2) {
12291
+ if (having === null) return rows;
12292
+ return rows.filter((row) => evalWhere(having, row, resolveFieldType, void 0, resolveFieldSemantics2));
12293
+ }
12294
+ function applyDistinct(rows, columns, scalarCache, resolveFieldType, resolveFieldSemantics2) {
12295
+ if (rows.length === 0) return rows;
12296
+ const keyFor = buildDistinctKeyBuilder(rows, columns, {
12297
+ scalarCache,
12298
+ resolveFieldType,
12299
+ resolveFieldSemantics: resolveFieldSemantics2
12300
+ });
12301
+ const seen = /* @__PURE__ */ new Set();
12302
+ return rows.filter((row) => {
12303
+ const key = keyFor(row);
12304
+ if (seen.has(key)) return false;
12305
+ seen.add(key);
12306
+ return true;
12307
+ });
12308
+ }
12309
+ function buildDistinctKeyBuilder(rows, columns, context) {
12310
+ let sortedWildcardKeys = [];
12311
+ if (columns.some((c) => c.type === "WILDCARD")) {
12312
+ const allKeys = /* @__PURE__ */ new Set();
12313
+ for (const row of rows) {
12314
+ for (const k of Object.keys(row)) allKeys.add(k);
12315
+ }
12316
+ sortedWildcardKeys = [...allKeys].sort();
12317
+ }
12318
+ let sortedParentKeys = [];
12319
+ if (columns.some((c) => c.type === "PARENT_WILDCARD")) {
12320
+ const parentKeys = /* @__PURE__ */ new Set();
12321
+ for (const row of rows) {
12322
+ for (const k of Object.keys(row)) {
12323
+ if (k.startsWith("_p.")) parentKeys.add(k);
12324
+ }
12325
+ }
12326
+ sortedParentKeys = [...parentKeys].sort();
12327
+ }
12328
+ const distinctContext = {
12329
+ ...context,
12330
+ wildcardKeys: sortedWildcardKeys,
12331
+ parentWildcardKeys: sortedParentKeys
12332
+ };
12333
+ return (row) => JSON.stringify(buildDistinctTuple(columns, row, distinctContext));
12334
+ }
12335
+ function applyOrderBy(rows, orderBy, optionOrders, sortKinds, fieldSemantics2, aliasEvaluator) {
12336
+ if (orderBy.length === 0) return rows;
12337
+ return sortDecoratedRows(rows, orderBy, optionOrders, sortKinds, fieldSemantics2, aliasEvaluator).rows.map((item) => item.row);
12338
+ }
12339
+ function sortDecoratedRows(rows, orderBy, optionOrders, sortKinds, fieldSemantics2, aliasEvaluator) {
12340
+ const keyMeta = orderBy.map(({ key }) => {
12341
+ if (key.type === "ARITH_KEY") return { semantics: syntheticSemantics("number") };
12342
+ if (key.type === "FUNC_KEY") {
12343
+ return { semantics: syntheticSemantics(NUMERIC_ORDER_FUNCTIONS.has(key.expr.func) ? "number" : "string") };
12344
+ }
12345
+ if (key.type === "GROUPING_KEY") return { semantics: syntheticSemantics("number") };
12346
+ const semantics = fieldSemantics2?.get(key.name);
12347
+ if (semantics) return { semantics };
12348
+ const orderMap = optionOrders?.get(key.name);
12349
+ if (orderMap) {
12350
+ return {
12351
+ semantics: {
12352
+ fieldType: "MULTI_SELECT",
12353
+ compareMode: "option",
12354
+ inSubtable: false,
12355
+ requiresCollectionOperators: false,
12356
+ optionOrder: orderMap
12357
+ }
12358
+ };
12359
+ }
12360
+ return { semantics: syntheticSemantics(sortKinds?.get(key.name) ?? "string") };
12361
+ });
12362
+ const decorated = rows.map((row) => ({
12363
+ row,
12364
+ keys: orderBy.map(({ key }, i) => {
12365
+ const s = evalOrderKey(key, row, aliasEvaluator);
12366
+ return { s };
12367
+ })
12368
+ }));
12369
+ const compare = (a, b) => compareDecoratedRows(a, b, orderBy, keyMeta);
12370
+ decorated.sort(compare);
12371
+ return { rows: decorated, compare };
12372
+ }
12373
+ function compareDecoratedRows(a, b, orderBy, keyMeta) {
12374
+ for (let i = 0; i < orderBy.length; i++) {
12375
+ const cmp = compareSortKeys(a.keys[i], b.keys[i], keyMeta[i]);
12376
+ if (cmp !== 0) return orderBy[i].direction === "ASC" ? cmp : -cmp;
12377
+ }
12378
+ return 0;
12379
+ }
12380
+ function compareSortKeys(a, b, meta) {
12381
+ return compareCanonicalValues(a.s, b.s, meta.semantics);
12382
+ }
12383
+ var NUMERIC_ORDER_FUNCTIONS = /* @__PURE__ */ new Set([
12384
+ "LENGTH",
12385
+ "LENGTH_CHAR",
12386
+ "INSTR",
12387
+ "ROUND",
12388
+ "FLOOR",
12389
+ "CEIL",
12390
+ "TRUNCATE",
12391
+ "YEAR",
12392
+ "MONTH",
12393
+ "DAY",
12394
+ "DATEDIFF",
12395
+ "ABS",
12396
+ "MOD",
12397
+ "POWER",
12398
+ "SQRT",
12399
+ "DAYOFWEEK",
12400
+ "QUARTER",
12401
+ "WEEK"
12402
+ ]);
12403
+ function evalOrderKey(key, row, aliasEvaluator) {
12404
+ switch (key.type) {
12405
+ case "FIELD_NAME":
12406
+ return aliasEvaluator?.(key.name, row) ?? row[key.name] ?? "";
12407
+ case "ARITH_KEY":
12408
+ return String(evalArithExpr(key.expr, row));
12409
+ case "FUNC_KEY":
12410
+ return evalStringFunc(key.expr, row);
12411
+ case "GROUPING_KEY":
12412
+ return evalGroupingRef(key.ref, row);
12413
+ }
12414
+ }
12415
+ function buildOrderByAliasEvaluator(columns, scalarCache, resolveFieldType, resolveFieldSemantics2) {
12416
+ const evaluators = /* @__PURE__ */ new Map();
12417
+ for (const [columnIndex, column] of columns.entries()) {
12418
+ if (!("alias" in column) || column.alias === null) continue;
12419
+ const alias = column.alias;
12420
+ switch (column.type) {
12421
+ case "FIELD":
12422
+ evaluators.set(alias, (row) => resolveFieldRef(row, column.field));
12423
+ break;
12424
+ case "LITERAL_COL":
12425
+ evaluators.set(alias, () => column.value);
12426
+ break;
12427
+ case "AGGREGATE": {
12428
+ const source = aggregateSyntheticName(column.func, column.distinct, column.arg);
12429
+ evaluators.set(alias, (row) => row[alias] ?? row[source] ?? "0");
12430
+ break;
12431
+ }
12432
+ case "ARITH_AGG_COL": {
12433
+ const source = aggArithDefaultKey(column.expr);
12434
+ evaluators.set(alias, (row) => row[alias] ?? row[source] ?? "0");
12435
+ break;
12436
+ }
12437
+ case "WINDOW_COL":
12438
+ evaluators.set(alias, (row) => row[alias] ?? "");
12439
+ break;
12440
+ case "ARITH_COL":
12441
+ evaluators.set(alias, (row) => String(evalArithExpr(column.expr, row)));
12442
+ break;
12443
+ case "STRFUNC_COL": {
12444
+ const source = stringFuncDefaultKey(column.expr);
12445
+ evaluators.set(alias, (row) => hasAggregateInStringFuncExpr2(column.expr) ? row[alias] ?? row[source] ?? evalStringFunc(column.expr, row, resolveFieldType, resolveFieldSemantics2) : evalStringFunc(column.expr, row, resolveFieldType, resolveFieldSemantics2));
12446
+ break;
12447
+ }
12448
+ case "CASE_COL":
12449
+ evaluators.set(alias, (row) => evalCaseWhen(column.expr, row, resolveFieldType, resolveFieldSemantics2));
12450
+ break;
12451
+ case "SCALAR_VALUE_COL": {
12452
+ const source = scalarValueDefaultKey(column.expr);
12453
+ evaluators.set(alias, (row) => scalarValueHasAggregate2(column.expr) ? row[alias] ?? row[source] ?? "" : String(evalScalarValueExpr(column.expr, row, resolveFieldType, resolveFieldSemantics2)));
12454
+ break;
12455
+ }
12456
+ case "SCALAR_SUBQUERY_COL":
12457
+ evaluators.set(alias, () => scalarCache?.get(columnIndex) ?? "");
12458
+ break;
12459
+ case "GROUPING_COL":
12460
+ evaluators.set(alias, (row) => evalGroupingRef(column.ref, row));
12461
+ break;
12462
+ case "VARIABLE_COL":
12463
+ break;
12464
+ }
12465
+ }
12466
+ return (name, row) => evaluators.get(name)?.(row);
12467
+ }
12468
+ function applyWindow(rows, columns, optionOrders, sortKinds, fieldSemantics2) {
12469
+ const windows = columns.filter((column) => column.type === "WINDOW_COL");
12470
+ if (rows.length === 0 || windows.length === 0) return rows;
12471
+ for (const window of windows) {
12472
+ const partitions = /* @__PURE__ */ new Map();
12473
+ for (const row of rows) {
12474
+ const key = JSON.stringify(window.partitionBy.map((ref) => resolveWindowField(row, ref)));
12475
+ const partition = partitions.get(key);
12476
+ if (partition) partition.push(row);
12477
+ else partitions.set(key, [row]);
12478
+ }
12479
+ for (const partition of partitions.values()) {
12480
+ const sortedResult = sortDecoratedRows(partition, window.orderBy, optionOrders, sortKinds, fieldSemantics2);
12481
+ const sorted = sortedResult.rows;
12482
+ let rank = 1;
12483
+ let denseRank = 1;
12484
+ for (let index = 0; index < sorted.length; index++) {
12485
+ if (index > 0 && sortedResult.compare(sorted[index - 1], sorted[index]) !== 0) {
12486
+ rank = index + 1;
12487
+ denseRank++;
12488
+ }
12489
+ const value = window.func === "ROW_NUMBER" ? index + 1 : window.func === "RANK" ? rank : denseRank;
12490
+ sorted[index].row[window.alias] = String(value);
12491
+ }
12492
+ }
12493
+ }
12494
+ return rows;
12495
+ }
12496
+ function resolveWindowField(row, ref) {
12497
+ const name = ref.tableAlias ? `${ref.tableAlias}.${ref.field}` : ref.field;
12498
+ return resolveFieldRef(row, name);
12499
+ }
12500
+ function applyLimit(rows, limit, offset) {
12501
+ const start = offset ?? 0;
12502
+ if (limit === null) return rows.slice(start);
12503
+ return rows.slice(start, start + limit);
12504
+ }
12505
+ function evaluateSelectColumnValue(column, row, columnIndex, context = {}) {
12506
+ switch (column.type) {
12507
+ case "VARIABLE_COL":
12508
+ throw new Error(`internal error: unresolved SELECT variable @${column.name}`);
12509
+ case "WILDCARD": {
12510
+ const keys = context.wildcardKeys ?? Object.keys(row);
12511
+ return {
12512
+ kind: "EXPANDED",
12513
+ entries: keys.map((key) => [key, row[key] !== void 0 ? row[key] : null])
12514
+ };
12515
+ }
12516
+ case "PARENT_WILDCARD": {
12517
+ const keys = context.parentWildcardKeys ?? Object.keys(row).filter((key) => key.startsWith("_p.")).sort();
12518
+ return {
12519
+ kind: "EXPANDED",
12520
+ entries: keys.map((key) => [key, row[key] !== void 0 ? row[key] : null])
12521
+ };
12522
+ }
12523
+ case "FIELD":
12524
+ return resolveFieldRef(row, column.field);
12525
+ case "LITERAL_COL":
12526
+ return column.value;
12527
+ case "AGGREGATE": {
12528
+ const source = aggregateSyntheticName(column.func, column.distinct, column.arg);
12529
+ return row[column.alias ?? source] ?? row[source] ?? "0";
12530
+ }
12531
+ case "ARITH_AGG_COL": {
12532
+ const source = column.alias ?? aggArithDefaultKey(column.expr);
12533
+ return row[source] ?? "0";
12534
+ }
12535
+ case "ARITH_COL":
12536
+ return String(evalArithExpr(column.expr, row));
12537
+ case "CASE_COL":
12538
+ return evalCaseWhen(
12539
+ column.expr,
12540
+ row,
12541
+ context.resolveFieldType,
12542
+ context.resolveFieldSemantics
12543
+ );
12544
+ case "GROUPING_COL":
12545
+ return evalGroupingRef(column.ref, row);
12546
+ case "STRFUNC_COL": {
12547
+ const source = stringFuncDefaultKey(column.expr);
12548
+ return hasAggregateInStringFuncExpr2(column.expr) ? row[column.alias ?? source] ?? row[source] ?? evalStringFunc(
12549
+ column.expr,
12550
+ row,
12551
+ context.resolveFieldType,
12552
+ context.resolveFieldSemantics
12553
+ ) : evalStringFunc(
12554
+ column.expr,
12555
+ row,
12556
+ context.resolveFieldType,
12557
+ context.resolveFieldSemantics
12558
+ );
12559
+ }
12560
+ case "SCALAR_VALUE_COL": {
12561
+ const source = scalarValueDefaultKey(column.expr);
12562
+ return scalarValueHasAggregate2(column.expr) ? row[column.alias ?? source] ?? row[source] ?? "" : String(evalScalarValueExpr(
12563
+ column.expr,
12564
+ row,
12565
+ context.resolveFieldType,
12566
+ context.resolveFieldSemantics
12567
+ ));
12568
+ }
12569
+ case "SCALAR_SUBQUERY_COL":
12570
+ return context.scalarCache?.get(columnIndex) ?? "";
12571
+ case "WINDOW_COL":
12572
+ return row[column.alias] ?? "";
12573
+ }
12574
+ }
12575
+ function buildDistinctTuple(columns, row, context = {}) {
12576
+ return columns.map((column, columnIndex) => {
12577
+ const value = evaluateSelectColumnValue(column, row, columnIndex, context);
12578
+ return typeof value === "string" ? value : value.entries.map(([, entryValue]) => entryValue);
12579
+ });
12580
+ }
12581
+ function project(rows, columns, scalarCache, resolveFieldType, sourceColumns2, resolveFieldSemantics2, hiddenQualifiedAliases) {
12582
+ if (columns.length === 1 && columns[0].type === "WILDCARD") {
12583
+ const projected2 = rows.map((row) => {
12584
+ const visible = stripHiddenQualifiedColumns(
12585
+ stripParentShortcutColumns(row),
12586
+ hiddenQualifiedAliases
12587
+ );
12588
+ const value = evaluateSelectColumnValue(columns[0], row, 0, {
12589
+ wildcardKeys: Object.keys(visible)
12590
+ });
12591
+ const out = {};
12592
+ if (typeof value !== "string") {
12593
+ for (const [key, entryValue] of value.entries) {
12594
+ if (entryValue !== null) out[key] = entryValue;
12595
+ }
12596
+ }
12597
+ return out;
12598
+ });
12599
+ const cols = projected2.length > 0 ? Object.keys(projected2[0]) : [...sourceColumns2 ?? []];
12600
+ return { rows: projected2, columns: cols };
12601
+ }
12602
+ const defaultFieldKeys = buildDefaultFieldOutputKeys(columns);
12603
+ const hasWildcard = columns.some(
12604
+ (col) => col.type === "WILDCARD" || col.type === "PARENT_WILDCARD"
12605
+ );
12606
+ const outputKeys = hasWildcard ? null : computeOutputKeys(columns, defaultFieldKeys);
12607
+ const orderedKeys = outputKeys ?? [];
12608
+ if (hasWildcard && rows.length === 0) {
12609
+ return { rows: [], columns: computeExplicitOutputKeys(columns, defaultFieldKeys) };
12610
+ }
12611
+ const projected = rows.map((row, rowIdx) => {
12612
+ const out = {};
12613
+ const evaluationContext = {
12614
+ scalarCache,
12615
+ resolveFieldType,
12616
+ resolveFieldSemantics: resolveFieldSemantics2,
12617
+ wildcardKeys: Object.keys(stripHiddenQualifiedColumns(
12618
+ stripParentShortcutColumns(row),
12619
+ hiddenQualifiedAliases
12620
+ )),
12621
+ parentWildcardKeys: Object.keys(row).filter((key) => key.startsWith("_p.")).sort()
12622
+ };
12623
+ for (const [colIdx, col] of columns.entries()) {
12624
+ const value = evaluateSelectColumnValue(col, row, colIdx, evaluationContext);
12625
+ switch (col.type) {
12626
+ case "VARIABLE_COL":
12627
+ break;
12628
+ case "WILDCARD":
12629
+ if (typeof value !== "string") {
12630
+ for (const [key, entryValue] of value.entries) {
12631
+ if (entryValue !== null) out[key] = entryValue;
12632
+ }
12633
+ }
12634
+ break;
12635
+ case "PARENT_WILDCARD": {
12636
+ if (typeof value !== "string") {
12637
+ for (const [key, entryValue] of value.entries) {
12638
+ if (entryValue !== null) out[key] = entryValue;
12639
+ if (rowIdx === 0) orderedKeys.push(key);
12640
+ }
12641
+ }
12642
+ break;
12643
+ }
12644
+ case "FIELD": {
12645
+ const key = outputKeys?.[colIdx] ?? col.alias ?? defaultFieldKeys.get(colIdx) ?? col.field;
12646
+ out[key] = value;
12647
+ if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
12648
+ break;
12649
+ }
12650
+ case "LITERAL_COL": {
12651
+ const key = outputKeys?.[colIdx] ?? col.alias ?? `'${col.value}'`;
12652
+ out[key] = value;
12653
+ if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
12654
+ break;
12655
+ }
12656
+ case "AGGREGATE": {
12657
+ const srcKey = aggregateSyntheticName(col.func, col.distinct, col.arg);
12658
+ const dstKey = outputKeys?.[colIdx] ?? col.alias ?? srcKey;
12659
+ out[dstKey] = value;
12660
+ if (outputKeys === null && rowIdx === 0) orderedKeys.push(dstKey);
12661
+ break;
12662
+ }
12663
+ case "ARITH_AGG_COL": {
12664
+ const key = outputKeys?.[colIdx] ?? col.alias ?? aggArithDefaultKey(col.expr);
12665
+ out[key] = value;
12666
+ if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
12667
+ break;
12668
+ }
12669
+ case "ARITH_COL": {
12670
+ const key = outputKeys?.[colIdx] ?? col.alias ?? arithColDefaultKey(col.expr);
12671
+ out[key] = value;
12672
+ if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
12673
+ break;
12674
+ }
12675
+ case "CASE_COL": {
12676
+ const key = outputKeys?.[colIdx] ?? col.alias ?? "case";
12677
+ out[key] = value;
12678
+ if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
12679
+ break;
12680
+ }
12681
+ case "GROUPING_COL": {
12682
+ const key = outputKeys?.[colIdx] ?? col.alias ?? `GROUPING(${col.ref.field.tableAlias ? `${col.ref.field.tableAlias}.` : ""}${col.ref.field.field})`;
12683
+ out[key] = value;
12684
+ if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
12685
+ break;
12686
+ }
12687
+ case "STRFUNC_COL": {
12688
+ const key = outputKeys?.[colIdx] ?? col.alias ?? stringFuncDefaultKey(col.expr);
12689
+ out[key] = value;
12690
+ if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
12691
+ break;
12692
+ }
12693
+ case "SCALAR_VALUE_COL": {
12694
+ const key = outputKeys?.[colIdx] ?? col.alias ?? scalarValueDefaultKey(col.expr);
12695
+ out[key] = value;
12696
+ if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
12697
+ break;
12698
+ }
12699
+ case "SCALAR_SUBQUERY_COL": {
12700
+ const key = outputKeys?.[colIdx] ?? col.alias ?? "(subquery)";
12701
+ out[key] = value;
12702
+ if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
12703
+ break;
12704
+ }
12705
+ case "WINDOW_COL": {
12706
+ const key = outputKeys?.[colIdx] ?? col.alias;
12707
+ out[key] = value;
12708
+ if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
12709
+ break;
12710
+ }
12437
12711
  }
12438
- return requireExactFunctionPushdown({
12439
- capability: "LOCAL_ONLY",
12440
- reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }, ...inner.reasons]
12441
- });
12442
- }
12443
- case "LOGICAL": {
12444
- const left = classifyNode(where.left, resolveField2);
12445
- const right = classifyNode(where.right, resolveField2);
12446
- return combineLogical(where.op, left, right);
12447
12712
  }
12448
- }
12713
+ return out;
12714
+ });
12715
+ return { rows: projected, columns: orderedKeys };
12449
12716
  }
12450
- function classifyBinary(op, left, right, resolveField2) {
12451
- if (right.type === "KINTONE_FUNC" && isRelativeDateFunctionName(right.name)) {
12452
- return classifyRelativeDateBinary(op, left, right, resolveField2);
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
- );
12717
+ function computeOutputKeys(columns, defaultFieldKeys) {
12718
+ return columns.map((col, colIdx) => computeOutputKey(col, colIdx, defaultFieldKeys));
12719
+ }
12720
+ function computeExplicitOutputKeys(columns, defaultFieldKeys) {
12721
+ const keys = [];
12722
+ const seen = /* @__PURE__ */ new Set();
12723
+ for (const [colIdx, col] of columns.entries()) {
12724
+ if (col.type === "WILDCARD" || col.type === "PARENT_WILDCARD") continue;
12725
+ const key = computeOutputKey(col, colIdx, defaultFieldKeys);
12726
+ if (!seen.has(key)) {
12727
+ seen.add(key);
12728
+ keys.push(key);
12472
12729
  }
12473
12730
  }
12474
- if (left.type !== "FIELD") return localExpression();
12475
- const semantics = resolveField2(left);
12476
- if (!semantics) {
12477
- return unsupported2("WHERE_FIELD_UNRESOLVED", left.field, void 0, normalizeOperator(op));
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
- );
12731
+ return keys;
12732
+ }
12733
+ function computeOutputKey(col, colIdx, defaultFieldKeys) {
12734
+ switch (col.type) {
12735
+ case "VARIABLE_COL":
12736
+ throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
12737
+ case "FIELD":
12738
+ return col.alias ?? defaultFieldKeys.get(colIdx) ?? col.field;
12739
+ case "LITERAL_COL":
12740
+ return col.alias ?? `'${col.value}'`;
12741
+ case "AGGREGATE":
12742
+ return col.alias ?? aggregateSyntheticName(col.func, col.distinct, col.arg);
12743
+ case "ARITH_AGG_COL":
12744
+ return col.alias ?? aggArithDefaultKey(col.expr);
12745
+ case "ARITH_COL":
12746
+ return col.alias ?? arithColDefaultKey(col.expr);
12747
+ case "CASE_COL":
12748
+ return col.alias ?? "case";
12749
+ case "STRFUNC_COL":
12750
+ return col.alias ?? stringFuncDefaultKey(col.expr);
12751
+ case "SCALAR_VALUE_COL":
12752
+ return col.alias ?? scalarValueDefaultKey(col.expr);
12753
+ case "GROUPING_COL":
12754
+ return col.alias ?? `GROUPING(${col.ref.field.tableAlias ? `${col.ref.field.tableAlias}.` : ""}${col.ref.field.field})`;
12755
+ case "SCALAR_SUBQUERY_COL":
12756
+ return col.alias ?? "(subquery)";
12757
+ case "WINDOW_COL":
12758
+ return col.alias;
12759
+ case "WILDCARD":
12760
+ case "PARENT_WILDCARD":
12761
+ throw new Error("internal: computeOutputKey received a wildcard column");
12487
12762
  }
12488
- if (!hasLocalContract(semantics.fieldType, op)) {
12489
- return unsupported2("WHERE_OPERATOR_UNSUPPORTED", left.field, semantics.fieldType, nativeOp);
12763
+ }
12764
+ function buildDefaultFieldOutputKeys(columns) {
12765
+ const qualifierCollisionCount = /* @__PURE__ */ new Map();
12766
+ for (const col of columns) {
12767
+ if (col.type !== "FIELD" || col.alias) continue;
12768
+ const unqualified = stripTableQualifier(col.field);
12769
+ qualifierCollisionCount.set(unqualified, (qualifierCollisionCount.get(unqualified) ?? 0) + 1);
12490
12770
  }
12491
- const native = nativeWhereOperatorsForType(semantics.fieldType);
12492
- const rightCanPush = right.type === "STRING" || right.type === "NUMBER" || right.type === "IN_LIST" || isLegacyKintoneFunction(right);
12493
- const structureAllows = !semantics.requiresCollectionOperators || nativeOp !== "=" && nativeOp !== "!=";
12494
- const sqlLikeIsResidual = op === "LIKE" || op === "NOT_LIKE";
12495
- if (rightCanPush && structureAllows && native.has(nativeOp) && !sqlLikeIsResidual) {
12496
- return {
12497
- capability: "EXACT_PUSHDOWN",
12498
- reasons: [{
12499
- code: "WHERE_EXACT",
12500
- field: left.field,
12501
- fieldType: semantics.fieldType,
12502
- operator: nativeOp
12503
- }]
12504
- };
12771
+ const keys = /* @__PURE__ */ new Map();
12772
+ for (const [idx, col] of columns.entries()) {
12773
+ if (col.type !== "FIELD" || col.alias) continue;
12774
+ const unqualified = stripTableQualifier(col.field);
12775
+ const duplicate = (qualifierCollisionCount.get(unqualified) ?? 0) > 1;
12776
+ const hasTableQualifier = col.field.includes(".") && !col.field.startsWith("_p.");
12777
+ keys.set(idx, duplicate && hasTableQualifier ? col.field : unqualified);
12505
12778
  }
12506
- return {
12507
- capability: "LOCAL_ONLY",
12508
- reasons: [{
12509
- code: "WHERE_RESIDUAL",
12510
- field: left.field,
12511
- fieldType: semantics.fieldType,
12512
- operator: nativeOp
12513
- }]
12514
- };
12779
+ return keys;
12515
12780
  }
12516
- function isLegacyKintoneFunction(value) {
12517
- return value.type === "KINTONE_FUNC" && LEGACY_KINTONE_FUNCTION_NAMES.has(value.name);
12781
+ function stripTableQualifier(field) {
12782
+ if (field.startsWith("_p.")) return field;
12783
+ const dot = field.indexOf(".");
12784
+ if (dot <= 0) return field;
12785
+ const unqualified = field.slice(dot + 1);
12786
+ return unqualified || field;
12518
12787
  }
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
- );
12788
+ function stripParentShortcutColumns(row) {
12789
+ const out = {};
12790
+ for (const [k, v] of Object.entries(row)) {
12791
+ if (k.startsWith("_p.")) continue;
12792
+ out[k] = v;
12530
12793
  }
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
- );
12794
+ return out;
12795
+ }
12796
+ function arithColDefaultKey(expr) {
12797
+ const nodeLabel = (n) => {
12798
+ if (n.type === "FIELD_REF") return n.field;
12799
+ if (n.type === "NUMBER") return numberLiteralText(n);
12800
+ if (n.type === "STRING_FUNC") return stringFuncDefaultKey(n);
12801
+ return `(${nodeLabel(n.left)}${n.op}${nodeLabel(n.right)})`;
12802
+ };
12803
+ if (expr.type === "ARITH") {
12804
+ return `${nodeLabel(expr.left)}${expr.op}${nodeLabel(expr.right)}`;
12541
12805
  }
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
- );
12806
+ return nodeLabel(expr);
12807
+ }
12808
+ function stringFuncDefaultKey(expr) {
12809
+ const argStrs = expr.args.map((a) => {
12810
+ if (a.type === "AGG_REF" || a.type === "AGG_ARITH") return aggArithDefaultKey(a);
12811
+ return scalarValueDefaultKey(a);
12812
+ });
12813
+ return `${expr.func}(${argStrs.join(",")})`;
12814
+ }
12815
+ function scalarValueDefaultKey(expr) {
12816
+ switch (expr.type) {
12817
+ case "STRING":
12818
+ return `'${expr.value}'`;
12819
+ case "NUMBER":
12820
+ return numberLiteralText(expr);
12821
+ case "VARIABLE":
12822
+ return `@${expr.name}`;
12823
+ case "FIELD":
12824
+ return expr.tableAlias ? `${expr.tableAlias}.${expr.field}` : expr.field;
12825
+ case "STRING_FUNC":
12826
+ return stringFuncDefaultKey(expr);
12827
+ case "CASE_WHEN":
12828
+ return "case";
12829
+ case "SCALAR_ARITH":
12830
+ return `${scalarValueDefaultKey(expr.left)}${expr.op}${scalarValueDefaultKey(expr.right)}`;
12831
+ case "CONCAT_OP":
12832
+ return `${scalarValueDefaultKey(expr.left)}||${scalarValueDefaultKey(expr.right)}`;
12551
12833
  }
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
- };
12562
12834
  }
12563
- function classifyRelativeDateBinary(op, left, right, resolveField2) {
12564
- const operator = normalizeOperator(op);
12565
- const functionName = right.name;
12566
- if (left.type !== "FIELD") {
12567
- return relativeDateUnsupported(
12568
- "WHERE_RELATIVE_DATE_CONTEXT_UNSUPPORTED",
12569
- functionName,
12570
- void 0,
12571
- void 0,
12572
- operator
12573
- );
12835
+ function hasAggregateInStringFuncArg(arg) {
12836
+ if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") return true;
12837
+ return scalarValueHasAggregate2(arg);
12838
+ }
12839
+ function scalarValueHasAggregate2(expr) {
12840
+ if (expr.type === "STRING_FUNC") return hasAggregateInStringFuncExpr2(expr);
12841
+ if (expr.type === "SCALAR_ARITH" || expr.type === "CONCAT_OP") {
12842
+ return scalarValueHasAggregate2(expr.left) || scalarValueHasAggregate2(expr.right);
12574
12843
  }
12575
- const semantics = resolveField2(left);
12576
- if (!semantics) {
12577
- return relativeDateUnsupported(
12578
- "WHERE_RELATIVE_DATE_FIELD_TYPE_UNSUPPORTED",
12579
- functionName,
12580
- left.field,
12581
- void 0,
12582
- operator
12583
- );
12844
+ if (expr.type === "CASE_WHEN") {
12845
+ return expr.branches.some((branch) => caseResultHasAggregate2(branch.result)) || expr.elseResult !== null && caseResultHasAggregate2(expr.elseResult);
12584
12846
  }
12585
- if (!hasValidRelativeDateArguments(right)) {
12586
- return relativeDateUnsupported(
12587
- "WHERE_RELATIVE_DATE_ARGUMENT_INVALID",
12588
- functionName,
12589
- left.field,
12590
- semantics.fieldType,
12591
- operator
12592
- );
12847
+ return false;
12848
+ }
12849
+ function caseResultHasAggregate2(result) {
12850
+ if (result.type === "ARRAY" || result.type === "FIELD_REF" || result.type === "ARITH") return false;
12851
+ return scalarValueHasAggregate2(result);
12852
+ }
12853
+ function hasAggregateInStringFuncExpr2(expr) {
12854
+ return expr.args.some((arg) => hasAggregateInStringFuncArg(arg));
12855
+ }
12856
+ function resolveAggInStringFuncArg(arg, rows, resolveAggSortKind) {
12857
+ if (arg.type === "AGG_REF") {
12858
+ const value = evalAggregate(arg.func, arg.distinct, arg.arg, arg.separator, rows, resolveAggSortKind);
12859
+ return typeof value === "number" ? { type: "NUMBER", value, raw: String(value) } : { type: "STRING", value };
12593
12860
  }
12594
- if (!RELATIVE_DATE_OPERATORS.has(operator)) {
12595
- return relativeDateUnsupported(
12596
- "WHERE_RELATIVE_DATE_OPERATOR_UNSUPPORTED",
12597
- functionName,
12598
- left.field,
12599
- semantics.fieldType,
12600
- operator
12601
- );
12861
+ if (arg.type === "AGG_ARITH") {
12862
+ const value = evalAggArithExpr(arg, rows, resolveAggSortKind);
12863
+ return { type: "NUMBER", value, raw: String(value) };
12602
12864
  }
12603
- if (!RELATIVE_DATE_FIELD_TYPES.has(semantics.fieldType) || semantics.inSubtable || semantics.requiresCollectionOperators) {
12604
- return relativeDateUnsupported(
12605
- "WHERE_RELATIVE_DATE_FIELD_TYPE_UNSUPPORTED",
12606
- functionName,
12607
- left.field,
12608
- semantics.fieldType,
12609
- operator
12610
- );
12865
+ if (arg.type === "STRING_FUNC") {
12866
+ return resolveAggInStringFuncExpr(arg, rows, resolveAggSortKind);
12611
12867
  }
12612
- return {
12613
- capability: "EXACT_PUSHDOWN",
12614
- reasons: [{
12615
- code: "WHERE_EXACT",
12616
- functionName,
12617
- field: left.field,
12618
- fieldType: semantics.fieldType,
12619
- operator
12620
- }]
12621
- };
12868
+ return resolveAggInScalarValue(arg, rows, resolveAggSortKind);
12622
12869
  }
12623
- function hasValidRelativeDateArguments(value) {
12624
- if (!("args" in value) || !value.args) return false;
12625
- switch (value.name) {
12626
- case "YESTERDAY":
12627
- case "TOMORROW":
12628
- case "THIS_YEAR":
12629
- case "LAST_YEAR":
12630
- case "NEXT_YEAR":
12631
- return value.args.kind === "NONE";
12632
- case "FROM_TODAY":
12633
- return value.args.kind === "FROM_TODAY" && Number.isSafeInteger(value.args.offset) && value.args.offsetText === String(value.args.offset === 0 ? 0 : value.args.offset) && (value.args.unit === "DAYS" || value.args.unit === "WEEKS" || value.args.unit === "MONTHS" || value.args.unit === "YEARS");
12634
- case "THIS_WEEK":
12635
- case "LAST_WEEK":
12636
- case "NEXT_WEEK":
12637
- return value.args.kind === "WEEK" && (value.args.weekday === null || value.args.weekday === "SUNDAY" || value.args.weekday === "MONDAY" || value.args.weekday === "TUESDAY" || value.args.weekday === "WEDNESDAY" || value.args.weekday === "THURSDAY" || value.args.weekday === "FRIDAY" || value.args.weekday === "SATURDAY");
12638
- case "THIS_MONTH":
12639
- case "LAST_MONTH":
12640
- case "NEXT_MONTH":
12641
- return value.args.kind === "MONTH" && (value.args.day === null || value.args.day === "LAST" || Number.isInteger(value.args.day) && value.args.day >= 1 && value.args.day <= 31);
12642
- default:
12643
- return false;
12870
+ function resolveAggInScalarValue(expr, rows, resolveAggSortKind) {
12871
+ if (expr.type === "STRING_FUNC") return resolveAggInStringFuncExpr(expr, rows, resolveAggSortKind);
12872
+ if (expr.type === "SCALAR_ARITH" || expr.type === "CONCAT_OP") {
12873
+ return {
12874
+ ...expr,
12875
+ left: resolveAggInScalarValue(expr.left, rows, resolveAggSortKind),
12876
+ right: resolveAggInScalarValue(expr.right, rows, resolveAggSortKind)
12877
+ };
12644
12878
  }
12879
+ return expr;
12645
12880
  }
12646
- function classifyLocalOnlyField(field, operator, resolveField2) {
12647
- const semantics = resolveField2(field);
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
- }
12657
- if (!LOCAL_SCALAR_TYPES.has(semantics.fieldType) && !LOCAL_COLLECTION_TYPES.has(semantics.fieldType)) {
12658
- return unsupported2("WHERE_OPERATOR_UNSUPPORTED", field.field, semantics.fieldType, operator);
12659
- }
12881
+ function resolveAggInStringFuncExpr(expr, rows, resolveAggSortKind) {
12660
12882
  return {
12661
- capability: "LOCAL_ONLY",
12662
- reasons: [{ code: "WHERE_RESIDUAL", field: field.field, fieldType: semantics.fieldType, operator }]
12883
+ type: "STRING_FUNC",
12884
+ func: expr.func,
12885
+ args: expr.args.map((arg) => resolveAggInStringFuncArg(arg, rows, resolveAggSortKind))
12663
12886
  };
12664
12887
  }
12665
- function isLocallyValidOperator(fieldType, operator) {
12666
- const policy = LOCAL_VALID_OPERATORS.get(fieldType);
12667
- return policy === void 0 || policy.has(operator);
12888
+ function stripHiddenQualifiedColumns(row, hiddenQualifiedAliases) {
12889
+ if (!hiddenQualifiedAliases || hiddenQualifiedAliases.size === 0) return row;
12890
+ const out = {};
12891
+ for (const [key, value] of Object.entries(row)) {
12892
+ if ([...hiddenQualifiedAliases].some((alias) => key.startsWith(`${alias}.`))) continue;
12893
+ out[key] = value;
12894
+ }
12895
+ return out;
12668
12896
  }
12669
- function hasLocalContract(fieldType, op) {
12670
- if (LOCAL_SCALAR_TYPES.has(fieldType)) return true;
12671
- if (!LOCAL_COLLECTION_TYPES.has(fieldType)) return false;
12672
- return op === "=" || op === "!=" || op === "<>" || op === "IN" || op === "NOT_IN" || op === "LIKE" || op === "NOT_LIKE" || op === "KLIKE" || op === "NOT_KLIKE";
12897
+ function flattenedColumns(columns, alias) {
12898
+ if (columns === void 0) return void 0;
12899
+ return alias ? columns.flatMap((column) => [`${alias}.${column}`, column]) : [...columns];
12673
12900
  }
12674
- function normalizeOperator(op) {
12675
- switch (op) {
12676
- case "<>":
12677
- return "!=";
12678
- case "IN":
12679
- return "in";
12680
- case "NOT_IN":
12681
- return "not in";
12682
- case "LIKE":
12683
- case "KLIKE":
12684
- return "like";
12685
- case "NOT_LIKE":
12686
- case "NOT_KLIKE":
12687
- return "not like";
12688
- default:
12689
- return op;
12901
+ function mergeKnownColumns(left, right, rows) {
12902
+ if (left === void 0 && right === void 0 && rows.length === 0) return void 0;
12903
+ return [.../* @__PURE__ */ new Set([
12904
+ ...left ?? [],
12905
+ ...right ?? [],
12906
+ ...Object.keys(rows[0] ?? {})
12907
+ ])];
12908
+ }
12909
+ function deriveOutputOrderSemantics(columns) {
12910
+ const result = /* @__PURE__ */ new Map();
12911
+ for (const column of columns) {
12912
+ if (!("alias" in column) || !column.alias) continue;
12913
+ if (column.type === "ARITH_COL" || column.type === "ARITH_AGG_COL" || column.type === "WINDOW_COL") {
12914
+ result.set(column.alias, syntheticSemantics("number"));
12915
+ } else if (column.type === "AGGREGATE") {
12916
+ if (column.func === "COUNT" || column.func === "SUM" || column.func === "AVG" || column.func === "STDDEV_POP" || column.func === "STDDEV_SAMP" || column.func === "VAR_POP" || column.func === "VAR_SAMP" || column.func === "MEDIAN") {
12917
+ result.set(column.alias, syntheticSemantics("number"));
12918
+ } else if (column.func === "GROUP_CONCAT") {
12919
+ result.set(column.alias, syntheticSemantics("string"));
12920
+ }
12921
+ } else if (column.type === "LITERAL_COL" || column.type === "CASE_COL" || column.type === "SCALAR_SUBQUERY_COL" || column.type === "SCALAR_VALUE_COL") {
12922
+ result.set(column.alias, syntheticSemantics("string"));
12923
+ } else if (column.type === "STRFUNC_COL") {
12924
+ result.set(column.alias, syntheticSemantics(NUMERIC_ORDER_FUNCTIONS.has(column.expr.func) ? "number" : "string"));
12925
+ }
12690
12926
  }
12927
+ return result;
12691
12928
  }
12692
- function combineLogical(op, left, right) {
12693
- const reasons = [...left.reasons, ...right.reasons];
12694
- if (left.capability === "UNSUPPORTED" || right.capability === "UNSUPPORTED") {
12695
- return requireExactFunctionPushdown({ capability: "UNSUPPORTED", reasons });
12929
+ function runFullScan(input) {
12930
+ const {
12931
+ stmt,
12932
+ tables,
12933
+ scalarCache,
12934
+ optionOrders,
12935
+ sortKinds,
12936
+ orderSemantics,
12937
+ fieldTypeResolver,
12938
+ fieldSemanticsResolver,
12939
+ havingFieldTypeResolver,
12940
+ havingFieldSemanticsResolver,
12941
+ aggregateSortKindResolver,
12942
+ appliedKlikes,
12943
+ sourceColumns: sourceColumns2,
12944
+ tableColumns,
12945
+ hiddenQualifiedAliases,
12946
+ resolvedGroupingSpec,
12947
+ plainGroupByPlan
12948
+ } = input;
12949
+ const effectiveOrderSemantics = deriveOutputOrderSemantics(stmt.columns);
12950
+ for (const [key, value] of orderSemantics ?? []) effectiveOrderSemantics.set(key, value);
12951
+ let rows = [];
12952
+ const mainAlias = stmt.from.alias;
12953
+ const mainRecords = tables.get(mainAlias) ?? tables.get(null) ?? [];
12954
+ rows = mainRecords.map((r) => flatten(r, mainAlias));
12955
+ let knownColumns = mergeKnownColumns(
12956
+ flattenedColumns(tableColumns?.get(mainAlias), mainAlias),
12957
+ void 0,
12958
+ rows
12959
+ );
12960
+ for (const join2 of stmt.joins) {
12961
+ const rightAlias = join2.table.alias;
12962
+ const rightRecords = tables.get(rightAlias) ?? [];
12963
+ const rightRows = rightRecords.map((r) => flatten(r, rightAlias));
12964
+ const rightColumns = mergeKnownColumns(
12965
+ flattenedColumns(tableColumns?.get(rightAlias), rightAlias),
12966
+ void 0,
12967
+ rightRows
12968
+ );
12969
+ rows = applyJoin(rows, rightRows, join2, {
12970
+ leftColumns: knownColumns,
12971
+ rightColumns
12972
+ });
12973
+ knownColumns = mergeKnownColumns(knownColumns, rightColumns, rows);
12696
12974
  }
12697
- if (left.capability === "EXACT_PUSHDOWN" && right.capability === "EXACT_PUSHDOWN") {
12698
- return { capability: "EXACT_PUSHDOWN", reasons };
12975
+ const filterWhere = input.residualWhere !== void 0 ? input.residualWhere : stmt.where;
12976
+ rows = applyFilter(rows, filterWhere, fieldTypeResolver, appliedKlikes, fieldSemanticsResolver);
12977
+ const grouping = normalizeGroupingSpec(stmt);
12978
+ if (grouping.type === "GROUPING_SETS") {
12979
+ if (!resolvedGroupingSpec) {
12980
+ throw new Error("internal error: B65 grouping sets require a metadata-resolved grouping spec.");
12981
+ }
12982
+ rows = applyGroupingSets(
12983
+ rows,
12984
+ resolvedGroupingSpec,
12985
+ stmt.columns,
12986
+ aggregateSortKindResolver,
12987
+ { maxGeneratedRows: B65_MAX_GENERATED_ROWS }
12988
+ );
12989
+ } else if (grouping.type === "PLAIN" || hasAggregateColumns(stmt.columns)) {
12990
+ rows = applyGroupBy(
12991
+ rows,
12992
+ grouping.type === "PLAIN" ? grouping.allItems : [],
12993
+ stmt.columns,
12994
+ aggregateSortKindResolver,
12995
+ grouping.type === "PLAIN" ? plainGroupByPlan : void 0,
12996
+ {
12997
+ scalarCache,
12998
+ resolveFieldType: fieldTypeResolver,
12999
+ resolveFieldSemantics: fieldSemanticsResolver
13000
+ }
13001
+ );
12699
13002
  }
12700
- if (op === "AND" && (left.capability === "EXACT_PUSHDOWN" || right.capability === "EXACT_PUSHDOWN" || left.capability === "SUPERSET_PREFILTER" || right.capability === "SUPERSET_PREFILTER")) {
12701
- return requireExactFunctionPushdown({
12702
- capability: "SUPERSET_PREFILTER",
12703
- reasons: [{ code: "WHERE_SUPERSET_PREFILTER" }, ...reasons]
12704
- });
13003
+ rows = applyHaving(rows, stmt.having, havingFieldTypeResolver, havingFieldSemanticsResolver);
13004
+ rows = applyWindow(rows, stmt.columns, optionOrders, sortKinds, effectiveOrderSemantics);
13005
+ if (stmt.distinct) {
13006
+ rows = applyDistinct(
13007
+ rows,
13008
+ stmt.columns,
13009
+ scalarCache,
13010
+ fieldTypeResolver,
13011
+ fieldSemanticsResolver
13012
+ );
12705
13013
  }
12706
- return requireExactFunctionPushdown({ capability: "LOCAL_ONLY", reasons });
12707
- }
12708
- function localExpression() {
12709
- return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
13014
+ rows = applyOrderBy(
13015
+ rows,
13016
+ stmt.orderBy,
13017
+ optionOrders,
13018
+ sortKinds,
13019
+ effectiveOrderSemantics,
13020
+ buildOrderByAliasEvaluator(stmt.columns, scalarCache, fieldTypeResolver, fieldSemanticsResolver)
13021
+ );
13022
+ rows = applyLimit(rows, stmt.limit, stmt.offset);
13023
+ return project(
13024
+ rows,
13025
+ stmt.columns,
13026
+ scalarCache,
13027
+ fieldTypeResolver,
13028
+ sourceColumns2,
13029
+ fieldSemanticsResolver,
13030
+ hiddenQualifiedAliases
13031
+ );
12710
13032
  }
12711
- function unsupported2(code, field, fieldType, operator) {
12712
- return { capability: "UNSUPPORTED", reasons: [{ code, field, fieldType, operator }] };
13033
+
13034
+ // src/converter/subtableAdapter.ts
13035
+ function expandSubtableRecords(parents, subtableCode) {
13036
+ const rows = [];
13037
+ for (const parent of parents) {
13038
+ const parentId = toFlatString(parent["$id"]?.value);
13039
+ const tableValue = readTableRows(parent[subtableCode]?.value);
13040
+ if (tableValue.length === 0) continue;
13041
+ for (let i = 0; i < tableValue.length; i++) {
13042
+ const row = tableValue[i];
13043
+ const out = {
13044
+ _pid: { value: parentId },
13045
+ _rid: { value: row.id ?? "" },
13046
+ _idx: { value: String(i) }
13047
+ };
13048
+ for (const [field, fieldValue] of Object.entries(parent)) {
13049
+ if (field === subtableCode) continue;
13050
+ const flat = toFlatString(fieldValue?.value);
13051
+ out[`_p.${field}`] = { value: flat };
13052
+ }
13053
+ for (const [field, cell] of Object.entries(row.value ?? {})) {
13054
+ out[field] = { value: toFlatString(cell?.value) };
13055
+ }
13056
+ rows.push(out);
13057
+ }
13058
+ }
13059
+ return rows;
12713
13060
  }
12714
- function legacyKintoneFunctionUnsupported(code, functionName, field, fieldType, operator) {
12715
- return requireExactFunctionPushdown({
12716
- capability: "UNSUPPORTED",
12717
- reasons: [{ code, functionName, field, fieldType, operator }]
12718
- });
13061
+ function readTableRows(value) {
13062
+ if (!Array.isArray(value)) return [];
13063
+ return value.filter((item) => !!item && typeof item === "object");
12719
13064
  }
12720
- function relativeDateUnsupported(code, functionName, field, fieldType, operator) {
12721
- return requireExactRelativeDatePushdown({
12722
- capability: "UNSUPPORTED",
12723
- reasons: [{ code, functionName, field, fieldType, operator }]
12724
- });
13065
+ function toFlatString(value) {
13066
+ if (value === null || value === void 0) return "";
13067
+ if (typeof value === "string") return value;
13068
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
13069
+ try {
13070
+ return JSON.stringify(value);
13071
+ } catch {
13072
+ return String(value);
13073
+ }
12725
13074
  }
12726
- function hasRelativeDateReason(reasons) {
12727
- return reasons.some(
12728
- (reason) => reason.code.startsWith("WHERE_RELATIVE_DATE_") || reason.functionName !== void 0 && isRelativeDateFunctionName(reason.functionName)
12729
- );
13075
+
13076
+ // src/core/dmlPrevalidation.ts
13077
+ function collectDmlPrevalidationSnapshotFields(fieldIndex) {
13078
+ return [
13079
+ "$id",
13080
+ .../* @__PURE__ */ new Set([
13081
+ ...fieldIndex.topLevel.map((field) => field.code).filter((code) => code !== "$id" && code !== "$revision"),
13082
+ ...fieldIndex.subtables.keys()
13083
+ ])
13084
+ ];
12730
13085
  }
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
- );
13086
+ function buildDmlValidationPostImage(snapshot, sparseRecord) {
13087
+ const postImage = deepClone(snapshot);
13088
+ for (const [field, cell] of Object.entries(sparseRecord)) {
13089
+ postImage[field] = deepClone(cell);
13090
+ }
13091
+ return postImage;
12735
13092
  }
12736
- function requireExactRelativeDatePushdown(result) {
12737
- if (result.capability === "EXACT_PUSHDOWN" || !hasRelativeDateReason(result.reasons) || result.reasons.some((reason) => reason.code === "WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN")) {
12738
- return result;
13093
+ function mergeDmlCandidateValidation(input) {
13094
+ const errors = [
13095
+ ...input.preErrors.map((row) => normalizePlainError(row)),
13096
+ ...input.postImageErrors.map((row) => normalizePlainError(row, row["$err_subtable"] !== "")),
13097
+ ...input.checkErrors.map((row) => normalizePlainError(row))
13098
+ ];
13099
+ const invalidRowNumbers = /* @__PURE__ */ new Set();
13100
+ if (errors.length > 0) invalidRowNumbers.add(input.rowNumber);
13101
+ const writeRecord = {};
13102
+ for (const field of new Set(input.setFields)) {
13103
+ if (!Object.prototype.hasOwnProperty.call(input.normalizedPostImage, field)) {
13104
+ throw new Error(`InternalError: normalized post-image is missing SET field: ${field}`);
13105
+ }
13106
+ writeRecord[field] = deepClone(input.normalizedPostImage[field]);
12739
13107
  }
12740
- const relative = result.reasons.find(
12741
- (reason) => reason.code.startsWith("WHERE_RELATIVE_DATE_") || reason.functionName !== void 0 && isRelativeDateFunctionName(reason.functionName)
12742
- );
12743
13108
  return {
12744
- capability: result.capability,
12745
- reasons: [
12746
- ...result.reasons,
12747
- {
12748
- code: "WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN",
12749
- functionName: relative.functionName,
12750
- field: relative.field,
12751
- fieldType: relative.fieldType,
12752
- operator: relative.operator
12753
- }
12754
- ]
13109
+ errors,
13110
+ invalidRows: invalidRowNumbers.size,
13111
+ invalidRowNumbers,
13112
+ writeRecord
12755
13113
  };
12756
13114
  }
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
- );
13115
+ function normalizePlainError(row, childCell = false) {
12766
13116
  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
- ]
13117
+ ...row,
13118
+ $err_value: childCell ? row["$err_value"] ?? "" : "",
13119
+ $err_subtable: childCell ? row["$err_subtable"] ?? "" : "",
13120
+ $err_subrow: childCell ? row["$err_subrow"] ?? "" : "",
13121
+ $err_subrow_id: childCell ? row["$err_subrow_id"] ?? "" : ""
12778
13122
  };
12779
13123
  }
12780
- function requireExactFunctionPushdown(result) {
12781
- return requireExactLegacyKintoneFunctionPushdown(
12782
- requireExactRelativeDatePushdown(result)
12783
- );
13124
+ function deepClone(value, seen = /* @__PURE__ */ new Map()) {
13125
+ if (value === null || typeof value !== "object") return value;
13126
+ const object = value;
13127
+ const existing = seen.get(object);
13128
+ if (existing !== void 0) return existing;
13129
+ if (Array.isArray(value)) {
13130
+ const clone2 = [];
13131
+ seen.set(object, clone2);
13132
+ for (const item of value) clone2.push(deepClone(item, seen));
13133
+ return clone2;
13134
+ }
13135
+ const clone = {};
13136
+ seen.set(object, clone);
13137
+ for (const [key, child] of Object.entries(value)) clone[key] = deepClone(child, seen);
13138
+ return clone;
12784
13139
  }
12785
13140
 
12786
13141
  // src/core/optimization/relativeDateFullScanExactPlan.ts
@@ -14507,7 +14862,7 @@ async function execute(sql, client, options = {}) {
14507
14862
  const guardedClient = wrapClientWithSearchAbort(
14508
14863
  countedClient,
14509
14864
  collector,
14510
- !isSelectLikeStatement(stmt)
14865
+ !isSelectLikeStatement(stmt) || statementContainsOuterJoin(stmt)
14511
14866
  );
14512
14867
  const result = await executeParsedStatement(
14513
14868
  stmt,
@@ -14626,9 +14981,14 @@ function wrapClientWithMetrics(client, metrics) {
14626
14981
  }
14627
14982
  };
14628
14983
  }
14984
+ var SEARCH_ABORT_FAIL_CLOSED = /* @__PURE__ */ Symbol("searchAbortFailClosed");
14629
14985
  function wrapClientWithSearchAbort(client, collector, failClosed) {
14986
+ if (failClosed && client[SEARCH_ABORT_FAIL_CLOSED]) {
14987
+ return client;
14988
+ }
14630
14989
  return {
14631
14990
  ...client,
14991
+ ...failClosed ? { [SEARCH_ABORT_FAIL_CLOSED]: true } : {},
14632
14992
  getRecords: async (params) => {
14633
14993
  const response = await client.getRecords(params);
14634
14994
  if (response.searchAborted) {
@@ -15113,7 +15473,7 @@ async function executeBatch(sql, client, options = {}) {
15113
15473
  const statementClient = wrapClientWithSearchAbort(
15114
15474
  countedClient,
15115
15475
  searchAbortCollector,
15116
- info.statementType !== "SELECT" && info.statementType !== "UNION" && info.statementType !== "WITH"
15476
+ info.statementType !== "SELECT" && info.statementType !== "UNION" && info.statementType !== "WITH" || statementContainsOuterJoin(statements[i])
15117
15477
  );
15118
15478
  const cursorScope = wrapClientWithCursorScope(statementClient);
15119
15479
  const outcome = await runWithDeadline(
@@ -16320,6 +16680,46 @@ function extractMainTypedPushdownCandidate(stmt) {
16320
16680
  if (!stmt.from.alias) return null;
16321
16681
  return extractTypedPushdownCandidates(stmt.where, { tableAlias: stmt.from.alias });
16322
16682
  }
16683
+ function buildRuntimeJoinPushdownPlan(stmt, metadata) {
16684
+ if (stmt.joins.length === 0 || stmt.where === null || stmt.joins.some((join2) => join2.type !== "INNER")) {
16685
+ return null;
16686
+ }
16687
+ const tables = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
16688
+ if (tables.some(
16689
+ (table) => table.alias === null || table.cteName !== null || Boolean(table.subtableCode)
16690
+ )) {
16691
+ return null;
16692
+ }
16693
+ const sources = tables.map((table) => ({
16694
+ alias: table.alias,
16695
+ appId: table.appId,
16696
+ sourceKind: "APP",
16697
+ fieldTypes: new Map([
16698
+ ...metadata.fieldTypesByApp.get(table.appId) ?? /* @__PURE__ */ new Map(),
16699
+ ["$id", "__ID__"]
16700
+ ]),
16701
+ fieldOptions: metadata.fieldOptionsByApp.get(table.appId)
16702
+ }));
16703
+ const plan = buildJoinPushdownPlan(stmt.where, sources);
16704
+ const conditionsByAlias = /* @__PURE__ */ new Map();
16705
+ const queriesByAlias = /* @__PURE__ */ new Map();
16706
+ for (const item of plan.items) {
16707
+ queriesByAlias.set(item.targetAlias, serializeJoinPushdownItem(item, sources));
16708
+ conditionsByAlias.set(item.targetAlias, item.predicate);
16709
+ }
16710
+ const mainAlias = stmt.from.alias;
16711
+ const mainCondition = conditionsByAlias.get(mainAlias) ?? null;
16712
+ const joinConditions = new Map(conditionsByAlias);
16713
+ joinConditions.delete(mainAlias);
16714
+ return {
16715
+ joinPlan: plan,
16716
+ queriesByAlias,
16717
+ mainCondition,
16718
+ joinConditions,
16719
+ appliedKlikes: plan.appliedKlikes,
16720
+ allKlikes: plan.allKlikes
16721
+ };
16722
+ }
16323
16723
  async function loadTypedPushdownMeta(stmt, client, cacheContext) {
16324
16724
  const candidatesByApp = /* @__PURE__ */ new Map();
16325
16725
  const addCandidate = (appId, candidate) => {
@@ -16329,6 +16729,13 @@ async function loadTypedPushdownMeta(stmt, client, cacheContext) {
16329
16729
  else candidatesByApp.set(appId, [candidate]);
16330
16730
  };
16331
16731
  addCandidate(stmt.from.appId, extractMainTypedPushdownCandidate(stmt));
16732
+ const directInnerJoin = stmt.joins.length > 0 && stmt.where !== null && stmt.joins.every((join2) => join2.type === "INNER") && [stmt.from, ...stmt.joins.map((join2) => join2.table)].every(
16733
+ (table) => table.alias !== null && table.cteName === null && !table.subtableCode
16734
+ );
16735
+ if (directInnerJoin) {
16736
+ addCandidate(stmt.from.appId, stmt.where);
16737
+ for (const join2 of stmt.joins) addCandidate(join2.table.appId, stmt.where);
16738
+ }
16332
16739
  if (stmt.where !== null) {
16333
16740
  for (const join2 of stmt.joins) {
16334
16741
  if (!join2.table.alias || join2.table.subtableCode || join2.table.cteName !== null) continue;
@@ -16939,8 +17346,9 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16939
17346
  whereNeedsFieldMetadata(stmt.having) || selectCaseConditionsNeedFieldMetadata(stmt)
16940
17347
  );
16941
17348
  const havingFieldSemanticsResolver = buildHavingFieldSemanticsResolver(stmt, fieldSemanticsResolver);
16942
- const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
17349
+ const pushdownPlan = buildRuntimeJoinPushdownPlan(stmt, pushdownMeta) ?? buildKlikePushdownPlan(stmt, pushdownMeta);
16943
17350
  validateKlikePushdownPlan(pushdownPlan);
17351
+ const fetchClient = client;
16944
17352
  const mainPushDown = pushdownPlan.mainCondition;
16945
17353
  const tableConditions = pushdownPlan.joinConditions;
16946
17354
  if (prefilterPlan && allowOriginalWherePushdown) {
@@ -16952,7 +17360,7 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16952
17360
  const mainFetch = constantFalse ? Promise.resolve([]) : fetchTableRecordsForFullScan(
16953
17361
  stmt,
16954
17362
  stmt.from,
16955
- client,
17363
+ fetchClient,
16956
17364
  maxRecords,
16957
17365
  parallel,
16958
17366
  true,
@@ -16976,7 +17384,7 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
16976
17384
  promise: fetchTableRecordsForFullScan(
16977
17385
  stmt,
16978
17386
  join2.table,
16979
- client,
17387
+ fetchClient,
16980
17388
  maxRecords,
16981
17389
  parallel,
16982
17390
  false,
@@ -17005,18 +17413,19 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
17005
17413
  stmt,
17006
17414
  join2,
17007
17415
  tables,
17008
- client,
17416
+ fetchClient,
17009
17417
  maxRecords,
17010
17418
  parallel,
17011
17419
  options.onLimitReached ?? "error",
17012
17420
  warnings,
17013
17421
  null,
17014
- plainGroupByPlan
17422
+ plainGroupByPlan,
17423
+ ""
17015
17424
  );
17016
17425
  const joinRecords = optimized ?? await fetchTableRecordsForFullScan(
17017
17426
  stmt,
17018
17427
  join2.table,
17019
- client,
17428
+ fetchClient,
17020
17429
  maxRecords,
17021
17430
  parallel,
17022
17431
  false,
@@ -17024,7 +17433,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
17024
17433
  warnings,
17025
17434
  null,
17026
17435
  true,
17027
- plainGroupByPlan
17436
+ plainGroupByPlan,
17437
+ ""
17028
17438
  );
17029
17439
  tables.set(join2.table.alias, joinRecords);
17030
17440
  }));
@@ -17212,8 +17622,9 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
17212
17622
  whereNeedsFieldMetadata(stmt.having) || selectCaseConditionsNeedFieldMetadata(stmt)
17213
17623
  );
17214
17624
  const havingFieldSemanticsResolver = buildHavingFieldSemanticsResolver(stmt, fieldSemanticsResolver);
17215
- const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
17625
+ const pushdownPlan = buildRuntimeJoinPushdownPlan(stmt, pushdownMeta) ?? buildKlikePushdownPlan(stmt, pushdownMeta);
17216
17626
  validateKlikePushdownPlan(pushdownPlan);
17627
+ const fetchClient = client;
17217
17628
  const scalarCache = await resolveScalarColumns(
17218
17629
  stmt.columns,
17219
17630
  client,
@@ -17234,7 +17645,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
17234
17645
  const mainRecords = await withCompleteInputPolicy(completePolicy, () => fetchTableRecordsForFullScan(
17235
17646
  stmt,
17236
17647
  stmt.from,
17237
- client,
17648
+ fetchClient,
17238
17649
  maxRecords,
17239
17650
  parallel,
17240
17651
  true,
@@ -17257,7 +17668,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
17257
17668
  stmt,
17258
17669
  join2,
17259
17670
  tables,
17260
- client,
17671
+ fetchClient,
17261
17672
  maxRecords,
17262
17673
  parallel,
17263
17674
  effectiveOptions.onLimitReached ?? "error",
@@ -17268,7 +17679,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
17268
17679
  const joinRecords = optimized ?? await fetchTableRecordsForFullScan(
17269
17680
  stmt,
17270
17681
  join2.table,
17271
- client,
17682
+ fetchClient,
17272
17683
  maxRecords,
17273
17684
  parallel,
17274
17685
  false,
@@ -17310,14 +17721,18 @@ function processRowToKintoneRecord(row) {
17310
17721
  Object.entries(row).map(([k, v]) => [k, { value: v ?? "" }])
17311
17722
  );
17312
17723
  }
17313
- async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, parallel, isMainTable, onLimit, warnings, pushDownCond = null, allowOriginalWherePushdown = true, plainGroupByPlan) {
17724
+ async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, parallel, isMainTable, onLimit, warnings, pushDownCond = null, allowOriginalWherePushdown = true, plainGroupByPlan, additionalPushQuery = "") {
17314
17725
  const fields = selectToFetchAllFields(stmt, table, plainGroupByPlan);
17315
17726
  const onTruncate = (max) => {
17316
17727
  warnings.add(`\u53D6\u5F97\u4E0A\u9650\uFF08${max} \u4EF6\uFF09\u306B\u9054\u3057\u305F\u305F\u3081\u3001${max} \u4EF6\u3067\u6253\u3061\u5207\u3063\u3066\u8868\u793A\u3057\u3066\u3044\u307E\u3059\u3002`);
17317
17728
  };
17318
17729
  if (!table.subtableCode) {
17319
17730
  const baseQuery = isMainTable && allowOriginalWherePushdown ? selectToFetchAllParams(stmt, table.appId).query : "";
17320
- const pushQuery = pushDownCond !== null ? whereToKintone(pushDownCond) : "";
17731
+ const pushQueries = [
17732
+ pushDownCond !== null ? whereToKintone(pushDownCond) : "",
17733
+ additionalPushQuery
17734
+ ].filter((query2) => query2 !== "");
17735
+ const pushQuery = pushQueries.length > 1 ? pushQueries.map((query2) => `(${query2})`).join(" and ") : pushQueries[0] ?? "";
17321
17736
  const query = baseQuery && pushQuery ? `(${baseQuery}) and (${pushQuery})` : baseQuery || pushQuery;
17322
17737
  const resolved = await fetchRecordsForSharedPlan(client.getRecords, table.appId, query, fields, {
17323
17738
  parallel,
@@ -17425,7 +17840,7 @@ function splitChunks(items, size) {
17425
17840
  var JOIN_IN_CHUNK_SIZE = 50;
17426
17841
  var JOIN_IN_MAX_CHUNKS = 6;
17427
17842
  var JOIN_IN_MAX_KEYS = JOIN_IN_CHUNK_SIZE * JOIN_IN_MAX_CHUNKS;
17428
- async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxRecords, parallel, onLimit, warnings, pushDownCond = null, plainGroupByPlan) {
17843
+ async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxRecords, parallel, onLimit, warnings, pushDownCond = null, plainGroupByPlan, additionalPushQuery = "") {
17429
17844
  if (join2.type !== "INNER") return null;
17430
17845
  if (!join2.table.alias) return null;
17431
17846
  if (join2.table.subtableCode) return null;
@@ -17471,7 +17886,14 @@ async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxR
17471
17886
  const seen = /* @__PURE__ */ new Set();
17472
17887
  for (const chunk3 of chunks) {
17473
17888
  const inClause = `${joinField} in (${chunk3.map(sqlQuote).join(",")})`;
17474
- const query = pushDownCond !== null ? `(${inClause}) and (${whereToKintone(pushDownCond)})` : inClause;
17889
+ const pushQueries = [
17890
+ pushDownCond !== null ? whereToKintone(pushDownCond) : "",
17891
+ additionalPushQuery
17892
+ ].filter((query2) => query2 !== "");
17893
+ const query = pushQueries.reduce(
17894
+ (combined, pushQuery) => `(${combined}) and (${pushQuery})`,
17895
+ inClause
17896
+ );
17475
17897
  const resolved = await fetchRecordsForSharedPlan(client.getRecords, join2.table.appId, query, fields, {
17476
17898
  parallel,
17477
17899
  maxRecords,
@@ -20676,6 +21098,7 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
20676
21098
  pending.forEach(([i], idx) => cache.set(i, values[idx]));
20677
21099
  return cache;
20678
21100
  }
21101
+ var explainJoinPushdownPlans = /* @__PURE__ */ new WeakMap();
20679
21102
  var validateExplainInfo = /* @__PURE__ */ new WeakMap();
20680
21103
  var applyParentExplainPlan = /* @__PURE__ */ new WeakMap();
20681
21104
  async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords = 1e4, relativeDatePlan) {
@@ -20737,6 +21160,9 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
20737
21160
  throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
20738
21161
  }
20739
21162
  capabilities.set(select, capability);
21163
+ const joinPushdownMeta = await loadTypedPushdownMeta(select, tracedClient, cacheContext);
21164
+ const joinPushdownPlan = buildRuntimeJoinPushdownPlan(select, joinPushdownMeta);
21165
+ if (joinPushdownPlan) explainJoinPushdownPlans.set(select, joinPushdownPlan);
20740
21166
  if (select.orderBy.length > 0 || select.columns.some((column) => column.type === "WINDOW_COL" && column.orderBy.length > 0)) {
20741
21167
  const meta = await buildOrderByMetaForSelect(select, tracedClient, cacheContext);
20742
21168
  if (select.orderMode !== "KINTONE_NATIVE") {
@@ -21477,7 +21903,26 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlan
21477
21903
  lines.push(` kintone query: ${displayedQuery || "(\u306A\u3057)"}`);
21478
21904
  lines.push(` fields: ${params.fields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : params.fields.join(", ")}`);
21479
21905
  } else {
21480
- const pushdownPlan = buildKlikePushdownPlan(stmt);
21906
+ const runtimeJoinPlan = explainJoinPushdownPlans.get(stmt);
21907
+ const pushdownPlan = runtimeJoinPlan ?? buildKlikePushdownPlan(stmt);
21908
+ if (stmt.joins.length > 0) {
21909
+ if (runtimeJoinPlan) {
21910
+ lines.push(" join pushdown plan: applied (runtime metadata resolved)");
21911
+ lines.push(" runtime plan timing: variables/subqueries resolved -> metadata resolved -> immutable plan");
21912
+ lines.push(" EXPLAIN unresolved subqueries: not applied (records API is not called)");
21913
+ lines.push(" residual: original WHERE");
21914
+ lines.push(` KLIKE applied nodes: ${runtimeJoinPlan.joinPlan.appliedKlikes.size}`);
21915
+ for (const rejection of runtimeJoinPlan.joinPlan.rejections) {
21916
+ lines.push(` join pushdown not applied: ${rejection.reason}`);
21917
+ }
21918
+ } else {
21919
+ const reason = stmt.joins.some((join2) => join2.type !== "INNER") ? "OUTER_JOIN" : [stmt.from, ...stmt.joins.map((join2) => join2.table)].some(
21920
+ (table) => table.cteName !== null || Boolean(table.subtableCode)
21921
+ ) ? "SOURCE_KIND" : "PLAN_NOT_APPLICABLE";
21922
+ lines.push(" join pushdown plan: not applied");
21923
+ lines.push(` join pushdown not applied: ${reason}`);
21924
+ }
21925
+ }
21481
21926
  const mainFields = selectToFetchAllFields(stmt, stmt.from, plainGroupByPlan);
21482
21927
  const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
21483
21928
  const mainPushDown = pushdownPlan.mainCondition;
@@ -21486,7 +21931,13 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlan
21486
21931
  const mainQ = mainPushDown !== null ? whereToKintone(mainPushDown) : exactOriginalWhere || "(\u5168\u4EF6\u53D6\u5F97)";
21487
21932
  lines.push(` app: APP${stmt.from.appId}${mainAliasStr} (${stmt.from.appId})`);
21488
21933
  lines.push(` kintone query: ${mainQ}`);
21489
- if (mainCandidate !== null) {
21934
+ const mainJoinItem = runtimeJoinPlan?.joinPlan.items.find(
21935
+ (item) => item.targetAlias === stmt.from.alias
21936
+ );
21937
+ if (mainJoinItem) {
21938
+ lines.push(` pushdown applied: ${runtimeJoinPlan.queriesByAlias.get(mainJoinItem.targetAlias)}`);
21939
+ lines.push(` relation: ${mainJoinItem.relation}`);
21940
+ } else if (!runtimeJoinPlan && mainCandidate !== null) {
21490
21941
  lines.push(` pushdown candidate: ${whereToKintone(mainCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u30FB\u5B9F\u5728\u78BA\u8A8D\u5F85\u3061\uFF09`);
21491
21942
  }
21492
21943
  lines.push(` fields: ${mainFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : mainFields.join(", ")}`);
@@ -21499,7 +21950,13 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlan
21499
21950
  const joinQ = joinPushDown !== null ? whereToKintone(joinPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
21500
21951
  lines.push(` ${joinType}: APP${join2.table.appId}${joinAliasStr} (${join2.table.appId})`);
21501
21952
  lines.push(` kintone query: ${joinQ}`);
21502
- if (joinCandidate !== null) {
21953
+ const joinPlanItem = runtimeJoinPlan?.joinPlan.items.find(
21954
+ (item) => item.targetAlias === join2.table.alias
21955
+ );
21956
+ if (joinPlanItem) {
21957
+ lines.push(` pushdown applied: ${runtimeJoinPlan.queriesByAlias.get(joinPlanItem.targetAlias)}`);
21958
+ lines.push(` relation: ${joinPlanItem.relation}`);
21959
+ } else if (!runtimeJoinPlan && joinCandidate !== null) {
21503
21960
  lines.push(` pushdown candidate: ${whereToKintone(joinCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u30FB\u5B9F\u5728\u78BA\u8A8D\u5F85\u3061\uFF09`);
21504
21961
  }
21505
21962
  lines.push(` fields: ${joinFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : joinFields.join(", ")}`);