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