@rex0220/kintone-sql-tools 3.19.0 → 3.21.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 +1273 -52
- package/dist-engine/index.cjs +11 -11
- package/dist-engine/index.mjs +11 -11
- package/dist-engine/ksql-engine.umd.js +11 -11
- package/dist-engine/meta/bundle-baseline.json +10 -10
- package/dist-engine/meta/cjs.json +124 -21
- package/dist-engine/meta/esm.json +124 -21
- package/dist-engine/meta/umd.json +124 -21
- package/dist-mcp/ksql-mcp.js +1313 -58
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +2 -1
package/dist-cli/ksql.js
CHANGED
|
@@ -783,6 +783,26 @@ function aggregateOperandLabel(node) {
|
|
|
783
783
|
return `${aggregateOperandLabel(node.left)}${node.op}${aggregateOperandLabel(node.right)}`;
|
|
784
784
|
}
|
|
785
785
|
|
|
786
|
+
// src/core/relativeDateFunction.ts
|
|
787
|
+
var RELATIVE_DATE_FUNCTION_NAMES = /* @__PURE__ */ new Set([
|
|
788
|
+
"YESTERDAY",
|
|
789
|
+
"TOMORROW",
|
|
790
|
+
"FROM_TODAY",
|
|
791
|
+
"THIS_WEEK",
|
|
792
|
+
"LAST_WEEK",
|
|
793
|
+
"NEXT_WEEK",
|
|
794
|
+
"THIS_MONTH",
|
|
795
|
+
"LAST_MONTH",
|
|
796
|
+
"NEXT_MONTH",
|
|
797
|
+
"THIS_YEAR",
|
|
798
|
+
"LAST_YEAR",
|
|
799
|
+
"NEXT_YEAR"
|
|
800
|
+
]);
|
|
801
|
+
var WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN = "WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN";
|
|
802
|
+
function isRelativeDateFunctionName(name) {
|
|
803
|
+
return RELATIVE_DATE_FUNCTION_NAMES.has(name);
|
|
804
|
+
}
|
|
805
|
+
|
|
786
806
|
// src/parser/parser.ts
|
|
787
807
|
var MAX_BATCH_STATEMENTS = 20;
|
|
788
808
|
var PARSER_SCALAR_FUNCTION_TOKEN_MAP = Object.freeze({
|
|
@@ -883,12 +903,35 @@ var PARSER_CONTEXTUAL_FUNCTION_TOKEN_MAP = Object.freeze({
|
|
|
883
903
|
function isContextualFunctionToken(kind) {
|
|
884
904
|
return PARSER_CONTEXTUAL_FUNCTION_TOKEN_MAP[kind] !== void 0;
|
|
885
905
|
}
|
|
906
|
+
var PARSER_IDENT_RELATIVE_DATE_FUNCTIONS = Object.freeze(
|
|
907
|
+
[...RELATIVE_DATE_FUNCTION_NAMES]
|
|
908
|
+
);
|
|
909
|
+
var RELATIVE_DATE_PERIOD_UNITS = /* @__PURE__ */ new Set(["DAYS", "WEEKS", "MONTHS", "YEARS"]);
|
|
910
|
+
var RELATIVE_DATE_WEEKDAYS = /* @__PURE__ */ new Set([
|
|
911
|
+
"SUNDAY",
|
|
912
|
+
"MONDAY",
|
|
913
|
+
"TUESDAY",
|
|
914
|
+
"WEDNESDAY",
|
|
915
|
+
"THURSDAY",
|
|
916
|
+
"FRIDAY",
|
|
917
|
+
"SATURDAY"
|
|
918
|
+
]);
|
|
919
|
+
function isRelativeDatePeriodUnit(value) {
|
|
920
|
+
return RELATIVE_DATE_PERIOD_UNITS.has(value);
|
|
921
|
+
}
|
|
922
|
+
function isRelativeDateWeekday(value) {
|
|
923
|
+
return RELATIVE_DATE_WEEKDAYS.has(value);
|
|
924
|
+
}
|
|
925
|
+
function isRelativeDateMonthDay(value) {
|
|
926
|
+
return Number.isInteger(value) && value >= 1 && value <= 31;
|
|
927
|
+
}
|
|
886
928
|
var PARSER_FUNCTION_SPELLINGS = Object.freeze(Array.from(/* @__PURE__ */ new Set([
|
|
887
929
|
...Object.keys(PARSER_SCALAR_FUNCTION_TOKEN_MAP),
|
|
888
930
|
...PARSER_IDENT_SCALAR_FUNCTIONS,
|
|
889
931
|
...Object.keys(PARSER_AGGREGATE_FUNCTION_TOKEN_MAP),
|
|
890
932
|
...Object.keys(PARSER_WINDOW_FUNCTION_TOKEN_MAP),
|
|
891
933
|
...Object.keys(PARSER_CONTEXTUAL_FUNCTION_TOKEN_MAP),
|
|
934
|
+
...PARSER_IDENT_RELATIVE_DATE_FUNCTIONS,
|
|
892
935
|
"IF"
|
|
893
936
|
])));
|
|
894
937
|
var FUNC_CALL_PREFIX_KINDS = /* @__PURE__ */ new Set([
|
|
@@ -989,6 +1032,8 @@ var Parser = class {
|
|
|
989
1032
|
this.insideAggregateArg = 0;
|
|
990
1033
|
/** GROUPING(field) is limited to the explicitly selected query context. */
|
|
991
1034
|
this.groupingFieldContext = "FORBIDDEN";
|
|
1035
|
+
/** True only while parsing an actual SQL WHERE clause (including nested groups). */
|
|
1036
|
+
this.allowRelativeDateFunctions = false;
|
|
992
1037
|
/** WITH 句で定義された CTE 名のセット(parseTableRef で参照) */
|
|
993
1038
|
this.cteNames = /* @__PURE__ */ new Set();
|
|
994
1039
|
/** パース中に出現した一時テーブル参照(#name)のトークン。単文 API での拒否に使う */
|
|
@@ -1500,7 +1545,7 @@ var Parser = class {
|
|
|
1500
1545
|
this.advance();
|
|
1501
1546
|
summary = true;
|
|
1502
1547
|
}
|
|
1503
|
-
const where = this.consume("WHERE" /* WHERE */) ? this.parseWhereExpr() : null;
|
|
1548
|
+
const where = this.consume("WHERE" /* WHERE */) ? this.parseWhereExpr(void 0, true) : null;
|
|
1504
1549
|
const checks = this.parseCheckGroups();
|
|
1505
1550
|
let errorTable;
|
|
1506
1551
|
if (this.consume("INTO" /* INTO */)) {
|
|
@@ -1719,7 +1764,7 @@ var Parser = class {
|
|
|
1719
1764
|
const hasFrom = this.consume("FROM" /* FROM */);
|
|
1720
1765
|
const from = hasFrom ? this.parseTableRef() : { appId: 0, alias: null, cteName: NO_FROM_CTE_NAME };
|
|
1721
1766
|
const joins = hasFrom ? this.parseJoins() : [];
|
|
1722
|
-
const where = this.consume("WHERE" /* WHERE */) ? this.parseWhereExpr() : null;
|
|
1767
|
+
const where = this.consume("WHERE" /* WHERE */) ? this.parseWhereExpr(void 0, true) : null;
|
|
1723
1768
|
let groupBy = [];
|
|
1724
1769
|
let grouping;
|
|
1725
1770
|
let having = null;
|
|
@@ -2586,13 +2631,16 @@ var Parser = class {
|
|
|
2586
2631
|
// ----------------------------------------------------------
|
|
2587
2632
|
// WHERE 式(再帰下降・優先順位付き)
|
|
2588
2633
|
// ----------------------------------------------------------
|
|
2589
|
-
parseWhereExpr(groupingFieldContext = this.groupingFieldContext) {
|
|
2634
|
+
parseWhereExpr(groupingFieldContext = this.groupingFieldContext, allowRelativeDateFunctions = this.allowRelativeDateFunctions) {
|
|
2590
2635
|
const previousContext = this.groupingFieldContext;
|
|
2591
|
-
|
|
2636
|
+
const previousAllowRelativeDateFunctions = this.allowRelativeDateFunctions;
|
|
2637
|
+
this.groupingFieldContext = groupingFieldContext ?? previousContext;
|
|
2638
|
+
this.allowRelativeDateFunctions = allowRelativeDateFunctions;
|
|
2592
2639
|
try {
|
|
2593
2640
|
return this.parseOrExpr();
|
|
2594
2641
|
} finally {
|
|
2595
2642
|
this.groupingFieldContext = previousContext;
|
|
2643
|
+
this.allowRelativeDateFunctions = previousAllowRelativeDateFunctions;
|
|
2596
2644
|
}
|
|
2597
2645
|
}
|
|
2598
2646
|
// OR(最低優先度)
|
|
@@ -2671,9 +2719,9 @@ var Parser = class {
|
|
|
2671
2719
|
return { type: "NULL_CHECK", field, not };
|
|
2672
2720
|
}
|
|
2673
2721
|
if (this.consume("BETWEEN" /* BETWEEN */)) {
|
|
2674
|
-
const low = this.
|
|
2722
|
+
const low = this.parseWhereSqlValue();
|
|
2675
2723
|
this.expect("AND" /* AND */);
|
|
2676
|
-
const high = this.
|
|
2724
|
+
const high = this.parseWhereSqlValue();
|
|
2677
2725
|
return {
|
|
2678
2726
|
type: "LOGICAL",
|
|
2679
2727
|
op: "AND",
|
|
@@ -2708,7 +2756,7 @@ var Parser = class {
|
|
|
2708
2756
|
return { type: "BINARY", op: "KLIKE", left: field, right: pattern };
|
|
2709
2757
|
}
|
|
2710
2758
|
const op = this.parseCompareOp();
|
|
2711
|
-
const right = this.
|
|
2759
|
+
const right = this.parseWhereSqlValue();
|
|
2712
2760
|
return { type: "BINARY", op, left: field, right };
|
|
2713
2761
|
}
|
|
2714
2762
|
parseCompareOp() {
|
|
@@ -2813,6 +2861,105 @@ var Parser = class {
|
|
|
2813
2861
|
return { type: "FIELD", tableAlias: qi.tableAlias, field: qi.field };
|
|
2814
2862
|
}
|
|
2815
2863
|
// 右辺の値
|
|
2864
|
+
parseWhereSqlValue() {
|
|
2865
|
+
const tok = this.peek();
|
|
2866
|
+
if (this.allowRelativeDateFunctions && tok.kind === "IDENT" /* IDENT */ && this.peekAt(1).kind === "(" /* LPAREN */ && isRelativeDateFunctionName(tok.value.toUpperCase())) {
|
|
2867
|
+
return this.parseRelativeDateFunction();
|
|
2868
|
+
}
|
|
2869
|
+
return this.parseSqlValue();
|
|
2870
|
+
}
|
|
2871
|
+
parseRelativeDateFunction() {
|
|
2872
|
+
const nameToken = this.expect("IDENT" /* IDENT */);
|
|
2873
|
+
const name = nameToken.value.toUpperCase();
|
|
2874
|
+
this.expect("(" /* LPAREN */);
|
|
2875
|
+
switch (name) {
|
|
2876
|
+
case "YESTERDAY":
|
|
2877
|
+
case "TOMORROW":
|
|
2878
|
+
case "THIS_YEAR":
|
|
2879
|
+
case "LAST_YEAR":
|
|
2880
|
+
case "NEXT_YEAR":
|
|
2881
|
+
this.expect(")" /* RPAREN */, `${name}() \u306F\u5F15\u6570\u3092\u53D7\u3051\u53D6\u308A\u307E\u305B\u3093`);
|
|
2882
|
+
return { type: "KINTONE_FUNC", name, args: { kind: "NONE" } };
|
|
2883
|
+
case "FROM_TODAY": {
|
|
2884
|
+
let sign = "";
|
|
2885
|
+
if (this.consume("-" /* MINUS */)) sign = "-";
|
|
2886
|
+
if (this.peek().kind === "+" /* PLUS */) {
|
|
2887
|
+
throw new ParseError("FROM_TODAY \u306E offset \u306B + \u7B26\u53F7\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", this.peek());
|
|
2888
|
+
}
|
|
2889
|
+
const offsetToken = this.expect(
|
|
2890
|
+
"NUMBER" /* NUMBER */,
|
|
2891
|
+
"FROM_TODAY \u306B\u306F\u6574\u6570 offset \u3068\u5358\u4F4D\u304C\u5FC5\u8981\u3067\u3059"
|
|
2892
|
+
);
|
|
2893
|
+
if (!/^\d+$/.test(offsetToken.value)) {
|
|
2894
|
+
throw new ParseError("FROM_TODAY \u306E offset \u306F10\u9032\u6574\u6570\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044", offsetToken);
|
|
2895
|
+
}
|
|
2896
|
+
const rawOffset = `${sign}${offsetToken.value}`;
|
|
2897
|
+
const offset = Number(rawOffset);
|
|
2898
|
+
if (!Number.isSafeInteger(offset)) {
|
|
2899
|
+
throw new ParseError("FROM_TODAY \u306E offset \u306F\u5B89\u5168\u306A\u6574\u6570\u306E\u7BC4\u56F2\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044", offsetToken);
|
|
2900
|
+
}
|
|
2901
|
+
this.expect("," /* COMMA */, "FROM_TODAY \u306E offset \u3068\u5358\u4F4D\u306F\u30AB\u30F3\u30DE\u3067\u533A\u5207\u3063\u3066\u304F\u3060\u3055\u3044");
|
|
2902
|
+
const unitToken = this.expect(
|
|
2903
|
+
"IDENT" /* IDENT */,
|
|
2904
|
+
"FROM_TODAY \u306E\u5358\u4F4D\u306B\u306F\u975E\u5F15\u7528\u306E DAYS / WEEKS / MONTHS / YEARS \u304C\u5FC5\u8981\u3067\u3059"
|
|
2905
|
+
);
|
|
2906
|
+
const unit = unitToken.value.toUpperCase();
|
|
2907
|
+
if (!isRelativeDatePeriodUnit(unit)) {
|
|
2908
|
+
throw new ParseError("FROM_TODAY \u306E\u5358\u4F4D\u306B\u306F DAYS / WEEKS / MONTHS / YEARS \u304C\u5FC5\u8981\u3067\u3059", unitToken);
|
|
2909
|
+
}
|
|
2910
|
+
this.expect(")" /* RPAREN */, "FROM_TODAY \u306F offset \u3068\u5358\u4F4D\u306E2\u5F15\u6570\u3060\u3051\u3092\u53D7\u3051\u53D6\u308A\u307E\u3059");
|
|
2911
|
+
const offsetText = String(offset === 0 ? 0 : offset);
|
|
2912
|
+
return {
|
|
2913
|
+
type: "KINTONE_FUNC",
|
|
2914
|
+
name,
|
|
2915
|
+
args: { kind: "FROM_TODAY", offset, offsetText, unit }
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2918
|
+
case "THIS_WEEK":
|
|
2919
|
+
case "LAST_WEEK":
|
|
2920
|
+
case "NEXT_WEEK": {
|
|
2921
|
+
let weekday = null;
|
|
2922
|
+
if (this.peek().kind !== ")" /* RPAREN */) {
|
|
2923
|
+
const weekdayToken = this.expect(
|
|
2924
|
+
"IDENT" /* IDENT */,
|
|
2925
|
+
`${name} \u306E\u66DC\u65E5\u306B\u306F\u975E\u5F15\u7528\u306E SUNDAY ... SATURDAY \u304C\u5FC5\u8981\u3067\u3059`
|
|
2926
|
+
);
|
|
2927
|
+
const candidate = weekdayToken.value.toUpperCase();
|
|
2928
|
+
if (!isRelativeDateWeekday(candidate)) {
|
|
2929
|
+
throw new ParseError(`${name} \u306E\u66DC\u65E5\u304C\u4E0D\u6B63\u3067\u3059`, weekdayToken);
|
|
2930
|
+
}
|
|
2931
|
+
weekday = candidate;
|
|
2932
|
+
}
|
|
2933
|
+
this.expect(")" /* RPAREN */, `${name} \u306F\u66DC\u65E5\u3092\u6700\u59271\u500B\u3060\u3051\u53D7\u3051\u53D6\u308A\u307E\u3059`);
|
|
2934
|
+
return { type: "KINTONE_FUNC", name, args: { kind: "WEEK", weekday } };
|
|
2935
|
+
}
|
|
2936
|
+
case "THIS_MONTH":
|
|
2937
|
+
case "LAST_MONTH":
|
|
2938
|
+
case "NEXT_MONTH": {
|
|
2939
|
+
let day = null;
|
|
2940
|
+
if (this.peek().kind !== ")" /* RPAREN */) {
|
|
2941
|
+
const dayToken = this.peek();
|
|
2942
|
+
if (dayToken.kind === "IDENT" /* IDENT */ && dayToken.value.toUpperCase() === "LAST") {
|
|
2943
|
+
this.advance();
|
|
2944
|
+
day = "LAST";
|
|
2945
|
+
} else if (dayToken.kind === "NUMBER" /* NUMBER */ && /^\d+$/.test(dayToken.value)) {
|
|
2946
|
+
this.advance();
|
|
2947
|
+
const candidate = Number(dayToken.value);
|
|
2948
|
+
if (!isRelativeDateMonthDay(candidate)) {
|
|
2949
|
+
throw new ParseError(`${name} \u306E\u65E5\u306F 1 \u304B\u3089 31 \u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044`, dayToken);
|
|
2950
|
+
}
|
|
2951
|
+
day = candidate;
|
|
2952
|
+
} else {
|
|
2953
|
+
throw new ParseError(`${name} \u306E\u65E5\u306F\u975E\u5F15\u7528\u306E LAST \u307E\u305F\u306F 1 \u304B\u3089 31 \u304C\u5FC5\u8981\u3067\u3059`, dayToken);
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
this.expect(")" /* RPAREN */, `${name} \u306F\u65E5\u3092\u6700\u59271\u500B\u3060\u3051\u53D7\u3051\u53D6\u308A\u307E\u3059`);
|
|
2957
|
+
return { type: "KINTONE_FUNC", name, args: { kind: "MONTH", day } };
|
|
2958
|
+
}
|
|
2959
|
+
default:
|
|
2960
|
+
throw new ParseError(`\u672A\u77E5\u306E\u76F8\u5BFE\u65E5\u4ED8\u95A2\u6570 ${name} \u3067\u3059`, nameToken);
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2816
2963
|
parseSqlValue() {
|
|
2817
2964
|
const tok = this.peek();
|
|
2818
2965
|
if (tok.kind === "VARIABLE" /* VARIABLE */) {
|
|
@@ -3382,7 +3529,7 @@ var Parser = class {
|
|
|
3382
3529
|
whereTok
|
|
3383
3530
|
);
|
|
3384
3531
|
}
|
|
3385
|
-
const where = this.parseWhereExpr();
|
|
3532
|
+
const where = this.parseWhereExpr(void 0, true);
|
|
3386
3533
|
const applyBlocks = [];
|
|
3387
3534
|
while (this.isApplyBlockStart()) applyBlocks.push(this.parseApplyBlock());
|
|
3388
3535
|
if (this.isSoftKeyword("APPLY") && this.peekAt(1).kind === "IDENT" /* IDENT */ && this.peekAt(1).value.toUpperCase() === "SUBTABLE") {
|
|
@@ -3514,7 +3661,7 @@ var Parser = class {
|
|
|
3514
3661
|
throw new ParseError("APPLY \u306E\u64CD\u4F5C\u306B\u306F PATCH / APPEND / REMOVE / ADD \u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
3515
3662
|
}
|
|
3516
3663
|
parseApplyRowSelector() {
|
|
3517
|
-
if (this.consume("WHERE" /* WHERE */)) return { kind: "WHERE", where: this.parseWhereExpr() };
|
|
3664
|
+
if (this.consume("WHERE" /* WHERE */)) return { kind: "WHERE", where: this.parseWhereExpr(void 0, true) };
|
|
3518
3665
|
if (this.consume("ALL" /* ALL */)) {
|
|
3519
3666
|
if (!this.isSoftKeyword("ROWS")) throw new ParseError("ALL \u306E\u5F8C\u306B\u306F ROWS \u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
3520
3667
|
this.advance();
|
|
@@ -3834,7 +3981,7 @@ var Parser = class {
|
|
|
3834
3981
|
whereTok
|
|
3835
3982
|
);
|
|
3836
3983
|
}
|
|
3837
|
-
const where = this.parseWhereExpr();
|
|
3984
|
+
const where = this.parseWhereExpr(void 0, true);
|
|
3838
3985
|
return subtableCode ? { type: "DELETE", appId, subtableCode, where } : { type: "DELETE", appId, where };
|
|
3839
3986
|
}
|
|
3840
3987
|
// ----------------------------------------------------------
|
|
@@ -3864,7 +4011,7 @@ var Parser = class {
|
|
|
3864
4011
|
if (!this.consume("WHERE" /* WHERE */)) {
|
|
3865
4012
|
throw new ParseError("REORDER \u306B\u306F WHERE \u53E5\u304C\u5FC5\u9808\u3067\u3059\uFF08\u8AA4\u64CD\u4F5C\u9632\u6B62\uFF09", whereTok);
|
|
3866
4013
|
}
|
|
3867
|
-
where = this.parseWhereExpr();
|
|
4014
|
+
where = this.parseWhereExpr(void 0, true);
|
|
3868
4015
|
}
|
|
3869
4016
|
return {
|
|
3870
4017
|
type: "REORDER",
|
|
@@ -4591,7 +4738,9 @@ function assertSafeParentPredicateNode(node, allowKlike) {
|
|
|
4591
4738
|
if ((type === "FIELD" || type === "FIELD_REF") && typeof item["field"] === "string" && /^(count|sum|avg|min|max|group_concat)\s*\(/i.test(item["field"])) {
|
|
4592
4739
|
unsupported("aggregate or window expressions in parent WHERE");
|
|
4593
4740
|
}
|
|
4594
|
-
if (type === "KINTONE_FUNC"
|
|
4741
|
+
if (type === "KINTONE_FUNC" && !(typeof item["name"] === "string" && isRelativeDateFunctionName(item["name"]))) {
|
|
4742
|
+
unsupported("non-deterministic kintone functions in parent WHERE");
|
|
4743
|
+
}
|
|
4595
4744
|
for (const value of Object.values(item)) assertSafeParentPredicateNode(value, allowKlike);
|
|
4596
4745
|
}
|
|
4597
4746
|
function assertSafeChildPredicate(where, idxSelectors) {
|
|
@@ -4612,7 +4761,9 @@ function assertSafeApplyNode(node, context, allowIdx = false) {
|
|
|
4612
4761
|
unsupported(`subqueries in ${context}`);
|
|
4613
4762
|
}
|
|
4614
4763
|
if (type === "WINDOW_COL" || type === "AGGREGATE" || type === "AGG_REF" || type === "AGG_ARITH" || type === "ARITH_AGG_COL") unsupported(`aggregate or window expressions in ${context}`);
|
|
4615
|
-
if (type === "KINTONE_FUNC"
|
|
4764
|
+
if (type === "KINTONE_FUNC" && !(typeof item["name"] === "string" && isRelativeDateFunctionName(item["name"]))) {
|
|
4765
|
+
unsupported(`non-deterministic kintone functions in ${context}`);
|
|
4766
|
+
}
|
|
4616
4767
|
if (type === "FIELD") {
|
|
4617
4768
|
const alias = item["tableAlias"];
|
|
4618
4769
|
const field = item["field"];
|
|
@@ -4864,7 +5015,67 @@ function convertString(v) {
|
|
|
4864
5015
|
return `"${v.value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
4865
5016
|
}
|
|
4866
5017
|
function convertKintoneFunc(v) {
|
|
4867
|
-
|
|
5018
|
+
switch (v.name) {
|
|
5019
|
+
case "TODAY":
|
|
5020
|
+
case "NOW":
|
|
5021
|
+
case "LOGINUSER":
|
|
5022
|
+
return `${v.name}()`;
|
|
5023
|
+
}
|
|
5024
|
+
return convertRelativeDateFunction(v);
|
|
5025
|
+
}
|
|
5026
|
+
function convertRelativeDateFunction(v) {
|
|
5027
|
+
switch (v.name) {
|
|
5028
|
+
case "YESTERDAY":
|
|
5029
|
+
case "TOMORROW":
|
|
5030
|
+
case "THIS_YEAR":
|
|
5031
|
+
case "LAST_YEAR":
|
|
5032
|
+
case "NEXT_YEAR":
|
|
5033
|
+
if (v.args?.kind !== "NONE") return failInvalidRelativeDateFunction();
|
|
5034
|
+
return `${v.name}()`;
|
|
5035
|
+
case "FROM_TODAY":
|
|
5036
|
+
if (v.args?.kind !== "FROM_TODAY" || !isValidRelativeDateOffset(v.args.offset, v.args.offsetText) || !RELATIVE_DATE_PERIOD_UNITS2.has(v.args.unit)) {
|
|
5037
|
+
return failInvalidRelativeDateFunction();
|
|
5038
|
+
}
|
|
5039
|
+
return `${v.name}(${v.args.offsetText}, ${v.args.unit})`;
|
|
5040
|
+
case "THIS_WEEK":
|
|
5041
|
+
case "LAST_WEEK":
|
|
5042
|
+
case "NEXT_WEEK":
|
|
5043
|
+
if (v.args?.kind !== "WEEK" || v.args.weekday !== null && !RELATIVE_DATE_WEEKDAYS2.has(v.args.weekday)) {
|
|
5044
|
+
return failInvalidRelativeDateFunction();
|
|
5045
|
+
}
|
|
5046
|
+
return v.args.weekday === null ? `${v.name}()` : `${v.name}(${v.args.weekday})`;
|
|
5047
|
+
case "THIS_MONTH":
|
|
5048
|
+
case "LAST_MONTH":
|
|
5049
|
+
case "NEXT_MONTH":
|
|
5050
|
+
if (v.args?.kind !== "MONTH" || !isValidRelativeDateMonthDay(v.args.day)) {
|
|
5051
|
+
return failInvalidRelativeDateFunction();
|
|
5052
|
+
}
|
|
5053
|
+
return v.args.day === null ? `${v.name}()` : `${v.name}(${v.args.day})`;
|
|
5054
|
+
}
|
|
5055
|
+
return failInvalidRelativeDateFunction();
|
|
5056
|
+
}
|
|
5057
|
+
var RELATIVE_DATE_PERIOD_UNITS2 = /* @__PURE__ */ new Set(["DAYS", "WEEKS", "MONTHS", "YEARS"]);
|
|
5058
|
+
var RELATIVE_DATE_WEEKDAYS2 = /* @__PURE__ */ new Set([
|
|
5059
|
+
"SUNDAY",
|
|
5060
|
+
"MONDAY",
|
|
5061
|
+
"TUESDAY",
|
|
5062
|
+
"WEDNESDAY",
|
|
5063
|
+
"THURSDAY",
|
|
5064
|
+
"FRIDAY",
|
|
5065
|
+
"SATURDAY"
|
|
5066
|
+
]);
|
|
5067
|
+
function isValidRelativeDateOffset(offset, offsetText) {
|
|
5068
|
+
if (!Number.isSafeInteger(offset)) return false;
|
|
5069
|
+
if (!/^(?:0|[1-9]\d*|-[1-9]\d*)$/.test(offsetText)) return false;
|
|
5070
|
+
return Number(offsetText) === offset;
|
|
5071
|
+
}
|
|
5072
|
+
function isValidRelativeDateMonthDay(day) {
|
|
5073
|
+
return day === null || day === "LAST" || typeof day === "number" && Number.isInteger(day) && day >= 1 && day <= 31;
|
|
5074
|
+
}
|
|
5075
|
+
function failInvalidRelativeDateFunction() {
|
|
5076
|
+
throw new KintoneQueryError(
|
|
5077
|
+
"internal error: invalid relative date function AST reached kintone query conversion"
|
|
5078
|
+
);
|
|
4868
5079
|
}
|
|
4869
5080
|
function convertInList(v, op) {
|
|
4870
5081
|
if (op !== "IN" && op !== "NOT_IN") {
|
|
@@ -7489,7 +7700,7 @@ function resolveValue(value, row, resolveFieldType, resolveFieldSemantics2) {
|
|
|
7489
7700
|
case "NUMBER":
|
|
7490
7701
|
return numberLiteralText(value);
|
|
7491
7702
|
case "KINTONE_FUNC":
|
|
7492
|
-
return
|
|
7703
|
+
return resolveKintoneFuncValue(value.name);
|
|
7493
7704
|
case "IN_LIST":
|
|
7494
7705
|
return "";
|
|
7495
7706
|
// IN は evalOp で別処理
|
|
@@ -7558,6 +7769,21 @@ function resolveKintoneFunc(name) {
|
|
|
7558
7769
|
return "";
|
|
7559
7770
|
}
|
|
7560
7771
|
}
|
|
7772
|
+
function resolveKintoneFuncValue(name) {
|
|
7773
|
+
if (isRelativeDateFunctionName(name)) {
|
|
7774
|
+
throw new Error(
|
|
7775
|
+
`${name}: ${WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN}`
|
|
7776
|
+
);
|
|
7777
|
+
}
|
|
7778
|
+
switch (name) {
|
|
7779
|
+
case "TODAY":
|
|
7780
|
+
case "NOW":
|
|
7781
|
+
case "LOGINUSER":
|
|
7782
|
+
return resolveKintoneFunc(name);
|
|
7783
|
+
default:
|
|
7784
|
+
throw new Error(`InternalError: unexpected KINTONE_FUNC name: ${name}`);
|
|
7785
|
+
}
|
|
7786
|
+
}
|
|
7561
7787
|
var likeRegexCache = /* @__PURE__ */ new Map();
|
|
7562
7788
|
var LIKE_REGEX_CACHE_MAX = 200;
|
|
7563
7789
|
function matchLike(value, pattern) {
|
|
@@ -10535,7 +10761,10 @@ function planKorder(input) {
|
|
|
10535
10761
|
if (stmt.from.cteName !== null || stmt.from.subtableCode || input.staticMode !== "SIMPLE") {
|
|
10536
10762
|
reasons.push("KORDER_QUERY_SHAPE_UNSUPPORTED");
|
|
10537
10763
|
}
|
|
10538
|
-
if (input.whereCapability !== "EXACT_PUSHDOWN")
|
|
10764
|
+
if (input.whereCapability !== "EXACT_PUSHDOWN") {
|
|
10765
|
+
reasons.push(...(input.whereReasons ?? []).filter((reason) => reason.functionName !== void 0).map((reason) => reason.code));
|
|
10766
|
+
reasons.push("KORDER_WHERE_NOT_EXACT");
|
|
10767
|
+
}
|
|
10539
10768
|
if (input.hasKlike) reasons.push("KORDER_KLIKE_UNSUPPORTED");
|
|
10540
10769
|
if (stmt.orderBy.length === 0) reasons.push("KORDER_KEY_REQUIRED");
|
|
10541
10770
|
for (const item of stmt.orderBy) {
|
|
@@ -11700,7 +11929,8 @@ function runFullScan(input) {
|
|
|
11700
11929
|
});
|
|
11701
11930
|
knownColumns = mergeKnownColumns(knownColumns, rightColumns, rows);
|
|
11702
11931
|
}
|
|
11703
|
-
|
|
11932
|
+
const filterWhere = input.residualWhere !== void 0 ? input.residualWhere : stmt.where;
|
|
11933
|
+
rows = applyFilter(rows, filterWhere, fieldTypeResolver, appliedKlikes, fieldSemanticsResolver);
|
|
11704
11934
|
const grouping = normalizeGroupingSpec(stmt);
|
|
11705
11935
|
if (grouping.type === "GROUPING_SETS") {
|
|
11706
11936
|
if (!resolvedGroupingSpec) {
|
|
@@ -11861,6 +12091,13 @@ function deepClone(value, seen = /* @__PURE__ */ new Map()) {
|
|
|
11861
12091
|
|
|
11862
12092
|
// src/core/optimization/whereCapability.ts
|
|
11863
12093
|
var RANGE_AND_EQUALITY = ["=", "!=", ">", "<", ">=", "<="];
|
|
12094
|
+
var RELATIVE_DATE_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
12095
|
+
"DATE",
|
|
12096
|
+
"DATETIME",
|
|
12097
|
+
"CREATED_TIME",
|
|
12098
|
+
"UPDATED_TIME"
|
|
12099
|
+
]);
|
|
12100
|
+
var RELATIVE_DATE_OPERATORS = new Set(RANGE_AND_EQUALITY);
|
|
11864
12101
|
var EQUALITY_IN = ["=", "!=", "in", "not in"];
|
|
11865
12102
|
var NATIVE_OPERATORS = /* @__PURE__ */ new Map([
|
|
11866
12103
|
["RECORD_NUMBER", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
|
|
@@ -11936,7 +12173,7 @@ function classifyNode(where, resolveField2) {
|
|
|
11936
12173
|
case "BOOLEAN":
|
|
11937
12174
|
return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
|
|
11938
12175
|
case "BINARY":
|
|
11939
|
-
return classifyBinary(where.op, where.left, where.right
|
|
12176
|
+
return classifyBinary(where.op, where.left, where.right, resolveField2);
|
|
11940
12177
|
case "NULL_CHECK":
|
|
11941
12178
|
if (where.field.type !== "FIELD") return localExpression();
|
|
11942
12179
|
return classifyLocalOnlyField(where.field, where.not ? "IS NOT NULL" : "IS NULL", resolveField2);
|
|
@@ -11946,7 +12183,14 @@ function classifyNode(where, resolveField2) {
|
|
|
11946
12183
|
return classifyNode(where.expr, resolveField2);
|
|
11947
12184
|
case "NOT": {
|
|
11948
12185
|
const inner = classifyNode(where.expr, resolveField2);
|
|
11949
|
-
|
|
12186
|
+
if (inner.capability !== "SUPERSET_PREFILTER") return inner;
|
|
12187
|
+
if (!hasRelativeDateReason(inner.reasons)) {
|
|
12188
|
+
return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
|
|
12189
|
+
}
|
|
12190
|
+
return requireExactRelativeDatePushdown({
|
|
12191
|
+
capability: "LOCAL_ONLY",
|
|
12192
|
+
reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }, ...inner.reasons]
|
|
12193
|
+
});
|
|
11950
12194
|
}
|
|
11951
12195
|
case "LOGICAL": {
|
|
11952
12196
|
const left = classifyNode(where.left, resolveField2);
|
|
@@ -11955,7 +12199,10 @@ function classifyNode(where, resolveField2) {
|
|
|
11955
12199
|
}
|
|
11956
12200
|
}
|
|
11957
12201
|
}
|
|
11958
|
-
function classifyBinary(op, left,
|
|
12202
|
+
function classifyBinary(op, left, right, resolveField2) {
|
|
12203
|
+
if (right.type === "KINTONE_FUNC" && isRelativeDateFunctionName(right.name)) {
|
|
12204
|
+
return classifyRelativeDateBinary(op, left, right, resolveField2);
|
|
12205
|
+
}
|
|
11959
12206
|
if (left.type !== "FIELD") return localExpression();
|
|
11960
12207
|
const semantics = resolveField2(left);
|
|
11961
12208
|
if (!semantics) {
|
|
@@ -11966,7 +12213,7 @@ function classifyBinary(op, left, rightType, resolveField2) {
|
|
|
11966
12213
|
}
|
|
11967
12214
|
const nativeOp = normalizeOperator(op);
|
|
11968
12215
|
const native = nativeWhereOperatorsForType(semantics.fieldType);
|
|
11969
|
-
const rightCanPush =
|
|
12216
|
+
const rightCanPush = right.type === "STRING" || right.type === "NUMBER" || right.type === "IN_LIST" || isLegacyKintoneFunction(right);
|
|
11970
12217
|
const structureAllows = !semantics.requiresCollectionOperators || nativeOp !== "=" && nativeOp !== "!=";
|
|
11971
12218
|
const sqlLikeIsResidual = op === "LIKE" || op === "NOT_LIKE";
|
|
11972
12219
|
if (rightCanPush && structureAllows && native.has(nativeOp) && !sqlLikeIsResidual) {
|
|
@@ -11990,6 +12237,92 @@ function classifyBinary(op, left, rightType, resolveField2) {
|
|
|
11990
12237
|
}]
|
|
11991
12238
|
};
|
|
11992
12239
|
}
|
|
12240
|
+
function isLegacyKintoneFunction(value) {
|
|
12241
|
+
return value.type === "KINTONE_FUNC" && (value.name === "TODAY" || value.name === "NOW" || value.name === "LOGINUSER");
|
|
12242
|
+
}
|
|
12243
|
+
function classifyRelativeDateBinary(op, left, right, resolveField2) {
|
|
12244
|
+
const operator = normalizeOperator(op);
|
|
12245
|
+
const functionName = right.name;
|
|
12246
|
+
if (left.type !== "FIELD") {
|
|
12247
|
+
return relativeDateUnsupported(
|
|
12248
|
+
"WHERE_RELATIVE_DATE_CONTEXT_UNSUPPORTED",
|
|
12249
|
+
functionName,
|
|
12250
|
+
void 0,
|
|
12251
|
+
void 0,
|
|
12252
|
+
operator
|
|
12253
|
+
);
|
|
12254
|
+
}
|
|
12255
|
+
const semantics = resolveField2(left);
|
|
12256
|
+
if (!semantics) {
|
|
12257
|
+
return relativeDateUnsupported(
|
|
12258
|
+
"WHERE_RELATIVE_DATE_FIELD_TYPE_UNSUPPORTED",
|
|
12259
|
+
functionName,
|
|
12260
|
+
left.field,
|
|
12261
|
+
void 0,
|
|
12262
|
+
operator
|
|
12263
|
+
);
|
|
12264
|
+
}
|
|
12265
|
+
if (!hasValidRelativeDateArguments(right)) {
|
|
12266
|
+
return relativeDateUnsupported(
|
|
12267
|
+
"WHERE_RELATIVE_DATE_ARGUMENT_INVALID",
|
|
12268
|
+
functionName,
|
|
12269
|
+
left.field,
|
|
12270
|
+
semantics.fieldType,
|
|
12271
|
+
operator
|
|
12272
|
+
);
|
|
12273
|
+
}
|
|
12274
|
+
if (!RELATIVE_DATE_OPERATORS.has(operator)) {
|
|
12275
|
+
return relativeDateUnsupported(
|
|
12276
|
+
"WHERE_RELATIVE_DATE_OPERATOR_UNSUPPORTED",
|
|
12277
|
+
functionName,
|
|
12278
|
+
left.field,
|
|
12279
|
+
semantics.fieldType,
|
|
12280
|
+
operator
|
|
12281
|
+
);
|
|
12282
|
+
}
|
|
12283
|
+
if (!RELATIVE_DATE_FIELD_TYPES.has(semantics.fieldType) || semantics.inSubtable || semantics.requiresCollectionOperators) {
|
|
12284
|
+
return relativeDateUnsupported(
|
|
12285
|
+
"WHERE_RELATIVE_DATE_FIELD_TYPE_UNSUPPORTED",
|
|
12286
|
+
functionName,
|
|
12287
|
+
left.field,
|
|
12288
|
+
semantics.fieldType,
|
|
12289
|
+
operator
|
|
12290
|
+
);
|
|
12291
|
+
}
|
|
12292
|
+
return {
|
|
12293
|
+
capability: "EXACT_PUSHDOWN",
|
|
12294
|
+
reasons: [{
|
|
12295
|
+
code: "WHERE_EXACT",
|
|
12296
|
+
functionName,
|
|
12297
|
+
field: left.field,
|
|
12298
|
+
fieldType: semantics.fieldType,
|
|
12299
|
+
operator
|
|
12300
|
+
}]
|
|
12301
|
+
};
|
|
12302
|
+
}
|
|
12303
|
+
function hasValidRelativeDateArguments(value) {
|
|
12304
|
+
if (!("args" in value) || !value.args) return false;
|
|
12305
|
+
switch (value.name) {
|
|
12306
|
+
case "YESTERDAY":
|
|
12307
|
+
case "TOMORROW":
|
|
12308
|
+
case "THIS_YEAR":
|
|
12309
|
+
case "LAST_YEAR":
|
|
12310
|
+
case "NEXT_YEAR":
|
|
12311
|
+
return value.args.kind === "NONE";
|
|
12312
|
+
case "FROM_TODAY":
|
|
12313
|
+
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");
|
|
12314
|
+
case "THIS_WEEK":
|
|
12315
|
+
case "LAST_WEEK":
|
|
12316
|
+
case "NEXT_WEEK":
|
|
12317
|
+
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");
|
|
12318
|
+
case "THIS_MONTH":
|
|
12319
|
+
case "LAST_MONTH":
|
|
12320
|
+
case "NEXT_MONTH":
|
|
12321
|
+
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);
|
|
12322
|
+
default:
|
|
12323
|
+
return false;
|
|
12324
|
+
}
|
|
12325
|
+
}
|
|
11993
12326
|
function classifyLocalOnlyField(field, operator, resolveField2) {
|
|
11994
12327
|
const semantics = resolveField2(field);
|
|
11995
12328
|
if (!semantics) return unsupported2("WHERE_FIELD_UNRESOLVED", field.field, void 0, operator);
|
|
@@ -12027,18 +12360,18 @@ function normalizeOperator(op) {
|
|
|
12027
12360
|
function combineLogical(op, left, right) {
|
|
12028
12361
|
const reasons = [...left.reasons, ...right.reasons];
|
|
12029
12362
|
if (left.capability === "UNSUPPORTED" || right.capability === "UNSUPPORTED") {
|
|
12030
|
-
return { capability: "UNSUPPORTED", reasons };
|
|
12363
|
+
return requireExactRelativeDatePushdown({ capability: "UNSUPPORTED", reasons });
|
|
12031
12364
|
}
|
|
12032
12365
|
if (left.capability === "EXACT_PUSHDOWN" && right.capability === "EXACT_PUSHDOWN") {
|
|
12033
12366
|
return { capability: "EXACT_PUSHDOWN", reasons };
|
|
12034
12367
|
}
|
|
12035
12368
|
if (op === "AND" && (left.capability === "EXACT_PUSHDOWN" || right.capability === "EXACT_PUSHDOWN" || left.capability === "SUPERSET_PREFILTER" || right.capability === "SUPERSET_PREFILTER")) {
|
|
12036
|
-
return {
|
|
12369
|
+
return requireExactRelativeDatePushdown({
|
|
12037
12370
|
capability: "SUPERSET_PREFILTER",
|
|
12038
12371
|
reasons: [{ code: "WHERE_SUPERSET_PREFILTER" }, ...reasons]
|
|
12039
|
-
};
|
|
12372
|
+
});
|
|
12040
12373
|
}
|
|
12041
|
-
return { capability: "LOCAL_ONLY", reasons };
|
|
12374
|
+
return requireExactRelativeDatePushdown({ capability: "LOCAL_ONLY", reasons });
|
|
12042
12375
|
}
|
|
12043
12376
|
function localExpression() {
|
|
12044
12377
|
return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
|
|
@@ -12046,6 +12379,656 @@ function localExpression() {
|
|
|
12046
12379
|
function unsupported2(code, field, fieldType, operator) {
|
|
12047
12380
|
return { capability: "UNSUPPORTED", reasons: [{ code, field, fieldType, operator }] };
|
|
12048
12381
|
}
|
|
12382
|
+
function relativeDateUnsupported(code, functionName, field, fieldType, operator) {
|
|
12383
|
+
return requireExactRelativeDatePushdown({
|
|
12384
|
+
capability: "UNSUPPORTED",
|
|
12385
|
+
reasons: [{ code, functionName, field, fieldType, operator }]
|
|
12386
|
+
});
|
|
12387
|
+
}
|
|
12388
|
+
function hasRelativeDateReason(reasons) {
|
|
12389
|
+
return reasons.some((reason) => reason.functionName !== void 0);
|
|
12390
|
+
}
|
|
12391
|
+
function requireExactRelativeDatePushdown(result) {
|
|
12392
|
+
if (result.capability === "EXACT_PUSHDOWN" || !hasRelativeDateReason(result.reasons) || result.reasons.some((reason) => reason.code === "WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN")) {
|
|
12393
|
+
return result;
|
|
12394
|
+
}
|
|
12395
|
+
const relative = result.reasons.find((reason) => reason.functionName !== void 0);
|
|
12396
|
+
return {
|
|
12397
|
+
capability: result.capability,
|
|
12398
|
+
reasons: [
|
|
12399
|
+
...result.reasons,
|
|
12400
|
+
{
|
|
12401
|
+
code: "WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN",
|
|
12402
|
+
functionName: relative.functionName,
|
|
12403
|
+
field: relative.field,
|
|
12404
|
+
fieldType: relative.fieldType,
|
|
12405
|
+
operator: relative.operator
|
|
12406
|
+
}
|
|
12407
|
+
]
|
|
12408
|
+
};
|
|
12409
|
+
}
|
|
12410
|
+
|
|
12411
|
+
// src/core/optimization/relativeDatePushdownGuard.ts
|
|
12412
|
+
function relativeDateFunctionNamesInNode(node, stopAtNestedSelect) {
|
|
12413
|
+
const names = [];
|
|
12414
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12415
|
+
const visit = (valueNode) => {
|
|
12416
|
+
if (Array.isArray(valueNode)) {
|
|
12417
|
+
valueNode.forEach(visit);
|
|
12418
|
+
return;
|
|
12419
|
+
}
|
|
12420
|
+
if (valueNode === null || typeof valueNode !== "object") return;
|
|
12421
|
+
const value = valueNode;
|
|
12422
|
+
if (value["type"] === "KINTONE_FUNC" && typeof value["name"] === "string" && isRelativeDateFunctionName(value["name"]) && !seen.has(value["name"])) {
|
|
12423
|
+
seen.add(value["name"]);
|
|
12424
|
+
names.push(value["name"]);
|
|
12425
|
+
return;
|
|
12426
|
+
}
|
|
12427
|
+
if (stopAtNestedSelect && value["type"] === "SELECT") return;
|
|
12428
|
+
Object.values(value).forEach(visit);
|
|
12429
|
+
};
|
|
12430
|
+
visit(node);
|
|
12431
|
+
return names;
|
|
12432
|
+
}
|
|
12433
|
+
function relativeDateFunctionNamesInWhere(where) {
|
|
12434
|
+
return relativeDateFunctionNamesInNode(where, true);
|
|
12435
|
+
}
|
|
12436
|
+
function nestedSelects(node, root) {
|
|
12437
|
+
const found = [];
|
|
12438
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12439
|
+
const visit = (value) => {
|
|
12440
|
+
if (Array.isArray(value)) {
|
|
12441
|
+
value.forEach(visit);
|
|
12442
|
+
return;
|
|
12443
|
+
}
|
|
12444
|
+
if (value === null || typeof value !== "object" || seen.has(value)) return;
|
|
12445
|
+
seen.add(value);
|
|
12446
|
+
const object = value;
|
|
12447
|
+
if (object["type"] === "SELECT" && value !== root) {
|
|
12448
|
+
found.push(value);
|
|
12449
|
+
return;
|
|
12450
|
+
}
|
|
12451
|
+
Object.values(object).forEach(visit);
|
|
12452
|
+
};
|
|
12453
|
+
visit(node);
|
|
12454
|
+
return found;
|
|
12455
|
+
}
|
|
12456
|
+
function collectSelect(select, path, candidates, forceForbidden, allowPhase2 = true) {
|
|
12457
|
+
const functionNames = relativeDateFunctionNamesInWhere(select.where);
|
|
12458
|
+
if (functionNames.length > 0) {
|
|
12459
|
+
candidates.push({
|
|
12460
|
+
kind: forceForbidden ? "FORBIDDEN" : "SELECT",
|
|
12461
|
+
source: select,
|
|
12462
|
+
where: select.where,
|
|
12463
|
+
functionNames,
|
|
12464
|
+
path,
|
|
12465
|
+
allowPhase2Prefilter: allowPhase2
|
|
12466
|
+
});
|
|
12467
|
+
}
|
|
12468
|
+
nestedSelects(select, select).forEach(
|
|
12469
|
+
(nested, index) => collectSelect(
|
|
12470
|
+
nested,
|
|
12471
|
+
`${path}.select-source[${index}]`,
|
|
12472
|
+
candidates,
|
|
12473
|
+
forceForbidden,
|
|
12474
|
+
allowPhase2
|
|
12475
|
+
)
|
|
12476
|
+
);
|
|
12477
|
+
}
|
|
12478
|
+
function collectUnion(union, path, candidates, forceForbidden) {
|
|
12479
|
+
if (union.left.type === "UNION") collectUnion(union.left, `${path}.left`, candidates, forceForbidden);
|
|
12480
|
+
else collectSelect(union.left, `${path}.left`, candidates, forceForbidden);
|
|
12481
|
+
collectSelect(union.right, `${path}.right`, candidates, forceForbidden);
|
|
12482
|
+
}
|
|
12483
|
+
function collectWith(statement, path, candidates, inheritedForbidden) {
|
|
12484
|
+
if (!inheritedForbidden && canInlineSingleCte(statement)) {
|
|
12485
|
+
collectSelect(buildInlinedQuery(statement), `${path}.inlined`, candidates, false);
|
|
12486
|
+
return;
|
|
12487
|
+
}
|
|
12488
|
+
statement.ctes.forEach((cte, index) => {
|
|
12489
|
+
if (cte.query.type === "SELECT") {
|
|
12490
|
+
collectSelect(cte.query, `${path}.cte[${index}]`, candidates, true);
|
|
12491
|
+
} else if (cte.query.type === "UNION") {
|
|
12492
|
+
collectUnion(cte.query, `${path}.cte[${index}]`, candidates, true);
|
|
12493
|
+
}
|
|
12494
|
+
});
|
|
12495
|
+
if (statement.query.type === "SELECT") {
|
|
12496
|
+
collectSelect(statement.query, `${path}.main`, candidates, true);
|
|
12497
|
+
} else {
|
|
12498
|
+
collectUnion(statement.query, `${path}.main`, candidates, true);
|
|
12499
|
+
}
|
|
12500
|
+
}
|
|
12501
|
+
function collectStatement(statement, path, candidates, forceForbidden = false) {
|
|
12502
|
+
switch (statement.type) {
|
|
12503
|
+
case "SELECT":
|
|
12504
|
+
collectSelect(statement, path, candidates, forceForbidden);
|
|
12505
|
+
return;
|
|
12506
|
+
case "UNION":
|
|
12507
|
+
collectUnion(statement, path, candidates, forceForbidden);
|
|
12508
|
+
return;
|
|
12509
|
+
case "WITH":
|
|
12510
|
+
collectWith(statement, path, candidates, forceForbidden);
|
|
12511
|
+
return;
|
|
12512
|
+
case "CREATE_TEMP_TABLE":
|
|
12513
|
+
if (statement.query.type === "WITH") collectWith(statement.query, `${path}.query`, candidates, true);
|
|
12514
|
+
else if (statement.query.type === "UNION") collectUnion(statement.query, `${path}.query`, candidates, true);
|
|
12515
|
+
else collectSelect(statement.query, `${path}.query`, candidates, true);
|
|
12516
|
+
return;
|
|
12517
|
+
case "EXPLAIN":
|
|
12518
|
+
collectStatement(statement.query, `${path}.query`, candidates, forceForbidden);
|
|
12519
|
+
return;
|
|
12520
|
+
case "VALIDATE":
|
|
12521
|
+
case "REORDER": {
|
|
12522
|
+
const functionNames = relativeDateFunctionNamesInWhere(statement.where);
|
|
12523
|
+
if (functionNames.length > 0) {
|
|
12524
|
+
candidates.push({
|
|
12525
|
+
kind: "FORBIDDEN",
|
|
12526
|
+
source: statement,
|
|
12527
|
+
where: statement.where,
|
|
12528
|
+
functionNames,
|
|
12529
|
+
path
|
|
12530
|
+
});
|
|
12531
|
+
}
|
|
12532
|
+
nestedSelects(statement, statement).forEach(
|
|
12533
|
+
(select, index) => collectSelect(select, `${path}.select-source[${index}]`, candidates, true)
|
|
12534
|
+
);
|
|
12535
|
+
return;
|
|
12536
|
+
}
|
|
12537
|
+
case "UPDATE":
|
|
12538
|
+
case "DELETE": {
|
|
12539
|
+
const functionNames = relativeDateFunctionNamesInWhere(statement.where);
|
|
12540
|
+
if (functionNames.length > 0) {
|
|
12541
|
+
const forbidden = forceForbidden || Boolean(statement.subtableCode) || statement.type === "UPDATE" && (statement.from != null || Boolean(statement.applyBlocks?.length));
|
|
12542
|
+
candidates.push({
|
|
12543
|
+
kind: forbidden ? "FORBIDDEN" : "DML",
|
|
12544
|
+
source: statement,
|
|
12545
|
+
where: statement.where,
|
|
12546
|
+
functionNames,
|
|
12547
|
+
path
|
|
12548
|
+
});
|
|
12549
|
+
}
|
|
12550
|
+
if (statement.type === "UPDATE" && statement.applyBlocks?.length) {
|
|
12551
|
+
const applyFunctions = relativeDateFunctionNamesInNode(statement.applyBlocks, true);
|
|
12552
|
+
if (applyFunctions.length > 0) {
|
|
12553
|
+
candidates.push({
|
|
12554
|
+
kind: "FORBIDDEN",
|
|
12555
|
+
source: statement,
|
|
12556
|
+
where: null,
|
|
12557
|
+
functionNames: applyFunctions,
|
|
12558
|
+
path: `${path}.apply`
|
|
12559
|
+
});
|
|
12560
|
+
}
|
|
12561
|
+
}
|
|
12562
|
+
nestedSelects(statement, statement).forEach(
|
|
12563
|
+
(select, index) => collectSelect(
|
|
12564
|
+
select,
|
|
12565
|
+
`${path}.select-source[${index}]`,
|
|
12566
|
+
candidates,
|
|
12567
|
+
forceForbidden,
|
|
12568
|
+
false
|
|
12569
|
+
)
|
|
12570
|
+
);
|
|
12571
|
+
return;
|
|
12572
|
+
}
|
|
12573
|
+
default:
|
|
12574
|
+
nestedSelects(statement, statement).forEach(
|
|
12575
|
+
(select, index) => collectSelect(
|
|
12576
|
+
select,
|
|
12577
|
+
`${path}.select-source[${index}]`,
|
|
12578
|
+
candidates,
|
|
12579
|
+
forceForbidden,
|
|
12580
|
+
false
|
|
12581
|
+
)
|
|
12582
|
+
);
|
|
12583
|
+
}
|
|
12584
|
+
}
|
|
12585
|
+
function serializationContainsFunctions(query, names) {
|
|
12586
|
+
return names.every((name) => new RegExp(`\\b${name}\\s*\\(`).test(query));
|
|
12587
|
+
}
|
|
12588
|
+
function allowRelativeDatePrefilterPlan(select, decomposition) {
|
|
12589
|
+
return decomposition.eligible === true && resolveSelectMode(select) === "FULL_SCAN" && select.orderMode !== "KINTONE_NATIVE" && select.from.cteName === null && !select.from.subtableCode && select.joins.length === 0;
|
|
12590
|
+
}
|
|
12591
|
+
function rejectedNode(candidate) {
|
|
12592
|
+
return {
|
|
12593
|
+
kind: candidate.kind,
|
|
12594
|
+
source: candidate.source,
|
|
12595
|
+
functionNames: candidate.functionNames,
|
|
12596
|
+
path: candidate.path,
|
|
12597
|
+
clientWhereEvaluation: true,
|
|
12598
|
+
allowed: false
|
|
12599
|
+
};
|
|
12600
|
+
}
|
|
12601
|
+
function relativeDateReasonCodes(capability) {
|
|
12602
|
+
const codes = (capability?.reasons ?? []).filter(
|
|
12603
|
+
(reason) => reason.functionName !== void 0
|
|
12604
|
+
).map((reason) => reason.code).filter((code) => code.startsWith("WHERE_RELATIVE_DATE_"));
|
|
12605
|
+
if (!codes.includes(WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN)) {
|
|
12606
|
+
codes.push(WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN);
|
|
12607
|
+
}
|
|
12608
|
+
return [...new Set(codes)];
|
|
12609
|
+
}
|
|
12610
|
+
function rejectionFor(candidate, capability) {
|
|
12611
|
+
const reasonCodes = relativeDateReasonCodes(capability);
|
|
12612
|
+
return {
|
|
12613
|
+
functionName: candidate.functionNames[0],
|
|
12614
|
+
path: candidate.path,
|
|
12615
|
+
code: reasonCodes.find(
|
|
12616
|
+
(code) => code !== WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN
|
|
12617
|
+
) ?? WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN,
|
|
12618
|
+
reasonCodes
|
|
12619
|
+
};
|
|
12620
|
+
}
|
|
12621
|
+
async function buildRelativeDatePushdownPlan(statement, resolver) {
|
|
12622
|
+
const candidates = [];
|
|
12623
|
+
collectStatement(statement, "statement", candidates);
|
|
12624
|
+
const nodes = [];
|
|
12625
|
+
for (const candidate of candidates) {
|
|
12626
|
+
if (candidate.kind === "FORBIDDEN") {
|
|
12627
|
+
const node2 = rejectedNode(candidate);
|
|
12628
|
+
nodes.push(node2);
|
|
12629
|
+
return {
|
|
12630
|
+
hasRelativeDate: true,
|
|
12631
|
+
nodes,
|
|
12632
|
+
allowed: false,
|
|
12633
|
+
rejection: rejectionFor(candidate)
|
|
12634
|
+
};
|
|
12635
|
+
}
|
|
12636
|
+
if (candidate.kind === "SELECT") {
|
|
12637
|
+
const select = candidate.source;
|
|
12638
|
+
const physicalTopLevel = select.from.cteName === null && !select.from.subtableCode && select.joins.length === 0;
|
|
12639
|
+
const selectMode = resolveSelectMode(select);
|
|
12640
|
+
const capability2 = await resolver.select(select);
|
|
12641
|
+
let restQuery2 = "";
|
|
12642
|
+
try {
|
|
12643
|
+
restQuery2 = candidate.where === null ? "" : whereToKintone(candidate.where);
|
|
12644
|
+
} catch {
|
|
12645
|
+
restQuery2 = "";
|
|
12646
|
+
}
|
|
12647
|
+
let allowed2 = physicalTopLevel && selectMode === "SIMPLE" && (select.orderBy.length === 0 || select.orderMode === "KINTONE_NATIVE") && capability2.capability === "EXACT_PUSHDOWN" && serializationContainsFunctions(restQuery2, candidate.functionNames);
|
|
12648
|
+
let prefilterPlan;
|
|
12649
|
+
let phase2PrefilterEligible;
|
|
12650
|
+
if (!allowed2 && candidate.allowPhase2Prefilter !== false && capability2.capability === "SUPERSET_PREFILTER" && resolver.prefilterDecomposition) {
|
|
12651
|
+
const decomposition = await resolver.prefilterDecomposition(select);
|
|
12652
|
+
if (decomposition?.eligible === true && allowRelativeDatePrefilterPlan(select, decomposition)) {
|
|
12653
|
+
prefilterPlan = decomposition.plan;
|
|
12654
|
+
phase2PrefilterEligible = true;
|
|
12655
|
+
allowed2 = true;
|
|
12656
|
+
}
|
|
12657
|
+
}
|
|
12658
|
+
const node2 = {
|
|
12659
|
+
kind: candidate.kind,
|
|
12660
|
+
source: candidate.source,
|
|
12661
|
+
functionNames: candidate.functionNames,
|
|
12662
|
+
path: candidate.path,
|
|
12663
|
+
selectMode,
|
|
12664
|
+
capability: capability2,
|
|
12665
|
+
restQuery: restQuery2,
|
|
12666
|
+
...prefilterPlan ? { prefilterPlan, phase2PrefilterEligible } : {},
|
|
12667
|
+
clientWhereEvaluation: !allowed2,
|
|
12668
|
+
allowed: allowed2
|
|
12669
|
+
};
|
|
12670
|
+
nodes.push(node2);
|
|
12671
|
+
if (!allowed2) {
|
|
12672
|
+
return {
|
|
12673
|
+
hasRelativeDate: true,
|
|
12674
|
+
nodes,
|
|
12675
|
+
allowed: false,
|
|
12676
|
+
rejection: rejectionFor(candidate, capability2)
|
|
12677
|
+
};
|
|
12678
|
+
}
|
|
12679
|
+
continue;
|
|
12680
|
+
}
|
|
12681
|
+
const dml = candidate.source;
|
|
12682
|
+
const capability = await resolver.dml(dml);
|
|
12683
|
+
let restQuery = "";
|
|
12684
|
+
try {
|
|
12685
|
+
restQuery = candidate.where === null ? "" : whereToKintone(candidate.where);
|
|
12686
|
+
} catch {
|
|
12687
|
+
restQuery = "";
|
|
12688
|
+
}
|
|
12689
|
+
const allowed = capability.capability === "EXACT_PUSHDOWN" && serializationContainsFunctions(restQuery, candidate.functionNames);
|
|
12690
|
+
const node = {
|
|
12691
|
+
kind: candidate.kind,
|
|
12692
|
+
source: candidate.source,
|
|
12693
|
+
functionNames: candidate.functionNames,
|
|
12694
|
+
path: candidate.path,
|
|
12695
|
+
capability,
|
|
12696
|
+
restQuery,
|
|
12697
|
+
clientWhereEvaluation: !allowed,
|
|
12698
|
+
allowed
|
|
12699
|
+
};
|
|
12700
|
+
nodes.push(node);
|
|
12701
|
+
if (!allowed) {
|
|
12702
|
+
return {
|
|
12703
|
+
hasRelativeDate: true,
|
|
12704
|
+
nodes,
|
|
12705
|
+
allowed: false,
|
|
12706
|
+
rejection: rejectionFor(candidate, capability)
|
|
12707
|
+
};
|
|
12708
|
+
}
|
|
12709
|
+
}
|
|
12710
|
+
return {
|
|
12711
|
+
hasRelativeDate: candidates.length > 0,
|
|
12712
|
+
nodes,
|
|
12713
|
+
allowed: true
|
|
12714
|
+
};
|
|
12715
|
+
}
|
|
12716
|
+
function assertRelativeDatePushdownPlan(plan) {
|
|
12717
|
+
if (!plan.allowed && plan.rejection) {
|
|
12718
|
+
const details = plan.rejection.reasonCodes.filter(
|
|
12719
|
+
(code) => code !== WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN
|
|
12720
|
+
);
|
|
12721
|
+
throw new Error(
|
|
12722
|
+
`${plan.rejection.functionName}: ${WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN}${details.length > 0 ? ` (reason=${details.join(", ")})` : ""} (path=${plan.rejection.path})`
|
|
12723
|
+
);
|
|
12724
|
+
}
|
|
12725
|
+
}
|
|
12726
|
+
|
|
12727
|
+
// src/core/optimization/relativeDatePrefilterPlan.ts
|
|
12728
|
+
function decomposeRelativeDatePrefilter(stmt, resolveField2, testSeam = {}) {
|
|
12729
|
+
const capability = classifyWhereCapability(stmt.where, resolveField2);
|
|
12730
|
+
const reject = (reasonCodes, disposition = "INELIGIBLE") => ({
|
|
12731
|
+
eligible: false,
|
|
12732
|
+
disposition,
|
|
12733
|
+
reasonCodes,
|
|
12734
|
+
capability: capability.capability,
|
|
12735
|
+
reasons: capability.reasons
|
|
12736
|
+
});
|
|
12737
|
+
if (stmt.where === null) return reject(["NO_WHERE"]);
|
|
12738
|
+
if (stmt.from.subtableCode) return reject(["SUBTABLE_UNSUPPORTED"]);
|
|
12739
|
+
if (stmt.joins.length > 0) return reject(["JOIN_UNSUPPORTED"]);
|
|
12740
|
+
if (stmt.from.cteName !== null || stmt.from.appId <= 0) {
|
|
12741
|
+
return reject(["NOT_DIRECT_PHYSICAL_APP"]);
|
|
12742
|
+
}
|
|
12743
|
+
const occurrences = collectRelativeOccurrences(stmt.where);
|
|
12744
|
+
if (occurrences.length === 0) return reject(["NO_RELATIVE_DATE"]);
|
|
12745
|
+
const spine = collectRelativeLeavesOnAndSpine(stmt.where, resolveField2);
|
|
12746
|
+
if (!spine.ok) return reject(spine.reasonCodes);
|
|
12747
|
+
if (!sameRelativeMultiset(occurrences, spine.leaves)) {
|
|
12748
|
+
return reject(["RELATIVE_DATE_LEAF_COUNT_MISMATCH"]);
|
|
12749
|
+
}
|
|
12750
|
+
if (capability.capability === "EXACT_PUSHDOWN") {
|
|
12751
|
+
return reject(["DEFER_TO_PHASE1"], "DEFER_PHASE1");
|
|
12752
|
+
}
|
|
12753
|
+
if (capability.capability !== "SUPERSET_PREFILTER") {
|
|
12754
|
+
return reject(["CAPABILITY_NOT_SUPERSET_PREFILTER"]);
|
|
12755
|
+
}
|
|
12756
|
+
const fieldMetadata = collectFieldMetadata(stmt.where, resolveField2);
|
|
12757
|
+
const safePlan = buildSingleTableKlikePushdownPlan(stmt.where, {
|
|
12758
|
+
tableAlias: stmt.from.alias ?? void 0,
|
|
12759
|
+
allowUnqualifiedFields: true,
|
|
12760
|
+
allowKlike: true,
|
|
12761
|
+
fieldTypes: fieldMetadata.fieldTypes,
|
|
12762
|
+
fieldOptions: fieldMetadata.fieldOptions
|
|
12763
|
+
});
|
|
12764
|
+
const serialize = testSeam.serialize ?? whereToKintone;
|
|
12765
|
+
const containsFunctions = testSeam.containsFunctions ?? serializationContainsFunctions;
|
|
12766
|
+
const confirmedLeaves = [];
|
|
12767
|
+
for (const leaf of spine.leaves) {
|
|
12768
|
+
const name = relativeNameOf(leaf);
|
|
12769
|
+
if (name === null) return reject(["RELATIVE_DATE_LEAF_COUNT_MISMATCH"]);
|
|
12770
|
+
let query;
|
|
12771
|
+
try {
|
|
12772
|
+
query = serialize(leaf);
|
|
12773
|
+
} catch {
|
|
12774
|
+
return reject(["PREFILTER_SERIALIZATION_FAILED"]);
|
|
12775
|
+
}
|
|
12776
|
+
if (!containsFunctions(query, [name])) {
|
|
12777
|
+
return reject(["PREFILTER_FUNCTION_MISSING"]);
|
|
12778
|
+
}
|
|
12779
|
+
confirmedLeaves.push(leaf);
|
|
12780
|
+
}
|
|
12781
|
+
const safeLeaves = collectBinaryIdentities(safePlan.condition);
|
|
12782
|
+
const adoptedLeaves = new Set(confirmedLeaves);
|
|
12783
|
+
const prefilterWhere = selectPrefilterInOriginalOrder(
|
|
12784
|
+
stmt.where,
|
|
12785
|
+
adoptedLeaves,
|
|
12786
|
+
safeLeaves
|
|
12787
|
+
);
|
|
12788
|
+
if (prefilterWhere === null) {
|
|
12789
|
+
return reject(["RELATIVE_DATE_LEAF_COUNT_MISMATCH"]);
|
|
12790
|
+
}
|
|
12791
|
+
let prefilterQuery;
|
|
12792
|
+
try {
|
|
12793
|
+
prefilterQuery = serialize(prefilterWhere);
|
|
12794
|
+
} catch {
|
|
12795
|
+
return reject(["PREFILTER_SERIALIZATION_FAILED"]);
|
|
12796
|
+
}
|
|
12797
|
+
const expectedNames = confirmedLeaves.map((leaf) => relativeNameOf(leaf));
|
|
12798
|
+
if (!containsFunctions(prefilterQuery, [...new Set(expectedNames)]) || !serializedMultisetContains(prefilterQuery, expectedNames)) {
|
|
12799
|
+
return reject(["PREFILTER_FUNCTION_MISSING"]);
|
|
12800
|
+
}
|
|
12801
|
+
const residualWhere = testSeam.rewriteResidual ? testSeam.rewriteResidual(stmt.where, adoptedLeaves) : replaceAdoptedLeaves(stmt.where, adoptedLeaves);
|
|
12802
|
+
if (residualWhere !== null && collectRelativeOccurrences(residualWhere).length > 0) {
|
|
12803
|
+
return reject(["RESIDUAL_RELATIVE_DATE_REMAINED"]);
|
|
12804
|
+
}
|
|
12805
|
+
if (residualWhere === null) {
|
|
12806
|
+
return reject(["DEFER_TO_PHASE1"], "DEFER_PHASE1");
|
|
12807
|
+
}
|
|
12808
|
+
const relativeFunctionNames = /* @__PURE__ */ new Set();
|
|
12809
|
+
for (const name of expectedNames) relativeFunctionNames.add(name);
|
|
12810
|
+
return {
|
|
12811
|
+
eligible: true,
|
|
12812
|
+
plan: {
|
|
12813
|
+
prefilterWhere,
|
|
12814
|
+
residualWhere,
|
|
12815
|
+
exactRelativeLeaves: confirmedLeaves,
|
|
12816
|
+
relativeFunctionNames,
|
|
12817
|
+
appliedKlikes: safePlan.appliedKlikes,
|
|
12818
|
+
capability: capability.capability,
|
|
12819
|
+
reasons: capability.reasons
|
|
12820
|
+
}
|
|
12821
|
+
};
|
|
12822
|
+
}
|
|
12823
|
+
function collectRelativeLeavesOnAndSpine(where, resolveField2) {
|
|
12824
|
+
const leaves = [];
|
|
12825
|
+
let failure = null;
|
|
12826
|
+
const visit = (node) => {
|
|
12827
|
+
if (failure !== null) return;
|
|
12828
|
+
switch (node.type) {
|
|
12829
|
+
case "GROUP":
|
|
12830
|
+
visit(node.expr);
|
|
12831
|
+
return;
|
|
12832
|
+
case "LOGICAL":
|
|
12833
|
+
if (node.op === "AND") {
|
|
12834
|
+
visit(node.left);
|
|
12835
|
+
visit(node.right);
|
|
12836
|
+
return;
|
|
12837
|
+
}
|
|
12838
|
+
if (collectRelativeOccurrences(node).length > 0) {
|
|
12839
|
+
failure = "RELATIVE_DATE_CONTEXT_UNSUPPORTED";
|
|
12840
|
+
}
|
|
12841
|
+
return;
|
|
12842
|
+
case "NOT":
|
|
12843
|
+
if (collectRelativeOccurrences(node).length > 0) {
|
|
12844
|
+
failure = "RELATIVE_DATE_CONTEXT_UNSUPPORTED";
|
|
12845
|
+
}
|
|
12846
|
+
return;
|
|
12847
|
+
case "BINARY": {
|
|
12848
|
+
const name = relativeNameOf(node);
|
|
12849
|
+
if (name === null) return;
|
|
12850
|
+
const result = classifyRelativeDateBinary(
|
|
12851
|
+
node.op,
|
|
12852
|
+
node.left,
|
|
12853
|
+
node.right,
|
|
12854
|
+
resolveField2
|
|
12855
|
+
);
|
|
12856
|
+
if (result.capability !== "EXACT_PUSHDOWN") {
|
|
12857
|
+
failure = "RELATIVE_DATE_LEAF_NOT_EXACT";
|
|
12858
|
+
return;
|
|
12859
|
+
}
|
|
12860
|
+
leaves.push(node);
|
|
12861
|
+
return;
|
|
12862
|
+
}
|
|
12863
|
+
case "EXISTS":
|
|
12864
|
+
if (collectRelativeOccurrences(node).length > 0) {
|
|
12865
|
+
failure = "RELATIVE_DATE_CONTEXT_UNSUPPORTED";
|
|
12866
|
+
}
|
|
12867
|
+
return;
|
|
12868
|
+
case "NULL_CHECK":
|
|
12869
|
+
case "BOOLEAN":
|
|
12870
|
+
return;
|
|
12871
|
+
}
|
|
12872
|
+
};
|
|
12873
|
+
visit(where);
|
|
12874
|
+
return failure === null ? { ok: true, leaves } : { ok: false, reasonCodes: [failure] };
|
|
12875
|
+
}
|
|
12876
|
+
function relativeNameOf(leaf) {
|
|
12877
|
+
return leaf.right.type === "KINTONE_FUNC" && isRelativeDateFunctionName(leaf.right.name) ? leaf.right.name : null;
|
|
12878
|
+
}
|
|
12879
|
+
function collectRelativeOccurrences(where) {
|
|
12880
|
+
const found = [];
|
|
12881
|
+
const visitWhere = (node) => {
|
|
12882
|
+
if (node.type === "BINARY" && relativeNameOf(node) !== null) {
|
|
12883
|
+
found.push(node);
|
|
12884
|
+
return;
|
|
12885
|
+
}
|
|
12886
|
+
switch (node.type) {
|
|
12887
|
+
case "LOGICAL":
|
|
12888
|
+
visitWhere(node.left);
|
|
12889
|
+
visitWhere(node.right);
|
|
12890
|
+
return;
|
|
12891
|
+
case "NOT":
|
|
12892
|
+
case "GROUP":
|
|
12893
|
+
visitWhere(node.expr);
|
|
12894
|
+
return;
|
|
12895
|
+
case "EXISTS":
|
|
12896
|
+
if (node.query.where !== null) visitWhere(node.query.where);
|
|
12897
|
+
return;
|
|
12898
|
+
case "BINARY":
|
|
12899
|
+
case "NULL_CHECK":
|
|
12900
|
+
case "BOOLEAN":
|
|
12901
|
+
return;
|
|
12902
|
+
}
|
|
12903
|
+
};
|
|
12904
|
+
visitWhere(where);
|
|
12905
|
+
return found;
|
|
12906
|
+
}
|
|
12907
|
+
function sameRelativeMultiset(occurrences, candidates) {
|
|
12908
|
+
if (occurrences.length !== candidates.length) return false;
|
|
12909
|
+
const remaining = new Set(candidates);
|
|
12910
|
+
for (const occurrence of occurrences) {
|
|
12911
|
+
if (!remaining.delete(occurrence)) return false;
|
|
12912
|
+
}
|
|
12913
|
+
return remaining.size === 0;
|
|
12914
|
+
}
|
|
12915
|
+
function collectBinaryIdentities(where) {
|
|
12916
|
+
const found = /* @__PURE__ */ new Set();
|
|
12917
|
+
const visit = (node) => {
|
|
12918
|
+
switch (node.type) {
|
|
12919
|
+
case "BINARY":
|
|
12920
|
+
found.add(node);
|
|
12921
|
+
return;
|
|
12922
|
+
case "LOGICAL":
|
|
12923
|
+
visit(node.left);
|
|
12924
|
+
visit(node.right);
|
|
12925
|
+
return;
|
|
12926
|
+
case "NOT":
|
|
12927
|
+
case "GROUP":
|
|
12928
|
+
visit(node.expr);
|
|
12929
|
+
return;
|
|
12930
|
+
case "NULL_CHECK":
|
|
12931
|
+
case "EXISTS":
|
|
12932
|
+
case "BOOLEAN":
|
|
12933
|
+
return;
|
|
12934
|
+
}
|
|
12935
|
+
};
|
|
12936
|
+
if (where !== null) visit(where);
|
|
12937
|
+
return found;
|
|
12938
|
+
}
|
|
12939
|
+
function selectPrefilterInOriginalOrder(where, relativeLeaves, safeLeaves) {
|
|
12940
|
+
switch (where.type) {
|
|
12941
|
+
case "BINARY":
|
|
12942
|
+
return relativeLeaves.has(where) || safeLeaves.has(where) ? where : null;
|
|
12943
|
+
case "LOGICAL":
|
|
12944
|
+
if (where.op !== "AND") return null;
|
|
12945
|
+
{
|
|
12946
|
+
const left = selectPrefilterInOriginalOrder(where.left, relativeLeaves, safeLeaves);
|
|
12947
|
+
const right = selectPrefilterInOriginalOrder(where.right, relativeLeaves, safeLeaves);
|
|
12948
|
+
if (left !== null && right !== null) return { ...where, left, right };
|
|
12949
|
+
return left ?? right;
|
|
12950
|
+
}
|
|
12951
|
+
case "GROUP": {
|
|
12952
|
+
const expr = selectPrefilterInOriginalOrder(where.expr, relativeLeaves, safeLeaves);
|
|
12953
|
+
return expr === null ? null : { ...where, expr };
|
|
12954
|
+
}
|
|
12955
|
+
case "NULL_CHECK":
|
|
12956
|
+
case "NOT":
|
|
12957
|
+
case "EXISTS":
|
|
12958
|
+
case "BOOLEAN":
|
|
12959
|
+
return null;
|
|
12960
|
+
}
|
|
12961
|
+
}
|
|
12962
|
+
var TRUE_PREDICATE = { type: "BOOLEAN", value: true };
|
|
12963
|
+
function replaceAdoptedLeaves(where, adoptedLeaves) {
|
|
12964
|
+
if (where.type === "BINARY" && adoptedLeaves.has(where)) return TRUE_PREDICATE;
|
|
12965
|
+
switch (where.type) {
|
|
12966
|
+
case "LOGICAL": {
|
|
12967
|
+
if (where.op !== "AND") return where;
|
|
12968
|
+
const left = replaceAdoptedLeaves(where.left, adoptedLeaves) ?? TRUE_PREDICATE;
|
|
12969
|
+
const right = replaceAdoptedLeaves(where.right, adoptedLeaves) ?? TRUE_PREDICATE;
|
|
12970
|
+
if (isTrue(left)) return isTrue(right) ? null : right;
|
|
12971
|
+
if (isTrue(right)) return left;
|
|
12972
|
+
if (left === where.left && right === where.right) return where;
|
|
12973
|
+
return { ...where, left, right };
|
|
12974
|
+
}
|
|
12975
|
+
case "GROUP": {
|
|
12976
|
+
const expr = replaceAdoptedLeaves(where.expr, adoptedLeaves) ?? TRUE_PREDICATE;
|
|
12977
|
+
if (isTrue(expr)) return TRUE_PREDICATE;
|
|
12978
|
+
return expr === where.expr ? where : { ...where, expr };
|
|
12979
|
+
}
|
|
12980
|
+
case "BINARY":
|
|
12981
|
+
case "NULL_CHECK":
|
|
12982
|
+
case "NOT":
|
|
12983
|
+
case "EXISTS":
|
|
12984
|
+
case "BOOLEAN":
|
|
12985
|
+
return where;
|
|
12986
|
+
}
|
|
12987
|
+
}
|
|
12988
|
+
function isTrue(where) {
|
|
12989
|
+
return where.type === "BOOLEAN" && where.value;
|
|
12990
|
+
}
|
|
12991
|
+
function collectFieldMetadata(where, resolveField2) {
|
|
12992
|
+
const fields = /* @__PURE__ */ new Map();
|
|
12993
|
+
const visitValue = (value) => {
|
|
12994
|
+
if (value === null || typeof value !== "object") return;
|
|
12995
|
+
if (Array.isArray(value)) {
|
|
12996
|
+
value.forEach(visitValue);
|
|
12997
|
+
return;
|
|
12998
|
+
}
|
|
12999
|
+
const record = value;
|
|
13000
|
+
if (record["type"] === "SELECT") return;
|
|
13001
|
+
if (record["type"] === "FIELD" && typeof record["field"] === "string" && typeof record["tableAlias"] !== "undefined") {
|
|
13002
|
+
fields.set(record["field"], value);
|
|
13003
|
+
return;
|
|
13004
|
+
}
|
|
13005
|
+
Object.values(record).forEach(visitValue);
|
|
13006
|
+
};
|
|
13007
|
+
visitValue(where);
|
|
13008
|
+
const fieldTypes = /* @__PURE__ */ new Map();
|
|
13009
|
+
const fieldOptions = /* @__PURE__ */ new Map();
|
|
13010
|
+
for (const [fieldCode, field] of fields) {
|
|
13011
|
+
const semantics = resolveField2(field);
|
|
13012
|
+
if (!semantics) continue;
|
|
13013
|
+
fieldTypes.set(fieldCode, semantics.fieldType);
|
|
13014
|
+
if (semantics.optionOrder) {
|
|
13015
|
+
fieldOptions.set(fieldCode, new Set(semantics.optionOrder.keys()));
|
|
13016
|
+
}
|
|
13017
|
+
}
|
|
13018
|
+
return { fieldTypes, fieldOptions };
|
|
13019
|
+
}
|
|
13020
|
+
function serializedMultisetContains(query, expectedNames) {
|
|
13021
|
+
const expected = /* @__PURE__ */ new Map();
|
|
13022
|
+
for (const name of expectedNames) {
|
|
13023
|
+
if (!RELATIVE_DATE_FUNCTION_NAMES.has(name)) return false;
|
|
13024
|
+
expected.set(name, (expected.get(name) ?? 0) + 1);
|
|
13025
|
+
}
|
|
13026
|
+
for (const [name, count] of expected) {
|
|
13027
|
+
const matches = query.match(new RegExp(`\\b${name}\\s*\\(`, "g"));
|
|
13028
|
+
if ((matches?.length ?? 0) < count) return false;
|
|
13029
|
+
}
|
|
13030
|
+
return true;
|
|
13031
|
+
}
|
|
12049
13032
|
|
|
12050
13033
|
// src/import/sourceLoader.ts
|
|
12051
13034
|
var IMPORT_MAX_BYTES = 10 * 1024 * 1024;
|
|
@@ -13102,7 +14085,29 @@ function attachSearchAbortWarning(result, collector) {
|
|
|
13102
14085
|
warnings.add(SEARCH_ABORTED_WARNING);
|
|
13103
14086
|
return { ...result, warnings: [...warnings] };
|
|
13104
14087
|
}
|
|
14088
|
+
async function assertRelativeDateExecutionPlan(stmt, client, cacheContext) {
|
|
14089
|
+
const plan = await resolveRelativeDateExecutionPlan(stmt, client, cacheContext);
|
|
14090
|
+
assertRelativeDatePushdownPlan(plan);
|
|
14091
|
+
return plan;
|
|
14092
|
+
}
|
|
14093
|
+
async function resolveRelativeDateExecutionPlan(stmt, client, cacheContext) {
|
|
14094
|
+
return buildRelativeDatePushdownPlan(stmt, {
|
|
14095
|
+
select: (select) => resolveSelectWhereCapability(select, client, cacheContext),
|
|
14096
|
+
dml: (dml) => resolveDmlWhereCapability(dml, client, cacheContext),
|
|
14097
|
+
prefilterDecomposition: async (select) => {
|
|
14098
|
+
if (select.where === null) return null;
|
|
14099
|
+
const resolver = await buildWhereFieldSemanticsResolver(
|
|
14100
|
+
select,
|
|
14101
|
+
client,
|
|
14102
|
+
cacheContext
|
|
14103
|
+
);
|
|
14104
|
+
return decomposeRelativeDatePrefilter(select, resolver);
|
|
14105
|
+
}
|
|
14106
|
+
});
|
|
14107
|
+
}
|
|
13105
14108
|
async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
14109
|
+
const relativeDatePlan = await resolveRelativeDateExecutionPlan(stmt, client, cacheContext);
|
|
14110
|
+
if (stmt.type !== "EXPLAIN") assertRelativeDatePushdownPlan(relativeDatePlan);
|
|
13106
14111
|
const unresolved = findVariableRef(stmt);
|
|
13107
14112
|
if (unresolved !== null && !isApplyParentKlikeStatement(stmt)) {
|
|
13108
14113
|
throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
|
|
@@ -13164,7 +14169,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
13164
14169
|
options.maxRecords ?? 1e4,
|
|
13165
14170
|
options.cursorMaxActive ?? 2,
|
|
13166
14171
|
stmt.query.type === "UPDATE" && stmt.query.applyBlocks?.length ? resolveApplyGuardLimit(options.dmlMaxRows, "dmlMaxRows", DEFAULT_APPLY_MAX_ROWS) : DEFAULT_APPLY_MAX_ROWS,
|
|
13167
|
-
stmt.query.type === "UPDATE" && stmt.query.applyBlocks?.length ? resolveApplyGuardLimit(options.dmlMaxSubtableRows, "dmlMaxSubtableRows", DEFAULT_APPLY_MAX_SUBTABLE_ROWS) : DEFAULT_APPLY_MAX_SUBTABLE_ROWS
|
|
14172
|
+
stmt.query.type === "UPDATE" && stmt.query.applyBlocks?.length ? resolveApplyGuardLimit(options.dmlMaxSubtableRows, "dmlMaxSubtableRows", DEFAULT_APPLY_MAX_SUBTABLE_ROWS) : DEFAULT_APPLY_MAX_SUBTABLE_ROWS,
|
|
14173
|
+
relativeDatePlan
|
|
13168
14174
|
);
|
|
13169
14175
|
// 一時テーブルはバッチスコープのため単文実行では拒否する(executeBatch を使う)
|
|
13170
14176
|
case "CREATE_TEMP_TABLE":
|
|
@@ -13548,6 +14554,7 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
13548
14554
|
if (stmt.type === "SET_VARIABLE") {
|
|
13549
14555
|
const resolvedStmt2 = resolveBatchVariableReferences(stmt, variables);
|
|
13550
14556
|
validateKlikeStatement(resolvedStmt2);
|
|
14557
|
+
await assertRelativeDateExecutionPlan(resolvedStmt2, client, cacheContext);
|
|
13551
14558
|
if (resolvedStmt2.expr.type === "ARRAY") {
|
|
13552
14559
|
variables.set(stmt.name, {
|
|
13553
14560
|
type: "array",
|
|
@@ -13604,6 +14611,7 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
13604
14611
|
assertApplyScope("phase15b", resolvedStmt);
|
|
13605
14612
|
assertApplyExecutionScope("phase15b", resolvedStmt);
|
|
13606
14613
|
validateKlikeStatement(resolvedStmt);
|
|
14614
|
+
await assertRelativeDateExecutionPlan(resolvedStmt, client, cacheContext);
|
|
13607
14615
|
if (resolvedStmt.type === "VALIDATE") {
|
|
13608
14616
|
const result = await executeExistingRecordValidationCore(
|
|
13609
14617
|
resolvedStmt,
|
|
@@ -14161,16 +15169,27 @@ function hasCanonicalOrder(stmt) {
|
|
|
14161
15169
|
(column) => column.type === "WINDOW_COL" && column.orderBy.length > 0
|
|
14162
15170
|
);
|
|
14163
15171
|
}
|
|
14164
|
-
async function
|
|
14165
|
-
if (stmt.subtableCode || stmt.type === "UPDATE" && stmt.from != null)
|
|
15172
|
+
async function resolveDmlWhereCapability(stmt, client, cacheContext) {
|
|
15173
|
+
if (stmt.subtableCode || stmt.type === "UPDATE" && stmt.from != null) {
|
|
15174
|
+
return {
|
|
15175
|
+
capability: "LOCAL_ONLY",
|
|
15176
|
+
reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }]
|
|
15177
|
+
};
|
|
15178
|
+
}
|
|
14166
15179
|
const fields = whereNeedsFieldMetadata(stmt.where) ? await getFieldsCached(stmt.appId, client, cacheContext) : [];
|
|
14167
15180
|
const byCode = new Map(fields.map((field) => [field.code, field]));
|
|
14168
|
-
if (stmt.where.type === "BOOLEAN" && stmt.where.value === false)
|
|
14169
|
-
|
|
15181
|
+
if (stmt.where.type === "BOOLEAN" && stmt.where.value === false) {
|
|
15182
|
+
return classifyWhereCapability(null, () => void 0);
|
|
15183
|
+
}
|
|
15184
|
+
return classifyWhereCapability(stmt.where, (field) => {
|
|
14170
15185
|
if (field.field === "$id") return resolveFieldSemantics({ fieldType: "__ID__" });
|
|
14171
15186
|
const info = byCode.get(field.field);
|
|
14172
15187
|
return info?.semantics ?? (info ? resolveFieldSemantics(info) : void 0);
|
|
14173
15188
|
});
|
|
15189
|
+
}
|
|
15190
|
+
async function assertDmlWhereCapability(stmt, client, cacheContext) {
|
|
15191
|
+
if (stmt.subtableCode || stmt.type === "UPDATE" && stmt.from != null) return;
|
|
15192
|
+
const result = await resolveDmlWhereCapability(stmt, client, cacheContext);
|
|
14174
15193
|
if (result.capability !== "EXACT_PUSHDOWN") {
|
|
14175
15194
|
throw new DmlConvertError(
|
|
14176
15195
|
`WHERE predicate cannot be represented by kintone REST (${formatWhereCapabilityFailure(result)})`
|
|
@@ -14192,6 +15211,14 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
|
|
|
14192
15211
|
if (whereCapability.capability === "UNSUPPORTED") {
|
|
14193
15212
|
throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(whereCapability)}).`);
|
|
14194
15213
|
}
|
|
15214
|
+
let prefilterPlan;
|
|
15215
|
+
if (whereCapability.capability === "SUPERSET_PREFILTER") {
|
|
15216
|
+
const resolver = await buildWhereFieldSemanticsResolver(stmt, client, cacheContext, cteCache);
|
|
15217
|
+
const decomposition = decomposeRelativeDatePrefilter(stmt, resolver);
|
|
15218
|
+
if (decomposition.eligible && allowRelativeDatePrefilterPlan(stmt, decomposition)) {
|
|
15219
|
+
prefilterPlan = decomposition.plan;
|
|
15220
|
+
}
|
|
15221
|
+
}
|
|
14195
15222
|
const staticMode = resolveSelectMode(stmt);
|
|
14196
15223
|
const mode = whereCapability.capability === "EXACT_PUSHDOWN" ? staticMode : "FULL_SCAN";
|
|
14197
15224
|
const orderMeta = await buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
|
|
@@ -14199,6 +15226,7 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
|
|
|
14199
15226
|
stmt,
|
|
14200
15227
|
staticMode: mode,
|
|
14201
15228
|
whereCapability: whereCapability.capability,
|
|
15229
|
+
whereReasons: whereCapability.reasons,
|
|
14202
15230
|
orderSemantics: orderMeta.semantics,
|
|
14203
15231
|
maxRecords: options.maxRecords ?? 1e4,
|
|
14204
15232
|
hasKlike: whereHasKlike(stmt.where)
|
|
@@ -14221,7 +15249,8 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
|
|
|
14221
15249
|
cacheContext,
|
|
14222
15250
|
cteCache,
|
|
14223
15251
|
whereCapability.capability === "EXACT_PUSHDOWN",
|
|
14224
|
-
orderMeta
|
|
15252
|
+
orderMeta,
|
|
15253
|
+
prefilterPlan
|
|
14225
15254
|
);
|
|
14226
15255
|
}
|
|
14227
15256
|
} catch (error) {
|
|
@@ -15139,7 +16168,7 @@ function buildSelectFieldTypeResolvers(stmt, fieldTypesByApp) {
|
|
|
15139
16168
|
};
|
|
15140
16169
|
return { row, having };
|
|
15141
16170
|
}
|
|
15142
|
-
async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache, allowOriginalWherePushdown = true, preloadedOrderMeta) {
|
|
16171
|
+
async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache, allowOriginalWherePushdown = true, preloadedOrderMeta, prefilterPlan) {
|
|
15143
16172
|
const maxRecords = options.maxRecords ?? 1e4;
|
|
15144
16173
|
const warnings = /* @__PURE__ */ new Set();
|
|
15145
16174
|
const parallel = options.fetchParallel ?? 1;
|
|
@@ -15165,6 +16194,10 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
15165
16194
|
validateKlikePushdownPlan(pushdownPlan);
|
|
15166
16195
|
const mainPushDown = pushdownPlan.mainCondition;
|
|
15167
16196
|
const tableConditions = pushdownPlan.joinConditions;
|
|
16197
|
+
if (prefilterPlan && allowOriginalWherePushdown) {
|
|
16198
|
+
throw new Error("internal error: relative-date prefilter must disable original WHERE pushdown.");
|
|
16199
|
+
}
|
|
16200
|
+
const mainFetchCondition = prefilterPlan ? prefilterPlan.prefilterWhere : mainPushDown;
|
|
15168
16201
|
const constantFalse = isConstantFalseWhere(stmt.where);
|
|
15169
16202
|
const mainFetch = constantFalse ? Promise.resolve([]) : fetchTableRecordsForFullScan(
|
|
15170
16203
|
stmt,
|
|
@@ -15175,7 +16208,7 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
15175
16208
|
true,
|
|
15176
16209
|
options.onLimitReached ?? "error",
|
|
15177
16210
|
warnings,
|
|
15178
|
-
|
|
16211
|
+
mainFetchCondition,
|
|
15179
16212
|
allowOriginalWherePushdown
|
|
15180
16213
|
);
|
|
15181
16214
|
const parallelJoins = [];
|
|
@@ -15256,7 +16289,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
15256
16289
|
havingFieldTypeResolver: fieldTypeResolvers.having,
|
|
15257
16290
|
havingFieldSemanticsResolver,
|
|
15258
16291
|
aggregateSortKindResolver,
|
|
15259
|
-
appliedKlikes: pushdownPlan.appliedKlikes,
|
|
16292
|
+
appliedKlikes: prefilterPlan?.appliedKlikes ?? pushdownPlan.appliedKlikes,
|
|
16293
|
+
...prefilterPlan ? { residualWhere: prefilterPlan.residualWhere } : {},
|
|
15260
16294
|
resolvedGroupingSpec: resolvedGroupingSpecs.get(stmt)
|
|
15261
16295
|
});
|
|
15262
16296
|
return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
|
|
@@ -15394,6 +16428,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
15394
16428
|
stmt,
|
|
15395
16429
|
staticMode: "FULL_SCAN",
|
|
15396
16430
|
whereCapability: whereCapability.capability,
|
|
16431
|
+
whereReasons: whereCapability.reasons,
|
|
15397
16432
|
orderSemantics: orderMeta.semantics,
|
|
15398
16433
|
maxRecords,
|
|
15399
16434
|
hasKlike: whereHasKlike(stmt.where)
|
|
@@ -18875,7 +19910,7 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
|
|
|
18875
19910
|
}
|
|
18876
19911
|
var validateExplainInfo = /* @__PURE__ */ new WeakMap();
|
|
18877
19912
|
var applyParentExplainPlan = /* @__PURE__ */ new WeakMap();
|
|
18878
|
-
async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords = 1e4) {
|
|
19913
|
+
async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords = 1e4, relativeDatePlan) {
|
|
18879
19914
|
const fieldApps = /* @__PURE__ */ new Set();
|
|
18880
19915
|
const processStatusApps = /* @__PURE__ */ new Set();
|
|
18881
19916
|
const numberPrecisionApps = /* @__PURE__ */ new Set();
|
|
@@ -18897,6 +19932,10 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
18897
19932
|
const capabilities = /* @__PURE__ */ new Map();
|
|
18898
19933
|
const orderPlans = /* @__PURE__ */ new Map();
|
|
18899
19934
|
const seen = /* @__PURE__ */ new Set();
|
|
19935
|
+
const sharedRelativeDatePlan = relativeDatePlan ?? await resolveRelativeDateExecutionPlan(query, tracedClient, cacheContext);
|
|
19936
|
+
const relativeNodeFor = (source) => sharedRelativeDatePlan.nodes.find(
|
|
19937
|
+
(node) => node.source === source || JSON.stringify(node.source) === JSON.stringify(source)
|
|
19938
|
+
);
|
|
18900
19939
|
const visit = async (node) => {
|
|
18901
19940
|
if (node === null || typeof node !== "object") return;
|
|
18902
19941
|
if (seen.has(node)) return;
|
|
@@ -18915,7 +19954,8 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
18915
19954
|
physicalApps.forEach((appId) => fieldApps.add(appId));
|
|
18916
19955
|
}
|
|
18917
19956
|
const capability = await resolveSelectWhereCapability(select, tracedClient, cacheContext);
|
|
18918
|
-
|
|
19957
|
+
const relativeNode = relativeNodeFor(select);
|
|
19958
|
+
if (capability.capability === "UNSUPPORTED" && !relativeNode) {
|
|
18919
19959
|
throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
|
|
18920
19960
|
}
|
|
18921
19961
|
capabilities.set(select, capability);
|
|
@@ -18929,12 +19969,13 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
18929
19969
|
}
|
|
18930
19970
|
}
|
|
18931
19971
|
const hasUnmaterializedSource = [select.from, ...select.joins.map((join2) => join2.table)].some((table) => table.cteName !== null);
|
|
18932
|
-
if (hasCanonicalOrder(select) && !hasUnmaterializedSource) {
|
|
19972
|
+
if (hasCanonicalOrder(select) && !hasUnmaterializedSource && relativeNode?.allowed !== false) {
|
|
18933
19973
|
const mode = capability.capability === "EXACT_PUSHDOWN" ? resolveSelectMode(select) : "FULL_SCAN";
|
|
18934
19974
|
orderPlans.set(select, (select.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
|
|
18935
19975
|
stmt: select,
|
|
18936
19976
|
staticMode: mode,
|
|
18937
19977
|
whereCapability: capability.capability,
|
|
19978
|
+
whereReasons: capability.reasons,
|
|
18938
19979
|
orderSemantics: meta.semantics,
|
|
18939
19980
|
maxRecords,
|
|
18940
19981
|
hasKlike: whereHasKlike(select.where)
|
|
@@ -19010,7 +20051,7 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
19010
20051
|
}
|
|
19011
20052
|
}
|
|
19012
20053
|
const dml = node;
|
|
19013
|
-
if (dml.type !== "UPDATE" || !usesApplyParentResidualSelection(dml)) {
|
|
20054
|
+
if ((dml.type !== "UPDATE" || !usesApplyParentResidualSelection(dml)) && relativeNodeFor(dml)?.allowed !== false) {
|
|
19014
20055
|
await assertDmlWhereCapability(dml, tracedClient, cacheContext);
|
|
19015
20056
|
}
|
|
19016
20057
|
}
|
|
@@ -19020,23 +20061,32 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
19020
20061
|
if (typeof query === "object" && query !== null && query.type === "WITH" && canInlineSingleCte(query)) {
|
|
19021
20062
|
const inlined = buildInlinedQuery(query);
|
|
19022
20063
|
const capability = await resolveSelectWhereCapability(inlined, tracedClient, cacheContext);
|
|
19023
|
-
|
|
20064
|
+
const relativeNode = relativeNodeFor(inlined);
|
|
20065
|
+
if (capability.capability === "UNSUPPORTED" && !relativeNode) {
|
|
19024
20066
|
throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
|
|
19025
20067
|
}
|
|
19026
20068
|
capabilities.set(inlined, capability);
|
|
19027
|
-
if (hasCanonicalOrder(inlined)) {
|
|
20069
|
+
if (hasCanonicalOrder(inlined) && relativeNode?.allowed !== false) {
|
|
19028
20070
|
const meta = await buildOrderByMetaForSelect(inlined, tracedClient, cacheContext);
|
|
19029
20071
|
orderPlans.set(inlined, (inlined.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
|
|
19030
20072
|
stmt: inlined,
|
|
19031
20073
|
staticMode: capability.capability === "EXACT_PUSHDOWN" ? resolveSelectMode(inlined) : "FULL_SCAN",
|
|
19032
20074
|
whereCapability: capability.capability,
|
|
20075
|
+
whereReasons: capability.reasons,
|
|
19033
20076
|
orderSemantics: meta.semantics,
|
|
19034
20077
|
maxRecords,
|
|
19035
20078
|
hasKlike: whereHasKlike(inlined.where)
|
|
19036
20079
|
}));
|
|
19037
20080
|
}
|
|
19038
20081
|
}
|
|
19039
|
-
return {
|
|
20082
|
+
return {
|
|
20083
|
+
capabilities,
|
|
20084
|
+
orderPlans,
|
|
20085
|
+
fieldApps,
|
|
20086
|
+
processStatusApps,
|
|
20087
|
+
numberPrecisionApps,
|
|
20088
|
+
relativeDatePlan: sharedRelativeDatePlan
|
|
20089
|
+
};
|
|
19040
20090
|
}
|
|
19041
20091
|
function explainMetadataLines(analysis) {
|
|
19042
20092
|
return [
|
|
@@ -19045,6 +20095,158 @@ function explainMetadataLines(analysis) {
|
|
|
19045
20095
|
...[...analysis.numberPrecisionApps].sort((a, b) => a - b).map((appId) => ` metadata API: number precision APP${appId}`)
|
|
19046
20096
|
];
|
|
19047
20097
|
}
|
|
20098
|
+
function renderResidualOperator(op) {
|
|
20099
|
+
switch (op) {
|
|
20100
|
+
case "NOT_LIKE":
|
|
20101
|
+
return "NOT LIKE";
|
|
20102
|
+
case "NOT_KLIKE":
|
|
20103
|
+
return "NOT KLIKE";
|
|
20104
|
+
case "NOT_IN":
|
|
20105
|
+
return "NOT IN";
|
|
20106
|
+
case "LIKE":
|
|
20107
|
+
case "KLIKE":
|
|
20108
|
+
case "IN":
|
|
20109
|
+
case "=":
|
|
20110
|
+
case "!=":
|
|
20111
|
+
case "<>":
|
|
20112
|
+
case ">":
|
|
20113
|
+
case "<":
|
|
20114
|
+
case ">=":
|
|
20115
|
+
case "<=":
|
|
20116
|
+
return op;
|
|
20117
|
+
default:
|
|
20118
|
+
return "<op>";
|
|
20119
|
+
}
|
|
20120
|
+
}
|
|
20121
|
+
function relativeReasonOperator(op) {
|
|
20122
|
+
return op === "<>" ? "!=" : op;
|
|
20123
|
+
}
|
|
20124
|
+
function renderResidualValue(node) {
|
|
20125
|
+
if (node === null || typeof node !== "object") return "<expr>";
|
|
20126
|
+
const value = node;
|
|
20127
|
+
switch (value["type"]) {
|
|
20128
|
+
case "FIELD":
|
|
20129
|
+
return typeof value["field"] === "string" ? `${typeof value["tableAlias"] === "string" ? `${value["tableAlias"]}.` : ""}${value["field"]}` : "<expr>";
|
|
20130
|
+
case "FIELD_REF":
|
|
20131
|
+
return typeof value["field"] === "string" ? value["field"] : "<expr>";
|
|
20132
|
+
case "NUMBER":
|
|
20133
|
+
return typeof value["raw"] === "string" ? value["raw"] : typeof value["value"] === "number" ? String(value["value"]) : "<expr>";
|
|
20134
|
+
case "STRING":
|
|
20135
|
+
return typeof value["value"] === "string" ? `'${value["value"].replace(/'/g, "''")}'` : "<expr>";
|
|
20136
|
+
case "VARIABLE":
|
|
20137
|
+
return typeof value["name"] === "string" ? `@${value["name"]}` : "<expr>";
|
|
20138
|
+
case "VARIABLE_IN_LIST":
|
|
20139
|
+
return typeof value["name"] === "string" ? `@${value["name"]}` : "<expr>";
|
|
20140
|
+
case "STRING_FUNC": {
|
|
20141
|
+
if (typeof value["func"] !== "string" || !Array.isArray(value["args"])) return "<expr>";
|
|
20142
|
+
return `${value["func"]}(${value["args"].map(renderResidualValue).join(", ")})`;
|
|
20143
|
+
}
|
|
20144
|
+
case "FUNC_FIELD":
|
|
20145
|
+
case "ARITH_FIELD":
|
|
20146
|
+
case "CASE_FIELD":
|
|
20147
|
+
case "ARITH_VALUE":
|
|
20148
|
+
case "CASE_VALUE":
|
|
20149
|
+
return renderResidualValue(value["expr"]);
|
|
20150
|
+
case "GROUPING_FIELD":
|
|
20151
|
+
return `GROUPING(${renderResidualValue(value["ref"])})`;
|
|
20152
|
+
case "GROUPING_REF":
|
|
20153
|
+
return renderResidualValue(value["field"]);
|
|
20154
|
+
case "ARITH":
|
|
20155
|
+
case "SCALAR_ARITH":
|
|
20156
|
+
case "CONCAT_OP":
|
|
20157
|
+
return `(${renderResidualValue(value["left"])} ${typeof value["op"] === "string" ? value["op"] : value["type"] === "CONCAT_OP" ? "||" : "<op>"} ${renderResidualValue(value["right"])})`;
|
|
20158
|
+
case "KINTONE_FUNC":
|
|
20159
|
+
return typeof value["name"] === "string" ? `${value["name"]}(...)` : "<expr>";
|
|
20160
|
+
case "IN_LIST":
|
|
20161
|
+
return Array.isArray(value["values"]) ? `(${value["values"].map(renderResidualValue).join(", ")})` : "<expr>";
|
|
20162
|
+
case "ARRAY":
|
|
20163
|
+
return Array.isArray(value["elements"]) ? `[${value["elements"].map(renderResidualValue).join(", ")}]` : "<expr>";
|
|
20164
|
+
case "CASE":
|
|
20165
|
+
case "CASE_WHEN":
|
|
20166
|
+
return "CASE ... END";
|
|
20167
|
+
default:
|
|
20168
|
+
return "<expr>";
|
|
20169
|
+
}
|
|
20170
|
+
}
|
|
20171
|
+
function renderRelativeDateResidualWhere(where) {
|
|
20172
|
+
try {
|
|
20173
|
+
const node = where;
|
|
20174
|
+
switch (node["type"]) {
|
|
20175
|
+
case "BINARY":
|
|
20176
|
+
return `${renderResidualValue(node["left"])} ${renderResidualOperator(node["op"])} ${renderResidualValue(node["right"])}`;
|
|
20177
|
+
case "NULL_CHECK":
|
|
20178
|
+
return `${renderResidualValue(node["field"])} IS ${node["not"] === true ? "NOT " : ""}NULL`;
|
|
20179
|
+
case "LOGICAL":
|
|
20180
|
+
return `(${renderRelativeDateResidualWhere(node["left"])} ${node["op"] === "AND" || node["op"] === "OR" ? node["op"] : "<op>"} ${renderRelativeDateResidualWhere(node["right"])})`;
|
|
20181
|
+
case "NOT":
|
|
20182
|
+
return `NOT (${renderRelativeDateResidualWhere(node["expr"])})`;
|
|
20183
|
+
case "GROUP":
|
|
20184
|
+
return `(${renderRelativeDateResidualWhere(node["expr"])})`;
|
|
20185
|
+
case "BOOLEAN":
|
|
20186
|
+
return node["value"] === true ? "TRUE" : node["value"] === false ? "FALSE" : "<expr>";
|
|
20187
|
+
default:
|
|
20188
|
+
return "<expr>";
|
|
20189
|
+
}
|
|
20190
|
+
} catch {
|
|
20191
|
+
return "<expr>";
|
|
20192
|
+
}
|
|
20193
|
+
}
|
|
20194
|
+
function relativeDateExplainLines(plan) {
|
|
20195
|
+
if (!plan.hasRelativeDate) return [];
|
|
20196
|
+
if (!plan.allowed && plan.rejection) {
|
|
20197
|
+
return [
|
|
20198
|
+
` relative date function: ${plan.rejection.functionName}`,
|
|
20199
|
+
" plan status: rejected",
|
|
20200
|
+
` reason: ${plan.rejection.reasonCodes.join(", ")}`,
|
|
20201
|
+
" client evaluation: forbidden",
|
|
20202
|
+
" records/cursor/mutation API during EXPLAIN: none"
|
|
20203
|
+
];
|
|
20204
|
+
}
|
|
20205
|
+
const lines = [];
|
|
20206
|
+
for (const node of plan.nodes) {
|
|
20207
|
+
const prefilterPlan = node.prefilterPlan;
|
|
20208
|
+
if (node.allowed && prefilterPlan?.prefilterWhere && prefilterPlan.residualWhere) {
|
|
20209
|
+
for (const leaf of prefilterPlan.exactRelativeLeaves) {
|
|
20210
|
+
const functionName = leaf.right.type === "KINTONE_FUNC" ? leaf.right.name : "(unknown)";
|
|
20211
|
+
const field = leaf.left.type === "FIELD" ? leaf.left.field : void 0;
|
|
20212
|
+
const operator = relativeReasonOperator(leaf.op);
|
|
20213
|
+
const detail = node.capability?.reasons.find(
|
|
20214
|
+
(reason) => reason.functionName === functionName && (field === void 0 || reason.field === field) && reason.operator === operator
|
|
20215
|
+
);
|
|
20216
|
+
lines.push(
|
|
20217
|
+
` relative date function: ${functionName}`,
|
|
20218
|
+
" relative date evaluation: kintone server exact prefilter",
|
|
20219
|
+
` field: ${detail?.field ?? field ?? "(unknown)"} (${detail?.fieldType ?? "unknown"})`,
|
|
20220
|
+
` operator: ${detail?.operator ?? operator}`
|
|
20221
|
+
);
|
|
20222
|
+
}
|
|
20223
|
+
const serverPrefilter = whereToKintone(prefilterPlan.prefilterWhere);
|
|
20224
|
+
lines.push(
|
|
20225
|
+
" where capability: SUPERSET_PREFILTER",
|
|
20226
|
+
` server prefilter: ${serverPrefilter}`,
|
|
20227
|
+
` client residual: ${renderRelativeDateResidualWhere(prefilterPlan.residualWhere)}`,
|
|
20228
|
+
" relative date client evaluations: 0",
|
|
20229
|
+
` kintone query: ${serverPrefilter}`
|
|
20230
|
+
);
|
|
20231
|
+
continue;
|
|
20232
|
+
}
|
|
20233
|
+
for (const functionName of node.functionNames) {
|
|
20234
|
+
const detail = node.capability?.reasons.find(
|
|
20235
|
+
(reason) => reason.functionName === functionName
|
|
20236
|
+
);
|
|
20237
|
+
lines.push(
|
|
20238
|
+
` relative date function: ${functionName}`,
|
|
20239
|
+
" evaluation: kintone server",
|
|
20240
|
+
` field: ${detail?.field ?? "(unknown)"} (${detail?.fieldType ?? "unknown"})`,
|
|
20241
|
+
` operator: ${detail?.operator ?? "(unknown)"}`,
|
|
20242
|
+
` where capability: ${node.capability?.capability ?? "(unknown)"}`,
|
|
20243
|
+
" client evaluation: forbidden",
|
|
20244
|
+
` kintone query: ${node.restQuery || "(\u306A\u3057)"}`
|
|
20245
|
+
);
|
|
20246
|
+
}
|
|
20247
|
+
}
|
|
20248
|
+
return lines;
|
|
20249
|
+
}
|
|
19048
20250
|
async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords = 1e4, cursorMaxActive = 2, enableImport = false, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS) {
|
|
19049
20251
|
const statements = parseSqlBatch(sql, enableImport);
|
|
19050
20252
|
const analysis = analyzeBatch(statements);
|
|
@@ -19055,15 +20257,25 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
|
|
|
19055
20257
|
const stmt = statements[i];
|
|
19056
20258
|
const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveBatchVariableReferences(stmt.expr, variables) } : stmt : resolveBatchVariableReferences(stmt, variables);
|
|
19057
20259
|
validateKlikeStatement(planStmt);
|
|
19058
|
-
const
|
|
19059
|
-
const
|
|
20260
|
+
const relativeDatePlan = await resolveRelativeDateExecutionPlan(planStmt, client, cacheContext);
|
|
20261
|
+
const whereAnalysis = await buildExplainWhereAnalysis(
|
|
19060
20262
|
planStmt,
|
|
19061
|
-
|
|
19062
|
-
|
|
19063
|
-
|
|
19064
|
-
|
|
19065
|
-
|
|
19066
|
-
)
|
|
20263
|
+
client,
|
|
20264
|
+
cacheContext,
|
|
20265
|
+
maxRecords,
|
|
20266
|
+
relativeDatePlan
|
|
20267
|
+
);
|
|
20268
|
+
const statementPlan = relativeDatePlan.hasRelativeDate && !relativeDatePlan.allowed ? relativeDateExplainLines(relativeDatePlan) : [
|
|
20269
|
+
...relativeDateExplainLines(relativeDatePlan),
|
|
20270
|
+
...addCursorConcurrency(buildBatchStatementPlan(
|
|
20271
|
+
planStmt,
|
|
20272
|
+
analysis.statements[i],
|
|
20273
|
+
whereAnalysis.capabilities,
|
|
20274
|
+
whereAnalysis.orderPlans,
|
|
20275
|
+
dmlMaxRows,
|
|
20276
|
+
dmlMaxSubtableRows
|
|
20277
|
+
), cursorMaxActive)
|
|
20278
|
+
];
|
|
19067
20279
|
const metadataPlan = explainMetadataLines(whereAnalysis);
|
|
19068
20280
|
plans.push({
|
|
19069
20281
|
index: i,
|
|
@@ -19172,10 +20384,19 @@ function buildPlanForBatchQuery(query, info, capabilities, orderPlans) {
|
|
|
19172
20384
|
lines.push(" note: \u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u3078\u306E WHERE \u30D7\u30C3\u30B7\u30E5\u30C0\u30A6\u30F3\u306F\u884C\u308F\u308C\u306A\u3044");
|
|
19173
20385
|
return lines;
|
|
19174
20386
|
}
|
|
19175
|
-
async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxActive, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS) {
|
|
19176
|
-
const
|
|
19177
|
-
const
|
|
20387
|
+
async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxActive, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, relativeDatePlan) {
|
|
20388
|
+
const sharedPlan = relativeDatePlan ?? await resolveRelativeDateExecutionPlan(stmt.query, client, cacheContext);
|
|
20389
|
+
const analysis = await buildExplainWhereAnalysis(
|
|
20390
|
+
stmt.query,
|
|
20391
|
+
client,
|
|
20392
|
+
cacheContext,
|
|
20393
|
+
maxRecords,
|
|
20394
|
+
sharedPlan
|
|
20395
|
+
);
|
|
20396
|
+
const relativeLines = relativeDateExplainLines(sharedPlan);
|
|
20397
|
+
const lines = sharedPlan.hasRelativeDate && !sharedPlan.allowed ? [...explainMetadataLines(analysis), ...relativeLines] : [
|
|
19178
20398
|
...explainMetadataLines(analysis),
|
|
20399
|
+
...relativeLines,
|
|
19179
20400
|
...addCursorConcurrency(
|
|
19180
20401
|
buildExplainPlan(
|
|
19181
20402
|
stmt.query,
|