@rex0220/kintone-sql-tools 2.1.2 → 2.3.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 +203 -109
- package/dist-mcp/ksql-mcp.js +204 -110
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
package/dist-cli/ksql.js
CHANGED
|
@@ -629,7 +629,22 @@ var Parser = class {
|
|
|
629
629
|
throw new ParseError("SET \u306E\u53F3\u8FBA\u3067 NULL \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\uFF08Phase 1a\uFF09", tok);
|
|
630
630
|
}
|
|
631
631
|
if (tok.kind === "(" /* LPAREN */ && this.peekAt(1).kind === "SELECT" /* SELECT */) {
|
|
632
|
-
|
|
632
|
+
this.advance();
|
|
633
|
+
const query = this.parseSelect();
|
|
634
|
+
this.expect(")" /* RPAREN */);
|
|
635
|
+
if (this.isArithOp(this.peek().kind)) {
|
|
636
|
+
throw new ParseError(
|
|
637
|
+
"\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u306E\u5F8C\u306B\u7B97\u8853\u6F14\u7B97\u5B50\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002\u30B5\u30D6\u30AF\u30A8\u30EA\u5185\u3067\u8A08\u7B97\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
638
|
+
this.peek()
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
const hasWildcard = query.columns.some(
|
|
642
|
+
(c) => c.type === "WILDCARD" || c.type === "PARENT_WILDCARD"
|
|
643
|
+
);
|
|
644
|
+
if (!hasWildcard && query.columns.length !== 1) {
|
|
645
|
+
throw new ParseError("scalar subquery in SET must return exactly 1 column.", tok);
|
|
646
|
+
}
|
|
647
|
+
return { type: "SCALAR_SUBQUERY", query };
|
|
633
648
|
}
|
|
634
649
|
if (tok.kind === "STRING" /* STRING */) {
|
|
635
650
|
this.advance();
|
|
@@ -2492,6 +2507,28 @@ function analyzeBatch(statements) {
|
|
|
2492
2507
|
};
|
|
2493
2508
|
}
|
|
2494
2509
|
|
|
2510
|
+
// src/core/scalarCompare.ts
|
|
2511
|
+
function compareScalarValues(op, leftStr, rightStr) {
|
|
2512
|
+
if (op === "=") return leftStr === rightStr;
|
|
2513
|
+
if (op === "!=" || op === "<>") return leftStr !== rightStr;
|
|
2514
|
+
const rightNum = Number(rightStr);
|
|
2515
|
+
if (leftStr === "" && rightStr !== "" && Number.isFinite(rightNum)) {
|
|
2516
|
+
return op === "<" || op === "<=";
|
|
2517
|
+
}
|
|
2518
|
+
const leftNum = Number(leftStr);
|
|
2519
|
+
const numeric = !Number.isNaN(leftNum) && !Number.isNaN(rightNum);
|
|
2520
|
+
switch (op) {
|
|
2521
|
+
case ">":
|
|
2522
|
+
return numeric ? leftNum > rightNum : leftStr > rightStr;
|
|
2523
|
+
case "<":
|
|
2524
|
+
return numeric ? leftNum < rightNum : leftStr < rightStr;
|
|
2525
|
+
case ">=":
|
|
2526
|
+
return numeric ? leftNum >= rightNum : leftStr >= rightStr;
|
|
2527
|
+
case "<=":
|
|
2528
|
+
return numeric ? leftNum <= rightNum : leftStr <= rightStr;
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2495
2532
|
// src/engine/pushDownNot.ts
|
|
2496
2533
|
function pushDownNot(expr) {
|
|
2497
2534
|
switch (expr.type) {
|
|
@@ -3466,24 +3503,7 @@ function evalOp(op, leftStr, right, row) {
|
|
|
3466
3503
|
return !matchLike(leftStr, pattern);
|
|
3467
3504
|
}
|
|
3468
3505
|
const rightStr = resolveValue(right, row);
|
|
3469
|
-
|
|
3470
|
-
const rightNum = Number(rightStr);
|
|
3471
|
-
const numeric = !Number.isNaN(leftNum) && !Number.isNaN(rightNum);
|
|
3472
|
-
switch (op) {
|
|
3473
|
-
case "=":
|
|
3474
|
-
return leftStr === rightStr;
|
|
3475
|
-
case "!=":
|
|
3476
|
-
case "<>":
|
|
3477
|
-
return leftStr !== rightStr;
|
|
3478
|
-
case ">":
|
|
3479
|
-
return numeric ? leftNum > rightNum : leftStr > rightStr;
|
|
3480
|
-
case "<":
|
|
3481
|
-
return numeric ? leftNum < rightNum : leftStr < rightStr;
|
|
3482
|
-
case ">=":
|
|
3483
|
-
return numeric ? leftNum >= rightNum : leftStr >= rightStr;
|
|
3484
|
-
case "<=":
|
|
3485
|
-
return numeric ? leftNum <= rightNum : leftStr <= rightStr;
|
|
3486
|
-
}
|
|
3506
|
+
return compareScalarValues(op, leftStr, rightStr);
|
|
3487
3507
|
}
|
|
3488
3508
|
function assertResolvedInListValues2(values) {
|
|
3489
3509
|
const unresolved = values.find((item) => item.type === "VARIABLE");
|
|
@@ -4086,60 +4106,59 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
|
|
|
4086
4106
|
}
|
|
4087
4107
|
|
|
4088
4108
|
// src/core/optimization/wherePredicatePushdown.ts
|
|
4089
|
-
function
|
|
4109
|
+
function extractSafePushdownLeaves(where, options = {}) {
|
|
4110
|
+
return extractAndLeaves(where, (expr) => isSafeComparison(expr, options));
|
|
4111
|
+
}
|
|
4112
|
+
function extractNumericPushdownCandidates(where, options = {}) {
|
|
4113
|
+
return extractAndLeaves(where, (expr) => isNumericCandidate(expr, options));
|
|
4114
|
+
}
|
|
4115
|
+
function extractAndLeaves(where, accept) {
|
|
4090
4116
|
switch (where.type) {
|
|
4091
4117
|
case "BINARY":
|
|
4092
|
-
|
|
4093
|
-
if (!isSingleTableField(where.left, tableAlias)) return null;
|
|
4094
|
-
if (!isPushDownableRight(where.right)) return null;
|
|
4095
|
-
return where;
|
|
4096
|
-
case "NULL_CHECK":
|
|
4097
|
-
if (!isSingleTableField(where.field, tableAlias)) return null;
|
|
4098
|
-
return where;
|
|
4118
|
+
return accept(where) ? where : null;
|
|
4099
4119
|
case "LOGICAL":
|
|
4100
|
-
if (where.op
|
|
4101
|
-
|
|
4102
|
-
const
|
|
4120
|
+
if (where.op !== "AND") return null;
|
|
4121
|
+
{
|
|
4122
|
+
const left = extractAndLeaves(where.left, accept);
|
|
4123
|
+
const right = extractAndLeaves(where.right, accept);
|
|
4103
4124
|
if (left && right) return { ...where, left, right };
|
|
4104
4125
|
return left ?? right ?? null;
|
|
4105
4126
|
}
|
|
4106
|
-
return referencesOnlyTable(where, tableAlias) ? where : null;
|
|
4107
|
-
case "NOT":
|
|
4108
4127
|
case "GROUP":
|
|
4109
|
-
return
|
|
4128
|
+
return extractAndLeaves(where.expr, accept);
|
|
4129
|
+
case "NULL_CHECK":
|
|
4130
|
+
case "NOT":
|
|
4110
4131
|
case "EXISTS":
|
|
4111
4132
|
return null;
|
|
4112
4133
|
}
|
|
4113
4134
|
}
|
|
4114
|
-
function
|
|
4115
|
-
if (
|
|
4116
|
-
|
|
4135
|
+
function isSafeComparison(expr, options) {
|
|
4136
|
+
if (isSafeIdComparison(expr, options)) return true;
|
|
4137
|
+
if (!isNumericCandidate(expr, options)) return false;
|
|
4138
|
+
return options.fieldTypes?.get(expr.left.field) === "NUMBER";
|
|
4117
4139
|
}
|
|
4118
|
-
function
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
case "KINTONE_FUNC":
|
|
4123
|
-
case "IN_LIST":
|
|
4124
|
-
return true;
|
|
4125
|
-
default:
|
|
4126
|
-
return false;
|
|
4127
|
-
}
|
|
4140
|
+
function isSafeIdComparison(expr, options) {
|
|
4141
|
+
if (!isTargetIdField(expr.left, options)) return false;
|
|
4142
|
+
if (expr.right.type !== "NUMBER") return false;
|
|
4143
|
+
return expr.op === "=" || expr.op === ">" || expr.op === "<" || expr.op === ">=" || expr.op === "<=";
|
|
4128
4144
|
}
|
|
4129
|
-
function
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4145
|
+
function isTargetIdField(field, options) {
|
|
4146
|
+
if (field.type !== "FIELD" || field.field !== "$id") return false;
|
|
4147
|
+
const targetAlias = options.tableAlias ?? null;
|
|
4148
|
+
if (field.tableAlias === targetAlias) return true;
|
|
4149
|
+
return options.allowUnqualifiedFields === true && field.tableAlias === null;
|
|
4150
|
+
}
|
|
4151
|
+
function isNumericCandidate(expr, options) {
|
|
4152
|
+
if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
|
|
4153
|
+
if (!isTargetField(expr.left, options)) return false;
|
|
4154
|
+
if (expr.right.type !== "NUMBER") return false;
|
|
4155
|
+
if (expr.op === "=") return true;
|
|
4156
|
+
return (expr.op === "<" || expr.op === ">") && Number.isSafeInteger(expr.right.value);
|
|
4157
|
+
}
|
|
4158
|
+
function isTargetField(field, options) {
|
|
4159
|
+
const targetAlias = options.tableAlias ?? null;
|
|
4160
|
+
if (field.tableAlias === targetAlias) return true;
|
|
4161
|
+
return options.allowUnqualifiedFields === true && field.tableAlias === null;
|
|
4143
4162
|
}
|
|
4144
4163
|
|
|
4145
4164
|
// src/engine/process.ts
|
|
@@ -4916,7 +4935,26 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
4916
4935
|
}
|
|
4917
4936
|
async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables, variables) {
|
|
4918
4937
|
if (stmt.type === "SET_VARIABLE") {
|
|
4919
|
-
|
|
4938
|
+
const resolvedStmt2 = resolveVariableRefs(stmt, variables);
|
|
4939
|
+
if (resolvedStmt2.expr.type === "SCALAR_SUBQUERY") {
|
|
4940
|
+
try {
|
|
4941
|
+
const value = await evaluateScalarSubquery(
|
|
4942
|
+
resolvedStmt2.expr.query,
|
|
4943
|
+
client,
|
|
4944
|
+
options,
|
|
4945
|
+
cacheContext,
|
|
4946
|
+
tempTables
|
|
4947
|
+
);
|
|
4948
|
+
variables.set(stmt.name, { type: "string", value });
|
|
4949
|
+
} catch (e) {
|
|
4950
|
+
if (e instanceof ScalarSubqueryError) {
|
|
4951
|
+
throw new Error(`ArgumentError: ${e.message}`);
|
|
4952
|
+
}
|
|
4953
|
+
throw e;
|
|
4954
|
+
}
|
|
4955
|
+
} else {
|
|
4956
|
+
variables.set(stmt.name, evaluateScalarExpr(resolvedStmt2.expr));
|
|
4957
|
+
}
|
|
4920
4958
|
return {};
|
|
4921
4959
|
}
|
|
4922
4960
|
const resolvedStmt = resolveVariableRefs(stmt, variables);
|
|
@@ -5079,6 +5117,12 @@ var AssertError = class extends Error {
|
|
|
5079
5117
|
this.name = "AssertError";
|
|
5080
5118
|
}
|
|
5081
5119
|
};
|
|
5120
|
+
var ScalarSubqueryError = class extends Error {
|
|
5121
|
+
constructor(message) {
|
|
5122
|
+
super(message);
|
|
5123
|
+
this.name = "ScalarSubqueryError";
|
|
5124
|
+
}
|
|
5125
|
+
};
|
|
5082
5126
|
async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
5083
5127
|
const left = await evalAssertOperand(stmt.left, client, options, cacheContext, tempTables);
|
|
5084
5128
|
if (stmt.op === "BETWEEN") {
|
|
@@ -5087,7 +5131,7 @@ async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
|
5087
5131
|
}
|
|
5088
5132
|
const low = await evalAssertOperand(stmt.low, client, options, cacheContext, tempTables);
|
|
5089
5133
|
const high = await evalAssertOperand(stmt.high, client, options, cacheContext, tempTables);
|
|
5090
|
-
if (!
|
|
5134
|
+
if (!compareScalarValues(">=", left, low) || !compareScalarValues("<=", left, high)) {
|
|
5091
5135
|
throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
|
|
5092
5136
|
}
|
|
5093
5137
|
return { type: "ASSERT", condition: stmt.text };
|
|
@@ -5096,7 +5140,7 @@ async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
|
5096
5140
|
throw new Error("ArgumentError: malformed ASSERT statement.");
|
|
5097
5141
|
}
|
|
5098
5142
|
const right = await evalAssertOperand(stmt.right, client, options, cacheContext, tempTables);
|
|
5099
|
-
if (!
|
|
5143
|
+
if (!compareScalarValues(stmt.op, left, right)) {
|
|
5100
5144
|
throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
|
|
5101
5145
|
}
|
|
5102
5146
|
return { type: "ASSERT", condition: stmt.text };
|
|
@@ -5112,25 +5156,38 @@ async function evalAssertOperand(operand, client, options, cacheContext, tempTab
|
|
|
5112
5156
|
case "ARITH":
|
|
5113
5157
|
return String(evalAssertArith(operand));
|
|
5114
5158
|
case "SCALAR_SUBQUERY": {
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5159
|
+
try {
|
|
5160
|
+
return await evaluateScalarSubquery(
|
|
5161
|
+
operand.query,
|
|
5162
|
+
client,
|
|
5163
|
+
options,
|
|
5164
|
+
cacheContext,
|
|
5165
|
+
tempTables
|
|
5120
5166
|
);
|
|
5167
|
+
} catch (e) {
|
|
5168
|
+
if (e instanceof ScalarSubqueryError) throw new AssertError(e.message);
|
|
5169
|
+
throw e;
|
|
5121
5170
|
}
|
|
5122
|
-
if (result.rowCount === 0) {
|
|
5123
|
-
throw new AssertError("scalar subquery returned no rows (expected 1 row).");
|
|
5124
|
-
}
|
|
5125
|
-
if (result.rowCount > 1) {
|
|
5126
|
-
const rows = probed && result.rowCount === 2 ? "2 or more rows" : `${result.rowCount} rows`;
|
|
5127
|
-
throw new AssertError(`scalar subquery returned ${rows} (expected 1 row).`);
|
|
5128
|
-
}
|
|
5129
|
-
const col = result.columns[0] ?? "";
|
|
5130
|
-
return result.rows[0]?.[col] ?? "";
|
|
5131
5171
|
}
|
|
5132
5172
|
}
|
|
5133
5173
|
}
|
|
5174
|
+
async function evaluateScalarSubquery(sourceQuery, client, options, cacheContext, tempTables) {
|
|
5175
|
+
const { query, probed } = withScalarProbeLimit(sourceQuery);
|
|
5176
|
+
const result = await runSubquery(query, client, options, cacheContext, tempTables);
|
|
5177
|
+
if (result.columns.length !== 1) {
|
|
5178
|
+
throw new ScalarSubqueryError(
|
|
5179
|
+
`scalar subquery returned ${result.columns.length} columns (expected 1 column).`
|
|
5180
|
+
);
|
|
5181
|
+
}
|
|
5182
|
+
if (result.rowCount === 0) {
|
|
5183
|
+
throw new ScalarSubqueryError("scalar subquery returned no rows (expected 1 row).");
|
|
5184
|
+
}
|
|
5185
|
+
if (result.rowCount > 1) {
|
|
5186
|
+
const rows = probed && result.rowCount === 2 ? "2 or more rows" : `${result.rowCount} rows`;
|
|
5187
|
+
throw new ScalarSubqueryError(`scalar subquery returned ${rows} (expected 1 row).`);
|
|
5188
|
+
}
|
|
5189
|
+
return result.rows[0]?.[result.columns[0]] ?? "";
|
|
5190
|
+
}
|
|
5134
5191
|
function withScalarProbeLimit(query) {
|
|
5135
5192
|
const hasAgg = query.groupBy.length > 0 || query.columns.some((c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL");
|
|
5136
5193
|
if (hasAgg || query.distinct || query.limit !== null) return { query, probed: false };
|
|
@@ -5156,26 +5213,6 @@ function evalAssertArith(node) {
|
|
|
5156
5213
|
}
|
|
5157
5214
|
throw new Error(`ArgumentError: unsupported operand in ASSERT expression: ${node.type}`);
|
|
5158
5215
|
}
|
|
5159
|
-
function compareAssertValues(op, leftStr, rightStr) {
|
|
5160
|
-
const leftNum = Number(leftStr);
|
|
5161
|
-
const rightNum = Number(rightStr);
|
|
5162
|
-
const numeric = !Number.isNaN(leftNum) && !Number.isNaN(rightNum);
|
|
5163
|
-
switch (op) {
|
|
5164
|
-
case "=":
|
|
5165
|
-
return leftStr === rightStr;
|
|
5166
|
-
case "!=":
|
|
5167
|
-
case "<>":
|
|
5168
|
-
return leftStr !== rightStr;
|
|
5169
|
-
case ">":
|
|
5170
|
-
return numeric ? leftNum > rightNum : leftStr > rightStr;
|
|
5171
|
-
case "<":
|
|
5172
|
-
return numeric ? leftNum < rightNum : leftStr < rightStr;
|
|
5173
|
-
case ">=":
|
|
5174
|
-
return numeric ? leftNum >= rightNum : leftStr >= rightStr;
|
|
5175
|
-
case "<=":
|
|
5176
|
-
return numeric ? leftNum <= rightNum : leftStr <= rightStr;
|
|
5177
|
-
}
|
|
5178
|
-
}
|
|
5179
5216
|
async function executeSelect(stmt, client, options, cacheContext, cteCache) {
|
|
5180
5217
|
if (isNoFromSelect(stmt)) {
|
|
5181
5218
|
return executeNoFromSelect(stmt);
|
|
@@ -5309,6 +5346,44 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
|
|
|
5309
5346
|
}
|
|
5310
5347
|
}
|
|
5311
5348
|
}
|
|
5349
|
+
function extractMainSafePushdown(stmt, fieldTypes) {
|
|
5350
|
+
if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
|
|
5351
|
+
if (stmt.joins.length === 0) {
|
|
5352
|
+
return extractSafePushdownLeaves(stmt.where, {
|
|
5353
|
+
tableAlias: stmt.from.alias ?? void 0,
|
|
5354
|
+
allowUnqualifiedFields: true,
|
|
5355
|
+
fieldTypes
|
|
5356
|
+
});
|
|
5357
|
+
}
|
|
5358
|
+
if (!stmt.from.alias) return null;
|
|
5359
|
+
return extractSafePushdownLeaves(stmt.where, { tableAlias: stmt.from.alias, fieldTypes });
|
|
5360
|
+
}
|
|
5361
|
+
function extractMainNumericPushdownCandidate(stmt) {
|
|
5362
|
+
if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
|
|
5363
|
+
if (stmt.joins.length === 0) {
|
|
5364
|
+
return extractNumericPushdownCandidates(stmt.where, {
|
|
5365
|
+
tableAlias: stmt.from.alias ?? void 0,
|
|
5366
|
+
allowUnqualifiedFields: true
|
|
5367
|
+
});
|
|
5368
|
+
}
|
|
5369
|
+
if (!stmt.from.alias) return null;
|
|
5370
|
+
return extractNumericPushdownCandidates(stmt.where, { tableAlias: stmt.from.alias });
|
|
5371
|
+
}
|
|
5372
|
+
async function loadNumericPushdownFieldTypes(stmt, client, cacheContext) {
|
|
5373
|
+
const appIds = /* @__PURE__ */ new Set();
|
|
5374
|
+
if (extractMainNumericPushdownCandidate(stmt) !== null) appIds.add(stmt.from.appId);
|
|
5375
|
+
if (stmt.where !== null) {
|
|
5376
|
+
for (const join2 of stmt.joins) {
|
|
5377
|
+
if (!join2.table.alias || join2.table.subtableCode || join2.table.cteName !== null) continue;
|
|
5378
|
+
const candidate = extractNumericPushdownCandidates(stmt.where, {
|
|
5379
|
+
tableAlias: join2.table.alias
|
|
5380
|
+
});
|
|
5381
|
+
if (candidate !== null) appIds.add(join2.table.appId);
|
|
5382
|
+
}
|
|
5383
|
+
}
|
|
5384
|
+
const entries = await Promise.all([...appIds].map(async (appId) => [appId, await getFieldTypeMap(appId, client, cacheContext)]));
|
|
5385
|
+
return new Map(entries);
|
|
5386
|
+
}
|
|
5312
5387
|
async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache) {
|
|
5313
5388
|
const maxRecords = options.maxRecords ?? 1e4;
|
|
5314
5389
|
const warnings = /* @__PURE__ */ new Set();
|
|
@@ -5317,20 +5392,22 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
5317
5392
|
resolveSubqueries(stmt.where, client, options, cacheContext, cteCache),
|
|
5318
5393
|
resolveSubqueries(stmt.having, client, options, cacheContext, cteCache)
|
|
5319
5394
|
]);
|
|
5395
|
+
const pushdownFieldTypes = await loadNumericPushdownFieldTypes(stmt, client, cacheContext);
|
|
5396
|
+
const mainPushDown = extractMainSafePushdown(
|
|
5397
|
+
stmt,
|
|
5398
|
+
pushdownFieldTypes.get(stmt.from.appId)
|
|
5399
|
+
);
|
|
5320
5400
|
const tableConditions = /* @__PURE__ */ new Map();
|
|
5321
5401
|
if (stmt.where !== null) {
|
|
5322
|
-
if (stmt.from.alias) {
|
|
5323
|
-
const cond = extractTableCondition(stmt.where, stmt.from.alias);
|
|
5324
|
-
if (cond) tableConditions.set(stmt.from.alias, cond);
|
|
5325
|
-
}
|
|
5326
5402
|
for (const join2 of stmt.joins) {
|
|
5327
|
-
if (join2.table.alias)
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5403
|
+
if (!join2.table.alias || join2.table.subtableCode || join2.table.cteName !== null) continue;
|
|
5404
|
+
const cond = extractSafePushdownLeaves(stmt.where, {
|
|
5405
|
+
tableAlias: join2.table.alias,
|
|
5406
|
+
fieldTypes: pushdownFieldTypes.get(join2.table.appId)
|
|
5407
|
+
});
|
|
5408
|
+
if (cond) tableConditions.set(join2.table.alias, cond);
|
|
5331
5409
|
}
|
|
5332
5410
|
}
|
|
5333
|
-
const mainPushDown = stmt.from.alias ? tableConditions.get(stmt.from.alias) ?? null : null;
|
|
5334
5411
|
const mainFetch = fetchTableRecordsForFullScan(
|
|
5335
5412
|
stmt,
|
|
5336
5413
|
stmt.from,
|
|
@@ -6612,7 +6689,7 @@ function buildBatchExplainPlans(sql) {
|
|
|
6612
6689
|
return {
|
|
6613
6690
|
statementCount: statements.length,
|
|
6614
6691
|
statements: statements.map((stmt, i) => {
|
|
6615
|
-
const planStmt = stmt.type === "SET_VARIABLE" ? stmt : resolveVariableRefs(stmt, variables);
|
|
6692
|
+
const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveVariableRefs(stmt.expr, variables) } : stmt : resolveVariableRefs(stmt, variables);
|
|
6616
6693
|
const result = {
|
|
6617
6694
|
index: i,
|
|
6618
6695
|
type: analysis.statements[i].statementType,
|
|
@@ -6641,6 +6718,15 @@ function buildBatchStatementPlan(stmt, info) {
|
|
|
6641
6718
|
];
|
|
6642
6719
|
}
|
|
6643
6720
|
if (stmt.type === "SET_VARIABLE") {
|
|
6721
|
+
if (stmt.expr.type === "SCALAR_SUBQUERY") {
|
|
6722
|
+
const subInfo = hasTempTableRef(stmt.expr.query) ? info : { ...info, tempTablesReferenced: [] };
|
|
6723
|
+
return [
|
|
6724
|
+
`SET @${stmt.name} = (SELECT ...)`,
|
|
6725
|
+
" value: \u30B5\u30D6\u30AF\u30A8\u30EA\u3092\u5B9F\u884C\u6642\u306B1\u56DE\u8A55\u4FA1\uFF081\u884C1\u5217\u30FB\u30D0\u30C3\u30C1\u5185\u5B9A\u6570\u30FB\u7D50\u679C\u30E1\u30BF\u30C7\u30FC\u30BF\u306B\u306F\u975E\u516C\u958B\uFF09",
|
|
6726
|
+
" subquery:",
|
|
6727
|
+
...buildPlanForBatchQuery(stmt.expr.query, subInfo).map((l) => ` ${l}`)
|
|
6728
|
+
];
|
|
6729
|
+
}
|
|
6644
6730
|
return [
|
|
6645
6731
|
`SET @${stmt.name} = <scalar expression>`,
|
|
6646
6732
|
" value: \u5B9F\u884C\u6642\u306B1\u56DE\u8A55\u4FA1\uFF08\u30D0\u30C3\u30C1\u5185\u5B9A\u6570\u30FB\u7D50\u679C\u30E1\u30BF\u30C7\u30FC\u30BF\u306B\u306F\u975E\u516C\u958B\uFF09"
|
|
@@ -6741,19 +6827,27 @@ function buildSelectPlan(stmt, label) {
|
|
|
6741
6827
|
} else {
|
|
6742
6828
|
const mainFields = selectToFetchAllFields(stmt, stmt.from);
|
|
6743
6829
|
const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
|
|
6744
|
-
const mainPushDown =
|
|
6830
|
+
const mainPushDown = extractMainSafePushdown(stmt);
|
|
6831
|
+
const mainCandidate = extractMainNumericPushdownCandidate(stmt);
|
|
6745
6832
|
const mainQ = mainPushDown !== null ? whereToKintone(mainPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
|
|
6746
6833
|
lines.push(` app: APP${stmt.from.appId}${mainAliasStr} (${stmt.from.appId})`);
|
|
6747
6834
|
lines.push(` kintone query: ${mainQ}`);
|
|
6835
|
+
if (mainCandidate !== null) {
|
|
6836
|
+
lines.push(` pushdown candidate: ${whereToKintone(mainCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u78BA\u8A8D\u5F85\u3061\uFF09`);
|
|
6837
|
+
}
|
|
6748
6838
|
lines.push(` fields: ${mainFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : mainFields.join(", ")}`);
|
|
6749
6839
|
for (const join2 of stmt.joins) {
|
|
6750
6840
|
const joinFields = selectToFetchAllFields(stmt, join2.table);
|
|
6751
6841
|
const joinAliasStr = join2.table.alias ? ` AS ${join2.table.alias}` : "";
|
|
6752
6842
|
const joinType = join2.type === "INNER" ? "JOIN" : `${join2.type} JOIN`;
|
|
6753
|
-
const joinPushDown = join2.table.alias && stmt.where ?
|
|
6843
|
+
const joinPushDown = join2.table.alias && !join2.table.subtableCode && join2.table.cteName === null && stmt.where ? extractSafePushdownLeaves(stmt.where, { tableAlias: join2.table.alias }) : null;
|
|
6844
|
+
const joinCandidate = join2.table.alias && !join2.table.subtableCode && join2.table.cteName === null && stmt.where ? extractNumericPushdownCandidates(stmt.where, { tableAlias: join2.table.alias }) : null;
|
|
6754
6845
|
const joinQ = joinPushDown !== null ? whereToKintone(joinPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
|
|
6755
6846
|
lines.push(` ${joinType}: APP${join2.table.appId}${joinAliasStr} (${join2.table.appId})`);
|
|
6756
6847
|
lines.push(` kintone query: ${joinQ}`);
|
|
6848
|
+
if (joinCandidate !== null) {
|
|
6849
|
+
lines.push(` pushdown candidate: ${whereToKintone(joinCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u78BA\u8A8D\u5F85\u3061\uFF09`);
|
|
6850
|
+
}
|
|
6757
6851
|
lines.push(` fields: ${joinFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : joinFields.join(", ")}`);
|
|
6758
6852
|
}
|
|
6759
6853
|
}
|
package/dist-mcp/ksql-mcp.js
CHANGED
|
@@ -31542,7 +31542,22 @@ var Parser = class {
|
|
|
31542
31542
|
throw new ParseError("SET \u306E\u53F3\u8FBA\u3067 NULL \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\uFF08Phase 1a\uFF09", tok);
|
|
31543
31543
|
}
|
|
31544
31544
|
if (tok.kind === "(" /* LPAREN */ && this.peekAt(1).kind === "SELECT" /* SELECT */) {
|
|
31545
|
-
|
|
31545
|
+
this.advance();
|
|
31546
|
+
const query = this.parseSelect();
|
|
31547
|
+
this.expect(")" /* RPAREN */);
|
|
31548
|
+
if (this.isArithOp(this.peek().kind)) {
|
|
31549
|
+
throw new ParseError(
|
|
31550
|
+
"\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u306E\u5F8C\u306B\u7B97\u8853\u6F14\u7B97\u5B50\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002\u30B5\u30D6\u30AF\u30A8\u30EA\u5185\u3067\u8A08\u7B97\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
31551
|
+
this.peek()
|
|
31552
|
+
);
|
|
31553
|
+
}
|
|
31554
|
+
const hasWildcard = query.columns.some(
|
|
31555
|
+
(c) => c.type === "WILDCARD" || c.type === "PARENT_WILDCARD"
|
|
31556
|
+
);
|
|
31557
|
+
if (!hasWildcard && query.columns.length !== 1) {
|
|
31558
|
+
throw new ParseError("scalar subquery in SET must return exactly 1 column.", tok);
|
|
31559
|
+
}
|
|
31560
|
+
return { type: "SCALAR_SUBQUERY", query };
|
|
31546
31561
|
}
|
|
31547
31562
|
if (tok.kind === "STRING" /* STRING */) {
|
|
31548
31563
|
this.advance();
|
|
@@ -33393,6 +33408,28 @@ function analyzeBatch(statements) {
|
|
|
33393
33408
|
};
|
|
33394
33409
|
}
|
|
33395
33410
|
|
|
33411
|
+
// src/core/scalarCompare.ts
|
|
33412
|
+
function compareScalarValues(op, leftStr, rightStr) {
|
|
33413
|
+
if (op === "=") return leftStr === rightStr;
|
|
33414
|
+
if (op === "!=" || op === "<>") return leftStr !== rightStr;
|
|
33415
|
+
const rightNum = Number(rightStr);
|
|
33416
|
+
if (leftStr === "" && rightStr !== "" && Number.isFinite(rightNum)) {
|
|
33417
|
+
return op === "<" || op === "<=";
|
|
33418
|
+
}
|
|
33419
|
+
const leftNum = Number(leftStr);
|
|
33420
|
+
const numeric = !Number.isNaN(leftNum) && !Number.isNaN(rightNum);
|
|
33421
|
+
switch (op) {
|
|
33422
|
+
case ">":
|
|
33423
|
+
return numeric ? leftNum > rightNum : leftStr > rightStr;
|
|
33424
|
+
case "<":
|
|
33425
|
+
return numeric ? leftNum < rightNum : leftStr < rightStr;
|
|
33426
|
+
case ">=":
|
|
33427
|
+
return numeric ? leftNum >= rightNum : leftStr >= rightStr;
|
|
33428
|
+
case "<=":
|
|
33429
|
+
return numeric ? leftNum <= rightNum : leftStr <= rightStr;
|
|
33430
|
+
}
|
|
33431
|
+
}
|
|
33432
|
+
|
|
33396
33433
|
// src/engine/pushDownNot.ts
|
|
33397
33434
|
function pushDownNot(expr) {
|
|
33398
33435
|
switch (expr.type) {
|
|
@@ -34367,24 +34404,7 @@ function evalOp(op, leftStr, right, row) {
|
|
|
34367
34404
|
return !matchLike(leftStr, pattern);
|
|
34368
34405
|
}
|
|
34369
34406
|
const rightStr = resolveValue(right, row);
|
|
34370
|
-
|
|
34371
|
-
const rightNum = Number(rightStr);
|
|
34372
|
-
const numeric = !Number.isNaN(leftNum) && !Number.isNaN(rightNum);
|
|
34373
|
-
switch (op) {
|
|
34374
|
-
case "=":
|
|
34375
|
-
return leftStr === rightStr;
|
|
34376
|
-
case "!=":
|
|
34377
|
-
case "<>":
|
|
34378
|
-
return leftStr !== rightStr;
|
|
34379
|
-
case ">":
|
|
34380
|
-
return numeric ? leftNum > rightNum : leftStr > rightStr;
|
|
34381
|
-
case "<":
|
|
34382
|
-
return numeric ? leftNum < rightNum : leftStr < rightStr;
|
|
34383
|
-
case ">=":
|
|
34384
|
-
return numeric ? leftNum >= rightNum : leftStr >= rightStr;
|
|
34385
|
-
case "<=":
|
|
34386
|
-
return numeric ? leftNum <= rightNum : leftStr <= rightStr;
|
|
34387
|
-
}
|
|
34407
|
+
return compareScalarValues(op, leftStr, rightStr);
|
|
34388
34408
|
}
|
|
34389
34409
|
function assertResolvedInListValues2(values) {
|
|
34390
34410
|
const unresolved = values.find((item) => item.type === "VARIABLE");
|
|
@@ -34987,60 +35007,59 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
|
|
|
34987
35007
|
}
|
|
34988
35008
|
|
|
34989
35009
|
// src/core/optimization/wherePredicatePushdown.ts
|
|
34990
|
-
function
|
|
35010
|
+
function extractSafePushdownLeaves(where, options = {}) {
|
|
35011
|
+
return extractAndLeaves(where, (expr) => isSafeComparison(expr, options));
|
|
35012
|
+
}
|
|
35013
|
+
function extractNumericPushdownCandidates(where, options = {}) {
|
|
35014
|
+
return extractAndLeaves(where, (expr) => isNumericCandidate(expr, options));
|
|
35015
|
+
}
|
|
35016
|
+
function extractAndLeaves(where, accept) {
|
|
34991
35017
|
switch (where.type) {
|
|
34992
35018
|
case "BINARY":
|
|
34993
|
-
|
|
34994
|
-
if (!isSingleTableField(where.left, tableAlias)) return null;
|
|
34995
|
-
if (!isPushDownableRight(where.right)) return null;
|
|
34996
|
-
return where;
|
|
34997
|
-
case "NULL_CHECK":
|
|
34998
|
-
if (!isSingleTableField(where.field, tableAlias)) return null;
|
|
34999
|
-
return where;
|
|
35019
|
+
return accept(where) ? where : null;
|
|
35000
35020
|
case "LOGICAL":
|
|
35001
|
-
if (where.op
|
|
35002
|
-
|
|
35003
|
-
const
|
|
35021
|
+
if (where.op !== "AND") return null;
|
|
35022
|
+
{
|
|
35023
|
+
const left = extractAndLeaves(where.left, accept);
|
|
35024
|
+
const right = extractAndLeaves(where.right, accept);
|
|
35004
35025
|
if (left && right) return { ...where, left, right };
|
|
35005
35026
|
return left ?? right ?? null;
|
|
35006
35027
|
}
|
|
35007
|
-
return referencesOnlyTable(where, tableAlias) ? where : null;
|
|
35008
|
-
case "NOT":
|
|
35009
35028
|
case "GROUP":
|
|
35010
|
-
return
|
|
35029
|
+
return extractAndLeaves(where.expr, accept);
|
|
35030
|
+
case "NULL_CHECK":
|
|
35031
|
+
case "NOT":
|
|
35011
35032
|
case "EXISTS":
|
|
35012
35033
|
return null;
|
|
35013
35034
|
}
|
|
35014
35035
|
}
|
|
35015
|
-
function
|
|
35016
|
-
if (
|
|
35017
|
-
|
|
35036
|
+
function isSafeComparison(expr, options) {
|
|
35037
|
+
if (isSafeIdComparison(expr, options)) return true;
|
|
35038
|
+
if (!isNumericCandidate(expr, options)) return false;
|
|
35039
|
+
return options.fieldTypes?.get(expr.left.field) === "NUMBER";
|
|
35018
35040
|
}
|
|
35019
|
-
function
|
|
35020
|
-
|
|
35021
|
-
|
|
35022
|
-
|
|
35023
|
-
case "KINTONE_FUNC":
|
|
35024
|
-
case "IN_LIST":
|
|
35025
|
-
return true;
|
|
35026
|
-
default:
|
|
35027
|
-
return false;
|
|
35028
|
-
}
|
|
35041
|
+
function isSafeIdComparison(expr, options) {
|
|
35042
|
+
if (!isTargetIdField(expr.left, options)) return false;
|
|
35043
|
+
if (expr.right.type !== "NUMBER") return false;
|
|
35044
|
+
return expr.op === "=" || expr.op === ">" || expr.op === "<" || expr.op === ">=" || expr.op === "<=";
|
|
35029
35045
|
}
|
|
35030
|
-
function
|
|
35031
|
-
|
|
35032
|
-
|
|
35033
|
-
|
|
35034
|
-
|
|
35035
|
-
|
|
35036
|
-
|
|
35037
|
-
|
|
35038
|
-
|
|
35039
|
-
|
|
35040
|
-
|
|
35041
|
-
|
|
35042
|
-
|
|
35043
|
-
|
|
35046
|
+
function isTargetIdField(field, options) {
|
|
35047
|
+
if (field.type !== "FIELD" || field.field !== "$id") return false;
|
|
35048
|
+
const targetAlias = options.tableAlias ?? null;
|
|
35049
|
+
if (field.tableAlias === targetAlias) return true;
|
|
35050
|
+
return options.allowUnqualifiedFields === true && field.tableAlias === null;
|
|
35051
|
+
}
|
|
35052
|
+
function isNumericCandidate(expr, options) {
|
|
35053
|
+
if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
|
|
35054
|
+
if (!isTargetField(expr.left, options)) return false;
|
|
35055
|
+
if (expr.right.type !== "NUMBER") return false;
|
|
35056
|
+
if (expr.op === "=") return true;
|
|
35057
|
+
return (expr.op === "<" || expr.op === ">") && Number.isSafeInteger(expr.right.value);
|
|
35058
|
+
}
|
|
35059
|
+
function isTargetField(field, options) {
|
|
35060
|
+
const targetAlias = options.tableAlias ?? null;
|
|
35061
|
+
if (field.tableAlias === targetAlias) return true;
|
|
35062
|
+
return options.allowUnqualifiedFields === true && field.tableAlias === null;
|
|
35044
35063
|
}
|
|
35045
35064
|
|
|
35046
35065
|
// src/engine/process.ts
|
|
@@ -35817,7 +35836,26 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
35817
35836
|
}
|
|
35818
35837
|
async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables, variables) {
|
|
35819
35838
|
if (stmt.type === "SET_VARIABLE") {
|
|
35820
|
-
|
|
35839
|
+
const resolvedStmt2 = resolveVariableRefs(stmt, variables);
|
|
35840
|
+
if (resolvedStmt2.expr.type === "SCALAR_SUBQUERY") {
|
|
35841
|
+
try {
|
|
35842
|
+
const value = await evaluateScalarSubquery(
|
|
35843
|
+
resolvedStmt2.expr.query,
|
|
35844
|
+
client,
|
|
35845
|
+
options,
|
|
35846
|
+
cacheContext,
|
|
35847
|
+
tempTables
|
|
35848
|
+
);
|
|
35849
|
+
variables.set(stmt.name, { type: "string", value });
|
|
35850
|
+
} catch (e) {
|
|
35851
|
+
if (e instanceof ScalarSubqueryError) {
|
|
35852
|
+
throw new Error(`ArgumentError: ${e.message}`);
|
|
35853
|
+
}
|
|
35854
|
+
throw e;
|
|
35855
|
+
}
|
|
35856
|
+
} else {
|
|
35857
|
+
variables.set(stmt.name, evaluateScalarExpr(resolvedStmt2.expr));
|
|
35858
|
+
}
|
|
35821
35859
|
return {};
|
|
35822
35860
|
}
|
|
35823
35861
|
const resolvedStmt = resolveVariableRefs(stmt, variables);
|
|
@@ -35980,6 +36018,12 @@ var AssertError = class extends Error {
|
|
|
35980
36018
|
this.name = "AssertError";
|
|
35981
36019
|
}
|
|
35982
36020
|
};
|
|
36021
|
+
var ScalarSubqueryError = class extends Error {
|
|
36022
|
+
constructor(message) {
|
|
36023
|
+
super(message);
|
|
36024
|
+
this.name = "ScalarSubqueryError";
|
|
36025
|
+
}
|
|
36026
|
+
};
|
|
35983
36027
|
async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
35984
36028
|
const left = await evalAssertOperand(stmt.left, client, options, cacheContext, tempTables);
|
|
35985
36029
|
if (stmt.op === "BETWEEN") {
|
|
@@ -35988,7 +36032,7 @@ async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
|
35988
36032
|
}
|
|
35989
36033
|
const low = await evalAssertOperand(stmt.low, client, options, cacheContext, tempTables);
|
|
35990
36034
|
const high = await evalAssertOperand(stmt.high, client, options, cacheContext, tempTables);
|
|
35991
|
-
if (!
|
|
36035
|
+
if (!compareScalarValues(">=", left, low) || !compareScalarValues("<=", left, high)) {
|
|
35992
36036
|
throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
|
|
35993
36037
|
}
|
|
35994
36038
|
return { type: "ASSERT", condition: stmt.text };
|
|
@@ -35997,7 +36041,7 @@ async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
|
35997
36041
|
throw new Error("ArgumentError: malformed ASSERT statement.");
|
|
35998
36042
|
}
|
|
35999
36043
|
const right = await evalAssertOperand(stmt.right, client, options, cacheContext, tempTables);
|
|
36000
|
-
if (!
|
|
36044
|
+
if (!compareScalarValues(stmt.op, left, right)) {
|
|
36001
36045
|
throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
|
|
36002
36046
|
}
|
|
36003
36047
|
return { type: "ASSERT", condition: stmt.text };
|
|
@@ -36013,25 +36057,38 @@ async function evalAssertOperand(operand, client, options, cacheContext, tempTab
|
|
|
36013
36057
|
case "ARITH":
|
|
36014
36058
|
return String(evalAssertArith(operand));
|
|
36015
36059
|
case "SCALAR_SUBQUERY": {
|
|
36016
|
-
|
|
36017
|
-
|
|
36018
|
-
|
|
36019
|
-
|
|
36020
|
-
|
|
36060
|
+
try {
|
|
36061
|
+
return await evaluateScalarSubquery(
|
|
36062
|
+
operand.query,
|
|
36063
|
+
client,
|
|
36064
|
+
options,
|
|
36065
|
+
cacheContext,
|
|
36066
|
+
tempTables
|
|
36021
36067
|
);
|
|
36068
|
+
} catch (e) {
|
|
36069
|
+
if (e instanceof ScalarSubqueryError) throw new AssertError(e.message);
|
|
36070
|
+
throw e;
|
|
36022
36071
|
}
|
|
36023
|
-
if (result.rowCount === 0) {
|
|
36024
|
-
throw new AssertError("scalar subquery returned no rows (expected 1 row).");
|
|
36025
|
-
}
|
|
36026
|
-
if (result.rowCount > 1) {
|
|
36027
|
-
const rows = probed && result.rowCount === 2 ? "2 or more rows" : `${result.rowCount} rows`;
|
|
36028
|
-
throw new AssertError(`scalar subquery returned ${rows} (expected 1 row).`);
|
|
36029
|
-
}
|
|
36030
|
-
const col = result.columns[0] ?? "";
|
|
36031
|
-
return result.rows[0]?.[col] ?? "";
|
|
36032
36072
|
}
|
|
36033
36073
|
}
|
|
36034
36074
|
}
|
|
36075
|
+
async function evaluateScalarSubquery(sourceQuery, client, options, cacheContext, tempTables) {
|
|
36076
|
+
const { query, probed } = withScalarProbeLimit(sourceQuery);
|
|
36077
|
+
const result = await runSubquery(query, client, options, cacheContext, tempTables);
|
|
36078
|
+
if (result.columns.length !== 1) {
|
|
36079
|
+
throw new ScalarSubqueryError(
|
|
36080
|
+
`scalar subquery returned ${result.columns.length} columns (expected 1 column).`
|
|
36081
|
+
);
|
|
36082
|
+
}
|
|
36083
|
+
if (result.rowCount === 0) {
|
|
36084
|
+
throw new ScalarSubqueryError("scalar subquery returned no rows (expected 1 row).");
|
|
36085
|
+
}
|
|
36086
|
+
if (result.rowCount > 1) {
|
|
36087
|
+
const rows = probed && result.rowCount === 2 ? "2 or more rows" : `${result.rowCount} rows`;
|
|
36088
|
+
throw new ScalarSubqueryError(`scalar subquery returned ${rows} (expected 1 row).`);
|
|
36089
|
+
}
|
|
36090
|
+
return result.rows[0]?.[result.columns[0]] ?? "";
|
|
36091
|
+
}
|
|
36035
36092
|
function withScalarProbeLimit(query) {
|
|
36036
36093
|
const hasAgg = query.groupBy.length > 0 || query.columns.some((c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL");
|
|
36037
36094
|
if (hasAgg || query.distinct || query.limit !== null) return { query, probed: false };
|
|
@@ -36057,26 +36114,6 @@ function evalAssertArith(node) {
|
|
|
36057
36114
|
}
|
|
36058
36115
|
throw new Error(`ArgumentError: unsupported operand in ASSERT expression: ${node.type}`);
|
|
36059
36116
|
}
|
|
36060
|
-
function compareAssertValues(op, leftStr, rightStr) {
|
|
36061
|
-
const leftNum = Number(leftStr);
|
|
36062
|
-
const rightNum = Number(rightStr);
|
|
36063
|
-
const numeric = !Number.isNaN(leftNum) && !Number.isNaN(rightNum);
|
|
36064
|
-
switch (op) {
|
|
36065
|
-
case "=":
|
|
36066
|
-
return leftStr === rightStr;
|
|
36067
|
-
case "!=":
|
|
36068
|
-
case "<>":
|
|
36069
|
-
return leftStr !== rightStr;
|
|
36070
|
-
case ">":
|
|
36071
|
-
return numeric ? leftNum > rightNum : leftStr > rightStr;
|
|
36072
|
-
case "<":
|
|
36073
|
-
return numeric ? leftNum < rightNum : leftStr < rightStr;
|
|
36074
|
-
case ">=":
|
|
36075
|
-
return numeric ? leftNum >= rightNum : leftStr >= rightStr;
|
|
36076
|
-
case "<=":
|
|
36077
|
-
return numeric ? leftNum <= rightNum : leftStr <= rightStr;
|
|
36078
|
-
}
|
|
36079
|
-
}
|
|
36080
36117
|
async function executeSelect(stmt, client, options, cacheContext, cteCache) {
|
|
36081
36118
|
if (isNoFromSelect(stmt)) {
|
|
36082
36119
|
return executeNoFromSelect(stmt);
|
|
@@ -36210,6 +36247,44 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
|
|
|
36210
36247
|
}
|
|
36211
36248
|
}
|
|
36212
36249
|
}
|
|
36250
|
+
function extractMainSafePushdown(stmt, fieldTypes) {
|
|
36251
|
+
if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
|
|
36252
|
+
if (stmt.joins.length === 0) {
|
|
36253
|
+
return extractSafePushdownLeaves(stmt.where, {
|
|
36254
|
+
tableAlias: stmt.from.alias ?? void 0,
|
|
36255
|
+
allowUnqualifiedFields: true,
|
|
36256
|
+
fieldTypes
|
|
36257
|
+
});
|
|
36258
|
+
}
|
|
36259
|
+
if (!stmt.from.alias) return null;
|
|
36260
|
+
return extractSafePushdownLeaves(stmt.where, { tableAlias: stmt.from.alias, fieldTypes });
|
|
36261
|
+
}
|
|
36262
|
+
function extractMainNumericPushdownCandidate(stmt) {
|
|
36263
|
+
if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
|
|
36264
|
+
if (stmt.joins.length === 0) {
|
|
36265
|
+
return extractNumericPushdownCandidates(stmt.where, {
|
|
36266
|
+
tableAlias: stmt.from.alias ?? void 0,
|
|
36267
|
+
allowUnqualifiedFields: true
|
|
36268
|
+
});
|
|
36269
|
+
}
|
|
36270
|
+
if (!stmt.from.alias) return null;
|
|
36271
|
+
return extractNumericPushdownCandidates(stmt.where, { tableAlias: stmt.from.alias });
|
|
36272
|
+
}
|
|
36273
|
+
async function loadNumericPushdownFieldTypes(stmt, client, cacheContext) {
|
|
36274
|
+
const appIds = /* @__PURE__ */ new Set();
|
|
36275
|
+
if (extractMainNumericPushdownCandidate(stmt) !== null) appIds.add(stmt.from.appId);
|
|
36276
|
+
if (stmt.where !== null) {
|
|
36277
|
+
for (const join of stmt.joins) {
|
|
36278
|
+
if (!join.table.alias || join.table.subtableCode || join.table.cteName !== null) continue;
|
|
36279
|
+
const candidate = extractNumericPushdownCandidates(stmt.where, {
|
|
36280
|
+
tableAlias: join.table.alias
|
|
36281
|
+
});
|
|
36282
|
+
if (candidate !== null) appIds.add(join.table.appId);
|
|
36283
|
+
}
|
|
36284
|
+
}
|
|
36285
|
+
const entries = await Promise.all([...appIds].map(async (appId) => [appId, await getFieldTypeMap(appId, client, cacheContext)]));
|
|
36286
|
+
return new Map(entries);
|
|
36287
|
+
}
|
|
36213
36288
|
async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache) {
|
|
36214
36289
|
const maxRecords2 = options.maxRecords ?? 1e4;
|
|
36215
36290
|
const warnings = /* @__PURE__ */ new Set();
|
|
@@ -36218,20 +36293,22 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
36218
36293
|
resolveSubqueries(stmt.where, client, options, cacheContext, cteCache),
|
|
36219
36294
|
resolveSubqueries(stmt.having, client, options, cacheContext, cteCache)
|
|
36220
36295
|
]);
|
|
36296
|
+
const pushdownFieldTypes = await loadNumericPushdownFieldTypes(stmt, client, cacheContext);
|
|
36297
|
+
const mainPushDown = extractMainSafePushdown(
|
|
36298
|
+
stmt,
|
|
36299
|
+
pushdownFieldTypes.get(stmt.from.appId)
|
|
36300
|
+
);
|
|
36221
36301
|
const tableConditions = /* @__PURE__ */ new Map();
|
|
36222
36302
|
if (stmt.where !== null) {
|
|
36223
|
-
if (stmt.from.alias) {
|
|
36224
|
-
const cond = extractTableCondition(stmt.where, stmt.from.alias);
|
|
36225
|
-
if (cond) tableConditions.set(stmt.from.alias, cond);
|
|
36226
|
-
}
|
|
36227
36303
|
for (const join of stmt.joins) {
|
|
36228
|
-
if (join.table.alias)
|
|
36229
|
-
|
|
36230
|
-
|
|
36231
|
-
|
|
36304
|
+
if (!join.table.alias || join.table.subtableCode || join.table.cteName !== null) continue;
|
|
36305
|
+
const cond = extractSafePushdownLeaves(stmt.where, {
|
|
36306
|
+
tableAlias: join.table.alias,
|
|
36307
|
+
fieldTypes: pushdownFieldTypes.get(join.table.appId)
|
|
36308
|
+
});
|
|
36309
|
+
if (cond) tableConditions.set(join.table.alias, cond);
|
|
36232
36310
|
}
|
|
36233
36311
|
}
|
|
36234
|
-
const mainPushDown = stmt.from.alias ? tableConditions.get(stmt.from.alias) ?? null : null;
|
|
36235
36312
|
const mainFetch = fetchTableRecordsForFullScan(
|
|
36236
36313
|
stmt,
|
|
36237
36314
|
stmt.from,
|
|
@@ -37513,7 +37590,7 @@ function buildBatchExplainPlans(sql) {
|
|
|
37513
37590
|
return {
|
|
37514
37591
|
statementCount: statements.length,
|
|
37515
37592
|
statements: statements.map((stmt, i) => {
|
|
37516
|
-
const planStmt = stmt.type === "SET_VARIABLE" ? stmt : resolveVariableRefs(stmt, variables);
|
|
37593
|
+
const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveVariableRefs(stmt.expr, variables) } : stmt : resolveVariableRefs(stmt, variables);
|
|
37517
37594
|
const result = {
|
|
37518
37595
|
index: i,
|
|
37519
37596
|
type: analysis.statements[i].statementType,
|
|
@@ -37542,6 +37619,15 @@ function buildBatchStatementPlan(stmt, info) {
|
|
|
37542
37619
|
];
|
|
37543
37620
|
}
|
|
37544
37621
|
if (stmt.type === "SET_VARIABLE") {
|
|
37622
|
+
if (stmt.expr.type === "SCALAR_SUBQUERY") {
|
|
37623
|
+
const subInfo = hasTempTableRef(stmt.expr.query) ? info : { ...info, tempTablesReferenced: [] };
|
|
37624
|
+
return [
|
|
37625
|
+
`SET @${stmt.name} = (SELECT ...)`,
|
|
37626
|
+
" value: \u30B5\u30D6\u30AF\u30A8\u30EA\u3092\u5B9F\u884C\u6642\u306B1\u56DE\u8A55\u4FA1\uFF081\u884C1\u5217\u30FB\u30D0\u30C3\u30C1\u5185\u5B9A\u6570\u30FB\u7D50\u679C\u30E1\u30BF\u30C7\u30FC\u30BF\u306B\u306F\u975E\u516C\u958B\uFF09",
|
|
37627
|
+
" subquery:",
|
|
37628
|
+
...buildPlanForBatchQuery(stmt.expr.query, subInfo).map((l) => ` ${l}`)
|
|
37629
|
+
];
|
|
37630
|
+
}
|
|
37545
37631
|
return [
|
|
37546
37632
|
`SET @${stmt.name} = <scalar expression>`,
|
|
37547
37633
|
" value: \u5B9F\u884C\u6642\u306B1\u56DE\u8A55\u4FA1\uFF08\u30D0\u30C3\u30C1\u5185\u5B9A\u6570\u30FB\u7D50\u679C\u30E1\u30BF\u30C7\u30FC\u30BF\u306B\u306F\u975E\u516C\u958B\uFF09"
|
|
@@ -37642,19 +37728,27 @@ function buildSelectPlan(stmt, label) {
|
|
|
37642
37728
|
} else {
|
|
37643
37729
|
const mainFields = selectToFetchAllFields(stmt, stmt.from);
|
|
37644
37730
|
const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
|
|
37645
|
-
const mainPushDown =
|
|
37731
|
+
const mainPushDown = extractMainSafePushdown(stmt);
|
|
37732
|
+
const mainCandidate = extractMainNumericPushdownCandidate(stmt);
|
|
37646
37733
|
const mainQ = mainPushDown !== null ? whereToKintone(mainPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
|
|
37647
37734
|
lines.push(` app: APP${stmt.from.appId}${mainAliasStr} (${stmt.from.appId})`);
|
|
37648
37735
|
lines.push(` kintone query: ${mainQ}`);
|
|
37736
|
+
if (mainCandidate !== null) {
|
|
37737
|
+
lines.push(` pushdown candidate: ${whereToKintone(mainCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u78BA\u8A8D\u5F85\u3061\uFF09`);
|
|
37738
|
+
}
|
|
37649
37739
|
lines.push(` fields: ${mainFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : mainFields.join(", ")}`);
|
|
37650
37740
|
for (const join of stmt.joins) {
|
|
37651
37741
|
const joinFields = selectToFetchAllFields(stmt, join.table);
|
|
37652
37742
|
const joinAliasStr = join.table.alias ? ` AS ${join.table.alias}` : "";
|
|
37653
37743
|
const joinType = join.type === "INNER" ? "JOIN" : `${join.type} JOIN`;
|
|
37654
|
-
const joinPushDown = join.table.alias && stmt.where ?
|
|
37744
|
+
const joinPushDown = join.table.alias && !join.table.subtableCode && join.table.cteName === null && stmt.where ? extractSafePushdownLeaves(stmt.where, { tableAlias: join.table.alias }) : null;
|
|
37745
|
+
const joinCandidate = join.table.alias && !join.table.subtableCode && join.table.cteName === null && stmt.where ? extractNumericPushdownCandidates(stmt.where, { tableAlias: join.table.alias }) : null;
|
|
37655
37746
|
const joinQ = joinPushDown !== null ? whereToKintone(joinPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
|
|
37656
37747
|
lines.push(` ${joinType}: APP${join.table.appId}${joinAliasStr} (${join.table.appId})`);
|
|
37657
37748
|
lines.push(` kintone query: ${joinQ}`);
|
|
37749
|
+
if (joinCandidate !== null) {
|
|
37750
|
+
lines.push(` pushdown candidate: ${whereToKintone(joinCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u78BA\u8A8D\u5F85\u3061\uFF09`);
|
|
37751
|
+
}
|
|
37658
37752
|
lines.push(` fields: ${joinFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : joinFields.join(", ")}`);
|
|
37659
37753
|
}
|
|
37660
37754
|
}
|
|
@@ -39982,7 +40076,7 @@ Options:
|
|
|
39982
40076
|
-h, --help Show help
|
|
39983
40077
|
`);
|
|
39984
40078
|
}
|
|
39985
|
-
var SERVER_VERSION = true ? "2.
|
|
40079
|
+
var SERVER_VERSION = true ? "2.3.0" : "0.0.0-dev";
|
|
39986
40080
|
function createServer(args) {
|
|
39987
40081
|
const server = new McpServer({
|
|
39988
40082
|
name: "ksql-mcp",
|
package/dist-mcpb/ksql-mcp.mcpb
CHANGED
|
Binary file
|