@rex0220/kintone-sql-tools 2.11.0 → 2.12.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/README.md +1 -1
- package/dist-cli/ksql.js +323 -5
- package/dist-mcp/ksql-mcp.js +331 -11
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ kintone アプリを SQL 風の構文で操作するツールセットです。
|
|
|
9
9
|
## 機能概要
|
|
10
10
|
|
|
11
11
|
- `SELECT`(JOIN/GROUP BY/HAVING/CTE/UNION)
|
|
12
|
-
- `INSERT` / `UPDATE` / `UPSERT` / `DELETE` / `REORDER`(`--allow-dml` 必須)
|
|
12
|
+
- `INSERT` / `UPDATE` / `UPDATE ... FROM` / `UPSERT` / `DELETE` / `REORDER`(`--allow-dml` 必須)
|
|
13
13
|
- `EXPLAIN`
|
|
14
14
|
- **バッチ実行(`;` 区切りの複文)と一時テーブル `CREATE TEMP TABLE #t AS SELECT ...`**(v1.4.0)
|
|
15
15
|
- CLI / MCP: read-only バッチ + DML バッチ(一時テーブル経由の `INSERT ... SELECT` を含む)
|
package/dist-cli/ksql.js
CHANGED
|
@@ -2054,6 +2054,26 @@ var Parser = class {
|
|
|
2054
2054
|
const { appId, subtableCode } = extractTableRef(name, this.prev());
|
|
2055
2055
|
this.expect("SET" /* SET */);
|
|
2056
2056
|
const assignments = this.parseAssignments();
|
|
2057
|
+
let from = null;
|
|
2058
|
+
if (this.consume("FROM" /* FROM */)) {
|
|
2059
|
+
const table = this.parseTableRef();
|
|
2060
|
+
if (table.subtableCode) {
|
|
2061
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u306B\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u306F\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093", this.prev());
|
|
2062
|
+
}
|
|
2063
|
+
if (table.cteName !== null && !table.cteName.startsWith("#")) {
|
|
2064
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u306F #temp \u307E\u305F\u306F APP<n> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08CTE \u306F\u975E\u5BFE\u5FDC\uFF09", this.prev());
|
|
2065
|
+
}
|
|
2066
|
+
if (!table.alias) {
|
|
2067
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u306B\u306F\u30A8\u30A4\u30EA\u30A2\u30B9\u304C\u5FC5\u8981\u3067\u3059", this.prev());
|
|
2068
|
+
}
|
|
2069
|
+
from = {
|
|
2070
|
+
appId: table.appId,
|
|
2071
|
+
cteName: table.cteName,
|
|
2072
|
+
alias: table.alias,
|
|
2073
|
+
joinKeyField: "",
|
|
2074
|
+
targetFilter: null
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2057
2077
|
const whereTok = this.peek();
|
|
2058
2078
|
if (!this.consume("WHERE" /* WHERE */)) {
|
|
2059
2079
|
throw new ParseError(
|
|
@@ -2062,8 +2082,129 @@ var Parser = class {
|
|
|
2062
2082
|
);
|
|
2063
2083
|
}
|
|
2064
2084
|
const where = this.parseWhereExpr();
|
|
2085
|
+
if (from !== null) {
|
|
2086
|
+
if (subtableCode) {
|
|
2087
|
+
throw new ParseError("\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB UPDATE ... FROM \u306F\u30B5\u30DD\u30FC\u30C8\u3057\u3066\u3044\u307E\u305B\u3093", whereTok);
|
|
2088
|
+
}
|
|
2089
|
+
this.validateUpdateFromAssignments(assignments, from.alias, whereTok);
|
|
2090
|
+
const decomposed = this.decomposeUpdateFromWhere(where, appId, from.alias, whereTok);
|
|
2091
|
+
from.joinKeyField = decomposed.joinKeyField;
|
|
2092
|
+
from.targetFilter = decomposed.targetFilter;
|
|
2093
|
+
} else if (assignments.some((a) => a.value.type === "SOURCE_FIELD")) {
|
|
2094
|
+
throw new ParseError(
|
|
2095
|
+
"SET \u306E\u5024\u306B\u306F\u30EA\u30C6\u30E9\u30EB\u30FB\u7B97\u8853\u5F0F\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u306E\u307F\u306F\u4E0D\u53EF\uFF09",
|
|
2096
|
+
whereTok
|
|
2097
|
+
);
|
|
2098
|
+
}
|
|
2099
|
+
if (from !== null) return { type: "UPDATE", appId, assignments, where, from };
|
|
2065
2100
|
return subtableCode ? { type: "UPDATE", appId, subtableCode, assignments, where } : { type: "UPDATE", appId, assignments, where };
|
|
2066
2101
|
}
|
|
2102
|
+
validateUpdateFromAssignments(assignments, sourceAlias, tok) {
|
|
2103
|
+
for (const assignment of assignments) {
|
|
2104
|
+
if (assignment.value.type === "SOURCE_FIELD") {
|
|
2105
|
+
if (assignment.value.alias.toLowerCase() !== sourceAlias.toLowerCase()) {
|
|
2106
|
+
throw new ParseError(`UPDATE ... FROM \u306E SET \u53C2\u7167\u306F\u30BD\u30FC\u30B9 alias ${sourceAlias} \u3067\u4FEE\u98FE\u3057\u3066\u304F\u3060\u3055\u3044`, tok);
|
|
2107
|
+
}
|
|
2108
|
+
continue;
|
|
2109
|
+
}
|
|
2110
|
+
if (this.nodeContainsQualifiedField(assignment.value, sourceAlias)) {
|
|
2111
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u5217\u306F SET \u306E\u76F4\u63A5\u5024\u3068\u3057\u3066\u306E\u307F\u53C2\u7167\u3067\u304D\u307E\u3059", tok);
|
|
2112
|
+
}
|
|
2113
|
+
if (assignment.value.type === "SCALAR_SUBQUERY") {
|
|
2114
|
+
throw new ParseError("UPDATE ... FROM \u306E SET \u3067\u306F\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
|
|
2115
|
+
}
|
|
2116
|
+
if (this.nodeContainsAnyQualifier(assignment.value)) {
|
|
2117
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BF\u30FC\u30B2\u30C3\u30C8\u5F0F\u3067\u306F\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u4FEE\u98FE\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044", tok);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
decomposeUpdateFromWhere(where, targetAppId, sourceAlias, tok) {
|
|
2122
|
+
const leaves = this.flattenTopLevelAnd(where);
|
|
2123
|
+
const joins = [];
|
|
2124
|
+
leaves.forEach((leaf, index) => {
|
|
2125
|
+
const sourceField = this.matchUpdateFromJoin(leaf, targetAppId, sourceAlias);
|
|
2126
|
+
if (sourceField !== null) joins.push({ index, sourceField });
|
|
2127
|
+
});
|
|
2128
|
+
if (joins.length !== 1) {
|
|
2129
|
+
throw new ParseError("UPDATE ... FROM \u306E WHERE \u306B\u306F target.$id = source.key \u306E\u7D50\u5408\u7B49\u5024\u304C\u3061\u3087\u3046\u30691\u3064\u5FC5\u8981\u3067\u3059", tok);
|
|
2130
|
+
}
|
|
2131
|
+
const join2 = joins[0];
|
|
2132
|
+
for (let i = 0; i < leaves.length; i++) {
|
|
2133
|
+
if (i !== join2.index && this.nodeContainsQualifiedField(leaves[i], sourceAlias)) {
|
|
2134
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9 alias \u306F\u7D50\u5408\u7B49\u5024\u4EE5\u5916\u306E WHERE \u6761\u4EF6\u3067\u306F\u53C2\u7167\u3067\u304D\u307E\u305B\u3093", tok);
|
|
2135
|
+
}
|
|
2136
|
+
if (i !== join2.index && this.nodeContainsForeignQualifier(leaves[i], targetAppId)) {
|
|
2137
|
+
throw new ParseError(`UPDATE ... FROM \u306E\u30BF\u30FC\u30B2\u30C3\u30C8\u30D5\u30A3\u30EB\u30BF\u306F APP${targetAppId} \u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u3060\u3051\u3092\u53C2\u7167\u3067\u304D\u307E\u3059`, tok);
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
const filters = leaves.filter((_, index) => index !== join2.index);
|
|
2141
|
+
const targetFilter = filters.reduce(
|
|
2142
|
+
(acc, expr) => acc === null ? expr : { type: "LOGICAL", op: "AND", left: acc, right: expr },
|
|
2143
|
+
null
|
|
2144
|
+
);
|
|
2145
|
+
return { joinKeyField: join2.sourceField, targetFilter };
|
|
2146
|
+
}
|
|
2147
|
+
flattenTopLevelAnd(expr) {
|
|
2148
|
+
if (expr.type === "GROUP") return this.flattenTopLevelAnd(expr.expr);
|
|
2149
|
+
if (expr.type === "LOGICAL" && expr.op === "AND") {
|
|
2150
|
+
return [...this.flattenTopLevelAnd(expr.left), ...this.flattenTopLevelAnd(expr.right)];
|
|
2151
|
+
}
|
|
2152
|
+
return [expr];
|
|
2153
|
+
}
|
|
2154
|
+
matchUpdateFromJoin(expr, targetAppId, sourceAlias) {
|
|
2155
|
+
if (expr.type !== "BINARY" || expr.op !== "=" || expr.left.type !== "FIELD") return null;
|
|
2156
|
+
const right = expr.right.type === "ARITH_VALUE" && expr.right.expr.type === "FIELD_REF" ? this.splitQualifiedField(expr.right.expr.field) : null;
|
|
2157
|
+
if (right === null) return null;
|
|
2158
|
+
const left = { alias: expr.left.tableAlias, field: expr.left.field };
|
|
2159
|
+
if (this.isTargetIdRef(left, targetAppId) && this.isSourceRef(right, sourceAlias)) return right.field;
|
|
2160
|
+
if (this.isSourceRef(left, sourceAlias) && this.isTargetIdRef(right, targetAppId)) return left.field;
|
|
2161
|
+
return null;
|
|
2162
|
+
}
|
|
2163
|
+
splitQualifiedField(field) {
|
|
2164
|
+
const dot = field.indexOf(".");
|
|
2165
|
+
return dot < 0 ? { alias: null, field } : { alias: field.slice(0, dot), field: field.slice(dot + 1) };
|
|
2166
|
+
}
|
|
2167
|
+
isTargetIdRef(ref, appId) {
|
|
2168
|
+
return ref.field === "$id" && (ref.alias === null || ref.alias.toLowerCase() === `app${appId}`.toLowerCase());
|
|
2169
|
+
}
|
|
2170
|
+
isSourceRef(ref, alias) {
|
|
2171
|
+
return ref.alias?.toLowerCase() === alias.toLowerCase();
|
|
2172
|
+
}
|
|
2173
|
+
nodeContainsQualifiedField(node, alias) {
|
|
2174
|
+
if (Array.isArray(node)) return node.some((v) => this.nodeContainsQualifiedField(v, alias));
|
|
2175
|
+
if (node === null || typeof node !== "object") return false;
|
|
2176
|
+
const obj = node;
|
|
2177
|
+
if (obj["type"] === "FIELD" && typeof obj["tableAlias"] === "string" && obj["tableAlias"].toLowerCase() === alias.toLowerCase()) return true;
|
|
2178
|
+
if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") {
|
|
2179
|
+
const ref = this.splitQualifiedField(obj["field"]);
|
|
2180
|
+
if (this.isSourceRef(ref, alias)) return true;
|
|
2181
|
+
}
|
|
2182
|
+
return Object.values(obj).some((v) => this.nodeContainsQualifiedField(v, alias));
|
|
2183
|
+
}
|
|
2184
|
+
nodeContainsForeignQualifier(node, targetAppId) {
|
|
2185
|
+
if (Array.isArray(node)) return node.some((v) => this.nodeContainsForeignQualifier(v, targetAppId));
|
|
2186
|
+
if (node === null || typeof node !== "object") return false;
|
|
2187
|
+
const obj = node;
|
|
2188
|
+
const expected = `app${targetAppId}`.toLowerCase();
|
|
2189
|
+
if (obj["type"] === "FIELD" && typeof obj["tableAlias"] === "string") {
|
|
2190
|
+
return obj["tableAlias"].toLowerCase() !== expected;
|
|
2191
|
+
}
|
|
2192
|
+
if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") {
|
|
2193
|
+
const ref = this.splitQualifiedField(obj["field"]);
|
|
2194
|
+
if (ref.alias !== null) return ref.alias.toLowerCase() !== expected;
|
|
2195
|
+
}
|
|
2196
|
+
return Object.values(obj).some((v) => this.nodeContainsForeignQualifier(v, targetAppId));
|
|
2197
|
+
}
|
|
2198
|
+
nodeContainsAnyQualifier(node) {
|
|
2199
|
+
if (Array.isArray(node)) return node.some((v) => this.nodeContainsAnyQualifier(v));
|
|
2200
|
+
if (node === null || typeof node !== "object") return false;
|
|
2201
|
+
const obj = node;
|
|
2202
|
+
if (obj["type"] === "FIELD" && typeof obj["tableAlias"] === "string") return true;
|
|
2203
|
+
if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") {
|
|
2204
|
+
if (this.splitQualifiedField(obj["field"]).alias !== null) return true;
|
|
2205
|
+
}
|
|
2206
|
+
return Object.values(obj).some((v) => this.nodeContainsAnyQualifier(v));
|
|
2207
|
+
}
|
|
2067
2208
|
parseAssignments() {
|
|
2068
2209
|
const assignments = [];
|
|
2069
2210
|
do {
|
|
@@ -2098,6 +2239,12 @@ var Parser = class {
|
|
|
2098
2239
|
const node = this.parseArithAddSub();
|
|
2099
2240
|
if (node.type === "NUMBER") return node;
|
|
2100
2241
|
if (node.type === "ARITH") return node;
|
|
2242
|
+
if (node.type === "FIELD_REF") {
|
|
2243
|
+
const dot = node.field.indexOf(".");
|
|
2244
|
+
if (dot > 0 && dot < node.field.length - 1) {
|
|
2245
|
+
return { type: "SOURCE_FIELD", alias: node.field.slice(0, dot), field: node.field.slice(dot + 1) };
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2101
2248
|
throw new ParseError(
|
|
2102
2249
|
"SET \u306E\u5024\u306B\u306F\u30EA\u30C6\u30E9\u30EB\u30FB\u7B97\u8853\u5F0F\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u306E\u307F\u306F\u4E0D\u53EF\uFF09",
|
|
2103
2250
|
tok
|
|
@@ -3685,7 +3832,8 @@ function analyzeBatch(statements) {
|
|
|
3685
3832
|
tempTablesDropped: dropped,
|
|
3686
3833
|
dependsOn: [...dependsOn].sort((a, b) => a - b),
|
|
3687
3834
|
tempOnlySource,
|
|
3688
|
-
targetAppId: isDmlType(statementType) && typeof stmt.appId === "number" ? stmt.appId : null
|
|
3835
|
+
targetAppId: isDmlType(statementType) && typeof stmt.appId === "number" ? stmt.appId : null,
|
|
3836
|
+
isUpdateFrom: stmt.type === "UPDATE" && stmt.from != null
|
|
3689
3837
|
});
|
|
3690
3838
|
});
|
|
3691
3839
|
const containsDml = results.some((r) => r.isDml);
|
|
@@ -4230,7 +4378,7 @@ function updateToPutBatches(stmt, ids, fieldTypes = /* @__PURE__ */ new Map()) {
|
|
|
4230
4378
|
function buildUpdateRecord(assignments, fieldTypes) {
|
|
4231
4379
|
const record = {};
|
|
4232
4380
|
for (const { field, value } of assignments) {
|
|
4233
|
-
if (value.type === "ARITH" || value.type === "CASE_VALUE") continue;
|
|
4381
|
+
if (value.type === "ARITH" || value.type === "CASE_VALUE" || value.type === "SOURCE_FIELD") continue;
|
|
4234
4382
|
record[field] = { value: toKintoneValue(value, fieldTypes.get(field)) };
|
|
4235
4383
|
}
|
|
4236
4384
|
return record;
|
|
@@ -4334,6 +4482,8 @@ function updateToPutBatchesArith(stmt, records, fieldTypes = /* @__PURE__ */ new
|
|
|
4334
4482
|
record[field] = { value: String(evalArith(value, raw)) };
|
|
4335
4483
|
} else if (value.type === "CASE_VALUE") {
|
|
4336
4484
|
record[field] = { value: evalCaseWhenValue(value.expr, row, fieldTypes.get(field)) };
|
|
4485
|
+
} else if (value.type === "SOURCE_FIELD") {
|
|
4486
|
+
throw new DmlConvertError("SOURCE_FIELD \u306F UPDATE ... FROM \u5C02\u7528\u3067\u3059");
|
|
4337
4487
|
} else {
|
|
4338
4488
|
record[field] = { value: toKintoneValue(value, fieldTypes.get(field)) };
|
|
4339
4489
|
}
|
|
@@ -4345,6 +4495,51 @@ function updateToPutBatchesArith(stmt, records, fieldTypes = /* @__PURE__ */ new
|
|
|
4345
4495
|
records: batch
|
|
4346
4496
|
}));
|
|
4347
4497
|
}
|
|
4498
|
+
var UPDATE_FROM_UNSUPPORTED_TYPES = /* @__PURE__ */ new Set([
|
|
4499
|
+
"CHECK_BOX",
|
|
4500
|
+
"MULTI_SELECT",
|
|
4501
|
+
"USER_SELECT",
|
|
4502
|
+
"ORGANIZATION_SELECT",
|
|
4503
|
+
"GROUP_SELECT",
|
|
4504
|
+
"FILE"
|
|
4505
|
+
]);
|
|
4506
|
+
function updateFromToPutBatches(stmt, matched, fieldTypes = /* @__PURE__ */ new Map()) {
|
|
4507
|
+
const updateRecords = matched.map(({ target, source }) => {
|
|
4508
|
+
const id = Number(target["$id"]?.value);
|
|
4509
|
+
if (!Number.isSafeInteger(id) || id <= 0) {
|
|
4510
|
+
throw new DmlConvertError("UPDATE ... FROM \u306E\u5BFE\u8C61\u30EC\u30B3\u30FC\u30C9\u756A\u53F7\u304C\u4E0D\u6B63\u3067\u3059");
|
|
4511
|
+
}
|
|
4512
|
+
const targetRow = kintoneRecordToProcessRow(target);
|
|
4513
|
+
const record = {};
|
|
4514
|
+
for (const { field, value } of stmt.assignments) {
|
|
4515
|
+
const fieldType = fieldTypes.get(field);
|
|
4516
|
+
if (value.type === "SOURCE_FIELD") {
|
|
4517
|
+
if (UPDATE_FROM_UNSUPPORTED_TYPES.has(fieldType ?? "")) {
|
|
4518
|
+
throw new DmlConvertError(`UPDATE ... FROM \u306E SOURCE_FIELD \u306F ${fieldType} \u30D5\u30A3\u30FC\u30EB\u30C9\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9: ${field}\uFF09`);
|
|
4519
|
+
}
|
|
4520
|
+
if (!Object.prototype.hasOwnProperty.call(source, value.field)) {
|
|
4521
|
+
throw new DmlConvertError(`UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u5217 ${value.field} \u304C\u5B58\u5728\u3057\u307E\u305B\u3093`);
|
|
4522
|
+
}
|
|
4523
|
+
const raw = source[value.field];
|
|
4524
|
+
if (typeof raw !== "string") {
|
|
4525
|
+
throw new DmlConvertError(`UPDATE ... FROM \u306E SOURCE_FIELD \u306F\u30B9\u30AB\u30E9\u30FC\u5024\u306E\u307F\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u3059\uFF08\u5217: ${value.field}\uFF09`);
|
|
4526
|
+
}
|
|
4527
|
+
if ((fieldType === "NUMBER" || fieldType === "CALC") && raw !== "" && !Number.isFinite(Number(raw))) {
|
|
4528
|
+
throw new DmlConvertError(`\u6570\u5024\u30D5\u30A3\u30FC\u30EB\u30C9 ${field} \u306B\u5909\u63DB\u3067\u304D\u306A\u3044\u5024\u3067\u3059: ${raw}`);
|
|
4529
|
+
}
|
|
4530
|
+
record[field] = { value: toKintoneValue({ type: "STRING", value: raw }, fieldType) };
|
|
4531
|
+
} else if (value.type === "ARITH") {
|
|
4532
|
+
record[field] = { value: String(evalArith(value, target)) };
|
|
4533
|
+
} else if (value.type === "CASE_VALUE") {
|
|
4534
|
+
record[field] = { value: evalCaseWhenValue(value.expr, targetRow, fieldType) };
|
|
4535
|
+
} else {
|
|
4536
|
+
record[field] = { value: toKintoneValue(value, fieldType) };
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
4539
|
+
return { id, record };
|
|
4540
|
+
});
|
|
4541
|
+
return chunk(updateRecords, 100).map((records) => ({ app: stmt.appId, records }));
|
|
4542
|
+
}
|
|
4348
4543
|
function kintoneRecordToProcessRow(raw) {
|
|
4349
4544
|
return Object.fromEntries(
|
|
4350
4545
|
Object.entries(raw).map(([k, v]) => [
|
|
@@ -5459,6 +5654,8 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
5459
5654
|
for (const s of analysis.statements) {
|
|
5460
5655
|
if (!s.isDml || s.tempTablesReferenced.length === 0) continue;
|
|
5461
5656
|
if (s.statementType === "INSERT_SELECT" || s.statementType === "UPSERT_SELECT") continue;
|
|
5657
|
+
const parsed = statements[s.index];
|
|
5658
|
+
if (parsed?.type === "UPDATE" && parsed.from?.cteName != null) continue;
|
|
5462
5659
|
throw new BatchAnalysisError(
|
|
5463
5660
|
`ArgumentError: temp table references in ${s.statementType} are not supported yet.`,
|
|
5464
5661
|
s.index
|
|
@@ -5615,6 +5812,9 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
5615
5812
|
if (resolvedStmt.type === "UPSERT_SELECT") {
|
|
5616
5813
|
return { result: await executeUpsertSelect(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
5617
5814
|
}
|
|
5815
|
+
if (resolvedStmt.type === "UPDATE" && resolvedStmt.from?.cteName != null) {
|
|
5816
|
+
return { result: await executeUpdate(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
5817
|
+
}
|
|
5618
5818
|
throw new Error(`ArgumentError: temp table references in ${stmt.type} are not supported yet.`);
|
|
5619
5819
|
}
|
|
5620
5820
|
return { result: await executeParsedStatement(resolvedStmt, client, options, cacheContext) };
|
|
@@ -6800,10 +7000,13 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
6800
7000
|
insertedCount: createdIds.flat().length
|
|
6801
7001
|
};
|
|
6802
7002
|
}
|
|
6803
|
-
async function executeUpdate(stmt, client, options, cacheContext) {
|
|
7003
|
+
async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
6804
7004
|
if (stmt.subtableCode) {
|
|
6805
7005
|
return executeUpdateSubtable(stmt, client, options, cacheContext);
|
|
6806
7006
|
}
|
|
7007
|
+
if (stmt.from != null) {
|
|
7008
|
+
return executeUpdateFrom(stmt, stmt.from, client, options, cacheContext, tempTables);
|
|
7009
|
+
}
|
|
6807
7010
|
const maxRecords = options.maxRecords ?? 1e4;
|
|
6808
7011
|
await resolveSetSubqueries(stmt.assignments, client, options, cacheContext);
|
|
6809
7012
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
@@ -6845,6 +7048,113 @@ async function executeUpdate(stmt, client, options, cacheContext) {
|
|
|
6845
7048
|
}
|
|
6846
7049
|
return { type: "UPDATE", updatedCount: ids.length };
|
|
6847
7050
|
}
|
|
7051
|
+
var UPDATE_FROM_ID_CHUNK_SIZE = 50;
|
|
7052
|
+
var UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES = /* @__PURE__ */ new Set([
|
|
7053
|
+
"CHECK_BOX",
|
|
7054
|
+
"MULTI_SELECT",
|
|
7055
|
+
"USER_SELECT",
|
|
7056
|
+
"ORGANIZATION_SELECT",
|
|
7057
|
+
"GROUP_SELECT",
|
|
7058
|
+
"FILE"
|
|
7059
|
+
]);
|
|
7060
|
+
async function executeUpdateFrom(stmt, from, client, options, cacheContext, tempTables) {
|
|
7061
|
+
const sourceFields = [...new Set(stmt.assignments.filter((a) => a.value.type === "SOURCE_FIELD").map((a) => a.value.type === "SOURCE_FIELD" ? a.value.field : ""))];
|
|
7062
|
+
const requiredSourceFields = [.../* @__PURE__ */ new Set([from.joinKeyField, ...sourceFields])];
|
|
7063
|
+
let sourceRows;
|
|
7064
|
+
if (from.cteName !== null) {
|
|
7065
|
+
const table = tempTables?.get(from.cteName);
|
|
7066
|
+
if (!table) throw new Error(`ArgumentError: temp table ${from.cteName} is not available.`);
|
|
7067
|
+
for (const field of requiredSourceFields) {
|
|
7068
|
+
if (!table.columns.includes(field)) {
|
|
7069
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${field} does not exist.`);
|
|
7070
|
+
}
|
|
7071
|
+
}
|
|
7072
|
+
sourceRows = table.rows;
|
|
7073
|
+
} else {
|
|
7074
|
+
const sourceTypes = await getFieldTypeMap(from.appId, client, cacheContext);
|
|
7075
|
+
for (const field of requiredSourceFields) {
|
|
7076
|
+
if (field !== "$id" && !sourceTypes.has(field)) {
|
|
7077
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${field} does not exist.`);
|
|
7078
|
+
}
|
|
7079
|
+
const type = sourceTypes.get(field);
|
|
7080
|
+
if (UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES.has(type ?? "")) {
|
|
7081
|
+
throw new Error(`ArgumentError: UPDATE ... FROM does not support source field type ${type} (${field}).`);
|
|
7082
|
+
}
|
|
7083
|
+
}
|
|
7084
|
+
const maxRecords = options.maxRecords ?? 1e4;
|
|
7085
|
+
const resolved = await fetchRecordsForSharedPlan(
|
|
7086
|
+
client.getRecords,
|
|
7087
|
+
from.appId,
|
|
7088
|
+
"",
|
|
7089
|
+
requiredSourceFields,
|
|
7090
|
+
{ maxRecords, parallel: options.fetchParallel ?? 1, onLimit: "error" }
|
|
7091
|
+
);
|
|
7092
|
+
sourceRows = resolved.records.map((record) => flatten(record, null));
|
|
7093
|
+
}
|
|
7094
|
+
const sourceById = /* @__PURE__ */ new Map();
|
|
7095
|
+
for (const row of sourceRows) {
|
|
7096
|
+
if (!Object.prototype.hasOwnProperty.call(row, from.joinKeyField)) {
|
|
7097
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${from.joinKeyField} does not exist.`);
|
|
7098
|
+
}
|
|
7099
|
+
const raw = row[from.joinKeyField];
|
|
7100
|
+
const text = typeof raw === "string" ? raw.trim() : "";
|
|
7101
|
+
const id = Number(text);
|
|
7102
|
+
if (text === "" || !Number.isSafeInteger(id) || id <= 0) {
|
|
7103
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source key must be a positive safe integer: ${String(raw)}`);
|
|
7104
|
+
}
|
|
7105
|
+
if (sourceById.has(id)) {
|
|
7106
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source has multiple rows for target $id ${id}.`);
|
|
7107
|
+
}
|
|
7108
|
+
sourceById.set(id, row);
|
|
7109
|
+
}
|
|
7110
|
+
const targetIds = [...sourceById.keys()];
|
|
7111
|
+
const targetFields = collectUpdateFromTargetFields(stmt);
|
|
7112
|
+
const filterQuery = from.targetFilter === null ? "" : updateToGetQuery({ ...stmt, from: null, where: from.targetFilter }).query;
|
|
7113
|
+
const targetRecords = [];
|
|
7114
|
+
for (const ids of splitChunks(targetIds, UPDATE_FROM_ID_CHUNK_SIZE)) {
|
|
7115
|
+
const idQuery = `$id in (${ids.map((id) => sqlQuote(String(id))).join(",")})`;
|
|
7116
|
+
const query = filterQuery ? `(${idQuery}) and (${filterQuery})` : idQuery;
|
|
7117
|
+
const resolved = await fetchRecordsForSharedPlan(
|
|
7118
|
+
client.getRecords,
|
|
7119
|
+
stmt.appId,
|
|
7120
|
+
query,
|
|
7121
|
+
targetFields,
|
|
7122
|
+
{ maxRecords: Math.max(ids.length, 1), parallel: options.fetchParallel ?? 1, onLimit: "error" }
|
|
7123
|
+
);
|
|
7124
|
+
targetRecords.push(...resolved.records);
|
|
7125
|
+
}
|
|
7126
|
+
if (options.confirm) {
|
|
7127
|
+
const ok = await options.confirm(targetRecords.length, "UPDATE");
|
|
7128
|
+
if (!ok) throw new OperationCancelledError("UPDATE", targetRecords.length);
|
|
7129
|
+
}
|
|
7130
|
+
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
7131
|
+
const matched = targetRecords.map((target) => {
|
|
7132
|
+
const id = Number(target["$id"]?.value);
|
|
7133
|
+
const source = sourceById.get(id);
|
|
7134
|
+
if (!source) throw new Error(`ArgumentError: UPDATE ... FROM could not resolve source row for target $id ${id}.`);
|
|
7135
|
+
return { target, source };
|
|
7136
|
+
});
|
|
7137
|
+
const batches = updateFromToPutBatches(stmt, matched, fieldTypes);
|
|
7138
|
+
for (const batch of batches) await client.putRecords(batch);
|
|
7139
|
+
return { type: "UPDATE", updatedCount: targetRecords.length };
|
|
7140
|
+
}
|
|
7141
|
+
function collectUpdateFromTargetFields(stmt) {
|
|
7142
|
+
const fields = /* @__PURE__ */ new Set(["$id"]);
|
|
7143
|
+
const visit = (node) => {
|
|
7144
|
+
if (Array.isArray(node)) {
|
|
7145
|
+
node.forEach(visit);
|
|
7146
|
+
return;
|
|
7147
|
+
}
|
|
7148
|
+
if (node === null || typeof node !== "object") return;
|
|
7149
|
+
const obj = node;
|
|
7150
|
+
if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") fields.add(obj["field"]);
|
|
7151
|
+
for (const value of Object.values(obj)) visit(value);
|
|
7152
|
+
};
|
|
7153
|
+
for (const assignment of stmt.assignments) {
|
|
7154
|
+
if (assignment.value.type !== "SOURCE_FIELD") visit(assignment.value);
|
|
7155
|
+
}
|
|
7156
|
+
return [...fields];
|
|
7157
|
+
}
|
|
6848
7158
|
async function executeDelete(stmt, client, options, cacheContext) {
|
|
6849
7159
|
if (stmt.subtableCode) {
|
|
6850
7160
|
return executeDeleteSubtable(stmt, client, options, cacheContext);
|
|
@@ -7770,9 +8080,16 @@ function buildUpdatePlan(stmt, label) {
|
|
|
7770
8080
|
const isSubq = stmt.assignments.some((a) => a.value.type === "SCALAR_SUBQUERY");
|
|
7771
8081
|
const lines = [];
|
|
7772
8082
|
if (label) lines.push(label);
|
|
7773
|
-
lines.push(` [UPDATE]`);
|
|
8083
|
+
lines.push(stmt.from ? ` [UPDATE FROM]` : ` [UPDATE]`);
|
|
7774
8084
|
lines.push(` target: APP${stmt.appId} (${stmt.appId})`);
|
|
7775
|
-
|
|
8085
|
+
if (stmt.from) {
|
|
8086
|
+
const source = stmt.from.cteName ?? `APP${stmt.from.appId}`;
|
|
8087
|
+
lines.push(` source: ${source} AS ${stmt.from.alias}`);
|
|
8088
|
+
lines.push(` join: APP${stmt.appId}.$id = ${stmt.from.alias}.${stmt.from.joinKeyField}`);
|
|
8089
|
+
lines.push(` target filter: ${stmt.from.targetFilter ? safeWhereToKintone(stmt.from.targetFilter) : "(none)"}`);
|
|
8090
|
+
} else {
|
|
8091
|
+
lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
|
|
8092
|
+
}
|
|
7776
8093
|
lines.push(` api: GET /k/v1/records.json \u2192 PUT /k/v1/records.json`);
|
|
7777
8094
|
const setTypes = [];
|
|
7778
8095
|
if (isArith) setTypes.push("\u7B97\u8853 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A08\u7B97\uFF09");
|
|
@@ -7885,6 +8202,7 @@ function formatAssignment(a) {
|
|
|
7885
8202
|
if (v.type === "ARITH") return `${a.field} = ${formatArithExprStr(v)}`;
|
|
7886
8203
|
if (v.type === "CASE_VALUE") return `${a.field} = CASE WHEN ...`;
|
|
7887
8204
|
if (v.type === "SCALAR_SUBQUERY") return `${a.field} = (SELECT ...)`;
|
|
8205
|
+
if (v.type === "SOURCE_FIELD") return `${a.field} = ${v.alias}.${v.field}`;
|
|
7888
8206
|
return `${a.field} = (${v.type})`;
|
|
7889
8207
|
}
|
|
7890
8208
|
function formatArithExprStr(expr) {
|
package/dist-mcp/ksql-mcp.js
CHANGED
|
@@ -32967,6 +32967,26 @@ var Parser = class {
|
|
|
32967
32967
|
const { appId, subtableCode } = extractTableRef(name, this.prev());
|
|
32968
32968
|
this.expect("SET" /* SET */);
|
|
32969
32969
|
const assignments = this.parseAssignments();
|
|
32970
|
+
let from = null;
|
|
32971
|
+
if (this.consume("FROM" /* FROM */)) {
|
|
32972
|
+
const table = this.parseTableRef();
|
|
32973
|
+
if (table.subtableCode) {
|
|
32974
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u306B\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u306F\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093", this.prev());
|
|
32975
|
+
}
|
|
32976
|
+
if (table.cteName !== null && !table.cteName.startsWith("#")) {
|
|
32977
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u306F #temp \u307E\u305F\u306F APP<n> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08CTE \u306F\u975E\u5BFE\u5FDC\uFF09", this.prev());
|
|
32978
|
+
}
|
|
32979
|
+
if (!table.alias) {
|
|
32980
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u306B\u306F\u30A8\u30A4\u30EA\u30A2\u30B9\u304C\u5FC5\u8981\u3067\u3059", this.prev());
|
|
32981
|
+
}
|
|
32982
|
+
from = {
|
|
32983
|
+
appId: table.appId,
|
|
32984
|
+
cteName: table.cteName,
|
|
32985
|
+
alias: table.alias,
|
|
32986
|
+
joinKeyField: "",
|
|
32987
|
+
targetFilter: null
|
|
32988
|
+
};
|
|
32989
|
+
}
|
|
32970
32990
|
const whereTok = this.peek();
|
|
32971
32991
|
if (!this.consume("WHERE" /* WHERE */)) {
|
|
32972
32992
|
throw new ParseError(
|
|
@@ -32975,8 +32995,129 @@ var Parser = class {
|
|
|
32975
32995
|
);
|
|
32976
32996
|
}
|
|
32977
32997
|
const where = this.parseWhereExpr();
|
|
32998
|
+
if (from !== null) {
|
|
32999
|
+
if (subtableCode) {
|
|
33000
|
+
throw new ParseError("\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB UPDATE ... FROM \u306F\u30B5\u30DD\u30FC\u30C8\u3057\u3066\u3044\u307E\u305B\u3093", whereTok);
|
|
33001
|
+
}
|
|
33002
|
+
this.validateUpdateFromAssignments(assignments, from.alias, whereTok);
|
|
33003
|
+
const decomposed = this.decomposeUpdateFromWhere(where, appId, from.alias, whereTok);
|
|
33004
|
+
from.joinKeyField = decomposed.joinKeyField;
|
|
33005
|
+
from.targetFilter = decomposed.targetFilter;
|
|
33006
|
+
} else if (assignments.some((a) => a.value.type === "SOURCE_FIELD")) {
|
|
33007
|
+
throw new ParseError(
|
|
33008
|
+
"SET \u306E\u5024\u306B\u306F\u30EA\u30C6\u30E9\u30EB\u30FB\u7B97\u8853\u5F0F\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u306E\u307F\u306F\u4E0D\u53EF\uFF09",
|
|
33009
|
+
whereTok
|
|
33010
|
+
);
|
|
33011
|
+
}
|
|
33012
|
+
if (from !== null) return { type: "UPDATE", appId, assignments, where, from };
|
|
32978
33013
|
return subtableCode ? { type: "UPDATE", appId, subtableCode, assignments, where } : { type: "UPDATE", appId, assignments, where };
|
|
32979
33014
|
}
|
|
33015
|
+
validateUpdateFromAssignments(assignments, sourceAlias, tok) {
|
|
33016
|
+
for (const assignment of assignments) {
|
|
33017
|
+
if (assignment.value.type === "SOURCE_FIELD") {
|
|
33018
|
+
if (assignment.value.alias.toLowerCase() !== sourceAlias.toLowerCase()) {
|
|
33019
|
+
throw new ParseError(`UPDATE ... FROM \u306E SET \u53C2\u7167\u306F\u30BD\u30FC\u30B9 alias ${sourceAlias} \u3067\u4FEE\u98FE\u3057\u3066\u304F\u3060\u3055\u3044`, tok);
|
|
33020
|
+
}
|
|
33021
|
+
continue;
|
|
33022
|
+
}
|
|
33023
|
+
if (this.nodeContainsQualifiedField(assignment.value, sourceAlias)) {
|
|
33024
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u5217\u306F SET \u306E\u76F4\u63A5\u5024\u3068\u3057\u3066\u306E\u307F\u53C2\u7167\u3067\u304D\u307E\u3059", tok);
|
|
33025
|
+
}
|
|
33026
|
+
if (assignment.value.type === "SCALAR_SUBQUERY") {
|
|
33027
|
+
throw new ParseError("UPDATE ... FROM \u306E SET \u3067\u306F\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
|
|
33028
|
+
}
|
|
33029
|
+
if (this.nodeContainsAnyQualifier(assignment.value)) {
|
|
33030
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BF\u30FC\u30B2\u30C3\u30C8\u5F0F\u3067\u306F\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u4FEE\u98FE\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044", tok);
|
|
33031
|
+
}
|
|
33032
|
+
}
|
|
33033
|
+
}
|
|
33034
|
+
decomposeUpdateFromWhere(where, targetAppId, sourceAlias, tok) {
|
|
33035
|
+
const leaves = this.flattenTopLevelAnd(where);
|
|
33036
|
+
const joins = [];
|
|
33037
|
+
leaves.forEach((leaf, index) => {
|
|
33038
|
+
const sourceField = this.matchUpdateFromJoin(leaf, targetAppId, sourceAlias);
|
|
33039
|
+
if (sourceField !== null) joins.push({ index, sourceField });
|
|
33040
|
+
});
|
|
33041
|
+
if (joins.length !== 1) {
|
|
33042
|
+
throw new ParseError("UPDATE ... FROM \u306E WHERE \u306B\u306F target.$id = source.key \u306E\u7D50\u5408\u7B49\u5024\u304C\u3061\u3087\u3046\u30691\u3064\u5FC5\u8981\u3067\u3059", tok);
|
|
33043
|
+
}
|
|
33044
|
+
const join = joins[0];
|
|
33045
|
+
for (let i = 0; i < leaves.length; i++) {
|
|
33046
|
+
if (i !== join.index && this.nodeContainsQualifiedField(leaves[i], sourceAlias)) {
|
|
33047
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9 alias \u306F\u7D50\u5408\u7B49\u5024\u4EE5\u5916\u306E WHERE \u6761\u4EF6\u3067\u306F\u53C2\u7167\u3067\u304D\u307E\u305B\u3093", tok);
|
|
33048
|
+
}
|
|
33049
|
+
if (i !== join.index && this.nodeContainsForeignQualifier(leaves[i], targetAppId)) {
|
|
33050
|
+
throw new ParseError(`UPDATE ... FROM \u306E\u30BF\u30FC\u30B2\u30C3\u30C8\u30D5\u30A3\u30EB\u30BF\u306F APP${targetAppId} \u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u3060\u3051\u3092\u53C2\u7167\u3067\u304D\u307E\u3059`, tok);
|
|
33051
|
+
}
|
|
33052
|
+
}
|
|
33053
|
+
const filters = leaves.filter((_, index) => index !== join.index);
|
|
33054
|
+
const targetFilter = filters.reduce(
|
|
33055
|
+
(acc, expr) => acc === null ? expr : { type: "LOGICAL", op: "AND", left: acc, right: expr },
|
|
33056
|
+
null
|
|
33057
|
+
);
|
|
33058
|
+
return { joinKeyField: join.sourceField, targetFilter };
|
|
33059
|
+
}
|
|
33060
|
+
flattenTopLevelAnd(expr) {
|
|
33061
|
+
if (expr.type === "GROUP") return this.flattenTopLevelAnd(expr.expr);
|
|
33062
|
+
if (expr.type === "LOGICAL" && expr.op === "AND") {
|
|
33063
|
+
return [...this.flattenTopLevelAnd(expr.left), ...this.flattenTopLevelAnd(expr.right)];
|
|
33064
|
+
}
|
|
33065
|
+
return [expr];
|
|
33066
|
+
}
|
|
33067
|
+
matchUpdateFromJoin(expr, targetAppId, sourceAlias) {
|
|
33068
|
+
if (expr.type !== "BINARY" || expr.op !== "=" || expr.left.type !== "FIELD") return null;
|
|
33069
|
+
const right = expr.right.type === "ARITH_VALUE" && expr.right.expr.type === "FIELD_REF" ? this.splitQualifiedField(expr.right.expr.field) : null;
|
|
33070
|
+
if (right === null) return null;
|
|
33071
|
+
const left = { alias: expr.left.tableAlias, field: expr.left.field };
|
|
33072
|
+
if (this.isTargetIdRef(left, targetAppId) && this.isSourceRef(right, sourceAlias)) return right.field;
|
|
33073
|
+
if (this.isSourceRef(left, sourceAlias) && this.isTargetIdRef(right, targetAppId)) return left.field;
|
|
33074
|
+
return null;
|
|
33075
|
+
}
|
|
33076
|
+
splitQualifiedField(field) {
|
|
33077
|
+
const dot = field.indexOf(".");
|
|
33078
|
+
return dot < 0 ? { alias: null, field } : { alias: field.slice(0, dot), field: field.slice(dot + 1) };
|
|
33079
|
+
}
|
|
33080
|
+
isTargetIdRef(ref, appId) {
|
|
33081
|
+
return ref.field === "$id" && (ref.alias === null || ref.alias.toLowerCase() === `app${appId}`.toLowerCase());
|
|
33082
|
+
}
|
|
33083
|
+
isSourceRef(ref, alias) {
|
|
33084
|
+
return ref.alias?.toLowerCase() === alias.toLowerCase();
|
|
33085
|
+
}
|
|
33086
|
+
nodeContainsQualifiedField(node, alias) {
|
|
33087
|
+
if (Array.isArray(node)) return node.some((v) => this.nodeContainsQualifiedField(v, alias));
|
|
33088
|
+
if (node === null || typeof node !== "object") return false;
|
|
33089
|
+
const obj = node;
|
|
33090
|
+
if (obj["type"] === "FIELD" && typeof obj["tableAlias"] === "string" && obj["tableAlias"].toLowerCase() === alias.toLowerCase()) return true;
|
|
33091
|
+
if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") {
|
|
33092
|
+
const ref = this.splitQualifiedField(obj["field"]);
|
|
33093
|
+
if (this.isSourceRef(ref, alias)) return true;
|
|
33094
|
+
}
|
|
33095
|
+
return Object.values(obj).some((v) => this.nodeContainsQualifiedField(v, alias));
|
|
33096
|
+
}
|
|
33097
|
+
nodeContainsForeignQualifier(node, targetAppId) {
|
|
33098
|
+
if (Array.isArray(node)) return node.some((v) => this.nodeContainsForeignQualifier(v, targetAppId));
|
|
33099
|
+
if (node === null || typeof node !== "object") return false;
|
|
33100
|
+
const obj = node;
|
|
33101
|
+
const expected = `app${targetAppId}`.toLowerCase();
|
|
33102
|
+
if (obj["type"] === "FIELD" && typeof obj["tableAlias"] === "string") {
|
|
33103
|
+
return obj["tableAlias"].toLowerCase() !== expected;
|
|
33104
|
+
}
|
|
33105
|
+
if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") {
|
|
33106
|
+
const ref = this.splitQualifiedField(obj["field"]);
|
|
33107
|
+
if (ref.alias !== null) return ref.alias.toLowerCase() !== expected;
|
|
33108
|
+
}
|
|
33109
|
+
return Object.values(obj).some((v) => this.nodeContainsForeignQualifier(v, targetAppId));
|
|
33110
|
+
}
|
|
33111
|
+
nodeContainsAnyQualifier(node) {
|
|
33112
|
+
if (Array.isArray(node)) return node.some((v) => this.nodeContainsAnyQualifier(v));
|
|
33113
|
+
if (node === null || typeof node !== "object") return false;
|
|
33114
|
+
const obj = node;
|
|
33115
|
+
if (obj["type"] === "FIELD" && typeof obj["tableAlias"] === "string") return true;
|
|
33116
|
+
if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") {
|
|
33117
|
+
if (this.splitQualifiedField(obj["field"]).alias !== null) return true;
|
|
33118
|
+
}
|
|
33119
|
+
return Object.values(obj).some((v) => this.nodeContainsAnyQualifier(v));
|
|
33120
|
+
}
|
|
32980
33121
|
parseAssignments() {
|
|
32981
33122
|
const assignments = [];
|
|
32982
33123
|
do {
|
|
@@ -33011,6 +33152,12 @@ var Parser = class {
|
|
|
33011
33152
|
const node = this.parseArithAddSub();
|
|
33012
33153
|
if (node.type === "NUMBER") return node;
|
|
33013
33154
|
if (node.type === "ARITH") return node;
|
|
33155
|
+
if (node.type === "FIELD_REF") {
|
|
33156
|
+
const dot = node.field.indexOf(".");
|
|
33157
|
+
if (dot > 0 && dot < node.field.length - 1) {
|
|
33158
|
+
return { type: "SOURCE_FIELD", alias: node.field.slice(0, dot), field: node.field.slice(dot + 1) };
|
|
33159
|
+
}
|
|
33160
|
+
}
|
|
33014
33161
|
throw new ParseError(
|
|
33015
33162
|
"SET \u306E\u5024\u306B\u306F\u30EA\u30C6\u30E9\u30EB\u30FB\u7B97\u8853\u5F0F\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u306E\u307F\u306F\u4E0D\u53EF\uFF09",
|
|
33016
33163
|
tok
|
|
@@ -34586,7 +34733,8 @@ function analyzeBatch(statements) {
|
|
|
34586
34733
|
tempTablesDropped: dropped,
|
|
34587
34734
|
dependsOn: [...dependsOn].sort((a, b) => a - b),
|
|
34588
34735
|
tempOnlySource,
|
|
34589
|
-
targetAppId: isDmlType(statementType) && typeof stmt.appId === "number" ? stmt.appId : null
|
|
34736
|
+
targetAppId: isDmlType(statementType) && typeof stmt.appId === "number" ? stmt.appId : null,
|
|
34737
|
+
isUpdateFrom: stmt.type === "UPDATE" && stmt.from != null
|
|
34590
34738
|
});
|
|
34591
34739
|
});
|
|
34592
34740
|
const containsDml = results.some((r) => r.isDml);
|
|
@@ -35131,7 +35279,7 @@ function updateToPutBatches(stmt, ids, fieldTypes = /* @__PURE__ */ new Map()) {
|
|
|
35131
35279
|
function buildUpdateRecord(assignments, fieldTypes) {
|
|
35132
35280
|
const record2 = {};
|
|
35133
35281
|
for (const { field, value } of assignments) {
|
|
35134
|
-
if (value.type === "ARITH" || value.type === "CASE_VALUE") continue;
|
|
35282
|
+
if (value.type === "ARITH" || value.type === "CASE_VALUE" || value.type === "SOURCE_FIELD") continue;
|
|
35135
35283
|
record2[field] = { value: toKintoneValue(value, fieldTypes.get(field)) };
|
|
35136
35284
|
}
|
|
35137
35285
|
return record2;
|
|
@@ -35235,6 +35383,8 @@ function updateToPutBatchesArith(stmt, records, fieldTypes = /* @__PURE__ */ new
|
|
|
35235
35383
|
record2[field] = { value: String(evalArith(value, raw)) };
|
|
35236
35384
|
} else if (value.type === "CASE_VALUE") {
|
|
35237
35385
|
record2[field] = { value: evalCaseWhenValue(value.expr, row, fieldTypes.get(field)) };
|
|
35386
|
+
} else if (value.type === "SOURCE_FIELD") {
|
|
35387
|
+
throw new DmlConvertError("SOURCE_FIELD \u306F UPDATE ... FROM \u5C02\u7528\u3067\u3059");
|
|
35238
35388
|
} else {
|
|
35239
35389
|
record2[field] = { value: toKintoneValue(value, fieldTypes.get(field)) };
|
|
35240
35390
|
}
|
|
@@ -35246,6 +35396,51 @@ function updateToPutBatchesArith(stmt, records, fieldTypes = /* @__PURE__ */ new
|
|
|
35246
35396
|
records: batch
|
|
35247
35397
|
}));
|
|
35248
35398
|
}
|
|
35399
|
+
var UPDATE_FROM_UNSUPPORTED_TYPES = /* @__PURE__ */ new Set([
|
|
35400
|
+
"CHECK_BOX",
|
|
35401
|
+
"MULTI_SELECT",
|
|
35402
|
+
"USER_SELECT",
|
|
35403
|
+
"ORGANIZATION_SELECT",
|
|
35404
|
+
"GROUP_SELECT",
|
|
35405
|
+
"FILE"
|
|
35406
|
+
]);
|
|
35407
|
+
function updateFromToPutBatches(stmt, matched, fieldTypes = /* @__PURE__ */ new Map()) {
|
|
35408
|
+
const updateRecords = matched.map(({ target, source }) => {
|
|
35409
|
+
const id = Number(target["$id"]?.value);
|
|
35410
|
+
if (!Number.isSafeInteger(id) || id <= 0) {
|
|
35411
|
+
throw new DmlConvertError("UPDATE ... FROM \u306E\u5BFE\u8C61\u30EC\u30B3\u30FC\u30C9\u756A\u53F7\u304C\u4E0D\u6B63\u3067\u3059");
|
|
35412
|
+
}
|
|
35413
|
+
const targetRow = kintoneRecordToProcessRow(target);
|
|
35414
|
+
const record2 = {};
|
|
35415
|
+
for (const { field, value } of stmt.assignments) {
|
|
35416
|
+
const fieldType = fieldTypes.get(field);
|
|
35417
|
+
if (value.type === "SOURCE_FIELD") {
|
|
35418
|
+
if (UPDATE_FROM_UNSUPPORTED_TYPES.has(fieldType ?? "")) {
|
|
35419
|
+
throw new DmlConvertError(`UPDATE ... FROM \u306E SOURCE_FIELD \u306F ${fieldType} \u30D5\u30A3\u30FC\u30EB\u30C9\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9: ${field}\uFF09`);
|
|
35420
|
+
}
|
|
35421
|
+
if (!Object.prototype.hasOwnProperty.call(source, value.field)) {
|
|
35422
|
+
throw new DmlConvertError(`UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u5217 ${value.field} \u304C\u5B58\u5728\u3057\u307E\u305B\u3093`);
|
|
35423
|
+
}
|
|
35424
|
+
const raw = source[value.field];
|
|
35425
|
+
if (typeof raw !== "string") {
|
|
35426
|
+
throw new DmlConvertError(`UPDATE ... FROM \u306E SOURCE_FIELD \u306F\u30B9\u30AB\u30E9\u30FC\u5024\u306E\u307F\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u3059\uFF08\u5217: ${value.field}\uFF09`);
|
|
35427
|
+
}
|
|
35428
|
+
if ((fieldType === "NUMBER" || fieldType === "CALC") && raw !== "" && !Number.isFinite(Number(raw))) {
|
|
35429
|
+
throw new DmlConvertError(`\u6570\u5024\u30D5\u30A3\u30FC\u30EB\u30C9 ${field} \u306B\u5909\u63DB\u3067\u304D\u306A\u3044\u5024\u3067\u3059: ${raw}`);
|
|
35430
|
+
}
|
|
35431
|
+
record2[field] = { value: toKintoneValue({ type: "STRING", value: raw }, fieldType) };
|
|
35432
|
+
} else if (value.type === "ARITH") {
|
|
35433
|
+
record2[field] = { value: String(evalArith(value, target)) };
|
|
35434
|
+
} else if (value.type === "CASE_VALUE") {
|
|
35435
|
+
record2[field] = { value: evalCaseWhenValue(value.expr, targetRow, fieldType) };
|
|
35436
|
+
} else {
|
|
35437
|
+
record2[field] = { value: toKintoneValue(value, fieldType) };
|
|
35438
|
+
}
|
|
35439
|
+
}
|
|
35440
|
+
return { id, record: record2 };
|
|
35441
|
+
});
|
|
35442
|
+
return chunk(updateRecords, 100).map((records) => ({ app: stmt.appId, records }));
|
|
35443
|
+
}
|
|
35249
35444
|
function kintoneRecordToProcessRow(raw) {
|
|
35250
35445
|
return Object.fromEntries(
|
|
35251
35446
|
Object.entries(raw).map(([k, v]) => [
|
|
@@ -36360,6 +36555,8 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
36360
36555
|
for (const s of analysis.statements) {
|
|
36361
36556
|
if (!s.isDml || s.tempTablesReferenced.length === 0) continue;
|
|
36362
36557
|
if (s.statementType === "INSERT_SELECT" || s.statementType === "UPSERT_SELECT") continue;
|
|
36558
|
+
const parsed = statements[s.index];
|
|
36559
|
+
if (parsed?.type === "UPDATE" && parsed.from?.cteName != null) continue;
|
|
36363
36560
|
throw new BatchAnalysisError(
|
|
36364
36561
|
`ArgumentError: temp table references in ${s.statementType} are not supported yet.`,
|
|
36365
36562
|
s.index
|
|
@@ -36516,6 +36713,9 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
36516
36713
|
if (resolvedStmt.type === "UPSERT_SELECT") {
|
|
36517
36714
|
return { result: await executeUpsertSelect(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
36518
36715
|
}
|
|
36716
|
+
if (resolvedStmt.type === "UPDATE" && resolvedStmt.from?.cteName != null) {
|
|
36717
|
+
return { result: await executeUpdate(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
36718
|
+
}
|
|
36519
36719
|
throw new Error(`ArgumentError: temp table references in ${stmt.type} are not supported yet.`);
|
|
36520
36720
|
}
|
|
36521
36721
|
return { result: await executeParsedStatement(resolvedStmt, client, options, cacheContext) };
|
|
@@ -37701,10 +37901,13 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
37701
37901
|
insertedCount: createdIds.flat().length
|
|
37702
37902
|
};
|
|
37703
37903
|
}
|
|
37704
|
-
async function executeUpdate(stmt, client, options, cacheContext) {
|
|
37904
|
+
async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
37705
37905
|
if (stmt.subtableCode) {
|
|
37706
37906
|
return executeUpdateSubtable(stmt, client, options, cacheContext);
|
|
37707
37907
|
}
|
|
37908
|
+
if (stmt.from != null) {
|
|
37909
|
+
return executeUpdateFrom(stmt, stmt.from, client, options, cacheContext, tempTables);
|
|
37910
|
+
}
|
|
37708
37911
|
const maxRecords2 = options.maxRecords ?? 1e4;
|
|
37709
37912
|
await resolveSetSubqueries(stmt.assignments, client, options, cacheContext);
|
|
37710
37913
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
@@ -37746,6 +37949,113 @@ async function executeUpdate(stmt, client, options, cacheContext) {
|
|
|
37746
37949
|
}
|
|
37747
37950
|
return { type: "UPDATE", updatedCount: ids.length };
|
|
37748
37951
|
}
|
|
37952
|
+
var UPDATE_FROM_ID_CHUNK_SIZE = 50;
|
|
37953
|
+
var UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES = /* @__PURE__ */ new Set([
|
|
37954
|
+
"CHECK_BOX",
|
|
37955
|
+
"MULTI_SELECT",
|
|
37956
|
+
"USER_SELECT",
|
|
37957
|
+
"ORGANIZATION_SELECT",
|
|
37958
|
+
"GROUP_SELECT",
|
|
37959
|
+
"FILE"
|
|
37960
|
+
]);
|
|
37961
|
+
async function executeUpdateFrom(stmt, from, client, options, cacheContext, tempTables) {
|
|
37962
|
+
const sourceFields = [...new Set(stmt.assignments.filter((a) => a.value.type === "SOURCE_FIELD").map((a) => a.value.type === "SOURCE_FIELD" ? a.value.field : ""))];
|
|
37963
|
+
const requiredSourceFields = [.../* @__PURE__ */ new Set([from.joinKeyField, ...sourceFields])];
|
|
37964
|
+
let sourceRows;
|
|
37965
|
+
if (from.cteName !== null) {
|
|
37966
|
+
const table = tempTables?.get(from.cteName);
|
|
37967
|
+
if (!table) throw new Error(`ArgumentError: temp table ${from.cteName} is not available.`);
|
|
37968
|
+
for (const field of requiredSourceFields) {
|
|
37969
|
+
if (!table.columns.includes(field)) {
|
|
37970
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${field} does not exist.`);
|
|
37971
|
+
}
|
|
37972
|
+
}
|
|
37973
|
+
sourceRows = table.rows;
|
|
37974
|
+
} else {
|
|
37975
|
+
const sourceTypes = await getFieldTypeMap(from.appId, client, cacheContext);
|
|
37976
|
+
for (const field of requiredSourceFields) {
|
|
37977
|
+
if (field !== "$id" && !sourceTypes.has(field)) {
|
|
37978
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${field} does not exist.`);
|
|
37979
|
+
}
|
|
37980
|
+
const type = sourceTypes.get(field);
|
|
37981
|
+
if (UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES.has(type ?? "")) {
|
|
37982
|
+
throw new Error(`ArgumentError: UPDATE ... FROM does not support source field type ${type} (${field}).`);
|
|
37983
|
+
}
|
|
37984
|
+
}
|
|
37985
|
+
const maxRecords2 = options.maxRecords ?? 1e4;
|
|
37986
|
+
const resolved = await fetchRecordsForSharedPlan(
|
|
37987
|
+
client.getRecords,
|
|
37988
|
+
from.appId,
|
|
37989
|
+
"",
|
|
37990
|
+
requiredSourceFields,
|
|
37991
|
+
{ maxRecords: maxRecords2, parallel: options.fetchParallel ?? 1, onLimit: "error" }
|
|
37992
|
+
);
|
|
37993
|
+
sourceRows = resolved.records.map((record2) => flatten(record2, null));
|
|
37994
|
+
}
|
|
37995
|
+
const sourceById = /* @__PURE__ */ new Map();
|
|
37996
|
+
for (const row of sourceRows) {
|
|
37997
|
+
if (!Object.prototype.hasOwnProperty.call(row, from.joinKeyField)) {
|
|
37998
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${from.joinKeyField} does not exist.`);
|
|
37999
|
+
}
|
|
38000
|
+
const raw = row[from.joinKeyField];
|
|
38001
|
+
const text = typeof raw === "string" ? raw.trim() : "";
|
|
38002
|
+
const id = Number(text);
|
|
38003
|
+
if (text === "" || !Number.isSafeInteger(id) || id <= 0) {
|
|
38004
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source key must be a positive safe integer: ${String(raw)}`);
|
|
38005
|
+
}
|
|
38006
|
+
if (sourceById.has(id)) {
|
|
38007
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source has multiple rows for target $id ${id}.`);
|
|
38008
|
+
}
|
|
38009
|
+
sourceById.set(id, row);
|
|
38010
|
+
}
|
|
38011
|
+
const targetIds = [...sourceById.keys()];
|
|
38012
|
+
const targetFields = collectUpdateFromTargetFields(stmt);
|
|
38013
|
+
const filterQuery = from.targetFilter === null ? "" : updateToGetQuery({ ...stmt, from: null, where: from.targetFilter }).query;
|
|
38014
|
+
const targetRecords = [];
|
|
38015
|
+
for (const ids of splitChunks(targetIds, UPDATE_FROM_ID_CHUNK_SIZE)) {
|
|
38016
|
+
const idQuery = `$id in (${ids.map((id) => sqlQuote(String(id))).join(",")})`;
|
|
38017
|
+
const query = filterQuery ? `(${idQuery}) and (${filterQuery})` : idQuery;
|
|
38018
|
+
const resolved = await fetchRecordsForSharedPlan(
|
|
38019
|
+
client.getRecords,
|
|
38020
|
+
stmt.appId,
|
|
38021
|
+
query,
|
|
38022
|
+
targetFields,
|
|
38023
|
+
{ maxRecords: Math.max(ids.length, 1), parallel: options.fetchParallel ?? 1, onLimit: "error" }
|
|
38024
|
+
);
|
|
38025
|
+
targetRecords.push(...resolved.records);
|
|
38026
|
+
}
|
|
38027
|
+
if (options.confirm) {
|
|
38028
|
+
const ok = await options.confirm(targetRecords.length, "UPDATE");
|
|
38029
|
+
if (!ok) throw new OperationCancelledError("UPDATE", targetRecords.length);
|
|
38030
|
+
}
|
|
38031
|
+
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
38032
|
+
const matched = targetRecords.map((target) => {
|
|
38033
|
+
const id = Number(target["$id"]?.value);
|
|
38034
|
+
const source = sourceById.get(id);
|
|
38035
|
+
if (!source) throw new Error(`ArgumentError: UPDATE ... FROM could not resolve source row for target $id ${id}.`);
|
|
38036
|
+
return { target, source };
|
|
38037
|
+
});
|
|
38038
|
+
const batches = updateFromToPutBatches(stmt, matched, fieldTypes);
|
|
38039
|
+
for (const batch of batches) await client.putRecords(batch);
|
|
38040
|
+
return { type: "UPDATE", updatedCount: targetRecords.length };
|
|
38041
|
+
}
|
|
38042
|
+
function collectUpdateFromTargetFields(stmt) {
|
|
38043
|
+
const fields = /* @__PURE__ */ new Set(["$id"]);
|
|
38044
|
+
const visit = (node) => {
|
|
38045
|
+
if (Array.isArray(node)) {
|
|
38046
|
+
node.forEach(visit);
|
|
38047
|
+
return;
|
|
38048
|
+
}
|
|
38049
|
+
if (node === null || typeof node !== "object") return;
|
|
38050
|
+
const obj = node;
|
|
38051
|
+
if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") fields.add(obj["field"]);
|
|
38052
|
+
for (const value of Object.values(obj)) visit(value);
|
|
38053
|
+
};
|
|
38054
|
+
for (const assignment of stmt.assignments) {
|
|
38055
|
+
if (assignment.value.type !== "SOURCE_FIELD") visit(assignment.value);
|
|
38056
|
+
}
|
|
38057
|
+
return [...fields];
|
|
38058
|
+
}
|
|
37749
38059
|
async function executeDelete(stmt, client, options, cacheContext) {
|
|
37750
38060
|
if (stmt.subtableCode) {
|
|
37751
38061
|
return executeDeleteSubtable(stmt, client, options, cacheContext);
|
|
@@ -38671,9 +38981,16 @@ function buildUpdatePlan(stmt, label) {
|
|
|
38671
38981
|
const isSubq = stmt.assignments.some((a) => a.value.type === "SCALAR_SUBQUERY");
|
|
38672
38982
|
const lines = [];
|
|
38673
38983
|
if (label) lines.push(label);
|
|
38674
|
-
lines.push(` [UPDATE]`);
|
|
38984
|
+
lines.push(stmt.from ? ` [UPDATE FROM]` : ` [UPDATE]`);
|
|
38675
38985
|
lines.push(` target: APP${stmt.appId} (${stmt.appId})`);
|
|
38676
|
-
|
|
38986
|
+
if (stmt.from) {
|
|
38987
|
+
const source = stmt.from.cteName ?? `APP${stmt.from.appId}`;
|
|
38988
|
+
lines.push(` source: ${source} AS ${stmt.from.alias}`);
|
|
38989
|
+
lines.push(` join: APP${stmt.appId}.$id = ${stmt.from.alias}.${stmt.from.joinKeyField}`);
|
|
38990
|
+
lines.push(` target filter: ${stmt.from.targetFilter ? safeWhereToKintone(stmt.from.targetFilter) : "(none)"}`);
|
|
38991
|
+
} else {
|
|
38992
|
+
lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
|
|
38993
|
+
}
|
|
38677
38994
|
lines.push(` api: GET /k/v1/records.json \u2192 PUT /k/v1/records.json`);
|
|
38678
38995
|
const setTypes = [];
|
|
38679
38996
|
if (isArith) setTypes.push("\u7B97\u8853 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A08\u7B97\uFF09");
|
|
@@ -38786,6 +39103,7 @@ function formatAssignment(a) {
|
|
|
38786
39103
|
if (v.type === "ARITH") return `${a.field} = ${formatArithExprStr(v)}`;
|
|
38787
39104
|
if (v.type === "CASE_VALUE") return `${a.field} = CASE WHEN ...`;
|
|
38788
39105
|
if (v.type === "SCALAR_SUBQUERY") return `${a.field} = (SELECT ...)`;
|
|
39106
|
+
if (v.type === "SOURCE_FIELD") return `${a.field} = ${v.alias}.${v.field}`;
|
|
38789
39107
|
return `${a.field} = (${v.type})`;
|
|
38790
39108
|
}
|
|
38791
39109
|
function formatArithExprStr(expr) {
|
|
@@ -40319,14 +40637,14 @@ function requireDmlApproval(input, toolName, suffix = "") {
|
|
|
40319
40637
|
}
|
|
40320
40638
|
function containsSelectBasedDml(statements) {
|
|
40321
40639
|
return statements.some(
|
|
40322
|
-
(s) => s.statementType === "INSERT_SELECT" || s.statementType === "UPSERT_SELECT"
|
|
40640
|
+
(s) => s.statementType === "INSERT_SELECT" || s.statementType === "UPSERT_SELECT" || s.isUpdateFrom === true
|
|
40323
40641
|
);
|
|
40324
40642
|
}
|
|
40325
40643
|
function resolveMutateRuntimeMaxRecords(statements, dmlMaxRows) {
|
|
40326
40644
|
return containsSelectBasedDml(statements) ? void 0 : dmlMaxRows + 1;
|
|
40327
40645
|
}
|
|
40328
40646
|
var READ_LIMIT_MESSAGE_FRAGMENT = "\u53D6\u5F97\u4EF6\u6570\u304C\u4E0A\u9650";
|
|
40329
|
-
var SELECT_BASED_DML_READ_LIMIT_HINT = "SELECT-based DML \u306E\u30BD\u30FC\u30B9\u8AAD\u307F\u53D6\u308A\u4E0A\u9650\u306F dmlMaxRows \u3067\u306F\u306A\u304F maxRecords \u89E3\u6C7A\u5024(KSQL_MAX_RECORDS / profile \u306E query.maxRecords\u3001\u65E2\u5B9A 500)\u3067\u5236\u5FA1\u3055\u308C\u307E\u3059\u3002dmlMaxRows \u306F\u5F71\u97FF\u884C\u6570\u30AC\u30FC\u30C9\u3067\u3059\u3002";
|
|
40647
|
+
var SELECT_BASED_DML_READ_LIMIT_HINT = "SELECT-based DML\uFF08UPDATE \u2026 FROM \u3092\u542B\u3080\uFF09\u306E\u30BD\u30FC\u30B9\u8AAD\u307F\u53D6\u308A\u4E0A\u9650\u306F dmlMaxRows \u3067\u306F\u306A\u304F maxRecords \u89E3\u6C7A\u5024(KSQL_MAX_RECORDS / profile \u306E query.maxRecords\u3001\u65E2\u5B9A 500)\u3067\u5236\u5FA1\u3055\u308C\u307E\u3059\u3002dmlMaxRows \u306F\u5F71\u97FF\u884C\u6570\u30AC\u30FC\u30C9\u3067\u3059\u3002";
|
|
40330
40648
|
function appendSelectBasedDmlReadLimitHint(err) {
|
|
40331
40649
|
if (err instanceof Error && err.message.includes(READ_LIMIT_MESSAGE_FRAGMENT)) {
|
|
40332
40650
|
const hinted = new Error(`${err.message} ${SELECT_BASED_DML_READ_LIMIT_HINT}`);
|
|
@@ -40381,7 +40699,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
40381
40699
|
tempTablesReferenced: s2.tempTablesReferenced,
|
|
40382
40700
|
tempTablesDropped: s2.tempTablesDropped,
|
|
40383
40701
|
tempOnlySource: s2.tempOnlySource,
|
|
40384
|
-
targetAppId: s2.targetAppId
|
|
40702
|
+
targetAppId: s2.targetAppId,
|
|
40703
|
+
isUpdateFrom: s2.isUpdateFrom
|
|
40385
40704
|
}));
|
|
40386
40705
|
const common = {
|
|
40387
40706
|
ok: true,
|
|
@@ -40584,7 +40903,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
40584
40903
|
const payload = buildBatchEnvelope(batchResult);
|
|
40585
40904
|
if (selectBasedDml) {
|
|
40586
40905
|
for (const entry of payload.statements) {
|
|
40587
|
-
|
|
40906
|
+
const statement = validation.statements.find((s) => s.index === entry.index);
|
|
40907
|
+
if (entry.type !== "INSERT_SELECT" && entry.type !== "UPSERT_SELECT" && statement?.isUpdateFrom !== true) continue;
|
|
40588
40908
|
const error51 = entry.error;
|
|
40589
40909
|
if (typeof error51?.message !== "string") continue;
|
|
40590
40910
|
if (!error51.message.includes(READ_LIMIT_MESSAGE_FRAGMENT)) continue;
|
|
@@ -40923,7 +41243,7 @@ Options:
|
|
|
40923
41243
|
-h, --help Show help
|
|
40924
41244
|
`);
|
|
40925
41245
|
}
|
|
40926
|
-
var SERVER_VERSION = true ? "2.
|
|
41246
|
+
var SERVER_VERSION = true ? "2.12.0" : "0.0.0-dev";
|
|
40927
41247
|
function createServer(args) {
|
|
40928
41248
|
const server = new McpServer({
|
|
40929
41249
|
name: "ksql-mcp",
|
|
@@ -40950,7 +41270,7 @@ function createServer(args) {
|
|
|
40950
41270
|
}, tools.queryTool);
|
|
40951
41271
|
server.registerTool("ksql_mutate", {
|
|
40952
41272
|
title: "Run mutating kSQL",
|
|
40953
|
-
description: "Execute DML kSQL with explicit allowDml, confirmText, and dmlMaxRows safety controls. Supports multi-statement DML batches with temp tables. INSERT/UPSERT INTO app ... SELECT supports app sources, temp tables, or joins of both. For UPSERT, dmlMaxRows counts inserts + updates. dmlMaxRows caps affected rows only, not source reads:
|
|
41273
|
+
description: "Execute DML kSQL with explicit allowDml, confirmText, and dmlMaxRows safety controls. Supports multi-statement DML batches with temp tables. INSERT/UPSERT INTO app ... SELECT supports app sources, temp tables, or joins of both. UPDATE ... FROM supports copying scalar fields from an app or temp table by matching target $id to one source key. For UPSERT, dmlMaxRows counts inserts + updates. dmlMaxRows caps affected rows only, not source reads: source SELECT and UPDATE ... FROM app reads use the runtime maxRecords (KSQL_MAX_RECORDS / profile query.maxRecords, default 500); temp tables hold at most 10000 rows by default (adjustable via tempTableMaxRows).",
|
|
40954
41274
|
inputSchema: mutateInputShape
|
|
40955
41275
|
}, tools.mutateTool);
|
|
40956
41276
|
server.registerTool("ksql_describe_app", {
|
package/dist-mcpb/ksql-mcp.mcpb
CHANGED
|
Binary file
|