@rex0220/kintone-sql-tools 3.19.0 → 3.20.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 +783 -47
- 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 +83 -19
- package/dist-engine/meta/esm.json +83 -19
- package/dist-engine/meta/umd.json +83 -19
- package/dist-mcp/ksql-mcp.js +822 -53
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -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) {
|
|
@@ -11861,6 +12090,13 @@ function deepClone(value, seen = /* @__PURE__ */ new Map()) {
|
|
|
11861
12090
|
|
|
11862
12091
|
// src/core/optimization/whereCapability.ts
|
|
11863
12092
|
var RANGE_AND_EQUALITY = ["=", "!=", ">", "<", ">=", "<="];
|
|
12093
|
+
var RELATIVE_DATE_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
12094
|
+
"DATE",
|
|
12095
|
+
"DATETIME",
|
|
12096
|
+
"CREATED_TIME",
|
|
12097
|
+
"UPDATED_TIME"
|
|
12098
|
+
]);
|
|
12099
|
+
var RELATIVE_DATE_OPERATORS = new Set(RANGE_AND_EQUALITY);
|
|
11864
12100
|
var EQUALITY_IN = ["=", "!=", "in", "not in"];
|
|
11865
12101
|
var NATIVE_OPERATORS = /* @__PURE__ */ new Map([
|
|
11866
12102
|
["RECORD_NUMBER", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
|
|
@@ -11936,7 +12172,7 @@ function classifyNode(where, resolveField2) {
|
|
|
11936
12172
|
case "BOOLEAN":
|
|
11937
12173
|
return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
|
|
11938
12174
|
case "BINARY":
|
|
11939
|
-
return classifyBinary(where.op, where.left, where.right
|
|
12175
|
+
return classifyBinary(where.op, where.left, where.right, resolveField2);
|
|
11940
12176
|
case "NULL_CHECK":
|
|
11941
12177
|
if (where.field.type !== "FIELD") return localExpression();
|
|
11942
12178
|
return classifyLocalOnlyField(where.field, where.not ? "IS NOT NULL" : "IS NULL", resolveField2);
|
|
@@ -11946,7 +12182,14 @@ function classifyNode(where, resolveField2) {
|
|
|
11946
12182
|
return classifyNode(where.expr, resolveField2);
|
|
11947
12183
|
case "NOT": {
|
|
11948
12184
|
const inner = classifyNode(where.expr, resolveField2);
|
|
11949
|
-
|
|
12185
|
+
if (inner.capability !== "SUPERSET_PREFILTER") return inner;
|
|
12186
|
+
if (!hasRelativeDateReason(inner.reasons)) {
|
|
12187
|
+
return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
|
|
12188
|
+
}
|
|
12189
|
+
return requireExactRelativeDatePushdown({
|
|
12190
|
+
capability: "LOCAL_ONLY",
|
|
12191
|
+
reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }, ...inner.reasons]
|
|
12192
|
+
});
|
|
11950
12193
|
}
|
|
11951
12194
|
case "LOGICAL": {
|
|
11952
12195
|
const left = classifyNode(where.left, resolveField2);
|
|
@@ -11955,7 +12198,10 @@ function classifyNode(where, resolveField2) {
|
|
|
11955
12198
|
}
|
|
11956
12199
|
}
|
|
11957
12200
|
}
|
|
11958
|
-
function classifyBinary(op, left,
|
|
12201
|
+
function classifyBinary(op, left, right, resolveField2) {
|
|
12202
|
+
if (right.type === "KINTONE_FUNC" && isRelativeDateFunctionName(right.name)) {
|
|
12203
|
+
return classifyRelativeDateBinary(op, left, right, resolveField2);
|
|
12204
|
+
}
|
|
11959
12205
|
if (left.type !== "FIELD") return localExpression();
|
|
11960
12206
|
const semantics = resolveField2(left);
|
|
11961
12207
|
if (!semantics) {
|
|
@@ -11966,7 +12212,7 @@ function classifyBinary(op, left, rightType, resolveField2) {
|
|
|
11966
12212
|
}
|
|
11967
12213
|
const nativeOp = normalizeOperator(op);
|
|
11968
12214
|
const native = nativeWhereOperatorsForType(semantics.fieldType);
|
|
11969
|
-
const rightCanPush =
|
|
12215
|
+
const rightCanPush = right.type === "STRING" || right.type === "NUMBER" || right.type === "IN_LIST" || isLegacyKintoneFunction(right);
|
|
11970
12216
|
const structureAllows = !semantics.requiresCollectionOperators || nativeOp !== "=" && nativeOp !== "!=";
|
|
11971
12217
|
const sqlLikeIsResidual = op === "LIKE" || op === "NOT_LIKE";
|
|
11972
12218
|
if (rightCanPush && structureAllows && native.has(nativeOp) && !sqlLikeIsResidual) {
|
|
@@ -11990,6 +12236,92 @@ function classifyBinary(op, left, rightType, resolveField2) {
|
|
|
11990
12236
|
}]
|
|
11991
12237
|
};
|
|
11992
12238
|
}
|
|
12239
|
+
function isLegacyKintoneFunction(value) {
|
|
12240
|
+
return value.type === "KINTONE_FUNC" && (value.name === "TODAY" || value.name === "NOW" || value.name === "LOGINUSER");
|
|
12241
|
+
}
|
|
12242
|
+
function classifyRelativeDateBinary(op, left, right, resolveField2) {
|
|
12243
|
+
const operator = normalizeOperator(op);
|
|
12244
|
+
const functionName = right.name;
|
|
12245
|
+
if (left.type !== "FIELD") {
|
|
12246
|
+
return relativeDateUnsupported(
|
|
12247
|
+
"WHERE_RELATIVE_DATE_CONTEXT_UNSUPPORTED",
|
|
12248
|
+
functionName,
|
|
12249
|
+
void 0,
|
|
12250
|
+
void 0,
|
|
12251
|
+
operator
|
|
12252
|
+
);
|
|
12253
|
+
}
|
|
12254
|
+
const semantics = resolveField2(left);
|
|
12255
|
+
if (!semantics) {
|
|
12256
|
+
return relativeDateUnsupported(
|
|
12257
|
+
"WHERE_RELATIVE_DATE_FIELD_TYPE_UNSUPPORTED",
|
|
12258
|
+
functionName,
|
|
12259
|
+
left.field,
|
|
12260
|
+
void 0,
|
|
12261
|
+
operator
|
|
12262
|
+
);
|
|
12263
|
+
}
|
|
12264
|
+
if (!hasValidRelativeDateArguments(right)) {
|
|
12265
|
+
return relativeDateUnsupported(
|
|
12266
|
+
"WHERE_RELATIVE_DATE_ARGUMENT_INVALID",
|
|
12267
|
+
functionName,
|
|
12268
|
+
left.field,
|
|
12269
|
+
semantics.fieldType,
|
|
12270
|
+
operator
|
|
12271
|
+
);
|
|
12272
|
+
}
|
|
12273
|
+
if (!RELATIVE_DATE_OPERATORS.has(operator)) {
|
|
12274
|
+
return relativeDateUnsupported(
|
|
12275
|
+
"WHERE_RELATIVE_DATE_OPERATOR_UNSUPPORTED",
|
|
12276
|
+
functionName,
|
|
12277
|
+
left.field,
|
|
12278
|
+
semantics.fieldType,
|
|
12279
|
+
operator
|
|
12280
|
+
);
|
|
12281
|
+
}
|
|
12282
|
+
if (!RELATIVE_DATE_FIELD_TYPES.has(semantics.fieldType) || semantics.inSubtable || semantics.requiresCollectionOperators) {
|
|
12283
|
+
return relativeDateUnsupported(
|
|
12284
|
+
"WHERE_RELATIVE_DATE_FIELD_TYPE_UNSUPPORTED",
|
|
12285
|
+
functionName,
|
|
12286
|
+
left.field,
|
|
12287
|
+
semantics.fieldType,
|
|
12288
|
+
operator
|
|
12289
|
+
);
|
|
12290
|
+
}
|
|
12291
|
+
return {
|
|
12292
|
+
capability: "EXACT_PUSHDOWN",
|
|
12293
|
+
reasons: [{
|
|
12294
|
+
code: "WHERE_EXACT",
|
|
12295
|
+
functionName,
|
|
12296
|
+
field: left.field,
|
|
12297
|
+
fieldType: semantics.fieldType,
|
|
12298
|
+
operator
|
|
12299
|
+
}]
|
|
12300
|
+
};
|
|
12301
|
+
}
|
|
12302
|
+
function hasValidRelativeDateArguments(value) {
|
|
12303
|
+
if (!("args" in value) || !value.args) return false;
|
|
12304
|
+
switch (value.name) {
|
|
12305
|
+
case "YESTERDAY":
|
|
12306
|
+
case "TOMORROW":
|
|
12307
|
+
case "THIS_YEAR":
|
|
12308
|
+
case "LAST_YEAR":
|
|
12309
|
+
case "NEXT_YEAR":
|
|
12310
|
+
return value.args.kind === "NONE";
|
|
12311
|
+
case "FROM_TODAY":
|
|
12312
|
+
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");
|
|
12313
|
+
case "THIS_WEEK":
|
|
12314
|
+
case "LAST_WEEK":
|
|
12315
|
+
case "NEXT_WEEK":
|
|
12316
|
+
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");
|
|
12317
|
+
case "THIS_MONTH":
|
|
12318
|
+
case "LAST_MONTH":
|
|
12319
|
+
case "NEXT_MONTH":
|
|
12320
|
+
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);
|
|
12321
|
+
default:
|
|
12322
|
+
return false;
|
|
12323
|
+
}
|
|
12324
|
+
}
|
|
11993
12325
|
function classifyLocalOnlyField(field, operator, resolveField2) {
|
|
11994
12326
|
const semantics = resolveField2(field);
|
|
11995
12327
|
if (!semantics) return unsupported2("WHERE_FIELD_UNRESOLVED", field.field, void 0, operator);
|
|
@@ -12027,18 +12359,18 @@ function normalizeOperator(op) {
|
|
|
12027
12359
|
function combineLogical(op, left, right) {
|
|
12028
12360
|
const reasons = [...left.reasons, ...right.reasons];
|
|
12029
12361
|
if (left.capability === "UNSUPPORTED" || right.capability === "UNSUPPORTED") {
|
|
12030
|
-
return { capability: "UNSUPPORTED", reasons };
|
|
12362
|
+
return requireExactRelativeDatePushdown({ capability: "UNSUPPORTED", reasons });
|
|
12031
12363
|
}
|
|
12032
12364
|
if (left.capability === "EXACT_PUSHDOWN" && right.capability === "EXACT_PUSHDOWN") {
|
|
12033
12365
|
return { capability: "EXACT_PUSHDOWN", reasons };
|
|
12034
12366
|
}
|
|
12035
12367
|
if (op === "AND" && (left.capability === "EXACT_PUSHDOWN" || right.capability === "EXACT_PUSHDOWN" || left.capability === "SUPERSET_PREFILTER" || right.capability === "SUPERSET_PREFILTER")) {
|
|
12036
|
-
return {
|
|
12368
|
+
return requireExactRelativeDatePushdown({
|
|
12037
12369
|
capability: "SUPERSET_PREFILTER",
|
|
12038
12370
|
reasons: [{ code: "WHERE_SUPERSET_PREFILTER" }, ...reasons]
|
|
12039
|
-
};
|
|
12371
|
+
});
|
|
12040
12372
|
}
|
|
12041
|
-
return { capability: "LOCAL_ONLY", reasons };
|
|
12373
|
+
return requireExactRelativeDatePushdown({ capability: "LOCAL_ONLY", reasons });
|
|
12042
12374
|
}
|
|
12043
12375
|
function localExpression() {
|
|
12044
12376
|
return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
|
|
@@ -12046,6 +12378,317 @@ function localExpression() {
|
|
|
12046
12378
|
function unsupported2(code, field, fieldType, operator) {
|
|
12047
12379
|
return { capability: "UNSUPPORTED", reasons: [{ code, field, fieldType, operator }] };
|
|
12048
12380
|
}
|
|
12381
|
+
function relativeDateUnsupported(code, functionName, field, fieldType, operator) {
|
|
12382
|
+
return requireExactRelativeDatePushdown({
|
|
12383
|
+
capability: "UNSUPPORTED",
|
|
12384
|
+
reasons: [{ code, functionName, field, fieldType, operator }]
|
|
12385
|
+
});
|
|
12386
|
+
}
|
|
12387
|
+
function hasRelativeDateReason(reasons) {
|
|
12388
|
+
return reasons.some((reason) => reason.functionName !== void 0);
|
|
12389
|
+
}
|
|
12390
|
+
function requireExactRelativeDatePushdown(result) {
|
|
12391
|
+
if (result.capability === "EXACT_PUSHDOWN" || !hasRelativeDateReason(result.reasons) || result.reasons.some((reason) => reason.code === "WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN")) {
|
|
12392
|
+
return result;
|
|
12393
|
+
}
|
|
12394
|
+
const relative = result.reasons.find((reason) => reason.functionName !== void 0);
|
|
12395
|
+
return {
|
|
12396
|
+
capability: result.capability,
|
|
12397
|
+
reasons: [
|
|
12398
|
+
...result.reasons,
|
|
12399
|
+
{
|
|
12400
|
+
code: "WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN",
|
|
12401
|
+
functionName: relative.functionName,
|
|
12402
|
+
field: relative.field,
|
|
12403
|
+
fieldType: relative.fieldType,
|
|
12404
|
+
operator: relative.operator
|
|
12405
|
+
}
|
|
12406
|
+
]
|
|
12407
|
+
};
|
|
12408
|
+
}
|
|
12409
|
+
|
|
12410
|
+
// src/core/optimization/relativeDatePushdownGuard.ts
|
|
12411
|
+
function relativeDateFunctionNamesInNode(node, stopAtNestedSelect) {
|
|
12412
|
+
const names = [];
|
|
12413
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12414
|
+
const visit = (valueNode) => {
|
|
12415
|
+
if (Array.isArray(valueNode)) {
|
|
12416
|
+
valueNode.forEach(visit);
|
|
12417
|
+
return;
|
|
12418
|
+
}
|
|
12419
|
+
if (valueNode === null || typeof valueNode !== "object") return;
|
|
12420
|
+
const value = valueNode;
|
|
12421
|
+
if (value["type"] === "KINTONE_FUNC" && typeof value["name"] === "string" && isRelativeDateFunctionName(value["name"]) && !seen.has(value["name"])) {
|
|
12422
|
+
seen.add(value["name"]);
|
|
12423
|
+
names.push(value["name"]);
|
|
12424
|
+
return;
|
|
12425
|
+
}
|
|
12426
|
+
if (stopAtNestedSelect && value["type"] === "SELECT") return;
|
|
12427
|
+
Object.values(value).forEach(visit);
|
|
12428
|
+
};
|
|
12429
|
+
visit(node);
|
|
12430
|
+
return names;
|
|
12431
|
+
}
|
|
12432
|
+
function relativeDateFunctionNamesInWhere(where) {
|
|
12433
|
+
return relativeDateFunctionNamesInNode(where, true);
|
|
12434
|
+
}
|
|
12435
|
+
function nestedSelects(node, root) {
|
|
12436
|
+
const found = [];
|
|
12437
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12438
|
+
const visit = (value) => {
|
|
12439
|
+
if (Array.isArray(value)) {
|
|
12440
|
+
value.forEach(visit);
|
|
12441
|
+
return;
|
|
12442
|
+
}
|
|
12443
|
+
if (value === null || typeof value !== "object" || seen.has(value)) return;
|
|
12444
|
+
seen.add(value);
|
|
12445
|
+
const object = value;
|
|
12446
|
+
if (object["type"] === "SELECT" && value !== root) {
|
|
12447
|
+
found.push(value);
|
|
12448
|
+
return;
|
|
12449
|
+
}
|
|
12450
|
+
Object.values(object).forEach(visit);
|
|
12451
|
+
};
|
|
12452
|
+
visit(node);
|
|
12453
|
+
return found;
|
|
12454
|
+
}
|
|
12455
|
+
function collectSelect(select, path, candidates, forceForbidden) {
|
|
12456
|
+
const functionNames = relativeDateFunctionNamesInWhere(select.where);
|
|
12457
|
+
if (functionNames.length > 0) {
|
|
12458
|
+
candidates.push({
|
|
12459
|
+
kind: forceForbidden ? "FORBIDDEN" : "SELECT",
|
|
12460
|
+
source: select,
|
|
12461
|
+
where: select.where,
|
|
12462
|
+
functionNames,
|
|
12463
|
+
path
|
|
12464
|
+
});
|
|
12465
|
+
}
|
|
12466
|
+
nestedSelects(select, select).forEach(
|
|
12467
|
+
(nested, index) => collectSelect(nested, `${path}.select-source[${index}]`, candidates, forceForbidden)
|
|
12468
|
+
);
|
|
12469
|
+
}
|
|
12470
|
+
function collectUnion(union, path, candidates, forceForbidden) {
|
|
12471
|
+
if (union.left.type === "UNION") collectUnion(union.left, `${path}.left`, candidates, forceForbidden);
|
|
12472
|
+
else collectSelect(union.left, `${path}.left`, candidates, forceForbidden);
|
|
12473
|
+
collectSelect(union.right, `${path}.right`, candidates, forceForbidden);
|
|
12474
|
+
}
|
|
12475
|
+
function collectWith(statement, path, candidates, inheritedForbidden) {
|
|
12476
|
+
if (!inheritedForbidden && canInlineSingleCte(statement)) {
|
|
12477
|
+
collectSelect(buildInlinedQuery(statement), `${path}.inlined`, candidates, false);
|
|
12478
|
+
return;
|
|
12479
|
+
}
|
|
12480
|
+
statement.ctes.forEach((cte, index) => {
|
|
12481
|
+
if (cte.query.type === "SELECT") {
|
|
12482
|
+
collectSelect(cte.query, `${path}.cte[${index}]`, candidates, true);
|
|
12483
|
+
} else if (cte.query.type === "UNION") {
|
|
12484
|
+
collectUnion(cte.query, `${path}.cte[${index}]`, candidates, true);
|
|
12485
|
+
}
|
|
12486
|
+
});
|
|
12487
|
+
if (statement.query.type === "SELECT") {
|
|
12488
|
+
collectSelect(statement.query, `${path}.main`, candidates, true);
|
|
12489
|
+
} else {
|
|
12490
|
+
collectUnion(statement.query, `${path}.main`, candidates, true);
|
|
12491
|
+
}
|
|
12492
|
+
}
|
|
12493
|
+
function collectStatement(statement, path, candidates, forceForbidden = false) {
|
|
12494
|
+
switch (statement.type) {
|
|
12495
|
+
case "SELECT":
|
|
12496
|
+
collectSelect(statement, path, candidates, forceForbidden);
|
|
12497
|
+
return;
|
|
12498
|
+
case "UNION":
|
|
12499
|
+
collectUnion(statement, path, candidates, forceForbidden);
|
|
12500
|
+
return;
|
|
12501
|
+
case "WITH":
|
|
12502
|
+
collectWith(statement, path, candidates, forceForbidden);
|
|
12503
|
+
return;
|
|
12504
|
+
case "CREATE_TEMP_TABLE":
|
|
12505
|
+
if (statement.query.type === "WITH") collectWith(statement.query, `${path}.query`, candidates, true);
|
|
12506
|
+
else if (statement.query.type === "UNION") collectUnion(statement.query, `${path}.query`, candidates, true);
|
|
12507
|
+
else collectSelect(statement.query, `${path}.query`, candidates, true);
|
|
12508
|
+
return;
|
|
12509
|
+
case "EXPLAIN":
|
|
12510
|
+
collectStatement(statement.query, `${path}.query`, candidates, forceForbidden);
|
|
12511
|
+
return;
|
|
12512
|
+
case "VALIDATE":
|
|
12513
|
+
case "REORDER": {
|
|
12514
|
+
const functionNames = relativeDateFunctionNamesInWhere(statement.where);
|
|
12515
|
+
if (functionNames.length > 0) {
|
|
12516
|
+
candidates.push({
|
|
12517
|
+
kind: "FORBIDDEN",
|
|
12518
|
+
source: statement,
|
|
12519
|
+
where: statement.where,
|
|
12520
|
+
functionNames,
|
|
12521
|
+
path
|
|
12522
|
+
});
|
|
12523
|
+
}
|
|
12524
|
+
nestedSelects(statement, statement).forEach(
|
|
12525
|
+
(select, index) => collectSelect(select, `${path}.select-source[${index}]`, candidates, true)
|
|
12526
|
+
);
|
|
12527
|
+
return;
|
|
12528
|
+
}
|
|
12529
|
+
case "UPDATE":
|
|
12530
|
+
case "DELETE": {
|
|
12531
|
+
const functionNames = relativeDateFunctionNamesInWhere(statement.where);
|
|
12532
|
+
if (functionNames.length > 0) {
|
|
12533
|
+
const forbidden = forceForbidden || Boolean(statement.subtableCode) || statement.type === "UPDATE" && (statement.from != null || Boolean(statement.applyBlocks?.length));
|
|
12534
|
+
candidates.push({
|
|
12535
|
+
kind: forbidden ? "FORBIDDEN" : "DML",
|
|
12536
|
+
source: statement,
|
|
12537
|
+
where: statement.where,
|
|
12538
|
+
functionNames,
|
|
12539
|
+
path
|
|
12540
|
+
});
|
|
12541
|
+
}
|
|
12542
|
+
if (statement.type === "UPDATE" && statement.applyBlocks?.length) {
|
|
12543
|
+
const applyFunctions = relativeDateFunctionNamesInNode(statement.applyBlocks, true);
|
|
12544
|
+
if (applyFunctions.length > 0) {
|
|
12545
|
+
candidates.push({
|
|
12546
|
+
kind: "FORBIDDEN",
|
|
12547
|
+
source: statement,
|
|
12548
|
+
where: null,
|
|
12549
|
+
functionNames: applyFunctions,
|
|
12550
|
+
path: `${path}.apply`
|
|
12551
|
+
});
|
|
12552
|
+
}
|
|
12553
|
+
}
|
|
12554
|
+
nestedSelects(statement, statement).forEach(
|
|
12555
|
+
(select, index) => collectSelect(select, `${path}.select-source[${index}]`, candidates, forceForbidden)
|
|
12556
|
+
);
|
|
12557
|
+
return;
|
|
12558
|
+
}
|
|
12559
|
+
default:
|
|
12560
|
+
nestedSelects(statement, statement).forEach(
|
|
12561
|
+
(select, index) => collectSelect(select, `${path}.select-source[${index}]`, candidates, forceForbidden)
|
|
12562
|
+
);
|
|
12563
|
+
}
|
|
12564
|
+
}
|
|
12565
|
+
function serializationContainsFunctions(query, names) {
|
|
12566
|
+
return names.every((name) => new RegExp(`\\b${name}\\s*\\(`).test(query));
|
|
12567
|
+
}
|
|
12568
|
+
function rejectedNode(candidate) {
|
|
12569
|
+
return {
|
|
12570
|
+
kind: candidate.kind,
|
|
12571
|
+
source: candidate.source,
|
|
12572
|
+
functionNames: candidate.functionNames,
|
|
12573
|
+
path: candidate.path,
|
|
12574
|
+
clientWhereEvaluation: true,
|
|
12575
|
+
allowed: false
|
|
12576
|
+
};
|
|
12577
|
+
}
|
|
12578
|
+
function relativeDateReasonCodes(capability) {
|
|
12579
|
+
const codes = (capability?.reasons ?? []).filter(
|
|
12580
|
+
(reason) => reason.functionName !== void 0
|
|
12581
|
+
).map((reason) => reason.code).filter((code) => code.startsWith("WHERE_RELATIVE_DATE_"));
|
|
12582
|
+
if (!codes.includes(WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN)) {
|
|
12583
|
+
codes.push(WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN);
|
|
12584
|
+
}
|
|
12585
|
+
return [...new Set(codes)];
|
|
12586
|
+
}
|
|
12587
|
+
function rejectionFor(candidate, capability) {
|
|
12588
|
+
const reasonCodes = relativeDateReasonCodes(capability);
|
|
12589
|
+
return {
|
|
12590
|
+
functionName: candidate.functionNames[0],
|
|
12591
|
+
path: candidate.path,
|
|
12592
|
+
code: reasonCodes.find(
|
|
12593
|
+
(code) => code !== WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN
|
|
12594
|
+
) ?? WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN,
|
|
12595
|
+
reasonCodes
|
|
12596
|
+
};
|
|
12597
|
+
}
|
|
12598
|
+
async function buildRelativeDatePushdownPlan(statement, resolver) {
|
|
12599
|
+
const candidates = [];
|
|
12600
|
+
collectStatement(statement, "statement", candidates);
|
|
12601
|
+
const nodes = [];
|
|
12602
|
+
for (const candidate of candidates) {
|
|
12603
|
+
if (candidate.kind === "FORBIDDEN") {
|
|
12604
|
+
const node2 = rejectedNode(candidate);
|
|
12605
|
+
nodes.push(node2);
|
|
12606
|
+
return {
|
|
12607
|
+
hasRelativeDate: true,
|
|
12608
|
+
nodes,
|
|
12609
|
+
allowed: false,
|
|
12610
|
+
rejection: rejectionFor(candidate)
|
|
12611
|
+
};
|
|
12612
|
+
}
|
|
12613
|
+
if (candidate.kind === "SELECT") {
|
|
12614
|
+
const select = candidate.source;
|
|
12615
|
+
const physicalTopLevel = select.from.cteName === null && !select.from.subtableCode && select.joins.length === 0;
|
|
12616
|
+
const selectMode = resolveSelectMode(select);
|
|
12617
|
+
const capability2 = await resolver.select(select);
|
|
12618
|
+
let restQuery2 = "";
|
|
12619
|
+
try {
|
|
12620
|
+
restQuery2 = candidate.where === null ? "" : whereToKintone(candidate.where);
|
|
12621
|
+
} catch {
|
|
12622
|
+
restQuery2 = "";
|
|
12623
|
+
}
|
|
12624
|
+
const allowed2 = physicalTopLevel && selectMode === "SIMPLE" && (select.orderBy.length === 0 || select.orderMode === "KINTONE_NATIVE") && capability2.capability === "EXACT_PUSHDOWN" && serializationContainsFunctions(restQuery2, candidate.functionNames);
|
|
12625
|
+
const node2 = {
|
|
12626
|
+
kind: candidate.kind,
|
|
12627
|
+
source: candidate.source,
|
|
12628
|
+
functionNames: candidate.functionNames,
|
|
12629
|
+
path: candidate.path,
|
|
12630
|
+
selectMode,
|
|
12631
|
+
capability: capability2,
|
|
12632
|
+
restQuery: restQuery2,
|
|
12633
|
+
clientWhereEvaluation: !allowed2,
|
|
12634
|
+
allowed: allowed2
|
|
12635
|
+
};
|
|
12636
|
+
nodes.push(node2);
|
|
12637
|
+
if (!allowed2) {
|
|
12638
|
+
return {
|
|
12639
|
+
hasRelativeDate: true,
|
|
12640
|
+
nodes,
|
|
12641
|
+
allowed: false,
|
|
12642
|
+
rejection: rejectionFor(candidate, capability2)
|
|
12643
|
+
};
|
|
12644
|
+
}
|
|
12645
|
+
continue;
|
|
12646
|
+
}
|
|
12647
|
+
const dml = candidate.source;
|
|
12648
|
+
const capability = await resolver.dml(dml);
|
|
12649
|
+
let restQuery = "";
|
|
12650
|
+
try {
|
|
12651
|
+
restQuery = candidate.where === null ? "" : whereToKintone(candidate.where);
|
|
12652
|
+
} catch {
|
|
12653
|
+
restQuery = "";
|
|
12654
|
+
}
|
|
12655
|
+
const allowed = capability.capability === "EXACT_PUSHDOWN" && serializationContainsFunctions(restQuery, candidate.functionNames);
|
|
12656
|
+
const node = {
|
|
12657
|
+
kind: candidate.kind,
|
|
12658
|
+
source: candidate.source,
|
|
12659
|
+
functionNames: candidate.functionNames,
|
|
12660
|
+
path: candidate.path,
|
|
12661
|
+
capability,
|
|
12662
|
+
restQuery,
|
|
12663
|
+
clientWhereEvaluation: !allowed,
|
|
12664
|
+
allowed
|
|
12665
|
+
};
|
|
12666
|
+
nodes.push(node);
|
|
12667
|
+
if (!allowed) {
|
|
12668
|
+
return {
|
|
12669
|
+
hasRelativeDate: true,
|
|
12670
|
+
nodes,
|
|
12671
|
+
allowed: false,
|
|
12672
|
+
rejection: rejectionFor(candidate, capability)
|
|
12673
|
+
};
|
|
12674
|
+
}
|
|
12675
|
+
}
|
|
12676
|
+
return {
|
|
12677
|
+
hasRelativeDate: candidates.length > 0,
|
|
12678
|
+
nodes,
|
|
12679
|
+
allowed: true
|
|
12680
|
+
};
|
|
12681
|
+
}
|
|
12682
|
+
function assertRelativeDatePushdownPlan(plan) {
|
|
12683
|
+
if (!plan.allowed && plan.rejection) {
|
|
12684
|
+
const details = plan.rejection.reasonCodes.filter(
|
|
12685
|
+
(code) => code !== WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN
|
|
12686
|
+
);
|
|
12687
|
+
throw new Error(
|
|
12688
|
+
`${plan.rejection.functionName}: ${WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN}${details.length > 0 ? ` (reason=${details.join(", ")})` : ""} (path=${plan.rejection.path})`
|
|
12689
|
+
);
|
|
12690
|
+
}
|
|
12691
|
+
}
|
|
12049
12692
|
|
|
12050
12693
|
// src/import/sourceLoader.ts
|
|
12051
12694
|
var IMPORT_MAX_BYTES = 10 * 1024 * 1024;
|
|
@@ -13102,7 +13745,20 @@ function attachSearchAbortWarning(result, collector) {
|
|
|
13102
13745
|
warnings.add(SEARCH_ABORTED_WARNING);
|
|
13103
13746
|
return { ...result, warnings: [...warnings] };
|
|
13104
13747
|
}
|
|
13748
|
+
async function assertRelativeDateExecutionPlan(stmt, client, cacheContext) {
|
|
13749
|
+
const plan = await resolveRelativeDateExecutionPlan(stmt, client, cacheContext);
|
|
13750
|
+
assertRelativeDatePushdownPlan(plan);
|
|
13751
|
+
return plan;
|
|
13752
|
+
}
|
|
13753
|
+
async function resolveRelativeDateExecutionPlan(stmt, client, cacheContext) {
|
|
13754
|
+
return buildRelativeDatePushdownPlan(stmt, {
|
|
13755
|
+
select: (select) => resolveSelectWhereCapability(select, client, cacheContext),
|
|
13756
|
+
dml: (dml) => resolveDmlWhereCapability(dml, client, cacheContext)
|
|
13757
|
+
});
|
|
13758
|
+
}
|
|
13105
13759
|
async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
13760
|
+
const relativeDatePlan = await resolveRelativeDateExecutionPlan(stmt, client, cacheContext);
|
|
13761
|
+
if (stmt.type !== "EXPLAIN") assertRelativeDatePushdownPlan(relativeDatePlan);
|
|
13106
13762
|
const unresolved = findVariableRef(stmt);
|
|
13107
13763
|
if (unresolved !== null && !isApplyParentKlikeStatement(stmt)) {
|
|
13108
13764
|
throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
|
|
@@ -13164,7 +13820,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
13164
13820
|
options.maxRecords ?? 1e4,
|
|
13165
13821
|
options.cursorMaxActive ?? 2,
|
|
13166
13822
|
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
|
|
13823
|
+
stmt.query.type === "UPDATE" && stmt.query.applyBlocks?.length ? resolveApplyGuardLimit(options.dmlMaxSubtableRows, "dmlMaxSubtableRows", DEFAULT_APPLY_MAX_SUBTABLE_ROWS) : DEFAULT_APPLY_MAX_SUBTABLE_ROWS,
|
|
13824
|
+
relativeDatePlan
|
|
13168
13825
|
);
|
|
13169
13826
|
// 一時テーブルはバッチスコープのため単文実行では拒否する(executeBatch を使う)
|
|
13170
13827
|
case "CREATE_TEMP_TABLE":
|
|
@@ -13548,6 +14205,7 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
13548
14205
|
if (stmt.type === "SET_VARIABLE") {
|
|
13549
14206
|
const resolvedStmt2 = resolveBatchVariableReferences(stmt, variables);
|
|
13550
14207
|
validateKlikeStatement(resolvedStmt2);
|
|
14208
|
+
await assertRelativeDateExecutionPlan(resolvedStmt2, client, cacheContext);
|
|
13551
14209
|
if (resolvedStmt2.expr.type === "ARRAY") {
|
|
13552
14210
|
variables.set(stmt.name, {
|
|
13553
14211
|
type: "array",
|
|
@@ -13604,6 +14262,7 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
13604
14262
|
assertApplyScope("phase15b", resolvedStmt);
|
|
13605
14263
|
assertApplyExecutionScope("phase15b", resolvedStmt);
|
|
13606
14264
|
validateKlikeStatement(resolvedStmt);
|
|
14265
|
+
await assertRelativeDateExecutionPlan(resolvedStmt, client, cacheContext);
|
|
13607
14266
|
if (resolvedStmt.type === "VALIDATE") {
|
|
13608
14267
|
const result = await executeExistingRecordValidationCore(
|
|
13609
14268
|
resolvedStmt,
|
|
@@ -14161,16 +14820,27 @@ function hasCanonicalOrder(stmt) {
|
|
|
14161
14820
|
(column) => column.type === "WINDOW_COL" && column.orderBy.length > 0
|
|
14162
14821
|
);
|
|
14163
14822
|
}
|
|
14164
|
-
async function
|
|
14165
|
-
if (stmt.subtableCode || stmt.type === "UPDATE" && stmt.from != null)
|
|
14823
|
+
async function resolveDmlWhereCapability(stmt, client, cacheContext) {
|
|
14824
|
+
if (stmt.subtableCode || stmt.type === "UPDATE" && stmt.from != null) {
|
|
14825
|
+
return {
|
|
14826
|
+
capability: "LOCAL_ONLY",
|
|
14827
|
+
reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }]
|
|
14828
|
+
};
|
|
14829
|
+
}
|
|
14166
14830
|
const fields = whereNeedsFieldMetadata(stmt.where) ? await getFieldsCached(stmt.appId, client, cacheContext) : [];
|
|
14167
14831
|
const byCode = new Map(fields.map((field) => [field.code, field]));
|
|
14168
|
-
if (stmt.where.type === "BOOLEAN" && stmt.where.value === false)
|
|
14169
|
-
|
|
14832
|
+
if (stmt.where.type === "BOOLEAN" && stmt.where.value === false) {
|
|
14833
|
+
return classifyWhereCapability(null, () => void 0);
|
|
14834
|
+
}
|
|
14835
|
+
return classifyWhereCapability(stmt.where, (field) => {
|
|
14170
14836
|
if (field.field === "$id") return resolveFieldSemantics({ fieldType: "__ID__" });
|
|
14171
14837
|
const info = byCode.get(field.field);
|
|
14172
14838
|
return info?.semantics ?? (info ? resolveFieldSemantics(info) : void 0);
|
|
14173
14839
|
});
|
|
14840
|
+
}
|
|
14841
|
+
async function assertDmlWhereCapability(stmt, client, cacheContext) {
|
|
14842
|
+
if (stmt.subtableCode || stmt.type === "UPDATE" && stmt.from != null) return;
|
|
14843
|
+
const result = await resolveDmlWhereCapability(stmt, client, cacheContext);
|
|
14174
14844
|
if (result.capability !== "EXACT_PUSHDOWN") {
|
|
14175
14845
|
throw new DmlConvertError(
|
|
14176
14846
|
`WHERE predicate cannot be represented by kintone REST (${formatWhereCapabilityFailure(result)})`
|
|
@@ -14199,6 +14869,7 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
|
|
|
14199
14869
|
stmt,
|
|
14200
14870
|
staticMode: mode,
|
|
14201
14871
|
whereCapability: whereCapability.capability,
|
|
14872
|
+
whereReasons: whereCapability.reasons,
|
|
14202
14873
|
orderSemantics: orderMeta.semantics,
|
|
14203
14874
|
maxRecords: options.maxRecords ?? 1e4,
|
|
14204
14875
|
hasKlike: whereHasKlike(stmt.where)
|
|
@@ -15394,6 +16065,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
15394
16065
|
stmt,
|
|
15395
16066
|
staticMode: "FULL_SCAN",
|
|
15396
16067
|
whereCapability: whereCapability.capability,
|
|
16068
|
+
whereReasons: whereCapability.reasons,
|
|
15397
16069
|
orderSemantics: orderMeta.semantics,
|
|
15398
16070
|
maxRecords,
|
|
15399
16071
|
hasKlike: whereHasKlike(stmt.where)
|
|
@@ -18875,7 +19547,7 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
|
|
|
18875
19547
|
}
|
|
18876
19548
|
var validateExplainInfo = /* @__PURE__ */ new WeakMap();
|
|
18877
19549
|
var applyParentExplainPlan = /* @__PURE__ */ new WeakMap();
|
|
18878
|
-
async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords = 1e4) {
|
|
19550
|
+
async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords = 1e4, relativeDatePlan) {
|
|
18879
19551
|
const fieldApps = /* @__PURE__ */ new Set();
|
|
18880
19552
|
const processStatusApps = /* @__PURE__ */ new Set();
|
|
18881
19553
|
const numberPrecisionApps = /* @__PURE__ */ new Set();
|
|
@@ -18897,6 +19569,10 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
18897
19569
|
const capabilities = /* @__PURE__ */ new Map();
|
|
18898
19570
|
const orderPlans = /* @__PURE__ */ new Map();
|
|
18899
19571
|
const seen = /* @__PURE__ */ new Set();
|
|
19572
|
+
const sharedRelativeDatePlan = relativeDatePlan ?? await resolveRelativeDateExecutionPlan(query, tracedClient, cacheContext);
|
|
19573
|
+
const relativeNodeFor = (source) => sharedRelativeDatePlan.nodes.find(
|
|
19574
|
+
(node) => node.source === source || JSON.stringify(node.source) === JSON.stringify(source)
|
|
19575
|
+
);
|
|
18900
19576
|
const visit = async (node) => {
|
|
18901
19577
|
if (node === null || typeof node !== "object") return;
|
|
18902
19578
|
if (seen.has(node)) return;
|
|
@@ -18915,7 +19591,8 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
18915
19591
|
physicalApps.forEach((appId) => fieldApps.add(appId));
|
|
18916
19592
|
}
|
|
18917
19593
|
const capability = await resolveSelectWhereCapability(select, tracedClient, cacheContext);
|
|
18918
|
-
|
|
19594
|
+
const relativeNode = relativeNodeFor(select);
|
|
19595
|
+
if (capability.capability === "UNSUPPORTED" && !relativeNode) {
|
|
18919
19596
|
throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
|
|
18920
19597
|
}
|
|
18921
19598
|
capabilities.set(select, capability);
|
|
@@ -18929,12 +19606,13 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
18929
19606
|
}
|
|
18930
19607
|
}
|
|
18931
19608
|
const hasUnmaterializedSource = [select.from, ...select.joins.map((join2) => join2.table)].some((table) => table.cteName !== null);
|
|
18932
|
-
if (hasCanonicalOrder(select) && !hasUnmaterializedSource) {
|
|
19609
|
+
if (hasCanonicalOrder(select) && !hasUnmaterializedSource && relativeNode?.allowed !== false) {
|
|
18933
19610
|
const mode = capability.capability === "EXACT_PUSHDOWN" ? resolveSelectMode(select) : "FULL_SCAN";
|
|
18934
19611
|
orderPlans.set(select, (select.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
|
|
18935
19612
|
stmt: select,
|
|
18936
19613
|
staticMode: mode,
|
|
18937
19614
|
whereCapability: capability.capability,
|
|
19615
|
+
whereReasons: capability.reasons,
|
|
18938
19616
|
orderSemantics: meta.semantics,
|
|
18939
19617
|
maxRecords,
|
|
18940
19618
|
hasKlike: whereHasKlike(select.where)
|
|
@@ -19010,7 +19688,7 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
19010
19688
|
}
|
|
19011
19689
|
}
|
|
19012
19690
|
const dml = node;
|
|
19013
|
-
if (dml.type !== "UPDATE" || !usesApplyParentResidualSelection(dml)) {
|
|
19691
|
+
if ((dml.type !== "UPDATE" || !usesApplyParentResidualSelection(dml)) && relativeNodeFor(dml)?.allowed !== false) {
|
|
19014
19692
|
await assertDmlWhereCapability(dml, tracedClient, cacheContext);
|
|
19015
19693
|
}
|
|
19016
19694
|
}
|
|
@@ -19020,23 +19698,32 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
19020
19698
|
if (typeof query === "object" && query !== null && query.type === "WITH" && canInlineSingleCte(query)) {
|
|
19021
19699
|
const inlined = buildInlinedQuery(query);
|
|
19022
19700
|
const capability = await resolveSelectWhereCapability(inlined, tracedClient, cacheContext);
|
|
19023
|
-
|
|
19701
|
+
const relativeNode = relativeNodeFor(inlined);
|
|
19702
|
+
if (capability.capability === "UNSUPPORTED" && !relativeNode) {
|
|
19024
19703
|
throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
|
|
19025
19704
|
}
|
|
19026
19705
|
capabilities.set(inlined, capability);
|
|
19027
|
-
if (hasCanonicalOrder(inlined)) {
|
|
19706
|
+
if (hasCanonicalOrder(inlined) && relativeNode?.allowed !== false) {
|
|
19028
19707
|
const meta = await buildOrderByMetaForSelect(inlined, tracedClient, cacheContext);
|
|
19029
19708
|
orderPlans.set(inlined, (inlined.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
|
|
19030
19709
|
stmt: inlined,
|
|
19031
19710
|
staticMode: capability.capability === "EXACT_PUSHDOWN" ? resolveSelectMode(inlined) : "FULL_SCAN",
|
|
19032
19711
|
whereCapability: capability.capability,
|
|
19712
|
+
whereReasons: capability.reasons,
|
|
19033
19713
|
orderSemantics: meta.semantics,
|
|
19034
19714
|
maxRecords,
|
|
19035
19715
|
hasKlike: whereHasKlike(inlined.where)
|
|
19036
19716
|
}));
|
|
19037
19717
|
}
|
|
19038
19718
|
}
|
|
19039
|
-
return {
|
|
19719
|
+
return {
|
|
19720
|
+
capabilities,
|
|
19721
|
+
orderPlans,
|
|
19722
|
+
fieldApps,
|
|
19723
|
+
processStatusApps,
|
|
19724
|
+
numberPrecisionApps,
|
|
19725
|
+
relativeDatePlan: sharedRelativeDatePlan
|
|
19726
|
+
};
|
|
19040
19727
|
}
|
|
19041
19728
|
function explainMetadataLines(analysis) {
|
|
19042
19729
|
return [
|
|
@@ -19045,6 +19732,36 @@ function explainMetadataLines(analysis) {
|
|
|
19045
19732
|
...[...analysis.numberPrecisionApps].sort((a, b) => a - b).map((appId) => ` metadata API: number precision APP${appId}`)
|
|
19046
19733
|
];
|
|
19047
19734
|
}
|
|
19735
|
+
function relativeDateExplainLines(plan) {
|
|
19736
|
+
if (!plan.hasRelativeDate) return [];
|
|
19737
|
+
if (!plan.allowed && plan.rejection) {
|
|
19738
|
+
return [
|
|
19739
|
+
` relative date function: ${plan.rejection.functionName}`,
|
|
19740
|
+
" plan status: rejected",
|
|
19741
|
+
` reason: ${plan.rejection.reasonCodes.join(", ")}`,
|
|
19742
|
+
" client evaluation: forbidden",
|
|
19743
|
+
" records/cursor/mutation API during EXPLAIN: none"
|
|
19744
|
+
];
|
|
19745
|
+
}
|
|
19746
|
+
const lines = [];
|
|
19747
|
+
for (const node of plan.nodes) {
|
|
19748
|
+
for (const functionName of node.functionNames) {
|
|
19749
|
+
const detail = node.capability?.reasons.find(
|
|
19750
|
+
(reason) => reason.functionName === functionName
|
|
19751
|
+
);
|
|
19752
|
+
lines.push(
|
|
19753
|
+
` relative date function: ${functionName}`,
|
|
19754
|
+
" evaluation: kintone server",
|
|
19755
|
+
` field: ${detail?.field ?? "(unknown)"} (${detail?.fieldType ?? "unknown"})`,
|
|
19756
|
+
` operator: ${detail?.operator ?? "(unknown)"}`,
|
|
19757
|
+
` where capability: ${node.capability?.capability ?? "(unknown)"}`,
|
|
19758
|
+
" client evaluation: forbidden",
|
|
19759
|
+
` kintone query: ${node.restQuery || "(\u306A\u3057)"}`
|
|
19760
|
+
);
|
|
19761
|
+
}
|
|
19762
|
+
}
|
|
19763
|
+
return lines;
|
|
19764
|
+
}
|
|
19048
19765
|
async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords = 1e4, cursorMaxActive = 2, enableImport = false, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS) {
|
|
19049
19766
|
const statements = parseSqlBatch(sql, enableImport);
|
|
19050
19767
|
const analysis = analyzeBatch(statements);
|
|
@@ -19055,15 +19772,25 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
|
|
|
19055
19772
|
const stmt = statements[i];
|
|
19056
19773
|
const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveBatchVariableReferences(stmt.expr, variables) } : stmt : resolveBatchVariableReferences(stmt, variables);
|
|
19057
19774
|
validateKlikeStatement(planStmt);
|
|
19058
|
-
const
|
|
19059
|
-
const
|
|
19775
|
+
const relativeDatePlan = await resolveRelativeDateExecutionPlan(planStmt, client, cacheContext);
|
|
19776
|
+
const whereAnalysis = await buildExplainWhereAnalysis(
|
|
19060
19777
|
planStmt,
|
|
19061
|
-
|
|
19062
|
-
|
|
19063
|
-
|
|
19064
|
-
|
|
19065
|
-
|
|
19066
|
-
)
|
|
19778
|
+
client,
|
|
19779
|
+
cacheContext,
|
|
19780
|
+
maxRecords,
|
|
19781
|
+
relativeDatePlan
|
|
19782
|
+
);
|
|
19783
|
+
const statementPlan = relativeDatePlan.hasRelativeDate && !relativeDatePlan.allowed ? relativeDateExplainLines(relativeDatePlan) : [
|
|
19784
|
+
...relativeDateExplainLines(relativeDatePlan),
|
|
19785
|
+
...addCursorConcurrency(buildBatchStatementPlan(
|
|
19786
|
+
planStmt,
|
|
19787
|
+
analysis.statements[i],
|
|
19788
|
+
whereAnalysis.capabilities,
|
|
19789
|
+
whereAnalysis.orderPlans,
|
|
19790
|
+
dmlMaxRows,
|
|
19791
|
+
dmlMaxSubtableRows
|
|
19792
|
+
), cursorMaxActive)
|
|
19793
|
+
];
|
|
19067
19794
|
const metadataPlan = explainMetadataLines(whereAnalysis);
|
|
19068
19795
|
plans.push({
|
|
19069
19796
|
index: i,
|
|
@@ -19172,10 +19899,19 @@ function buildPlanForBatchQuery(query, info, capabilities, orderPlans) {
|
|
|
19172
19899
|
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
19900
|
return lines;
|
|
19174
19901
|
}
|
|
19175
|
-
async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxActive, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS) {
|
|
19176
|
-
const
|
|
19177
|
-
const
|
|
19902
|
+
async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxActive, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, relativeDatePlan) {
|
|
19903
|
+
const sharedPlan = relativeDatePlan ?? await resolveRelativeDateExecutionPlan(stmt.query, client, cacheContext);
|
|
19904
|
+
const analysis = await buildExplainWhereAnalysis(
|
|
19905
|
+
stmt.query,
|
|
19906
|
+
client,
|
|
19907
|
+
cacheContext,
|
|
19908
|
+
maxRecords,
|
|
19909
|
+
sharedPlan
|
|
19910
|
+
);
|
|
19911
|
+
const relativeLines = relativeDateExplainLines(sharedPlan);
|
|
19912
|
+
const lines = sharedPlan.hasRelativeDate && !sharedPlan.allowed ? [...explainMetadataLines(analysis), ...relativeLines] : [
|
|
19178
19913
|
...explainMetadataLines(analysis),
|
|
19914
|
+
...relativeLines,
|
|
19179
19915
|
...addCursorConcurrency(
|
|
19180
19916
|
buildExplainPlan(
|
|
19181
19917
|
stmt.query,
|