@rex0220/kintone-sql-tools 2.1.2 → 2.2.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 +124 -92
- package/dist-mcp/ksql-mcp.js +125 -93
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
package/dist-cli/ksql.js
CHANGED
|
@@ -2492,6 +2492,28 @@ function analyzeBatch(statements) {
|
|
|
2492
2492
|
};
|
|
2493
2493
|
}
|
|
2494
2494
|
|
|
2495
|
+
// src/core/scalarCompare.ts
|
|
2496
|
+
function compareScalarValues(op, leftStr, rightStr) {
|
|
2497
|
+
if (op === "=") return leftStr === rightStr;
|
|
2498
|
+
if (op === "!=" || op === "<>") return leftStr !== rightStr;
|
|
2499
|
+
const rightNum = Number(rightStr);
|
|
2500
|
+
if (leftStr === "" && rightStr !== "" && Number.isFinite(rightNum)) {
|
|
2501
|
+
return op === "<" || op === "<=";
|
|
2502
|
+
}
|
|
2503
|
+
const leftNum = Number(leftStr);
|
|
2504
|
+
const numeric = !Number.isNaN(leftNum) && !Number.isNaN(rightNum);
|
|
2505
|
+
switch (op) {
|
|
2506
|
+
case ">":
|
|
2507
|
+
return numeric ? leftNum > rightNum : leftStr > rightStr;
|
|
2508
|
+
case "<":
|
|
2509
|
+
return numeric ? leftNum < rightNum : leftStr < rightStr;
|
|
2510
|
+
case ">=":
|
|
2511
|
+
return numeric ? leftNum >= rightNum : leftStr >= rightStr;
|
|
2512
|
+
case "<=":
|
|
2513
|
+
return numeric ? leftNum <= rightNum : leftStr <= rightStr;
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2495
2517
|
// src/engine/pushDownNot.ts
|
|
2496
2518
|
function pushDownNot(expr) {
|
|
2497
2519
|
switch (expr.type) {
|
|
@@ -3466,24 +3488,7 @@ function evalOp(op, leftStr, right, row) {
|
|
|
3466
3488
|
return !matchLike(leftStr, pattern);
|
|
3467
3489
|
}
|
|
3468
3490
|
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
|
-
}
|
|
3491
|
+
return compareScalarValues(op, leftStr, rightStr);
|
|
3487
3492
|
}
|
|
3488
3493
|
function assertResolvedInListValues2(values) {
|
|
3489
3494
|
const unresolved = values.find((item) => item.type === "VARIABLE");
|
|
@@ -4086,60 +4091,59 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
|
|
|
4086
4091
|
}
|
|
4087
4092
|
|
|
4088
4093
|
// src/core/optimization/wherePredicatePushdown.ts
|
|
4089
|
-
function
|
|
4094
|
+
function extractSafePushdownLeaves(where, options = {}) {
|
|
4095
|
+
return extractAndLeaves(where, (expr) => isSafeComparison(expr, options));
|
|
4096
|
+
}
|
|
4097
|
+
function extractNumericPushdownCandidates(where, options = {}) {
|
|
4098
|
+
return extractAndLeaves(where, (expr) => isNumericCandidate(expr, options));
|
|
4099
|
+
}
|
|
4100
|
+
function extractAndLeaves(where, accept) {
|
|
4090
4101
|
switch (where.type) {
|
|
4091
4102
|
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;
|
|
4103
|
+
return accept(where) ? where : null;
|
|
4099
4104
|
case "LOGICAL":
|
|
4100
|
-
if (where.op
|
|
4101
|
-
|
|
4102
|
-
const
|
|
4105
|
+
if (where.op !== "AND") return null;
|
|
4106
|
+
{
|
|
4107
|
+
const left = extractAndLeaves(where.left, accept);
|
|
4108
|
+
const right = extractAndLeaves(where.right, accept);
|
|
4103
4109
|
if (left && right) return { ...where, left, right };
|
|
4104
4110
|
return left ?? right ?? null;
|
|
4105
4111
|
}
|
|
4106
|
-
return referencesOnlyTable(where, tableAlias) ? where : null;
|
|
4107
|
-
case "NOT":
|
|
4108
4112
|
case "GROUP":
|
|
4109
|
-
return
|
|
4113
|
+
return extractAndLeaves(where.expr, accept);
|
|
4114
|
+
case "NULL_CHECK":
|
|
4115
|
+
case "NOT":
|
|
4110
4116
|
case "EXISTS":
|
|
4111
4117
|
return null;
|
|
4112
4118
|
}
|
|
4113
4119
|
}
|
|
4114
|
-
function
|
|
4115
|
-
if (
|
|
4116
|
-
|
|
4120
|
+
function isSafeComparison(expr, options) {
|
|
4121
|
+
if (isSafeIdComparison(expr, options)) return true;
|
|
4122
|
+
if (!isNumericCandidate(expr, options)) return false;
|
|
4123
|
+
return options.fieldTypes?.get(expr.left.field) === "NUMBER";
|
|
4117
4124
|
}
|
|
4118
|
-
function
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
case "KINTONE_FUNC":
|
|
4123
|
-
case "IN_LIST":
|
|
4124
|
-
return true;
|
|
4125
|
-
default:
|
|
4126
|
-
return false;
|
|
4127
|
-
}
|
|
4125
|
+
function isSafeIdComparison(expr, options) {
|
|
4126
|
+
if (!isTargetIdField(expr.left, options)) return false;
|
|
4127
|
+
if (expr.right.type !== "NUMBER") return false;
|
|
4128
|
+
return expr.op === "=" || expr.op === ">" || expr.op === "<" || expr.op === ">=" || expr.op === "<=";
|
|
4128
4129
|
}
|
|
4129
|
-
function
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4130
|
+
function isTargetIdField(field, options) {
|
|
4131
|
+
if (field.type !== "FIELD" || field.field !== "$id") return false;
|
|
4132
|
+
const targetAlias = options.tableAlias ?? null;
|
|
4133
|
+
if (field.tableAlias === targetAlias) return true;
|
|
4134
|
+
return options.allowUnqualifiedFields === true && field.tableAlias === null;
|
|
4135
|
+
}
|
|
4136
|
+
function isNumericCandidate(expr, options) {
|
|
4137
|
+
if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
|
|
4138
|
+
if (!isTargetField(expr.left, options)) return false;
|
|
4139
|
+
if (expr.right.type !== "NUMBER") return false;
|
|
4140
|
+
if (expr.op === "=") return true;
|
|
4141
|
+
return (expr.op === "<" || expr.op === ">") && Number.isSafeInteger(expr.right.value);
|
|
4142
|
+
}
|
|
4143
|
+
function isTargetField(field, options) {
|
|
4144
|
+
const targetAlias = options.tableAlias ?? null;
|
|
4145
|
+
if (field.tableAlias === targetAlias) return true;
|
|
4146
|
+
return options.allowUnqualifiedFields === true && field.tableAlias === null;
|
|
4143
4147
|
}
|
|
4144
4148
|
|
|
4145
4149
|
// src/engine/process.ts
|
|
@@ -5087,7 +5091,7 @@ async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
|
5087
5091
|
}
|
|
5088
5092
|
const low = await evalAssertOperand(stmt.low, client, options, cacheContext, tempTables);
|
|
5089
5093
|
const high = await evalAssertOperand(stmt.high, client, options, cacheContext, tempTables);
|
|
5090
|
-
if (!
|
|
5094
|
+
if (!compareScalarValues(">=", left, low) || !compareScalarValues("<=", left, high)) {
|
|
5091
5095
|
throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
|
|
5092
5096
|
}
|
|
5093
5097
|
return { type: "ASSERT", condition: stmt.text };
|
|
@@ -5096,7 +5100,7 @@ async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
|
5096
5100
|
throw new Error("ArgumentError: malformed ASSERT statement.");
|
|
5097
5101
|
}
|
|
5098
5102
|
const right = await evalAssertOperand(stmt.right, client, options, cacheContext, tempTables);
|
|
5099
|
-
if (!
|
|
5103
|
+
if (!compareScalarValues(stmt.op, left, right)) {
|
|
5100
5104
|
throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
|
|
5101
5105
|
}
|
|
5102
5106
|
return { type: "ASSERT", condition: stmt.text };
|
|
@@ -5156,26 +5160,6 @@ function evalAssertArith(node) {
|
|
|
5156
5160
|
}
|
|
5157
5161
|
throw new Error(`ArgumentError: unsupported operand in ASSERT expression: ${node.type}`);
|
|
5158
5162
|
}
|
|
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
5163
|
async function executeSelect(stmt, client, options, cacheContext, cteCache) {
|
|
5180
5164
|
if (isNoFromSelect(stmt)) {
|
|
5181
5165
|
return executeNoFromSelect(stmt);
|
|
@@ -5309,6 +5293,44 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
|
|
|
5309
5293
|
}
|
|
5310
5294
|
}
|
|
5311
5295
|
}
|
|
5296
|
+
function extractMainSafePushdown(stmt, fieldTypes) {
|
|
5297
|
+
if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
|
|
5298
|
+
if (stmt.joins.length === 0) {
|
|
5299
|
+
return extractSafePushdownLeaves(stmt.where, {
|
|
5300
|
+
tableAlias: stmt.from.alias ?? void 0,
|
|
5301
|
+
allowUnqualifiedFields: true,
|
|
5302
|
+
fieldTypes
|
|
5303
|
+
});
|
|
5304
|
+
}
|
|
5305
|
+
if (!stmt.from.alias) return null;
|
|
5306
|
+
return extractSafePushdownLeaves(stmt.where, { tableAlias: stmt.from.alias, fieldTypes });
|
|
5307
|
+
}
|
|
5308
|
+
function extractMainNumericPushdownCandidate(stmt) {
|
|
5309
|
+
if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
|
|
5310
|
+
if (stmt.joins.length === 0) {
|
|
5311
|
+
return extractNumericPushdownCandidates(stmt.where, {
|
|
5312
|
+
tableAlias: stmt.from.alias ?? void 0,
|
|
5313
|
+
allowUnqualifiedFields: true
|
|
5314
|
+
});
|
|
5315
|
+
}
|
|
5316
|
+
if (!stmt.from.alias) return null;
|
|
5317
|
+
return extractNumericPushdownCandidates(stmt.where, { tableAlias: stmt.from.alias });
|
|
5318
|
+
}
|
|
5319
|
+
async function loadNumericPushdownFieldTypes(stmt, client, cacheContext) {
|
|
5320
|
+
const appIds = /* @__PURE__ */ new Set();
|
|
5321
|
+
if (extractMainNumericPushdownCandidate(stmt) !== null) appIds.add(stmt.from.appId);
|
|
5322
|
+
if (stmt.where !== null) {
|
|
5323
|
+
for (const join2 of stmt.joins) {
|
|
5324
|
+
if (!join2.table.alias || join2.table.subtableCode || join2.table.cteName !== null) continue;
|
|
5325
|
+
const candidate = extractNumericPushdownCandidates(stmt.where, {
|
|
5326
|
+
tableAlias: join2.table.alias
|
|
5327
|
+
});
|
|
5328
|
+
if (candidate !== null) appIds.add(join2.table.appId);
|
|
5329
|
+
}
|
|
5330
|
+
}
|
|
5331
|
+
const entries = await Promise.all([...appIds].map(async (appId) => [appId, await getFieldTypeMap(appId, client, cacheContext)]));
|
|
5332
|
+
return new Map(entries);
|
|
5333
|
+
}
|
|
5312
5334
|
async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache) {
|
|
5313
5335
|
const maxRecords = options.maxRecords ?? 1e4;
|
|
5314
5336
|
const warnings = /* @__PURE__ */ new Set();
|
|
@@ -5317,20 +5339,22 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
5317
5339
|
resolveSubqueries(stmt.where, client, options, cacheContext, cteCache),
|
|
5318
5340
|
resolveSubqueries(stmt.having, client, options, cacheContext, cteCache)
|
|
5319
5341
|
]);
|
|
5342
|
+
const pushdownFieldTypes = await loadNumericPushdownFieldTypes(stmt, client, cacheContext);
|
|
5343
|
+
const mainPushDown = extractMainSafePushdown(
|
|
5344
|
+
stmt,
|
|
5345
|
+
pushdownFieldTypes.get(stmt.from.appId)
|
|
5346
|
+
);
|
|
5320
5347
|
const tableConditions = /* @__PURE__ */ new Map();
|
|
5321
5348
|
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
5349
|
for (const join2 of stmt.joins) {
|
|
5327
|
-
if (join2.table.alias)
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5350
|
+
if (!join2.table.alias || join2.table.subtableCode || join2.table.cteName !== null) continue;
|
|
5351
|
+
const cond = extractSafePushdownLeaves(stmt.where, {
|
|
5352
|
+
tableAlias: join2.table.alias,
|
|
5353
|
+
fieldTypes: pushdownFieldTypes.get(join2.table.appId)
|
|
5354
|
+
});
|
|
5355
|
+
if (cond) tableConditions.set(join2.table.alias, cond);
|
|
5331
5356
|
}
|
|
5332
5357
|
}
|
|
5333
|
-
const mainPushDown = stmt.from.alias ? tableConditions.get(stmt.from.alias) ?? null : null;
|
|
5334
5358
|
const mainFetch = fetchTableRecordsForFullScan(
|
|
5335
5359
|
stmt,
|
|
5336
5360
|
stmt.from,
|
|
@@ -6741,19 +6765,27 @@ function buildSelectPlan(stmt, label) {
|
|
|
6741
6765
|
} else {
|
|
6742
6766
|
const mainFields = selectToFetchAllFields(stmt, stmt.from);
|
|
6743
6767
|
const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
|
|
6744
|
-
const mainPushDown =
|
|
6768
|
+
const mainPushDown = extractMainSafePushdown(stmt);
|
|
6769
|
+
const mainCandidate = extractMainNumericPushdownCandidate(stmt);
|
|
6745
6770
|
const mainQ = mainPushDown !== null ? whereToKintone(mainPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
|
|
6746
6771
|
lines.push(` app: APP${stmt.from.appId}${mainAliasStr} (${stmt.from.appId})`);
|
|
6747
6772
|
lines.push(` kintone query: ${mainQ}`);
|
|
6773
|
+
if (mainCandidate !== null) {
|
|
6774
|
+
lines.push(` pushdown candidate: ${whereToKintone(mainCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u78BA\u8A8D\u5F85\u3061\uFF09`);
|
|
6775
|
+
}
|
|
6748
6776
|
lines.push(` fields: ${mainFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : mainFields.join(", ")}`);
|
|
6749
6777
|
for (const join2 of stmt.joins) {
|
|
6750
6778
|
const joinFields = selectToFetchAllFields(stmt, join2.table);
|
|
6751
6779
|
const joinAliasStr = join2.table.alias ? ` AS ${join2.table.alias}` : "";
|
|
6752
6780
|
const joinType = join2.type === "INNER" ? "JOIN" : `${join2.type} JOIN`;
|
|
6753
|
-
const joinPushDown = join2.table.alias && stmt.where ?
|
|
6781
|
+
const joinPushDown = join2.table.alias && !join2.table.subtableCode && join2.table.cteName === null && stmt.where ? extractSafePushdownLeaves(stmt.where, { tableAlias: join2.table.alias }) : null;
|
|
6782
|
+
const joinCandidate = join2.table.alias && !join2.table.subtableCode && join2.table.cteName === null && stmt.where ? extractNumericPushdownCandidates(stmt.where, { tableAlias: join2.table.alias }) : null;
|
|
6754
6783
|
const joinQ = joinPushDown !== null ? whereToKintone(joinPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
|
|
6755
6784
|
lines.push(` ${joinType}: APP${join2.table.appId}${joinAliasStr} (${join2.table.appId})`);
|
|
6756
6785
|
lines.push(` kintone query: ${joinQ}`);
|
|
6786
|
+
if (joinCandidate !== null) {
|
|
6787
|
+
lines.push(` pushdown candidate: ${whereToKintone(joinCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u78BA\u8A8D\u5F85\u3061\uFF09`);
|
|
6788
|
+
}
|
|
6757
6789
|
lines.push(` fields: ${joinFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : joinFields.join(", ")}`);
|
|
6758
6790
|
}
|
|
6759
6791
|
}
|
package/dist-mcp/ksql-mcp.js
CHANGED
|
@@ -33393,6 +33393,28 @@ function analyzeBatch(statements) {
|
|
|
33393
33393
|
};
|
|
33394
33394
|
}
|
|
33395
33395
|
|
|
33396
|
+
// src/core/scalarCompare.ts
|
|
33397
|
+
function compareScalarValues(op, leftStr, rightStr) {
|
|
33398
|
+
if (op === "=") return leftStr === rightStr;
|
|
33399
|
+
if (op === "!=" || op === "<>") return leftStr !== rightStr;
|
|
33400
|
+
const rightNum = Number(rightStr);
|
|
33401
|
+
if (leftStr === "" && rightStr !== "" && Number.isFinite(rightNum)) {
|
|
33402
|
+
return op === "<" || op === "<=";
|
|
33403
|
+
}
|
|
33404
|
+
const leftNum = Number(leftStr);
|
|
33405
|
+
const numeric = !Number.isNaN(leftNum) && !Number.isNaN(rightNum);
|
|
33406
|
+
switch (op) {
|
|
33407
|
+
case ">":
|
|
33408
|
+
return numeric ? leftNum > rightNum : leftStr > rightStr;
|
|
33409
|
+
case "<":
|
|
33410
|
+
return numeric ? leftNum < rightNum : leftStr < rightStr;
|
|
33411
|
+
case ">=":
|
|
33412
|
+
return numeric ? leftNum >= rightNum : leftStr >= rightStr;
|
|
33413
|
+
case "<=":
|
|
33414
|
+
return numeric ? leftNum <= rightNum : leftStr <= rightStr;
|
|
33415
|
+
}
|
|
33416
|
+
}
|
|
33417
|
+
|
|
33396
33418
|
// src/engine/pushDownNot.ts
|
|
33397
33419
|
function pushDownNot(expr) {
|
|
33398
33420
|
switch (expr.type) {
|
|
@@ -34367,24 +34389,7 @@ function evalOp(op, leftStr, right, row) {
|
|
|
34367
34389
|
return !matchLike(leftStr, pattern);
|
|
34368
34390
|
}
|
|
34369
34391
|
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
|
-
}
|
|
34392
|
+
return compareScalarValues(op, leftStr, rightStr);
|
|
34388
34393
|
}
|
|
34389
34394
|
function assertResolvedInListValues2(values) {
|
|
34390
34395
|
const unresolved = values.find((item) => item.type === "VARIABLE");
|
|
@@ -34987,60 +34992,59 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
|
|
|
34987
34992
|
}
|
|
34988
34993
|
|
|
34989
34994
|
// src/core/optimization/wherePredicatePushdown.ts
|
|
34990
|
-
function
|
|
34995
|
+
function extractSafePushdownLeaves(where, options = {}) {
|
|
34996
|
+
return extractAndLeaves(where, (expr) => isSafeComparison(expr, options));
|
|
34997
|
+
}
|
|
34998
|
+
function extractNumericPushdownCandidates(where, options = {}) {
|
|
34999
|
+
return extractAndLeaves(where, (expr) => isNumericCandidate(expr, options));
|
|
35000
|
+
}
|
|
35001
|
+
function extractAndLeaves(where, accept) {
|
|
34991
35002
|
switch (where.type) {
|
|
34992
35003
|
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;
|
|
35004
|
+
return accept(where) ? where : null;
|
|
35000
35005
|
case "LOGICAL":
|
|
35001
|
-
if (where.op
|
|
35002
|
-
|
|
35003
|
-
const
|
|
35006
|
+
if (where.op !== "AND") return null;
|
|
35007
|
+
{
|
|
35008
|
+
const left = extractAndLeaves(where.left, accept);
|
|
35009
|
+
const right = extractAndLeaves(where.right, accept);
|
|
35004
35010
|
if (left && right) return { ...where, left, right };
|
|
35005
35011
|
return left ?? right ?? null;
|
|
35006
35012
|
}
|
|
35007
|
-
return referencesOnlyTable(where, tableAlias) ? where : null;
|
|
35008
|
-
case "NOT":
|
|
35009
35013
|
case "GROUP":
|
|
35010
|
-
return
|
|
35014
|
+
return extractAndLeaves(where.expr, accept);
|
|
35015
|
+
case "NULL_CHECK":
|
|
35016
|
+
case "NOT":
|
|
35011
35017
|
case "EXISTS":
|
|
35012
35018
|
return null;
|
|
35013
35019
|
}
|
|
35014
35020
|
}
|
|
35015
|
-
function
|
|
35016
|
-
if (
|
|
35017
|
-
|
|
35021
|
+
function isSafeComparison(expr, options) {
|
|
35022
|
+
if (isSafeIdComparison(expr, options)) return true;
|
|
35023
|
+
if (!isNumericCandidate(expr, options)) return false;
|
|
35024
|
+
return options.fieldTypes?.get(expr.left.field) === "NUMBER";
|
|
35018
35025
|
}
|
|
35019
|
-
function
|
|
35020
|
-
|
|
35021
|
-
|
|
35022
|
-
|
|
35023
|
-
case "KINTONE_FUNC":
|
|
35024
|
-
case "IN_LIST":
|
|
35025
|
-
return true;
|
|
35026
|
-
default:
|
|
35027
|
-
return false;
|
|
35028
|
-
}
|
|
35026
|
+
function isSafeIdComparison(expr, options) {
|
|
35027
|
+
if (!isTargetIdField(expr.left, options)) return false;
|
|
35028
|
+
if (expr.right.type !== "NUMBER") return false;
|
|
35029
|
+
return expr.op === "=" || expr.op === ">" || expr.op === "<" || expr.op === ">=" || expr.op === "<=";
|
|
35029
35030
|
}
|
|
35030
|
-
function
|
|
35031
|
-
|
|
35032
|
-
|
|
35033
|
-
|
|
35034
|
-
|
|
35035
|
-
|
|
35036
|
-
|
|
35037
|
-
|
|
35038
|
-
|
|
35039
|
-
|
|
35040
|
-
|
|
35041
|
-
|
|
35042
|
-
|
|
35043
|
-
|
|
35031
|
+
function isTargetIdField(field, options) {
|
|
35032
|
+
if (field.type !== "FIELD" || field.field !== "$id") return false;
|
|
35033
|
+
const targetAlias = options.tableAlias ?? null;
|
|
35034
|
+
if (field.tableAlias === targetAlias) return true;
|
|
35035
|
+
return options.allowUnqualifiedFields === true && field.tableAlias === null;
|
|
35036
|
+
}
|
|
35037
|
+
function isNumericCandidate(expr, options) {
|
|
35038
|
+
if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
|
|
35039
|
+
if (!isTargetField(expr.left, options)) return false;
|
|
35040
|
+
if (expr.right.type !== "NUMBER") return false;
|
|
35041
|
+
if (expr.op === "=") return true;
|
|
35042
|
+
return (expr.op === "<" || expr.op === ">") && Number.isSafeInteger(expr.right.value);
|
|
35043
|
+
}
|
|
35044
|
+
function isTargetField(field, options) {
|
|
35045
|
+
const targetAlias = options.tableAlias ?? null;
|
|
35046
|
+
if (field.tableAlias === targetAlias) return true;
|
|
35047
|
+
return options.allowUnqualifiedFields === true && field.tableAlias === null;
|
|
35044
35048
|
}
|
|
35045
35049
|
|
|
35046
35050
|
// src/engine/process.ts
|
|
@@ -35988,7 +35992,7 @@ async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
|
35988
35992
|
}
|
|
35989
35993
|
const low = await evalAssertOperand(stmt.low, client, options, cacheContext, tempTables);
|
|
35990
35994
|
const high = await evalAssertOperand(stmt.high, client, options, cacheContext, tempTables);
|
|
35991
|
-
if (!
|
|
35995
|
+
if (!compareScalarValues(">=", left, low) || !compareScalarValues("<=", left, high)) {
|
|
35992
35996
|
throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
|
|
35993
35997
|
}
|
|
35994
35998
|
return { type: "ASSERT", condition: stmt.text };
|
|
@@ -35997,7 +36001,7 @@ async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
|
35997
36001
|
throw new Error("ArgumentError: malformed ASSERT statement.");
|
|
35998
36002
|
}
|
|
35999
36003
|
const right = await evalAssertOperand(stmt.right, client, options, cacheContext, tempTables);
|
|
36000
|
-
if (!
|
|
36004
|
+
if (!compareScalarValues(stmt.op, left, right)) {
|
|
36001
36005
|
throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
|
|
36002
36006
|
}
|
|
36003
36007
|
return { type: "ASSERT", condition: stmt.text };
|
|
@@ -36057,26 +36061,6 @@ function evalAssertArith(node) {
|
|
|
36057
36061
|
}
|
|
36058
36062
|
throw new Error(`ArgumentError: unsupported operand in ASSERT expression: ${node.type}`);
|
|
36059
36063
|
}
|
|
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
36064
|
async function executeSelect(stmt, client, options, cacheContext, cteCache) {
|
|
36081
36065
|
if (isNoFromSelect(stmt)) {
|
|
36082
36066
|
return executeNoFromSelect(stmt);
|
|
@@ -36210,6 +36194,44 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
|
|
|
36210
36194
|
}
|
|
36211
36195
|
}
|
|
36212
36196
|
}
|
|
36197
|
+
function extractMainSafePushdown(stmt, fieldTypes) {
|
|
36198
|
+
if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
|
|
36199
|
+
if (stmt.joins.length === 0) {
|
|
36200
|
+
return extractSafePushdownLeaves(stmt.where, {
|
|
36201
|
+
tableAlias: stmt.from.alias ?? void 0,
|
|
36202
|
+
allowUnqualifiedFields: true,
|
|
36203
|
+
fieldTypes
|
|
36204
|
+
});
|
|
36205
|
+
}
|
|
36206
|
+
if (!stmt.from.alias) return null;
|
|
36207
|
+
return extractSafePushdownLeaves(stmt.where, { tableAlias: stmt.from.alias, fieldTypes });
|
|
36208
|
+
}
|
|
36209
|
+
function extractMainNumericPushdownCandidate(stmt) {
|
|
36210
|
+
if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
|
|
36211
|
+
if (stmt.joins.length === 0) {
|
|
36212
|
+
return extractNumericPushdownCandidates(stmt.where, {
|
|
36213
|
+
tableAlias: stmt.from.alias ?? void 0,
|
|
36214
|
+
allowUnqualifiedFields: true
|
|
36215
|
+
});
|
|
36216
|
+
}
|
|
36217
|
+
if (!stmt.from.alias) return null;
|
|
36218
|
+
return extractNumericPushdownCandidates(stmt.where, { tableAlias: stmt.from.alias });
|
|
36219
|
+
}
|
|
36220
|
+
async function loadNumericPushdownFieldTypes(stmt, client, cacheContext) {
|
|
36221
|
+
const appIds = /* @__PURE__ */ new Set();
|
|
36222
|
+
if (extractMainNumericPushdownCandidate(stmt) !== null) appIds.add(stmt.from.appId);
|
|
36223
|
+
if (stmt.where !== null) {
|
|
36224
|
+
for (const join of stmt.joins) {
|
|
36225
|
+
if (!join.table.alias || join.table.subtableCode || join.table.cteName !== null) continue;
|
|
36226
|
+
const candidate = extractNumericPushdownCandidates(stmt.where, {
|
|
36227
|
+
tableAlias: join.table.alias
|
|
36228
|
+
});
|
|
36229
|
+
if (candidate !== null) appIds.add(join.table.appId);
|
|
36230
|
+
}
|
|
36231
|
+
}
|
|
36232
|
+
const entries = await Promise.all([...appIds].map(async (appId) => [appId, await getFieldTypeMap(appId, client, cacheContext)]));
|
|
36233
|
+
return new Map(entries);
|
|
36234
|
+
}
|
|
36213
36235
|
async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache) {
|
|
36214
36236
|
const maxRecords2 = options.maxRecords ?? 1e4;
|
|
36215
36237
|
const warnings = /* @__PURE__ */ new Set();
|
|
@@ -36218,20 +36240,22 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
36218
36240
|
resolveSubqueries(stmt.where, client, options, cacheContext, cteCache),
|
|
36219
36241
|
resolveSubqueries(stmt.having, client, options, cacheContext, cteCache)
|
|
36220
36242
|
]);
|
|
36243
|
+
const pushdownFieldTypes = await loadNumericPushdownFieldTypes(stmt, client, cacheContext);
|
|
36244
|
+
const mainPushDown = extractMainSafePushdown(
|
|
36245
|
+
stmt,
|
|
36246
|
+
pushdownFieldTypes.get(stmt.from.appId)
|
|
36247
|
+
);
|
|
36221
36248
|
const tableConditions = /* @__PURE__ */ new Map();
|
|
36222
36249
|
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
36250
|
for (const join of stmt.joins) {
|
|
36228
|
-
if (join.table.alias)
|
|
36229
|
-
|
|
36230
|
-
|
|
36231
|
-
|
|
36251
|
+
if (!join.table.alias || join.table.subtableCode || join.table.cteName !== null) continue;
|
|
36252
|
+
const cond = extractSafePushdownLeaves(stmt.where, {
|
|
36253
|
+
tableAlias: join.table.alias,
|
|
36254
|
+
fieldTypes: pushdownFieldTypes.get(join.table.appId)
|
|
36255
|
+
});
|
|
36256
|
+
if (cond) tableConditions.set(join.table.alias, cond);
|
|
36232
36257
|
}
|
|
36233
36258
|
}
|
|
36234
|
-
const mainPushDown = stmt.from.alias ? tableConditions.get(stmt.from.alias) ?? null : null;
|
|
36235
36259
|
const mainFetch = fetchTableRecordsForFullScan(
|
|
36236
36260
|
stmt,
|
|
36237
36261
|
stmt.from,
|
|
@@ -37642,19 +37666,27 @@ function buildSelectPlan(stmt, label) {
|
|
|
37642
37666
|
} else {
|
|
37643
37667
|
const mainFields = selectToFetchAllFields(stmt, stmt.from);
|
|
37644
37668
|
const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
|
|
37645
|
-
const mainPushDown =
|
|
37669
|
+
const mainPushDown = extractMainSafePushdown(stmt);
|
|
37670
|
+
const mainCandidate = extractMainNumericPushdownCandidate(stmt);
|
|
37646
37671
|
const mainQ = mainPushDown !== null ? whereToKintone(mainPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
|
|
37647
37672
|
lines.push(` app: APP${stmt.from.appId}${mainAliasStr} (${stmt.from.appId})`);
|
|
37648
37673
|
lines.push(` kintone query: ${mainQ}`);
|
|
37674
|
+
if (mainCandidate !== null) {
|
|
37675
|
+
lines.push(` pushdown candidate: ${whereToKintone(mainCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u78BA\u8A8D\u5F85\u3061\uFF09`);
|
|
37676
|
+
}
|
|
37649
37677
|
lines.push(` fields: ${mainFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : mainFields.join(", ")}`);
|
|
37650
37678
|
for (const join of stmt.joins) {
|
|
37651
37679
|
const joinFields = selectToFetchAllFields(stmt, join.table);
|
|
37652
37680
|
const joinAliasStr = join.table.alias ? ` AS ${join.table.alias}` : "";
|
|
37653
37681
|
const joinType = join.type === "INNER" ? "JOIN" : `${join.type} JOIN`;
|
|
37654
|
-
const joinPushDown = join.table.alias && stmt.where ?
|
|
37682
|
+
const joinPushDown = join.table.alias && !join.table.subtableCode && join.table.cteName === null && stmt.where ? extractSafePushdownLeaves(stmt.where, { tableAlias: join.table.alias }) : null;
|
|
37683
|
+
const joinCandidate = join.table.alias && !join.table.subtableCode && join.table.cteName === null && stmt.where ? extractNumericPushdownCandidates(stmt.where, { tableAlias: join.table.alias }) : null;
|
|
37655
37684
|
const joinQ = joinPushDown !== null ? whereToKintone(joinPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
|
|
37656
37685
|
lines.push(` ${joinType}: APP${join.table.appId}${joinAliasStr} (${join.table.appId})`);
|
|
37657
37686
|
lines.push(` kintone query: ${joinQ}`);
|
|
37687
|
+
if (joinCandidate !== null) {
|
|
37688
|
+
lines.push(` pushdown candidate: ${whereToKintone(joinCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u78BA\u8A8D\u5F85\u3061\uFF09`);
|
|
37689
|
+
}
|
|
37658
37690
|
lines.push(` fields: ${joinFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : joinFields.join(", ")}`);
|
|
37659
37691
|
}
|
|
37660
37692
|
}
|
|
@@ -39982,7 +40014,7 @@ Options:
|
|
|
39982
40014
|
-h, --help Show help
|
|
39983
40015
|
`);
|
|
39984
40016
|
}
|
|
39985
|
-
var SERVER_VERSION = true ? "2.
|
|
40017
|
+
var SERVER_VERSION = true ? "2.2.0" : "0.0.0-dev";
|
|
39986
40018
|
function createServer(args) {
|
|
39987
40019
|
const server = new McpServer({
|
|
39988
40020
|
name: "ksql-mcp",
|
package/dist-mcpb/ksql-mcp.mcpb
CHANGED
|
Binary file
|