@rex0220/kintone-sql-tools 1.3.0 → 1.4.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 +13 -2
- package/dist-cli/ksql.js +1178 -290
- package/dist-mcp/ksql-mcp.js +953 -97
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
package/dist-cli/ksql.js
CHANGED
|
@@ -22,6 +22,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
24
|
HELP_TEXT: () => HELP_TEXT,
|
|
25
|
+
buildBatchDmlConfirmMessage: () => buildBatchDmlConfirmMessage,
|
|
26
|
+
buildBatchStatementSummary: () => buildBatchStatementSummary,
|
|
25
27
|
buildOutput: () => buildOutput,
|
|
26
28
|
extractAppIds: () => extractAppIds,
|
|
27
29
|
normalizeAppKey: () => normalizeAppKey,
|
|
@@ -134,11 +136,12 @@ var KEYWORDS = /* @__PURE__ */ new Map([
|
|
|
134
136
|
|
|
135
137
|
// src/lexer/lexer.ts
|
|
136
138
|
var LexError = class extends Error {
|
|
137
|
-
constructor(message, pos, input) {
|
|
139
|
+
constructor(message, pos, input, unterminated = false) {
|
|
138
140
|
const around = input.slice(Math.max(0, pos - 10), pos + 10);
|
|
139
141
|
super(`${message}\uFF08\u4F4D\u7F6E ${pos}\u3001\u524D\u5F8C: \u300C${around}\u300D\uFF09`);
|
|
140
142
|
this.pos = pos;
|
|
141
143
|
this.input = input;
|
|
144
|
+
this.unterminated = unterminated;
|
|
142
145
|
this.name = "LexError";
|
|
143
146
|
}
|
|
144
147
|
};
|
|
@@ -175,6 +178,7 @@ var Lexer = class {
|
|
|
175
178
|
const opTok = this.tryReadOperator(start);
|
|
176
179
|
if (opTok) return opTok;
|
|
177
180
|
if (isIdentStart(ch)) return this.readIdentOrKeyword(start);
|
|
181
|
+
if (ch === "#") return this.readHashIdent(start);
|
|
178
182
|
throw new LexError(
|
|
179
183
|
`\u4E88\u671F\u3057\u306A\u3044\u6587\u5B57 \u300C${ch}\u300D \u3067\u3059`,
|
|
180
184
|
this.pos,
|
|
@@ -203,7 +207,7 @@ var Lexer = class {
|
|
|
203
207
|
this.pos++;
|
|
204
208
|
}
|
|
205
209
|
}
|
|
206
|
-
throw new LexError("\u6587\u5B57\u5217\u30EA\u30C6\u30E9\u30EB\u304C\u9589\u3058\u3089\u308C\u3066\u3044\u307E\u305B\u3093", start, this.input);
|
|
210
|
+
throw new LexError("\u6587\u5B57\u5217\u30EA\u30C6\u30E9\u30EB\u304C\u9589\u3058\u3089\u308C\u3066\u3044\u307E\u305B\u3093", start, this.input, true);
|
|
207
211
|
}
|
|
208
212
|
// ----------------------------------------------------------
|
|
209
213
|
// バッククォート識別子: `field name`
|
|
@@ -223,7 +227,8 @@ var Lexer = class {
|
|
|
223
227
|
throw new LexError(
|
|
224
228
|
"\u30D0\u30C3\u30AF\u30AF\u30A9\u30FC\u30C8\u8B58\u5225\u5B50\u304C\u9589\u3058\u3089\u308C\u3066\u3044\u307E\u305B\u3093",
|
|
225
229
|
start,
|
|
226
|
-
this.input
|
|
230
|
+
this.input,
|
|
231
|
+
true
|
|
227
232
|
);
|
|
228
233
|
}
|
|
229
234
|
// ----------------------------------------------------------
|
|
@@ -330,6 +335,30 @@ var Lexer = class {
|
|
|
330
335
|
return this.makeToken(kind, value, start);
|
|
331
336
|
}
|
|
332
337
|
// ----------------------------------------------------------
|
|
338
|
+
// 一時テーブル識別子: #temp
|
|
339
|
+
// # は先頭のみ有効。isIdentStart に # を加えると isIdentContinue 経由で
|
|
340
|
+
// 識別子の途中(APP#x 等)にも許容されてしまうため、専用分岐で読む。
|
|
341
|
+
// ----------------------------------------------------------
|
|
342
|
+
readHashIdent(start) {
|
|
343
|
+
this.pos++;
|
|
344
|
+
const next = this.input[this.pos] ?? "";
|
|
345
|
+
if (!isIdentStart(next)) {
|
|
346
|
+
throw new LexError("\u300C#\u300D \u306E\u76F4\u5F8C\u306B\u306F\u8B58\u5225\u5B50\u304C\u5FC5\u8981\u3067\u3059", start, this.input);
|
|
347
|
+
}
|
|
348
|
+
while (this.pos < this.input.length && isIdentContinue(this.input[this.pos])) {
|
|
349
|
+
this.pos++;
|
|
350
|
+
}
|
|
351
|
+
const value = this.input.slice(start, this.pos);
|
|
352
|
+
if (this.input[this.pos] === "@") {
|
|
353
|
+
throw new LexError(
|
|
354
|
+
`@profile is not allowed on temp table ${value}.`,
|
|
355
|
+
this.pos,
|
|
356
|
+
this.input
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
return this.makeToken("IDENT" /* IDENT */, value, start);
|
|
360
|
+
}
|
|
361
|
+
// ----------------------------------------------------------
|
|
333
362
|
// 空白・コメントをスキップ
|
|
334
363
|
// ----------------------------------------------------------
|
|
335
364
|
skipWhitespaceAndComments() {
|
|
@@ -346,14 +375,25 @@ var Lexer = class {
|
|
|
346
375
|
continue;
|
|
347
376
|
}
|
|
348
377
|
if (ch === "/" && this.input[this.pos + 1] === "*") {
|
|
378
|
+
const commentStart = this.pos;
|
|
349
379
|
this.pos += 2;
|
|
380
|
+
let closed = false;
|
|
350
381
|
while (this.pos < this.input.length) {
|
|
351
382
|
if (this.input[this.pos] === "*" && this.input[this.pos + 1] === "/") {
|
|
352
383
|
this.pos += 2;
|
|
384
|
+
closed = true;
|
|
353
385
|
break;
|
|
354
386
|
}
|
|
355
387
|
this.pos++;
|
|
356
388
|
}
|
|
389
|
+
if (!closed) {
|
|
390
|
+
throw new LexError(
|
|
391
|
+
"\u30D6\u30ED\u30C3\u30AF\u30B3\u30E1\u30F3\u30C8\u304C\u9589\u3058\u3089\u308C\u3066\u3044\u307E\u305B\u3093",
|
|
392
|
+
commentStart,
|
|
393
|
+
this.input,
|
|
394
|
+
true
|
|
395
|
+
);
|
|
396
|
+
}
|
|
357
397
|
continue;
|
|
358
398
|
}
|
|
359
399
|
break;
|
|
@@ -387,6 +427,7 @@ function isJapanese(cp) {
|
|
|
387
427
|
}
|
|
388
428
|
|
|
389
429
|
// src/parser/parser.ts
|
|
430
|
+
var MAX_BATCH_STATEMENTS = 20;
|
|
390
431
|
var ParseError = class extends Error {
|
|
391
432
|
constructor(message, token) {
|
|
392
433
|
super(`${message}\uFF08\u4F4D\u7F6E ${token.pos}\u3001\u30C8\u30FC\u30AF\u30F3: \u300C${token.value}\u300D\uFF09`);
|
|
@@ -400,15 +441,54 @@ var Parser = class {
|
|
|
400
441
|
this.pos = 0;
|
|
401
442
|
/** WITH 句で定義された CTE 名のセット(parseTableRef で参照) */
|
|
402
443
|
this.cteNames = /* @__PURE__ */ new Set();
|
|
444
|
+
/** パース中に出現した一時テーブル参照(#name)のトークン。単文 API での拒否に使う */
|
|
445
|
+
this.tempTableRefs = [];
|
|
403
446
|
}
|
|
404
447
|
// ----------------------------------------------------------
|
|
405
448
|
// 公開 API
|
|
406
449
|
// ----------------------------------------------------------
|
|
450
|
+
/** 単文をパースする(従来 API。複文が渡されたらエラー) */
|
|
407
451
|
parse() {
|
|
408
|
-
const
|
|
409
|
-
if (
|
|
452
|
+
const stmts = this.parseStatements();
|
|
453
|
+
if (stmts.length === 0) {
|
|
454
|
+
throw new ParseError("SQL \u6587\u304C\u3042\u308A\u307E\u305B\u3093", this.peek());
|
|
455
|
+
}
|
|
456
|
+
if (stmts.length > 1) {
|
|
457
|
+
throw new ParseError(
|
|
458
|
+
"\u3053\u306E API \u306F\u5358\u6587\u306E\u307F\u53D7\u3051\u4ED8\u3051\u307E\u3059\uFF08\u8907\u6587\u306F\u30D0\u30C3\u30C1\u5B9F\u884C API \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\uFF09",
|
|
459
|
+
this.peek()
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
if (this.tempTableRefs.length > 0) {
|
|
463
|
+
const tok = this.tempTableRefs[0];
|
|
464
|
+
throw new ParseError(
|
|
465
|
+
`temp table ${tok.value} is not defined in this batch.`,
|
|
466
|
+
tok
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
return stmts[0];
|
|
470
|
+
}
|
|
471
|
+
/** 複文(`;` 区切り)をパースする。空文はスキップする */
|
|
472
|
+
parseStatements() {
|
|
473
|
+
const stmts = [];
|
|
474
|
+
while (true) {
|
|
475
|
+
while (this.peek().kind === ";" /* SEMICOLON */) this.advance();
|
|
476
|
+
if (this.peek().kind === "EOF" /* EOF */) break;
|
|
477
|
+
const startTok = this.peek();
|
|
478
|
+
stmts.push(this.parseStatement());
|
|
479
|
+
if (stmts.length > MAX_BATCH_STATEMENTS) {
|
|
480
|
+
throw new ParseError(
|
|
481
|
+
`batch exceeds ${MAX_BATCH_STATEMENTS} statements.`,
|
|
482
|
+
startTok
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
const after = this.peek();
|
|
486
|
+
if (after.kind !== ";" /* SEMICOLON */ && after.kind !== "EOF" /* EOF */) {
|
|
487
|
+
throw new ParseError("\u6587\u306E\u533A\u5207\u308A\u306B\u306F ; \u304C\u5FC5\u8981\u3067\u3059", after);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
410
490
|
this.expect("EOF" /* EOF */);
|
|
411
|
-
return
|
|
491
|
+
return stmts;
|
|
412
492
|
}
|
|
413
493
|
// ----------------------------------------------------------
|
|
414
494
|
// Statement ディスパッチ
|
|
@@ -437,12 +517,63 @@ var Parser = class {
|
|
|
437
517
|
return this.parseDescribe();
|
|
438
518
|
case "EXPLAIN" /* EXPLAIN */:
|
|
439
519
|
return this.parseExplain();
|
|
520
|
+
case "IDENT" /* IDENT */: {
|
|
521
|
+
const upper = tok.value.toUpperCase();
|
|
522
|
+
if (upper === "CREATE") return this.parseCreateTempTable();
|
|
523
|
+
if (upper === "DROP") return this.parseDropTempTable();
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
440
526
|
default:
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
throw new ParseError(
|
|
530
|
+
"SELECT / INSERT / UPDATE / DELETE / REORDER / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
|
|
531
|
+
tok
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
// ----------------------------------------------------------
|
|
535
|
+
// CREATE TEMP TABLE / DROP TEMP TABLE(バッチ内一時テーブル)
|
|
536
|
+
// CREATE / DROP / TEMP / TABLE は予約語にしない(ソフトキーワード)
|
|
537
|
+
// ----------------------------------------------------------
|
|
538
|
+
parseCreateTempTable() {
|
|
539
|
+
this.advance();
|
|
540
|
+
this.expectSoftKeyword("TEMP", "CREATE \u306E\u5F8C\u306B\u306F TEMP TABLE \u304C\u5FC5\u8981\u3067\u3059\uFF08\u4F8B: CREATE TEMP TABLE #temp AS SELECT ...\uFF09");
|
|
541
|
+
this.expectSoftKeyword("TABLE", "CREATE TEMP \u306E\u5F8C\u306B\u306F TABLE \u304C\u5FC5\u8981\u3067\u3059");
|
|
542
|
+
const name = this.parseTempTableName();
|
|
543
|
+
this.expect("AS" /* AS */, "CREATE TEMP TABLE \u306B\u306F AS SELECT \u304C\u5FC5\u8981\u3067\u3059");
|
|
544
|
+
const tok = this.peek();
|
|
545
|
+
let query;
|
|
546
|
+
if (tok.kind === "WITH" /* WITH */) {
|
|
547
|
+
query = this.parseWith();
|
|
548
|
+
} else if (tok.kind === "SELECT" /* SELECT */) {
|
|
549
|
+
query = this.tryParseUnionChain(this.parseSelect());
|
|
550
|
+
} else {
|
|
551
|
+
throw new ParseError("CREATE TEMP TABLE ... AS \u306E\u5F8C\u306B\u306F SELECT / WITH \u304C\u5FC5\u8981\u3067\u3059", tok);
|
|
445
552
|
}
|
|
553
|
+
return { type: "CREATE_TEMP_TABLE", name, query };
|
|
554
|
+
}
|
|
555
|
+
parseDropTempTable() {
|
|
556
|
+
this.advance();
|
|
557
|
+
this.expectSoftKeyword("TEMP", "DROP \u306E\u5F8C\u306B\u306F TEMP TABLE \u304C\u5FC5\u8981\u3067\u3059\uFF08\u4F8B: DROP TEMP TABLE #temp\uFF09");
|
|
558
|
+
this.expectSoftKeyword("TABLE", "DROP TEMP \u306E\u5F8C\u306B\u306F TABLE \u304C\u5FC5\u8981\u3067\u3059");
|
|
559
|
+
const name = this.parseTempTableName();
|
|
560
|
+
return { type: "DROP_TEMP_TABLE", name };
|
|
561
|
+
}
|
|
562
|
+
expectSoftKeyword(word, msg) {
|
|
563
|
+
const tok = this.peek();
|
|
564
|
+
if (tok.kind === "IDENT" /* IDENT */ && tok.value.toUpperCase() === word) {
|
|
565
|
+
this.advance();
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
throw new ParseError(msg, tok);
|
|
569
|
+
}
|
|
570
|
+
parseTempTableName() {
|
|
571
|
+
const tok = this.peek();
|
|
572
|
+
if (tok.kind === "IDENT" /* IDENT */ && tok.value.startsWith("#")) {
|
|
573
|
+
this.advance();
|
|
574
|
+
return tok.value;
|
|
575
|
+
}
|
|
576
|
+
throw new ParseError("\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u540D\u306F # \u3067\u59CB\u307E\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\uFF08\u4F8B: #temp\uFF09", tok);
|
|
446
577
|
}
|
|
447
578
|
parseShow() {
|
|
448
579
|
this.advance();
|
|
@@ -999,24 +1130,38 @@ var Parser = class {
|
|
|
999
1130
|
// FROM / JOIN
|
|
1000
1131
|
// ----------------------------------------------------------
|
|
1001
1132
|
parseTableRef() {
|
|
1002
|
-
const
|
|
1133
|
+
const nameTok = this.peek();
|
|
1134
|
+
const name = this.parseTableName();
|
|
1135
|
+
if (nameTok.kind === "IDENT" /* IDENT */ && name.startsWith("#")) {
|
|
1136
|
+
this.tempTableRefs.push(this.prev());
|
|
1137
|
+
const alias2 = this.consume("AS" /* AS */) ? this.parseTableAliasName() : this.tryParseImplicitAlias();
|
|
1138
|
+
return { appId: 0, alias: alias2, cteName: name };
|
|
1139
|
+
}
|
|
1003
1140
|
if (this.cteNames.has(name)) {
|
|
1004
|
-
const alias2 = this.consume("AS" /* AS */) ? this.
|
|
1141
|
+
const alias2 = this.consume("AS" /* AS */) ? this.parseTableAliasName() : this.tryParseImplicitAlias();
|
|
1005
1142
|
return { appId: 0, alias: alias2, cteName: name };
|
|
1006
1143
|
}
|
|
1007
1144
|
const { appId, subtableCode } = extractTableRef(name, this.prev());
|
|
1008
1145
|
if (subtableCode) {
|
|
1009
|
-
const alias2 = this.consume("AS" /* AS */) ? this.
|
|
1146
|
+
const alias2 = this.consume("AS" /* AS */) ? this.parseTableAliasName() : this.tryParseImplicitAlias();
|
|
1010
1147
|
return { appId, alias: alias2, cteName: null, subtableCode };
|
|
1011
1148
|
}
|
|
1012
1149
|
const implicit = this.tryParseImplicitAlias();
|
|
1013
|
-
const alias = this.consume("AS" /* AS */) ? this.
|
|
1150
|
+
const alias = this.consume("AS" /* AS */) ? this.parseTableAliasName() : implicit ?? name;
|
|
1014
1151
|
return { appId, alias, cteName: null };
|
|
1015
1152
|
}
|
|
1153
|
+
// テーブル alias 名を読む(IDENT / BIDENT)。alias 位置の # は BIDENT でも拒否
|
|
1154
|
+
parseTableAliasName() {
|
|
1155
|
+
const tok = this.peek();
|
|
1156
|
+
if ((tok.kind === "IDENT" /* IDENT */ || tok.kind === "BIDENT" /* BIDENT */) && tok.value.startsWith("#")) {
|
|
1157
|
+
throw new ParseError("\u30A8\u30A4\u30EA\u30A2\u30B9\u540D\u306B # \u3067\u59CB\u307E\u308B\u540D\u524D\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
|
|
1158
|
+
}
|
|
1159
|
+
return this.parseIdentifier();
|
|
1160
|
+
}
|
|
1016
1161
|
tryParseImplicitAlias() {
|
|
1017
1162
|
const k = this.peek().kind;
|
|
1018
1163
|
if (k === "IDENT" /* IDENT */ || k === "BIDENT" /* BIDENT */) {
|
|
1019
|
-
return this.
|
|
1164
|
+
return this.parseTableAliasName();
|
|
1020
1165
|
}
|
|
1021
1166
|
return null;
|
|
1022
1167
|
}
|
|
@@ -1421,6 +1566,7 @@ var Parser = class {
|
|
|
1421
1566
|
parseInsert() {
|
|
1422
1567
|
this.expect("INSERT" /* INSERT */);
|
|
1423
1568
|
this.expect("INTO" /* INTO */);
|
|
1569
|
+
this.rejectTempTableDml();
|
|
1424
1570
|
const name = this.parseIdentifier();
|
|
1425
1571
|
const { appId, subtableCode } = extractTableRef(name, this.prev());
|
|
1426
1572
|
this.expect("(" /* LPAREN */);
|
|
@@ -1446,6 +1592,7 @@ var Parser = class {
|
|
|
1446
1592
|
parseUpsert() {
|
|
1447
1593
|
this.expect("UPSERT" /* UPSERT */);
|
|
1448
1594
|
this.expect("INTO" /* INTO */);
|
|
1595
|
+
this.rejectTempTableDml();
|
|
1449
1596
|
const name = this.parseIdentifier();
|
|
1450
1597
|
const { appId, subtableCode } = extractTableRef(name, this.prev());
|
|
1451
1598
|
if (subtableCode) {
|
|
@@ -1530,6 +1677,7 @@ var Parser = class {
|
|
|
1530
1677
|
// ----------------------------------------------------------
|
|
1531
1678
|
parseUpdate() {
|
|
1532
1679
|
this.expect("UPDATE" /* UPDATE */);
|
|
1680
|
+
this.rejectTempTableDml();
|
|
1533
1681
|
const name = this.parseIdentifier();
|
|
1534
1682
|
const { appId, subtableCode } = extractTableRef(name, this.prev());
|
|
1535
1683
|
this.expect("SET" /* SET */);
|
|
@@ -1605,6 +1753,7 @@ var Parser = class {
|
|
|
1605
1753
|
parseDelete() {
|
|
1606
1754
|
this.expect("DELETE" /* DELETE */);
|
|
1607
1755
|
this.expect("FROM" /* FROM */);
|
|
1756
|
+
this.rejectTempTableDml();
|
|
1608
1757
|
const name = this.parseIdentifier();
|
|
1609
1758
|
const { appId, subtableCode } = extractTableRef(name, this.prev());
|
|
1610
1759
|
const whereTok = this.peek();
|
|
@@ -1623,6 +1772,7 @@ var Parser = class {
|
|
|
1623
1772
|
parseReorder() {
|
|
1624
1773
|
this.expect("REORDER" /* REORDER */);
|
|
1625
1774
|
const all = this.consume("ALL" /* ALL */);
|
|
1775
|
+
this.rejectTempTableDml();
|
|
1626
1776
|
const name = this.parseIdentifier();
|
|
1627
1777
|
const { appId, subtableCode } = extractTableRef(name, this.prev());
|
|
1628
1778
|
if (!subtableCode) {
|
|
@@ -1704,8 +1854,29 @@ var Parser = class {
|
|
|
1704
1854
|
}
|
|
1705
1855
|
return n;
|
|
1706
1856
|
}
|
|
1707
|
-
// 識別子(IDENT / BIDENT
|
|
1857
|
+
// 識別子(IDENT / BIDENT)を読む。# 始まりの一時テーブル名は不可
|
|
1858
|
+
//(temp マーカーはレキサが生成する IDENT のみ。`#field` のような
|
|
1859
|
+
// バッククォート識別子は # で始まる通常フィールド名として許容する)
|
|
1708
1860
|
parseIdentifier() {
|
|
1861
|
+
const tok = this.peek();
|
|
1862
|
+
if (tok.kind === "IDENT" /* IDENT */ || tok.kind === "BIDENT" /* BIDENT */) {
|
|
1863
|
+
if (tok.kind === "IDENT" /* IDENT */ && tok.value.startsWith("#")) {
|
|
1864
|
+
throw new ParseError(
|
|
1865
|
+
"\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u540D\uFF08# \u3067\u59CB\u307E\u308B\u540D\u524D\uFF09\u306F FROM / JOIN / CREATE / DROP TEMP TABLE \u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059",
|
|
1866
|
+
tok
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
this.advance();
|
|
1870
|
+
return tok.value;
|
|
1871
|
+
}
|
|
1872
|
+
throw new ParseError(
|
|
1873
|
+
"\u30D5\u30A3\u30FC\u30EB\u30C9\u540D\u307E\u305F\u306F\u30C6\u30FC\u30D6\u30EB\u540D\u304C\u5FC5\u8981\u3067\u3059",
|
|
1874
|
+
tok
|
|
1875
|
+
);
|
|
1876
|
+
}
|
|
1877
|
+
// テーブル名(IDENT / BIDENT)を読む。# 始まりの一時テーブル名を許容する
|
|
1878
|
+
//(一時テーブルを受理してよいのはテーブル参照位置のみ。他は parseIdentifier を使う)
|
|
1879
|
+
parseTableName() {
|
|
1709
1880
|
const tok = this.peek();
|
|
1710
1881
|
if (tok.kind === "IDENT" /* IDENT */ || tok.kind === "BIDENT" /* BIDENT */) {
|
|
1711
1882
|
this.advance();
|
|
@@ -1716,10 +1887,23 @@ var Parser = class {
|
|
|
1716
1887
|
tok
|
|
1717
1888
|
);
|
|
1718
1889
|
}
|
|
1890
|
+
// DML の対象テーブル位置に一時テーブルが指定されていたら拒否する
|
|
1891
|
+
rejectTempTableDml() {
|
|
1892
|
+
const tok = this.peek();
|
|
1893
|
+
if (tok.kind === "IDENT" /* IDENT */ && tok.value.startsWith("#")) {
|
|
1894
|
+
throw new ParseError(
|
|
1895
|
+
`DML on temp table ${tok.value} is not supported.`,
|
|
1896
|
+
tok
|
|
1897
|
+
);
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1719
1900
|
// エイリアス名: IDENT / BIDENT に加え、キーワードも許容する
|
|
1720
1901
|
// 例: SELECT SUM(金額) AS avg → "avg" は AVG キーワードだが alias として有効
|
|
1721
1902
|
parseAliasName() {
|
|
1722
1903
|
const tok = this.peek();
|
|
1904
|
+
if (tok.value.startsWith("#")) {
|
|
1905
|
+
throw new ParseError("\u30A8\u30A4\u30EA\u30A2\u30B9\u540D\u306B # \u3067\u59CB\u307E\u308B\u540D\u524D\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
|
|
1906
|
+
}
|
|
1723
1907
|
if (tok.kind === "IDENT" /* IDENT */ || tok.kind === "BIDENT" /* BIDENT */ || KEYWORDS.has(tok.value.toUpperCase())) {
|
|
1724
1908
|
this.advance();
|
|
1725
1909
|
return tok.value.toLowerCase();
|
|
@@ -1776,6 +1960,171 @@ function extractTableRef(name, tok) {
|
|
|
1776
1960
|
return { appId: Number(m[1]), subtableCode: m[2] ?? null };
|
|
1777
1961
|
}
|
|
1778
1962
|
|
|
1963
|
+
// src/core/dmlGuard.ts
|
|
1964
|
+
function getStatementType(stmt) {
|
|
1965
|
+
if (!stmt || typeof stmt !== "object") return "UNKNOWN";
|
|
1966
|
+
const obj = stmt;
|
|
1967
|
+
return typeof obj.type === "string" ? obj.type : "UNKNOWN";
|
|
1968
|
+
}
|
|
1969
|
+
function isDmlType(type) {
|
|
1970
|
+
return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
|
|
1971
|
+
}
|
|
1972
|
+
function isReadOnlyType(type) {
|
|
1973
|
+
return type === "SELECT" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE";
|
|
1974
|
+
}
|
|
1975
|
+
function hasWhereClause(stmt) {
|
|
1976
|
+
if (!stmt || typeof stmt !== "object") return false;
|
|
1977
|
+
const obj = stmt;
|
|
1978
|
+
return obj.where !== null && obj.where !== void 0;
|
|
1979
|
+
}
|
|
1980
|
+
function isNoFromSelectStatement(stmt) {
|
|
1981
|
+
if (!stmt || typeof stmt !== "object") return false;
|
|
1982
|
+
const obj = stmt;
|
|
1983
|
+
return obj.type === "SELECT" && obj.from?.appId === 0 && obj.from?.cteName === "__NO_FROM__";
|
|
1984
|
+
}
|
|
1985
|
+
function getInsertValuesCount(stmt) {
|
|
1986
|
+
if (!stmt || typeof stmt !== "object") return null;
|
|
1987
|
+
const obj = stmt;
|
|
1988
|
+
if (obj.type !== "INSERT") return null;
|
|
1989
|
+
return Array.isArray(obj.values) ? obj.values.length : null;
|
|
1990
|
+
}
|
|
1991
|
+
function collectDmlTargetFields(stmt) {
|
|
1992
|
+
if (!stmt || typeof stmt !== "object") return [];
|
|
1993
|
+
const obj = stmt;
|
|
1994
|
+
if (!obj.type) return [];
|
|
1995
|
+
if (obj.type === "UPDATE") {
|
|
1996
|
+
return (obj.assignments ?? []).map((a) => a.field).filter((f) => Boolean(f));
|
|
1997
|
+
}
|
|
1998
|
+
if (obj.type === "INSERT" || obj.type === "INSERT_SELECT" || obj.type === "UPSERT" || obj.type === "UPSERT_SELECT") {
|
|
1999
|
+
return [...obj.fields ?? [], ...obj.keyFields ?? []];
|
|
2000
|
+
}
|
|
2001
|
+
return [];
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
// src/core/batch.ts
|
|
2005
|
+
var MAX_TEMP_TABLES = 16;
|
|
2006
|
+
var BatchAnalysisError = class extends Error {
|
|
2007
|
+
constructor(message, statementIndex) {
|
|
2008
|
+
super(message);
|
|
2009
|
+
this.statementIndex = statementIndex;
|
|
2010
|
+
}
|
|
2011
|
+
};
|
|
2012
|
+
function collectRefs(node, tempRefs, appIds) {
|
|
2013
|
+
if (Array.isArray(node)) {
|
|
2014
|
+
for (const v of node) collectRefs(v, tempRefs, appIds);
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
if (node !== null && typeof node === "object") {
|
|
2018
|
+
const obj = node;
|
|
2019
|
+
const cte = obj["cteName"];
|
|
2020
|
+
if (typeof cte === "string" && cte.startsWith("#")) tempRefs.add(cte);
|
|
2021
|
+
const appId = obj["appId"];
|
|
2022
|
+
if (typeof appId === "number" && appId > 0) appIds.add(appId);
|
|
2023
|
+
for (const v of Object.values(obj)) collectRefs(v, tempRefs, appIds);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
function analyzeBatch(statements) {
|
|
2027
|
+
if (statements.length === 0) {
|
|
2028
|
+
throw new BatchAnalysisError("ArgumentError: SQL is empty.", 0);
|
|
2029
|
+
}
|
|
2030
|
+
if (statements.length === 1) {
|
|
2031
|
+
const t = statements[0].type;
|
|
2032
|
+
if (t === "CREATE_TEMP_TABLE" || t === "DROP_TEMP_TABLE") {
|
|
2033
|
+
const verb = t === "CREATE_TEMP_TABLE" ? "CREATE TEMP TABLE" : "DROP TEMP TABLE";
|
|
2034
|
+
throw new BatchAnalysisError(
|
|
2035
|
+
`ArgumentError: ${verb} requires a batch (temp tables are batch-scoped).`,
|
|
2036
|
+
0
|
|
2037
|
+
);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
const defined = /* @__PURE__ */ new Map();
|
|
2041
|
+
const createdOrder = [];
|
|
2042
|
+
const results = [];
|
|
2043
|
+
statements.forEach((stmt, index) => {
|
|
2044
|
+
const statementType = getStatementType(stmt);
|
|
2045
|
+
const created = [];
|
|
2046
|
+
const dropped = [];
|
|
2047
|
+
const refs = /* @__PURE__ */ new Set();
|
|
2048
|
+
const stmtAppIds = /* @__PURE__ */ new Set();
|
|
2049
|
+
const dependsOn = /* @__PURE__ */ new Set();
|
|
2050
|
+
if (stmt.type === "CREATE_TEMP_TABLE") {
|
|
2051
|
+
collectRefs(stmt.query, refs, stmtAppIds);
|
|
2052
|
+
} else if (stmt.type === "DROP_TEMP_TABLE") {
|
|
2053
|
+
} else {
|
|
2054
|
+
collectRefs(stmt, refs, stmtAppIds);
|
|
2055
|
+
}
|
|
2056
|
+
let tempOnlySource = false;
|
|
2057
|
+
if (stmt.type === "INSERT_SELECT" || stmt.type === "UPSERT_SELECT") {
|
|
2058
|
+
const srcTemp = /* @__PURE__ */ new Set();
|
|
2059
|
+
const srcApps = /* @__PURE__ */ new Set();
|
|
2060
|
+
collectRefs(stmt.select, srcTemp, srcApps);
|
|
2061
|
+
tempOnlySource = srcTemp.size > 0 && srcApps.size === 0;
|
|
2062
|
+
}
|
|
2063
|
+
for (const name of refs) {
|
|
2064
|
+
const at = defined.get(name);
|
|
2065
|
+
if (at === void 0) {
|
|
2066
|
+
throw new BatchAnalysisError(
|
|
2067
|
+
`ParseError: temp table ${name} is not defined in this batch.`,
|
|
2068
|
+
index
|
|
2069
|
+
);
|
|
2070
|
+
}
|
|
2071
|
+
dependsOn.add(at);
|
|
2072
|
+
}
|
|
2073
|
+
if (stmt.type === "CREATE_TEMP_TABLE") {
|
|
2074
|
+
if (defined.has(stmt.name)) {
|
|
2075
|
+
throw new BatchAnalysisError(
|
|
2076
|
+
`ParseError: temp table ${stmt.name} is already defined.`,
|
|
2077
|
+
index
|
|
2078
|
+
);
|
|
2079
|
+
}
|
|
2080
|
+
defined.set(stmt.name, index);
|
|
2081
|
+
createdOrder.push(stmt.name);
|
|
2082
|
+
created.push(stmt.name);
|
|
2083
|
+
if (defined.size > MAX_TEMP_TABLES) {
|
|
2084
|
+
throw new BatchAnalysisError(
|
|
2085
|
+
`ParseError: batch exceeds ${MAX_TEMP_TABLES} temp tables.`,
|
|
2086
|
+
index
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
if (stmt.type === "DROP_TEMP_TABLE") {
|
|
2091
|
+
const at = defined.get(stmt.name);
|
|
2092
|
+
if (at === void 0) {
|
|
2093
|
+
throw new BatchAnalysisError(
|
|
2094
|
+
`ParseError: temp table ${stmt.name} is not defined in this batch.`,
|
|
2095
|
+
index
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
dependsOn.add(at);
|
|
2099
|
+
dropped.push(stmt.name);
|
|
2100
|
+
defined.delete(stmt.name);
|
|
2101
|
+
}
|
|
2102
|
+
results.push({
|
|
2103
|
+
index,
|
|
2104
|
+
statementType,
|
|
2105
|
+
isDml: isDmlType(statementType),
|
|
2106
|
+
isReadOnly: isReadOnlyType(statementType),
|
|
2107
|
+
hasWhere: hasWhereClause(stmt),
|
|
2108
|
+
insertValuesCount: getInsertValuesCount(stmt),
|
|
2109
|
+
appIds: [...stmtAppIds].sort((a, b) => a - b),
|
|
2110
|
+
tempTablesCreated: created,
|
|
2111
|
+
tempTablesReferenced: [...refs],
|
|
2112
|
+
tempTablesDropped: dropped,
|
|
2113
|
+
dependsOn: [...dependsOn].sort((a, b) => a - b),
|
|
2114
|
+
tempOnlySource,
|
|
2115
|
+
targetAppId: isDmlType(statementType) && typeof stmt.appId === "number" ? stmt.appId : null
|
|
2116
|
+
});
|
|
2117
|
+
});
|
|
2118
|
+
const containsDml = results.some((r) => r.isDml);
|
|
2119
|
+
return {
|
|
2120
|
+
statementCount: statements.length,
|
|
2121
|
+
isReadOnlyBatch: !containsDml && results.every((r) => r.isReadOnly),
|
|
2122
|
+
containsDml,
|
|
2123
|
+
tempTables: createdOrder,
|
|
2124
|
+
statements: results
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
|
|
1779
2128
|
// src/engine/pushDownNot.ts
|
|
1780
2129
|
function pushDownNot(expr) {
|
|
1781
2130
|
switch (expr.type) {
|
|
@@ -3453,8 +3802,10 @@ function applyGroupBy(rows, groupByKeys, columns) {
|
|
|
3453
3802
|
}
|
|
3454
3803
|
for (const col of columns) {
|
|
3455
3804
|
if (col.type === "AGGREGATE") {
|
|
3456
|
-
const
|
|
3457
|
-
|
|
3805
|
+
const syntheticKey = aggregateSyntheticName2(col.func, col.distinct, col.arg);
|
|
3806
|
+
const value = String(evalAggregate(col.func, col.distinct, col.arg, groupRows));
|
|
3807
|
+
outRow[col.alias ?? syntheticKey] = value;
|
|
3808
|
+
if (col.alias) outRow[syntheticKey] = value;
|
|
3458
3809
|
} else if (col.type === "ARITH_AGG_COL") {
|
|
3459
3810
|
const outputKey = col.alias ?? aggArithDefaultKey(col.expr);
|
|
3460
3811
|
outRow[outputKey] = String(evalAggArithExpr(col.expr, groupRows));
|
|
@@ -3965,6 +4316,9 @@ function wrapClientWithMetrics(client, metrics) {
|
|
|
3965
4316
|
async function executeStatement(sql, client, options) {
|
|
3966
4317
|
const cacheContext = options.cacheContext ?? "default";
|
|
3967
4318
|
const stmt = parseSql(sql);
|
|
4319
|
+
return executeParsedStatement(stmt, client, options, cacheContext);
|
|
4320
|
+
}
|
|
4321
|
+
async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
3968
4322
|
switch (stmt.type) {
|
|
3969
4323
|
case "SELECT":
|
|
3970
4324
|
return executeSelect(stmt, client, options, cacheContext);
|
|
@@ -3992,9 +4346,181 @@ async function executeStatement(sql, client, options) {
|
|
|
3992
4346
|
return executeDescribe(stmt, client, cacheContext);
|
|
3993
4347
|
case "EXPLAIN":
|
|
3994
4348
|
return executeExplain(stmt);
|
|
4349
|
+
// 一時テーブルはバッチスコープのため単文実行では拒否する(executeBatch を使う)
|
|
4350
|
+
case "CREATE_TEMP_TABLE":
|
|
4351
|
+
throw new Error("ArgumentError: CREATE TEMP TABLE requires a batch (temp tables are batch-scoped).");
|
|
4352
|
+
case "DROP_TEMP_TABLE":
|
|
4353
|
+
throw new Error("ArgumentError: DROP TEMP TABLE requires a batch (temp tables are batch-scoped).");
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
var TEMP_TABLE_MAX_ROWS = 1e4;
|
|
4357
|
+
var BatchTimeoutError = class extends Error {
|
|
4358
|
+
constructor() {
|
|
4359
|
+
super("TimeoutError: batch timeout exceeded.");
|
|
4360
|
+
}
|
|
4361
|
+
};
|
|
4362
|
+
async function executeBatch(sql, client, options = {}) {
|
|
4363
|
+
const statements = parseSqlBatch(sql);
|
|
4364
|
+
const analysis = analyzeBatch(statements);
|
|
4365
|
+
if (options.continueOnError && analysis.containsDml) {
|
|
4366
|
+
throw new Error("ArgumentError: continueOnError is not allowed for batches containing DML.");
|
|
4367
|
+
}
|
|
4368
|
+
for (const s of analysis.statements) {
|
|
4369
|
+
if (!s.isDml || s.tempTablesReferenced.length === 0) continue;
|
|
4370
|
+
if (s.statementType === "INSERT_SELECT" && s.tempOnlySource) continue;
|
|
4371
|
+
throw new BatchAnalysisError(
|
|
4372
|
+
s.statementType === "INSERT_SELECT" ? `ArgumentError: INSERT_SELECT in a batch must select from temp tables only. (statement ${s.index})` : `ArgumentError: temp table references in ${s.statementType} are not supported yet.`,
|
|
4373
|
+
s.index
|
|
4374
|
+
);
|
|
4375
|
+
}
|
|
4376
|
+
const metrics = createEmptyMetrics();
|
|
4377
|
+
const countedClient = wrapClientWithMetrics(client, metrics);
|
|
4378
|
+
const startedAt = Date.now();
|
|
4379
|
+
const deadline = options.timeoutMs != null ? startedAt + options.timeoutMs : null;
|
|
4380
|
+
const cacheContext = options.cacheContext ?? "default";
|
|
4381
|
+
const tempTables = /* @__PURE__ */ new Map();
|
|
4382
|
+
const results = [];
|
|
4383
|
+
const failed = /* @__PURE__ */ new Set();
|
|
4384
|
+
let aborted = null;
|
|
4385
|
+
for (let i = 0; i < statements.length; i++) {
|
|
4386
|
+
const info = analysis.statements[i];
|
|
4387
|
+
const base = { index: i, type: info.statementType };
|
|
4388
|
+
if (aborted) {
|
|
4389
|
+
results.push({ ...base, status: "skipped", skippedReason: aborted });
|
|
4390
|
+
failed.add(i);
|
|
4391
|
+
continue;
|
|
4392
|
+
}
|
|
4393
|
+
const brokenDep = info.dependsOn.find((d) => failed.has(d));
|
|
4394
|
+
if (brokenDep !== void 0) {
|
|
4395
|
+
const depName = analysis.statements[brokenDep].tempTablesCreated[0] ?? `statement ${brokenDep}`;
|
|
4396
|
+
results.push({ ...base, status: "skipped", skippedReason: `dependency: ${depName}` });
|
|
4397
|
+
failed.add(i);
|
|
4398
|
+
continue;
|
|
4399
|
+
}
|
|
4400
|
+
if (deadline !== null && Date.now() >= deadline) {
|
|
4401
|
+
results.push({ ...base, status: "skipped", skippedReason: "timeout" });
|
|
4402
|
+
failed.add(i);
|
|
4403
|
+
aborted = "timeout";
|
|
4404
|
+
continue;
|
|
4405
|
+
}
|
|
4406
|
+
try {
|
|
4407
|
+
const remaining = deadline !== null ? deadline - Date.now() : null;
|
|
4408
|
+
const outcome = await runWithDeadline(
|
|
4409
|
+
executeBatchStatement(statements[i], info, countedClient, options, cacheContext, tempTables),
|
|
4410
|
+
remaining
|
|
4411
|
+
);
|
|
4412
|
+
results.push({ ...base, status: "success", ...outcome });
|
|
4413
|
+
} catch (e) {
|
|
4414
|
+
results.push({ ...base, status: "error", error: toBatchStatementError(e) });
|
|
4415
|
+
failed.add(i);
|
|
4416
|
+
if (e instanceof BatchTimeoutError) {
|
|
4417
|
+
aborted = "timeout";
|
|
4418
|
+
} else if (!options.continueOnError) {
|
|
4419
|
+
aborted = "fail-fast";
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4422
|
+
}
|
|
4423
|
+
metrics.elapsedMs = Date.now() - startedAt;
|
|
4424
|
+
return {
|
|
4425
|
+
ok: results.every((r) => r.status === "success"),
|
|
4426
|
+
statementCount: statements.length,
|
|
4427
|
+
statements: results,
|
|
4428
|
+
analysis,
|
|
4429
|
+
metrics
|
|
4430
|
+
};
|
|
4431
|
+
}
|
|
4432
|
+
async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables) {
|
|
4433
|
+
if (stmt.type === "CREATE_TEMP_TABLE") {
|
|
4434
|
+
const materializeOptions = {
|
|
4435
|
+
...options,
|
|
4436
|
+
maxRecords: options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
|
|
4437
|
+
onLimitReached: "error"
|
|
4438
|
+
};
|
|
4439
|
+
const result = await runSelectLike(stmt.query, client, materializeOptions, cacheContext, tempTables);
|
|
4440
|
+
tempTables.set(stmt.name, result.rows);
|
|
4441
|
+
return { tempTable: stmt.name, rowCount: result.rows.length };
|
|
4442
|
+
}
|
|
4443
|
+
if (stmt.type === "DROP_TEMP_TABLE") {
|
|
4444
|
+
tempTables.delete(stmt.name);
|
|
4445
|
+
return { tempTable: stmt.name };
|
|
4446
|
+
}
|
|
4447
|
+
if (stmt.type === "EXPLAIN") {
|
|
4448
|
+
return { result: await executeParsedStatement(stmt, client, options, cacheContext) };
|
|
4449
|
+
}
|
|
4450
|
+
if (info.tempTablesReferenced.length > 0) {
|
|
4451
|
+
if (stmt.type === "SELECT" || stmt.type === "UNION") {
|
|
4452
|
+
return { result: await executeQueryWithCte(stmt, client, options, tempTables, cacheContext) };
|
|
4453
|
+
}
|
|
4454
|
+
if (stmt.type === "WITH") {
|
|
4455
|
+
return { result: await executeWith(stmt, client, options, cacheContext, tempTables) };
|
|
4456
|
+
}
|
|
4457
|
+
if (stmt.type === "INSERT_SELECT") {
|
|
4458
|
+
return { result: await executeInsertSelect(stmt, client, options, cacheContext, tempTables) };
|
|
4459
|
+
}
|
|
4460
|
+
throw new Error(`ArgumentError: temp table references in ${stmt.type} are not supported yet.`);
|
|
4461
|
+
}
|
|
4462
|
+
return { result: await executeParsedStatement(stmt, client, options, cacheContext) };
|
|
4463
|
+
}
|
|
4464
|
+
async function runSelectLike(query, client, options, cacheContext, tempTables) {
|
|
4465
|
+
if (query.type === "WITH") {
|
|
4466
|
+
return executeWith(query, client, options, cacheContext, tempTables);
|
|
4467
|
+
}
|
|
4468
|
+
return executeQueryWithCte(query, client, options, tempTables, cacheContext);
|
|
4469
|
+
}
|
|
4470
|
+
async function runWithDeadline(work, remainingMs) {
|
|
4471
|
+
if (remainingMs === null) return work;
|
|
4472
|
+
if (remainingMs <= 0) {
|
|
4473
|
+
void work.catch(() => {
|
|
4474
|
+
});
|
|
4475
|
+
throw new BatchTimeoutError();
|
|
4476
|
+
}
|
|
4477
|
+
let timer;
|
|
4478
|
+
try {
|
|
4479
|
+
return await Promise.race([
|
|
4480
|
+
work,
|
|
4481
|
+
new Promise((_, reject) => {
|
|
4482
|
+
timer = setTimeout(() => reject(new BatchTimeoutError()), remainingMs);
|
|
4483
|
+
})
|
|
4484
|
+
]);
|
|
4485
|
+
} catch (e) {
|
|
4486
|
+
if (e instanceof BatchTimeoutError) {
|
|
4487
|
+
void work.catch(() => {
|
|
4488
|
+
});
|
|
4489
|
+
}
|
|
4490
|
+
throw e;
|
|
4491
|
+
} finally {
|
|
4492
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
4493
|
+
}
|
|
4494
|
+
}
|
|
4495
|
+
function toBatchStatementError(e) {
|
|
4496
|
+
if (e instanceof Error) {
|
|
4497
|
+
const name = e.name !== "Error" ? e.name : null;
|
|
4498
|
+
return { code: name ?? codeFromMessagePrefix(e.message), message: e.message };
|
|
4499
|
+
}
|
|
4500
|
+
if (e !== null && typeof e === "object") {
|
|
4501
|
+
const obj = e;
|
|
4502
|
+
const message2 = typeof obj.message === "string" && obj.message.length > 0 ? obj.message : safeJsonStringify(e);
|
|
4503
|
+
const code = typeof obj.code === "string" && obj.code.length > 0 ? obj.code : codeFromMessagePrefix(message2);
|
|
4504
|
+
return { code, message: message2 };
|
|
3995
4505
|
}
|
|
4506
|
+
const message = String(e);
|
|
4507
|
+
return { code: codeFromMessagePrefix(message), message };
|
|
3996
4508
|
}
|
|
3997
|
-
|
|
4509
|
+
function codeFromMessagePrefix(message) {
|
|
4510
|
+
return message.match(/^([A-Za-z]+Error):/)?.[1] ?? "Error";
|
|
4511
|
+
}
|
|
4512
|
+
function safeJsonStringify(v) {
|
|
4513
|
+
try {
|
|
4514
|
+
return JSON.stringify(v) ?? String(v);
|
|
4515
|
+
} catch {
|
|
4516
|
+
return String(v);
|
|
4517
|
+
}
|
|
4518
|
+
}
|
|
4519
|
+
function parseSqlBatch(sql) {
|
|
4520
|
+
const tokens = new Lexer(sql).tokenize();
|
|
4521
|
+
return new Parser(tokens).parseStatements();
|
|
4522
|
+
}
|
|
4523
|
+
async function executeSelect(stmt, client, options, cacheContext, cteCache) {
|
|
3998
4524
|
if (isNoFromSelect(stmt)) {
|
|
3999
4525
|
return executeNoFromSelect(stmt);
|
|
4000
4526
|
}
|
|
@@ -4003,7 +4529,7 @@ async function executeSelect(stmt, client, options, cacheContext) {
|
|
|
4003
4529
|
if (mode === "SIMPLE") {
|
|
4004
4530
|
return executeSimpleSelect(stmt, client, options, cacheContext);
|
|
4005
4531
|
} else {
|
|
4006
|
-
return executeFullScanSelect(stmt, client, options, cacheContext);
|
|
4532
|
+
return executeFullScanSelect(stmt, client, options, cacheContext, cteCache);
|
|
4007
4533
|
}
|
|
4008
4534
|
}
|
|
4009
4535
|
function isNoFromSelect(stmt) {
|
|
@@ -4127,13 +4653,13 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
|
|
|
4127
4653
|
}
|
|
4128
4654
|
}
|
|
4129
4655
|
}
|
|
4130
|
-
async function executeFullScanSelect(stmt, client, options, cacheContext) {
|
|
4656
|
+
async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache) {
|
|
4131
4657
|
const maxRecords = options.maxRecords ?? 1e4;
|
|
4132
4658
|
const warnings = /* @__PURE__ */ new Set();
|
|
4133
4659
|
const parallel = options.fetchParallel ?? 1;
|
|
4134
4660
|
await Promise.all([
|
|
4135
|
-
resolveSubqueries(stmt.where, client, options, cacheContext),
|
|
4136
|
-
resolveSubqueries(stmt.having, client, options, cacheContext)
|
|
4661
|
+
resolveSubqueries(stmt.where, client, options, cacheContext, cteCache),
|
|
4662
|
+
resolveSubqueries(stmt.having, client, options, cacheContext, cteCache)
|
|
4137
4663
|
]);
|
|
4138
4664
|
const tableConditions = /* @__PURE__ */ new Map();
|
|
4139
4665
|
if (stmt.where !== null) {
|
|
@@ -4183,7 +4709,7 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
|
|
|
4183
4709
|
onOptJoins.push(join2);
|
|
4184
4710
|
}
|
|
4185
4711
|
}
|
|
4186
|
-
const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext);
|
|
4712
|
+
const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
|
|
4187
4713
|
const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
|
|
4188
4714
|
scalarCachePromise.catch(() => {
|
|
4189
4715
|
});
|
|
@@ -4252,11 +4778,11 @@ function deduplicateRows(rows, columns) {
|
|
|
4252
4778
|
return true;
|
|
4253
4779
|
});
|
|
4254
4780
|
}
|
|
4255
|
-
async function executeWith(stmt, client, options, cacheContext) {
|
|
4256
|
-
if (canInlineSingleCte(stmt)) {
|
|
4781
|
+
async function executeWith(stmt, client, options, cacheContext, seed) {
|
|
4782
|
+
if ((seed == null || seed.size === 0) && canInlineSingleCte(stmt)) {
|
|
4257
4783
|
return executeSelect(buildInlinedQuery(stmt), client, options, cacheContext);
|
|
4258
4784
|
}
|
|
4259
|
-
const cteCache =
|
|
4785
|
+
const cteCache = new Map(seed ?? []);
|
|
4260
4786
|
for (const cte of stmt.ctes) {
|
|
4261
4787
|
let result;
|
|
4262
4788
|
if (cte.query.type === "SHOW_APPS") {
|
|
@@ -4368,7 +4894,7 @@ async function executeQueryWithCte(query, client, options, cteCache, cacheContex
|
|
|
4368
4894
|
}
|
|
4369
4895
|
const hasCteRef = query.from.cteName != null || query.joins.some((j) => j.table.cteName != null);
|
|
4370
4896
|
if (!hasCteRef) {
|
|
4371
|
-
return executeSelect(query, client, options, cacheContext);
|
|
4897
|
+
return executeSelect(query, client, options, cacheContext, cteCache);
|
|
4372
4898
|
}
|
|
4373
4899
|
return executeFullScanWithCte(query, client, options, cteCache, cacheContext);
|
|
4374
4900
|
}
|
|
@@ -4377,10 +4903,10 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
4377
4903
|
const warnings = /* @__PURE__ */ new Set();
|
|
4378
4904
|
const parallel = options.fetchParallel ?? 1;
|
|
4379
4905
|
await Promise.all([
|
|
4380
|
-
resolveSubqueries(stmt.where, client, options, cacheContext),
|
|
4381
|
-
resolveSubqueries(stmt.having, client, options, cacheContext)
|
|
4906
|
+
resolveSubqueries(stmt.where, client, options, cacheContext, cteCache),
|
|
4907
|
+
resolveSubqueries(stmt.having, client, options, cacheContext, cteCache)
|
|
4382
4908
|
]);
|
|
4383
|
-
const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext);
|
|
4909
|
+
const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
|
|
4384
4910
|
const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
|
|
4385
4911
|
scalarCachePromise.catch(() => {
|
|
4386
4912
|
});
|
|
@@ -4778,14 +5304,18 @@ async function executeInsert(stmt, client, options, cacheContext) {
|
|
|
4778
5304
|
insertedCount: createdIds.flat().length
|
|
4779
5305
|
};
|
|
4780
5306
|
}
|
|
4781
|
-
async function executeInsertSelect(stmt, client, options, cacheContext) {
|
|
4782
|
-
const selectResult = await executeSelect(stmt.select, client, options, cacheContext);
|
|
5307
|
+
async function executeInsertSelect(stmt, client, options, cacheContext, cteCache) {
|
|
5308
|
+
const selectResult = cteCache !== void 0 && cteCache.size > 0 ? await executeQueryWithCte(stmt.select, client, options, cteCache, cacheContext) : await executeSelect(stmt.select, client, options, cacheContext);
|
|
4783
5309
|
const { rows, columns } = selectResult;
|
|
4784
5310
|
if (columns.length !== stmt.fields.length) {
|
|
4785
5311
|
throw new Error(
|
|
4786
5312
|
`SELECT \u306E\u5217\u6570\uFF08${columns.length}\uFF09\u3068 INSERT \u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u6570\uFF08${stmt.fields.length}\uFF09\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093`
|
|
4787
5313
|
);
|
|
4788
5314
|
}
|
|
5315
|
+
if (options.confirm) {
|
|
5316
|
+
const ok = await options.confirm(rows.length, "INSERT");
|
|
5317
|
+
if (!ok) throw new OperationCancelledError("INSERT", rows.length);
|
|
5318
|
+
}
|
|
4789
5319
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
4790
5320
|
const allRecords = rows.map((row) => {
|
|
4791
5321
|
const record = {};
|
|
@@ -5333,24 +5863,30 @@ function parseSql(sql) {
|
|
|
5333
5863
|
throw e;
|
|
5334
5864
|
}
|
|
5335
5865
|
}
|
|
5336
|
-
async function resolveSubqueries(where, client, options, cacheContext) {
|
|
5866
|
+
async function resolveSubqueries(where, client, options, cacheContext, cteCache) {
|
|
5337
5867
|
const tasks = [];
|
|
5338
|
-
collectSubqueryTasks(where, client, options, cacheContext, tasks);
|
|
5868
|
+
collectSubqueryTasks(where, client, options, cacheContext, tasks, cteCache);
|
|
5339
5869
|
await Promise.all(tasks);
|
|
5340
5870
|
}
|
|
5341
|
-
function
|
|
5871
|
+
function runSubquery(query, client, options, cacheContext, cteCache) {
|
|
5872
|
+
if (cteCache !== void 0 && cteCache.size > 0) {
|
|
5873
|
+
return executeQueryWithCte(query, client, options, cteCache, cacheContext);
|
|
5874
|
+
}
|
|
5875
|
+
return executeSelect(query, client, options, cacheContext);
|
|
5876
|
+
}
|
|
5877
|
+
function collectSubqueryTasks(where, client, options, cacheContext, tasks, cteCache) {
|
|
5342
5878
|
if (where === null) return;
|
|
5343
5879
|
switch (where.type) {
|
|
5344
5880
|
case "BINARY": {
|
|
5345
5881
|
const right = where.right;
|
|
5346
5882
|
if (right.type === "SUBQUERY_IN_LIST") {
|
|
5347
|
-
tasks.push(
|
|
5883
|
+
tasks.push(runSubquery(right.query, client, options, cacheContext, cteCache).then((result) => {
|
|
5348
5884
|
const col = right.column ?? (result.columns[0] ?? "");
|
|
5349
5885
|
right.resolved = new Set(result.rows.map((r) => r[col] ?? ""));
|
|
5350
5886
|
}));
|
|
5351
5887
|
}
|
|
5352
5888
|
if (right.type === "SCALAR_SUBQUERY") {
|
|
5353
|
-
tasks.push(
|
|
5889
|
+
tasks.push(runSubquery(right.query, client, options, cacheContext, cteCache).then((result) => {
|
|
5354
5890
|
if (result.rowCount === 0) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u5024\u3092\u8FD4\u3057\u307E\u305B\u3093\u3067\u3057\u305F");
|
|
5355
5891
|
if (result.rowCount > 1) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u8907\u6570\u884C\u3092\u8FD4\u3057\u307E\u3057\u305F\uFF081\u884C\u306E\u307F\u8A31\u53EF\uFF09");
|
|
5356
5892
|
const col = result.columns[0] ?? "";
|
|
@@ -5360,16 +5896,16 @@ function collectSubqueryTasks(where, client, options, cacheContext, tasks) {
|
|
|
5360
5896
|
break;
|
|
5361
5897
|
}
|
|
5362
5898
|
case "LOGICAL":
|
|
5363
|
-
collectSubqueryTasks(where.left, client, options, cacheContext, tasks);
|
|
5364
|
-
collectSubqueryTasks(where.right, client, options, cacheContext, tasks);
|
|
5899
|
+
collectSubqueryTasks(where.left, client, options, cacheContext, tasks, cteCache);
|
|
5900
|
+
collectSubqueryTasks(where.right, client, options, cacheContext, tasks, cteCache);
|
|
5365
5901
|
break;
|
|
5366
5902
|
case "NOT":
|
|
5367
5903
|
case "GROUP":
|
|
5368
|
-
collectSubqueryTasks(where.expr, client, options, cacheContext, tasks);
|
|
5904
|
+
collectSubqueryTasks(where.expr, client, options, cacheContext, tasks, cteCache);
|
|
5369
5905
|
break;
|
|
5370
5906
|
case "EXISTS": {
|
|
5371
5907
|
const node = where;
|
|
5372
|
-
tasks.push(
|
|
5908
|
+
tasks.push(runSubquery(node.query, client, options, cacheContext, cteCache).then((result) => {
|
|
5373
5909
|
node.resolved = result.rowCount > 0;
|
|
5374
5910
|
}));
|
|
5375
5911
|
break;
|
|
@@ -5387,7 +5923,7 @@ async function resolveSetSubqueries(assignments, client, options, cacheContext)
|
|
|
5387
5923
|
a.value = { type: "STRING", value: resolved };
|
|
5388
5924
|
}
|
|
5389
5925
|
}
|
|
5390
|
-
async function resolveScalarColumns(columns, client, options, cacheContext) {
|
|
5926
|
+
async function resolveScalarColumns(columns, client, options, cacheContext, cteCache) {
|
|
5391
5927
|
const byQuery = /* @__PURE__ */ new Map();
|
|
5392
5928
|
const pending = [];
|
|
5393
5929
|
for (let i = 0; i < columns.length; i++) {
|
|
@@ -5396,7 +5932,7 @@ async function resolveScalarColumns(columns, client, options, cacheContext) {
|
|
|
5396
5932
|
const key = JSON.stringify(col.query);
|
|
5397
5933
|
let promise = byQuery.get(key);
|
|
5398
5934
|
if (!promise) {
|
|
5399
|
-
promise =
|
|
5935
|
+
promise = runSubquery(col.query, client, options, cacheContext, cteCache).then((result) => {
|
|
5400
5936
|
if (result.rowCount === 0) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u5024\u3092\u8FD4\u3057\u307E\u305B\u3093\u3067\u3057\u305F");
|
|
5401
5937
|
if (result.rowCount > 1) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u8907\u6570\u884C\u3092\u8FD4\u3057\u307E\u3057\u305F\uFF081\u884C\u306E\u307F\u8A31\u53EF\uFF09");
|
|
5402
5938
|
const firstCol = result.columns[0] ?? "";
|
|
@@ -5411,6 +5947,59 @@ async function resolveScalarColumns(columns, client, options, cacheContext) {
|
|
|
5411
5947
|
pending.forEach(([i], idx) => cache.set(i, values[idx]));
|
|
5412
5948
|
return cache;
|
|
5413
5949
|
}
|
|
5950
|
+
function buildBatchExplainPlans(sql) {
|
|
5951
|
+
const statements = parseSqlBatch(sql);
|
|
5952
|
+
const analysis = analyzeBatch(statements);
|
|
5953
|
+
return {
|
|
5954
|
+
statementCount: statements.length,
|
|
5955
|
+
statements: statements.map((stmt, i) => ({
|
|
5956
|
+
index: i,
|
|
5957
|
+
type: analysis.statements[i].statementType,
|
|
5958
|
+
plan: buildBatchStatementPlan(stmt, analysis.statements[i])
|
|
5959
|
+
}))
|
|
5960
|
+
};
|
|
5961
|
+
}
|
|
5962
|
+
function buildBatchStatementPlan(stmt, info) {
|
|
5963
|
+
if (stmt.type === "CREATE_TEMP_TABLE") {
|
|
5964
|
+
return [
|
|
5965
|
+
`CREATE TEMP TABLE ${stmt.name}`,
|
|
5966
|
+
` scope: batch\uFF08\u30D0\u30C3\u30C1\u7D42\u4E86\u6642\u306B\u81EA\u52D5\u7834\u68C4\uFF09`,
|
|
5967
|
+
` rows: \u5B9F\u4F53\u5316\u524D\u306E\u305F\u3081\u4E0D\u660E\uFF08\u4E0A\u9650 ${TEMP_TABLE_MAX_ROWS} \u884C\u3001\u8D85\u904E\u306F\u30A8\u30E9\u30FC\uFF09`,
|
|
5968
|
+
...buildPlanForBatchQuery(stmt.query, info).map((l) => ` ${l}`)
|
|
5969
|
+
];
|
|
5970
|
+
}
|
|
5971
|
+
if (stmt.type === "DROP_TEMP_TABLE") {
|
|
5972
|
+
return [
|
|
5973
|
+
`DROP TEMP TABLE ${stmt.name}`,
|
|
5974
|
+
" \u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u30B9\u30C8\u30A2\u306E\u89E3\u653E\u306E\u307F\uFF08kintone \u30A2\u30AF\u30BB\u30B9\u306A\u3057\uFF09"
|
|
5975
|
+
];
|
|
5976
|
+
}
|
|
5977
|
+
if (stmt.type === "SHOW_APPS") return ["SHOW APPS\uFF08\u30A2\u30D7\u30EA\u4E00\u89A7\u306E\u53D6\u5F97\uFF09"];
|
|
5978
|
+
if (stmt.type === "DESCRIBE") return [`DESCRIBE APP${stmt.appId}\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u5B9A\u7FA9\u306E\u53D6\u5F97\uFF09`];
|
|
5979
|
+
if (stmt.type === "EXPLAIN") return buildPlanForBatchQuery(stmt.query, info);
|
|
5980
|
+
return buildPlanForBatchQuery(stmt, info);
|
|
5981
|
+
}
|
|
5982
|
+
function buildPlanForBatchQuery(query, info) {
|
|
5983
|
+
if (info.tempTablesReferenced.length === 0) {
|
|
5984
|
+
return buildExplainPlan(query);
|
|
5985
|
+
}
|
|
5986
|
+
const lines = [];
|
|
5987
|
+
if (query.type === "INSERT_SELECT") {
|
|
5988
|
+
lines.push(
|
|
5989
|
+
`INSERT INTO APP${query.appId} ... SELECT\uFF08\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u30BD\u30FC\u30B9\u3002\u5B9F\u884C\u6642\u306B\u4EF6\u6570\u78BA\u5B9A \u2192 dmlMaxRows \u9069\u7528\uFF09`
|
|
5990
|
+
);
|
|
5991
|
+
}
|
|
5992
|
+
lines.push(" mode: FULL_SCAN\uFF08\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u53C2\u7167\uFF09");
|
|
5993
|
+
lines.push(
|
|
5994
|
+
` temp: ${info.tempTablesReferenced.join(", ")}\uFF08\u30A4\u30F3\u30E1\u30E2\u30EA\u8D70\u67FB\u3002\u5B9F\u4F53\u5316\u524D\u306E\u305F\u3081\u884C\u6570\u4E0D\u660E\uFF09`
|
|
5995
|
+
);
|
|
5996
|
+
const apps = info.appIds.filter((a) => query.type !== "INSERT_SELECT" || a !== query.appId);
|
|
5997
|
+
if (apps.length > 0) {
|
|
5998
|
+
lines.push(` app: ${apps.map((a) => `APP${a}`).join(", ")}`);
|
|
5999
|
+
}
|
|
6000
|
+
lines.push(" note: \u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u3078\u306E WHERE \u30D7\u30C3\u30B7\u30E5\u30C0\u30A6\u30F3\u306F\u884C\u308F\u308C\u306A\u3044");
|
|
6001
|
+
return lines;
|
|
6002
|
+
}
|
|
5414
6003
|
function executeExplain(stmt) {
|
|
5415
6004
|
const lines = buildExplainPlan(stmt.query);
|
|
5416
6005
|
return {
|
|
@@ -5731,6 +6320,10 @@ function parseSqlStatement(sql) {
|
|
|
5731
6320
|
const tokens = new Lexer(sql).tokenize();
|
|
5732
6321
|
return new Parser(tokens).parse();
|
|
5733
6322
|
}
|
|
6323
|
+
function parseSqlStatements(sql) {
|
|
6324
|
+
const tokens = new Lexer(sql).tokenize();
|
|
6325
|
+
return new Parser(tokens).parseStatements();
|
|
6326
|
+
}
|
|
5734
6327
|
|
|
5735
6328
|
// src/core/displayFormat.ts
|
|
5736
6329
|
var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
@@ -5816,207 +6409,6 @@ function isSubtableRow(v) {
|
|
|
5816
6409
|
return typeof obj.id === "string" && typeof obj.value === "object" && obj.value !== null;
|
|
5817
6410
|
}
|
|
5818
6411
|
|
|
5819
|
-
// src/cli/nodeKintoneClient.ts
|
|
5820
|
-
function createNodeKintoneClient(baseUrl, tokenResolver) {
|
|
5821
|
-
const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
|
|
5822
|
-
const apiBasePath = tokenResolver.guestSpaceId && tokenResolver.guestSpaceId > 0 ? `/k/guest/${tokenResolver.guestSpaceId}/v1` : "/k/v1";
|
|
5823
|
-
async function requestJson(path, init, appIdForToken) {
|
|
5824
|
-
const headers = new Headers(init.headers ?? {});
|
|
5825
|
-
if (tokenResolver.auth.type === "token") {
|
|
5826
|
-
headers.set("X-Cybozu-API-Token", tokenResolver.auth.resolveToken(appIdForToken));
|
|
5827
|
-
} else {
|
|
5828
|
-
const credentials = `${tokenResolver.auth.username}:${tokenResolver.auth.password}`;
|
|
5829
|
-
const encoded = Buffer.from(credentials, "utf-8").toString("base64");
|
|
5830
|
-
headers.set("X-Cybozu-Authorization", encoded);
|
|
5831
|
-
}
|
|
5832
|
-
headers.set("Accept", "application/json");
|
|
5833
|
-
const method = String(init.method ?? "GET").toUpperCase();
|
|
5834
|
-
if (method !== "GET" && method !== "HEAD") {
|
|
5835
|
-
headers.set("Content-Type", "application/json");
|
|
5836
|
-
}
|
|
5837
|
-
const timeoutMs = tokenResolver.timeoutMs ?? 3e4;
|
|
5838
|
-
const url = `${normalizedBaseUrl}${path}`;
|
|
5839
|
-
if (tokenResolver.debug) {
|
|
5840
|
-
tokenResolver.log?.(`[debug] request ${String(init.method ?? "GET")} ${url}`);
|
|
5841
|
-
if (tokenResolver.debugHeaders) {
|
|
5842
|
-
const authHeader = headers.get("X-Cybozu-API-Token") ? "X-Cybozu-API-Token=***" : headers.get("X-Cybozu-Authorization") ? "X-Cybozu-Authorization=***" : "Auth=(none)";
|
|
5843
|
-
tokenResolver.log?.(
|
|
5844
|
-
`[debug] request-headers ${authHeader} Content-Type=${headers.get("Content-Type") ?? "(none)"} Accept=${headers.get("Accept") ?? "(none)"}`
|
|
5845
|
-
);
|
|
5846
|
-
}
|
|
5847
|
-
}
|
|
5848
|
-
const res = await fetch(url, {
|
|
5849
|
-
...init,
|
|
5850
|
-
headers,
|
|
5851
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
5852
|
-
});
|
|
5853
|
-
if (!res.ok) {
|
|
5854
|
-
const bodyText = await res.text();
|
|
5855
|
-
if (tokenResolver.debug) {
|
|
5856
|
-
tokenResolver.log?.(`[debug] response status=${res.status} body=${bodyText}`);
|
|
5857
|
-
}
|
|
5858
|
-
throw new Error(`kintone API error ${res.status}: ${bodyText}`);
|
|
5859
|
-
}
|
|
5860
|
-
if (tokenResolver.debug) {
|
|
5861
|
-
tokenResolver.log?.(`[debug] response status=${res.status}`);
|
|
5862
|
-
}
|
|
5863
|
-
return await res.json();
|
|
5864
|
-
}
|
|
5865
|
-
function shouldRetryWithRecordNumberOrder(path, bodyText) {
|
|
5866
|
-
if (!path.includes("/v1/records.json?")) return false;
|
|
5867
|
-
if (!bodyText.includes('"code":"CB_IL02"')) return false;
|
|
5868
|
-
const queryPart = path.split("query=")[1] ?? "";
|
|
5869
|
-
const query = decodeURIComponent(queryPart.split("&")[0] ?? "");
|
|
5870
|
-
if (!query.includes("limit")) return false;
|
|
5871
|
-
if (!query.includes("offset")) return false;
|
|
5872
|
-
if (query.toLowerCase().includes("order by")) return false;
|
|
5873
|
-
return true;
|
|
5874
|
-
}
|
|
5875
|
-
function rewriteQueryWithRecordNumberOrder(path) {
|
|
5876
|
-
const [base, rest] = path.split("query=");
|
|
5877
|
-
if (!rest) return path;
|
|
5878
|
-
const [encodedQuery, ...tail] = rest.split("&");
|
|
5879
|
-
const query = decodeURIComponent(encodedQuery ?? "");
|
|
5880
|
-
const rewritten = `order by \u30EC\u30B3\u30FC\u30C9\u756A\u53F7 asc ${query}`.trim();
|
|
5881
|
-
const nextQuery = encodeURIComponent(rewritten);
|
|
5882
|
-
return `${base}query=${nextQuery}${tail.length > 0 ? `&${tail.join("&")}` : ""}`;
|
|
5883
|
-
}
|
|
5884
|
-
return {
|
|
5885
|
-
async getRecords(params) {
|
|
5886
|
-
const queryPart = `query=${encodeURIComponent(params.query)}`;
|
|
5887
|
-
const appPart = `app=${encodeURIComponent(String(params.app))}`;
|
|
5888
|
-
const fieldParts = params.fields.map((f) => `fields[]=${encodeURIComponent(f)}`);
|
|
5889
|
-
const qs = [appPart, queryPart, ...fieldParts].join("&");
|
|
5890
|
-
if (tokenResolver.debug) {
|
|
5891
|
-
tokenResolver.log?.(
|
|
5892
|
-
`[debug] getRecords app=${params.app} query="${params.query}" fields=${params.fields.length > 0 ? params.fields.join(",") : "(all)"} auth=${tokenResolver.auth.type}`
|
|
5893
|
-
);
|
|
5894
|
-
}
|
|
5895
|
-
const path = `${apiBasePath}/records.json?${qs}`;
|
|
5896
|
-
try {
|
|
5897
|
-
return await requestJson(
|
|
5898
|
-
path,
|
|
5899
|
-
{ method: "GET" },
|
|
5900
|
-
params.app
|
|
5901
|
-
);
|
|
5902
|
-
} catch (err) {
|
|
5903
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
5904
|
-
if (!shouldRetryWithRecordNumberOrder(path, msg)) throw err;
|
|
5905
|
-
const retryPath = rewriteQueryWithRecordNumberOrder(path);
|
|
5906
|
-
if (tokenResolver.debug) {
|
|
5907
|
-
tokenResolver.log?.("[debug] retry with fallback query order by \u30EC\u30B3\u30FC\u30C9\u756A\u53F7 asc");
|
|
5908
|
-
}
|
|
5909
|
-
return await requestJson(
|
|
5910
|
-
retryPath,
|
|
5911
|
-
{ method: "GET" },
|
|
5912
|
-
params.app
|
|
5913
|
-
);
|
|
5914
|
-
}
|
|
5915
|
-
},
|
|
5916
|
-
async postRecords(_params) {
|
|
5917
|
-
const res = await requestJson(
|
|
5918
|
-
`${apiBasePath}/records.json`,
|
|
5919
|
-
{
|
|
5920
|
-
method: "POST",
|
|
5921
|
-
body: JSON.stringify({
|
|
5922
|
-
app: _params.app,
|
|
5923
|
-
records: _params.records
|
|
5924
|
-
})
|
|
5925
|
-
},
|
|
5926
|
-
_params.app
|
|
5927
|
-
);
|
|
5928
|
-
return { ids: res.ids };
|
|
5929
|
-
},
|
|
5930
|
-
async putRecords(_params) {
|
|
5931
|
-
await requestJson(
|
|
5932
|
-
`${apiBasePath}/records.json`,
|
|
5933
|
-
{
|
|
5934
|
-
method: "PUT",
|
|
5935
|
-
body: JSON.stringify({
|
|
5936
|
-
app: _params.app,
|
|
5937
|
-
records: _params.records
|
|
5938
|
-
})
|
|
5939
|
-
},
|
|
5940
|
-
_params.app
|
|
5941
|
-
);
|
|
5942
|
-
},
|
|
5943
|
-
async deleteRecords(_params) {
|
|
5944
|
-
await requestJson(
|
|
5945
|
-
`${apiBasePath}/records.json`,
|
|
5946
|
-
{
|
|
5947
|
-
method: "DELETE",
|
|
5948
|
-
body: JSON.stringify({
|
|
5949
|
-
app: _params.app,
|
|
5950
|
-
ids: _params.ids
|
|
5951
|
-
})
|
|
5952
|
-
},
|
|
5953
|
-
_params.app
|
|
5954
|
-
);
|
|
5955
|
-
},
|
|
5956
|
-
async getApps() {
|
|
5957
|
-
const PAGE = 100;
|
|
5958
|
-
const all = [];
|
|
5959
|
-
let offset = 0;
|
|
5960
|
-
while (true) {
|
|
5961
|
-
const qs = new URLSearchParams();
|
|
5962
|
-
qs.set("limit", String(PAGE));
|
|
5963
|
-
qs.set("offset", String(offset));
|
|
5964
|
-
const res = await requestJson(
|
|
5965
|
-
`${apiBasePath}/apps.json?${qs.toString()}`,
|
|
5966
|
-
{ method: "GET" },
|
|
5967
|
-
0
|
|
5968
|
-
);
|
|
5969
|
-
for (const app of res.apps) {
|
|
5970
|
-
all.push({
|
|
5971
|
-
appId: Number(app.appId),
|
|
5972
|
-
name: app.name,
|
|
5973
|
-
description: app.description
|
|
5974
|
-
});
|
|
5975
|
-
}
|
|
5976
|
-
if (res.apps.length < PAGE) break;
|
|
5977
|
-
offset += PAGE;
|
|
5978
|
-
}
|
|
5979
|
-
return all;
|
|
5980
|
-
},
|
|
5981
|
-
async getFields(appId) {
|
|
5982
|
-
const qs = new URLSearchParams();
|
|
5983
|
-
qs.set("app", String(appId));
|
|
5984
|
-
const res = await requestJson(
|
|
5985
|
-
`${apiBasePath}/app/form/fields.json?${qs.toString()}`,
|
|
5986
|
-
{ method: "GET" },
|
|
5987
|
-
appId
|
|
5988
|
-
);
|
|
5989
|
-
return Object.values(res.properties).map((f) => ({
|
|
5990
|
-
code: f.code,
|
|
5991
|
-
label: f.label,
|
|
5992
|
-
fieldType: f.type,
|
|
5993
|
-
optionOrder: toOptionOrderMap(f.options),
|
|
5994
|
-
sortKind: detectSortKind(f.type, f.format)
|
|
5995
|
-
}));
|
|
5996
|
-
}
|
|
5997
|
-
};
|
|
5998
|
-
}
|
|
5999
|
-
function toOptionOrderMap(options) {
|
|
6000
|
-
if (!options || typeof options !== "object") return void 0;
|
|
6001
|
-
const order = {};
|
|
6002
|
-
let hasAny = false;
|
|
6003
|
-
for (const [label, meta] of Object.entries(options)) {
|
|
6004
|
-
const n = Number(meta?.index);
|
|
6005
|
-
if (!Number.isFinite(n)) continue;
|
|
6006
|
-
order[label] = n;
|
|
6007
|
-
hasAny = true;
|
|
6008
|
-
}
|
|
6009
|
-
return hasAny ? order : void 0;
|
|
6010
|
-
}
|
|
6011
|
-
function detectSortKind(fieldType, calcFormat) {
|
|
6012
|
-
if (fieldType === "NUMBER" || fieldType === "RECORD_NUMBER") return "number";
|
|
6013
|
-
if (fieldType === "CALC") {
|
|
6014
|
-
if (calcFormat === "NUMBER" || calcFormat === "NUMBER_DIGIT") return "number";
|
|
6015
|
-
return "string";
|
|
6016
|
-
}
|
|
6017
|
-
return void 0;
|
|
6018
|
-
}
|
|
6019
|
-
|
|
6020
6412
|
// src/node/appProfiles.ts
|
|
6021
6413
|
var import_fs = require("fs");
|
|
6022
6414
|
function parseTokenMap(raw) {
|
|
@@ -6214,42 +6606,373 @@ function formatResolvedAppProfiles(sql, defaultProfile) {
|
|
|
6214
6606
|
return [...parsed.appBindingByMappedApp.values()].map((b) => `APP${b.appId}->${b.profile}`).join(", ");
|
|
6215
6607
|
}
|
|
6216
6608
|
|
|
6217
|
-
// src/
|
|
6218
|
-
function
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6609
|
+
// src/cli/consoleInput.ts
|
|
6610
|
+
function decideConsoleInput(buffer, line) {
|
|
6611
|
+
const t = line.trim();
|
|
6612
|
+
if (t.startsWith(":")) return { kind: "meta" };
|
|
6613
|
+
const newBuffer = buffer.length > 0 ? `${buffer}
|
|
6614
|
+
${line}` : line;
|
|
6615
|
+
if (newBuffer.trim().length === 0) return { kind: "ignore" };
|
|
6616
|
+
if (isBatchConstruction(newBuffer)) return { kind: "continue", buffer: newBuffer };
|
|
6617
|
+
if (!t.endsWith(";")) return { kind: "continue", buffer: newBuffer };
|
|
6618
|
+
const parsed = tryParseStatements(newBuffer);
|
|
6619
|
+
if (parsed.kind === "ok") {
|
|
6620
|
+
if (parsed.count === 0) return { kind: "ignore" };
|
|
6621
|
+
if (parsed.hasTempTable) return { kind: "continue", buffer: newBuffer };
|
|
6622
|
+
return parsed.count === 1 ? { kind: "execute-single", sql: newBuffer } : { kind: "execute-batch", sql: newBuffer };
|
|
6623
|
+
}
|
|
6624
|
+
if (parsed.continuable) return { kind: "continue", buffer: newBuffer };
|
|
6625
|
+
return { kind: "error", message: parsed.message };
|
|
6626
|
+
}
|
|
6627
|
+
function decideRun(buffer) {
|
|
6628
|
+
if (buffer.trim().length === 0) {
|
|
6629
|
+
return { kind: "error", message: "ArgumentError: input buffer is empty (nothing to :run)" };
|
|
6630
|
+
}
|
|
6631
|
+
const parsed = tryParseStatements(buffer);
|
|
6632
|
+
if (parsed.kind === "fail") return { kind: "error", message: parsed.message };
|
|
6633
|
+
return { kind: "execute-batch", sql: buffer };
|
|
6634
|
+
}
|
|
6635
|
+
function isBatchConstruction(buffer) {
|
|
6636
|
+
return /^create\s+temp\s+table\b/i.test(stripLeadingCommentsAndWs(buffer));
|
|
6637
|
+
}
|
|
6638
|
+
function stripLeadingCommentsAndWs(sql) {
|
|
6639
|
+
let s = sql;
|
|
6640
|
+
while (true) {
|
|
6641
|
+
const before = s;
|
|
6642
|
+
s = s.replace(/^\s+/, "");
|
|
6643
|
+
s = s.replace(/^--[^\n]*(\n|$)/, "");
|
|
6644
|
+
s = s.replace(/^\/\*[\s\S]*?\*\//, "");
|
|
6645
|
+
if (s === before) return s;
|
|
6646
|
+
}
|
|
6222
6647
|
}
|
|
6223
|
-
function
|
|
6224
|
-
|
|
6648
|
+
function toParseInput(sql) {
|
|
6649
|
+
try {
|
|
6650
|
+
return normalizeSqlAppProfiles(sql, "console").normalizedSql;
|
|
6651
|
+
} catch {
|
|
6652
|
+
return sql;
|
|
6653
|
+
}
|
|
6225
6654
|
}
|
|
6226
|
-
function
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6655
|
+
function tryParseStatements(sql) {
|
|
6656
|
+
try {
|
|
6657
|
+
const stmts = new Parser(new Lexer(toParseInput(sql)).tokenize()).parseStatements();
|
|
6658
|
+
return {
|
|
6659
|
+
kind: "ok",
|
|
6660
|
+
count: stmts.length,
|
|
6661
|
+
hasTempTable: stmts.some(
|
|
6662
|
+
(s) => s.type === "CREATE_TEMP_TABLE" || s.type === "DROP_TEMP_TABLE"
|
|
6663
|
+
)
|
|
6664
|
+
};
|
|
6665
|
+
} catch (e) {
|
|
6666
|
+
if (e instanceof LexError) {
|
|
6667
|
+
return { kind: "fail", continuable: e.unterminated, message: e.message };
|
|
6668
|
+
}
|
|
6669
|
+
if (e instanceof ParseError) {
|
|
6670
|
+
return { kind: "fail", continuable: e.token.kind === "EOF" /* EOF */, message: e.message };
|
|
6671
|
+
}
|
|
6672
|
+
throw e;
|
|
6673
|
+
}
|
|
6230
6674
|
}
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6675
|
+
|
|
6676
|
+
// src/api/requestGate.ts
|
|
6677
|
+
var DEFAULT_MAX_CONCURRENT = 10;
|
|
6678
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
6679
|
+
var DEFAULT_BASE_DELAY_MS = 500;
|
|
6680
|
+
var DEFAULT_MAX_DELAY_MS = 8e3;
|
|
6681
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 429, 502, 503, 504]);
|
|
6682
|
+
function isRetryableError(err) {
|
|
6683
|
+
if (!(err instanceof Error)) return false;
|
|
6684
|
+
const status = err.message.match(/^kintone API error (\d{3}):/);
|
|
6685
|
+
if (status) return RETRYABLE_STATUSES.has(Number(status[1]));
|
|
6686
|
+
if (err.name === "AbortError" || err.name === "TimeoutError") return true;
|
|
6687
|
+
if (/fetch failed/i.test(err.message)) return true;
|
|
6688
|
+
return false;
|
|
6235
6689
|
}
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6690
|
+
var RequestGate = class {
|
|
6691
|
+
constructor(options = {}) {
|
|
6692
|
+
this.active = 0;
|
|
6693
|
+
this.waiters = [];
|
|
6694
|
+
this.maxConcurrent = clampInt(options.maxConcurrent ?? DEFAULT_MAX_CONCURRENT, 1, 50);
|
|
6695
|
+
this.maxRetries = clampInt(options.maxRetries ?? DEFAULT_MAX_RETRIES, 0, 10);
|
|
6696
|
+
this.baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
|
|
6697
|
+
this.maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
|
|
6698
|
+
this.sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
6699
|
+
this.random = options.random ?? Math.random;
|
|
6700
|
+
}
|
|
6701
|
+
/** 現在の同時実行数(テスト・診断用) */
|
|
6702
|
+
get activeCount() {
|
|
6703
|
+
return this.active;
|
|
6704
|
+
}
|
|
6705
|
+
get limit() {
|
|
6706
|
+
return this.maxConcurrent;
|
|
6707
|
+
}
|
|
6708
|
+
/** GET 系: セマフォ + リトライ付きで実行する */
|
|
6709
|
+
async runReadOnly(fn) {
|
|
6710
|
+
let attempt = 0;
|
|
6711
|
+
while (true) {
|
|
6712
|
+
try {
|
|
6713
|
+
return await this.withSlot(fn);
|
|
6714
|
+
} catch (err) {
|
|
6715
|
+
if (attempt >= this.maxRetries || !isRetryableError(err)) throw err;
|
|
6716
|
+
await this.sleep(this.backoffDelay(attempt));
|
|
6717
|
+
attempt += 1;
|
|
6718
|
+
}
|
|
6719
|
+
}
|
|
6720
|
+
}
|
|
6721
|
+
/** 書き込み系: セマフォのみ(リトライしない — 二重実行防止) */
|
|
6722
|
+
async runMutation(fn) {
|
|
6723
|
+
return this.withSlot(fn);
|
|
6724
|
+
}
|
|
6725
|
+
async withSlot(fn) {
|
|
6726
|
+
await this.acquire();
|
|
6727
|
+
try {
|
|
6728
|
+
return await fn();
|
|
6729
|
+
} finally {
|
|
6730
|
+
this.release();
|
|
6731
|
+
}
|
|
6732
|
+
}
|
|
6733
|
+
async acquire() {
|
|
6734
|
+
if (this.active < this.maxConcurrent) {
|
|
6735
|
+
this.active += 1;
|
|
6736
|
+
return;
|
|
6737
|
+
}
|
|
6738
|
+
await new Promise((resolve2) => this.waiters.push(resolve2));
|
|
6739
|
+
this.active += 1;
|
|
6740
|
+
}
|
|
6741
|
+
release() {
|
|
6742
|
+
this.active -= 1;
|
|
6743
|
+
const next = this.waiters.shift();
|
|
6744
|
+
if (next) next();
|
|
6745
|
+
}
|
|
6746
|
+
/** 指数バックオフ + ジッタ(attempt: 0 始まり) */
|
|
6747
|
+
backoffDelay(attempt) {
|
|
6748
|
+
const base = Math.min(this.baseDelayMs * 2 ** attempt, this.maxDelayMs);
|
|
6749
|
+
const jitter = 1 + (this.random() - 0.5) * 0.5;
|
|
6750
|
+
return Math.round(base * jitter);
|
|
6751
|
+
}
|
|
6752
|
+
};
|
|
6753
|
+
function withRequestGate(client, gate) {
|
|
6754
|
+
return {
|
|
6755
|
+
getRecords: (params) => gate.runReadOnly(() => client.getRecords(params)),
|
|
6756
|
+
getApps: () => gate.runReadOnly(() => client.getApps()),
|
|
6757
|
+
getFields: (appId) => gate.runReadOnly(() => client.getFields(appId)),
|
|
6758
|
+
postRecords: (params) => gate.runMutation(() => client.postRecords(params)),
|
|
6759
|
+
putRecords: (params) => gate.runMutation(() => client.putRecords(params)),
|
|
6760
|
+
deleteRecords: (params) => gate.runMutation(() => client.deleteRecords(params))
|
|
6761
|
+
};
|
|
6241
6762
|
}
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6763
|
+
var globalGate = null;
|
|
6764
|
+
function getGlobalRequestGate(limitHint) {
|
|
6765
|
+
if (globalGate === null) {
|
|
6766
|
+
const envValue = Number(process.env.KSQL_MAX_CONCURRENT);
|
|
6767
|
+
const limit = Number.isInteger(envValue) && envValue > 0 ? envValue : limitHint;
|
|
6768
|
+
globalGate = new RequestGate({ maxConcurrent: limit });
|
|
6248
6769
|
}
|
|
6249
|
-
|
|
6250
|
-
|
|
6770
|
+
return globalGate;
|
|
6771
|
+
}
|
|
6772
|
+
function clampInt(v, min, max) {
|
|
6773
|
+
if (!Number.isFinite(v)) return min;
|
|
6774
|
+
return Math.max(min, Math.min(max, Math.trunc(v)));
|
|
6775
|
+
}
|
|
6776
|
+
|
|
6777
|
+
// src/cli/nodeKintoneClient.ts
|
|
6778
|
+
function createNodeKintoneClient(baseUrl, tokenResolver) {
|
|
6779
|
+
const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
|
|
6780
|
+
const apiBasePath = tokenResolver.guestSpaceId && tokenResolver.guestSpaceId > 0 ? `/k/guest/${tokenResolver.guestSpaceId}/v1` : "/k/v1";
|
|
6781
|
+
async function requestJson(path, init, appIdForToken) {
|
|
6782
|
+
const headers = new Headers(init.headers ?? {});
|
|
6783
|
+
if (tokenResolver.auth.type === "token") {
|
|
6784
|
+
headers.set("X-Cybozu-API-Token", tokenResolver.auth.resolveToken(appIdForToken));
|
|
6785
|
+
} else {
|
|
6786
|
+
const credentials = `${tokenResolver.auth.username}:${tokenResolver.auth.password}`;
|
|
6787
|
+
const encoded = Buffer.from(credentials, "utf-8").toString("base64");
|
|
6788
|
+
headers.set("X-Cybozu-Authorization", encoded);
|
|
6789
|
+
}
|
|
6790
|
+
headers.set("Accept", "application/json");
|
|
6791
|
+
const method = String(init.method ?? "GET").toUpperCase();
|
|
6792
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
6793
|
+
headers.set("Content-Type", "application/json");
|
|
6794
|
+
}
|
|
6795
|
+
const timeoutMs = tokenResolver.timeoutMs ?? 3e4;
|
|
6796
|
+
const url = `${normalizedBaseUrl}${path}`;
|
|
6797
|
+
if (tokenResolver.debug) {
|
|
6798
|
+
tokenResolver.log?.(`[debug] request ${String(init.method ?? "GET")} ${url}`);
|
|
6799
|
+
if (tokenResolver.debugHeaders) {
|
|
6800
|
+
const authHeader = headers.get("X-Cybozu-API-Token") ? "X-Cybozu-API-Token=***" : headers.get("X-Cybozu-Authorization") ? "X-Cybozu-Authorization=***" : "Auth=(none)";
|
|
6801
|
+
tokenResolver.log?.(
|
|
6802
|
+
`[debug] request-headers ${authHeader} Content-Type=${headers.get("Content-Type") ?? "(none)"} Accept=${headers.get("Accept") ?? "(none)"}`
|
|
6803
|
+
);
|
|
6804
|
+
}
|
|
6805
|
+
}
|
|
6806
|
+
const res = await fetch(url, {
|
|
6807
|
+
...init,
|
|
6808
|
+
headers,
|
|
6809
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
6810
|
+
});
|
|
6811
|
+
if (!res.ok) {
|
|
6812
|
+
const bodyText = await res.text();
|
|
6813
|
+
if (tokenResolver.debug) {
|
|
6814
|
+
tokenResolver.log?.(`[debug] response status=${res.status} body=${bodyText}`);
|
|
6815
|
+
}
|
|
6816
|
+
throw new Error(`kintone API error ${res.status}: ${bodyText}`);
|
|
6817
|
+
}
|
|
6818
|
+
if (tokenResolver.debug) {
|
|
6819
|
+
tokenResolver.log?.(`[debug] response status=${res.status}`);
|
|
6820
|
+
}
|
|
6821
|
+
return await res.json();
|
|
6251
6822
|
}
|
|
6252
|
-
|
|
6823
|
+
function shouldRetryWithRecordNumberOrder(path, bodyText) {
|
|
6824
|
+
if (!path.includes("/v1/records.json?")) return false;
|
|
6825
|
+
if (!bodyText.includes('"code":"CB_IL02"')) return false;
|
|
6826
|
+
const queryPart = path.split("query=")[1] ?? "";
|
|
6827
|
+
const query = decodeURIComponent(queryPart.split("&")[0] ?? "");
|
|
6828
|
+
if (!query.includes("limit")) return false;
|
|
6829
|
+
if (!query.includes("offset")) return false;
|
|
6830
|
+
if (query.toLowerCase().includes("order by")) return false;
|
|
6831
|
+
return true;
|
|
6832
|
+
}
|
|
6833
|
+
function rewriteQueryWithRecordNumberOrder(path) {
|
|
6834
|
+
const [base, rest] = path.split("query=");
|
|
6835
|
+
if (!rest) return path;
|
|
6836
|
+
const [encodedQuery, ...tail] = rest.split("&");
|
|
6837
|
+
const query = decodeURIComponent(encodedQuery ?? "");
|
|
6838
|
+
const rewritten = `order by \u30EC\u30B3\u30FC\u30C9\u756A\u53F7 asc ${query}`.trim();
|
|
6839
|
+
const nextQuery = encodeURIComponent(rewritten);
|
|
6840
|
+
return `${base}query=${nextQuery}${tail.length > 0 ? `&${tail.join("&")}` : ""}`;
|
|
6841
|
+
}
|
|
6842
|
+
return {
|
|
6843
|
+
async getRecords(params) {
|
|
6844
|
+
const queryPart = `query=${encodeURIComponent(params.query)}`;
|
|
6845
|
+
const appPart = `app=${encodeURIComponent(String(params.app))}`;
|
|
6846
|
+
const fieldParts = params.fields.map((f) => `fields[]=${encodeURIComponent(f)}`);
|
|
6847
|
+
const qs = [appPart, queryPart, ...fieldParts].join("&");
|
|
6848
|
+
if (tokenResolver.debug) {
|
|
6849
|
+
tokenResolver.log?.(
|
|
6850
|
+
`[debug] getRecords app=${params.app} query="${params.query}" fields=${params.fields.length > 0 ? params.fields.join(",") : "(all)"} auth=${tokenResolver.auth.type}`
|
|
6851
|
+
);
|
|
6852
|
+
}
|
|
6853
|
+
const path = `${apiBasePath}/records.json?${qs}`;
|
|
6854
|
+
try {
|
|
6855
|
+
return await requestJson(
|
|
6856
|
+
path,
|
|
6857
|
+
{ method: "GET" },
|
|
6858
|
+
params.app
|
|
6859
|
+
);
|
|
6860
|
+
} catch (err) {
|
|
6861
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6862
|
+
if (!shouldRetryWithRecordNumberOrder(path, msg)) throw err;
|
|
6863
|
+
const retryPath = rewriteQueryWithRecordNumberOrder(path);
|
|
6864
|
+
if (tokenResolver.debug) {
|
|
6865
|
+
tokenResolver.log?.("[debug] retry with fallback query order by \u30EC\u30B3\u30FC\u30C9\u756A\u53F7 asc");
|
|
6866
|
+
}
|
|
6867
|
+
return await requestJson(
|
|
6868
|
+
retryPath,
|
|
6869
|
+
{ method: "GET" },
|
|
6870
|
+
params.app
|
|
6871
|
+
);
|
|
6872
|
+
}
|
|
6873
|
+
},
|
|
6874
|
+
async postRecords(_params) {
|
|
6875
|
+
const res = await requestJson(
|
|
6876
|
+
`${apiBasePath}/records.json`,
|
|
6877
|
+
{
|
|
6878
|
+
method: "POST",
|
|
6879
|
+
body: JSON.stringify({
|
|
6880
|
+
app: _params.app,
|
|
6881
|
+
records: _params.records
|
|
6882
|
+
})
|
|
6883
|
+
},
|
|
6884
|
+
_params.app
|
|
6885
|
+
);
|
|
6886
|
+
return { ids: res.ids };
|
|
6887
|
+
},
|
|
6888
|
+
async putRecords(_params) {
|
|
6889
|
+
await requestJson(
|
|
6890
|
+
`${apiBasePath}/records.json`,
|
|
6891
|
+
{
|
|
6892
|
+
method: "PUT",
|
|
6893
|
+
body: JSON.stringify({
|
|
6894
|
+
app: _params.app,
|
|
6895
|
+
records: _params.records
|
|
6896
|
+
})
|
|
6897
|
+
},
|
|
6898
|
+
_params.app
|
|
6899
|
+
);
|
|
6900
|
+
},
|
|
6901
|
+
async deleteRecords(_params) {
|
|
6902
|
+
await requestJson(
|
|
6903
|
+
`${apiBasePath}/records.json`,
|
|
6904
|
+
{
|
|
6905
|
+
method: "DELETE",
|
|
6906
|
+
body: JSON.stringify({
|
|
6907
|
+
app: _params.app,
|
|
6908
|
+
ids: _params.ids
|
|
6909
|
+
})
|
|
6910
|
+
},
|
|
6911
|
+
_params.app
|
|
6912
|
+
);
|
|
6913
|
+
},
|
|
6914
|
+
async getApps() {
|
|
6915
|
+
const PAGE = 100;
|
|
6916
|
+
const all = [];
|
|
6917
|
+
let offset = 0;
|
|
6918
|
+
while (true) {
|
|
6919
|
+
const qs = new URLSearchParams();
|
|
6920
|
+
qs.set("limit", String(PAGE));
|
|
6921
|
+
qs.set("offset", String(offset));
|
|
6922
|
+
const res = await requestJson(
|
|
6923
|
+
`${apiBasePath}/apps.json?${qs.toString()}`,
|
|
6924
|
+
{ method: "GET" },
|
|
6925
|
+
0
|
|
6926
|
+
);
|
|
6927
|
+
for (const app of res.apps) {
|
|
6928
|
+
all.push({
|
|
6929
|
+
appId: Number(app.appId),
|
|
6930
|
+
name: app.name,
|
|
6931
|
+
description: app.description
|
|
6932
|
+
});
|
|
6933
|
+
}
|
|
6934
|
+
if (res.apps.length < PAGE) break;
|
|
6935
|
+
offset += PAGE;
|
|
6936
|
+
}
|
|
6937
|
+
return all;
|
|
6938
|
+
},
|
|
6939
|
+
async getFields(appId) {
|
|
6940
|
+
const qs = new URLSearchParams();
|
|
6941
|
+
qs.set("app", String(appId));
|
|
6942
|
+
const res = await requestJson(
|
|
6943
|
+
`${apiBasePath}/app/form/fields.json?${qs.toString()}`,
|
|
6944
|
+
{ method: "GET" },
|
|
6945
|
+
appId
|
|
6946
|
+
);
|
|
6947
|
+
return Object.values(res.properties).map((f) => ({
|
|
6948
|
+
code: f.code,
|
|
6949
|
+
label: f.label,
|
|
6950
|
+
fieldType: f.type,
|
|
6951
|
+
optionOrder: toOptionOrderMap(f.options),
|
|
6952
|
+
sortKind: detectSortKind(f.type, f.format)
|
|
6953
|
+
}));
|
|
6954
|
+
}
|
|
6955
|
+
};
|
|
6956
|
+
}
|
|
6957
|
+
function toOptionOrderMap(options) {
|
|
6958
|
+
if (!options || typeof options !== "object") return void 0;
|
|
6959
|
+
const order = {};
|
|
6960
|
+
let hasAny = false;
|
|
6961
|
+
for (const [label, meta] of Object.entries(options)) {
|
|
6962
|
+
const n = Number(meta?.index);
|
|
6963
|
+
if (!Number.isFinite(n)) continue;
|
|
6964
|
+
order[label] = n;
|
|
6965
|
+
hasAny = true;
|
|
6966
|
+
}
|
|
6967
|
+
return hasAny ? order : void 0;
|
|
6968
|
+
}
|
|
6969
|
+
function detectSortKind(fieldType, calcFormat) {
|
|
6970
|
+
if (fieldType === "NUMBER" || fieldType === "RECORD_NUMBER") return "number";
|
|
6971
|
+
if (fieldType === "CALC") {
|
|
6972
|
+
if (calcFormat === "NUMBER" || calcFormat === "NUMBER_DIGIT") return "number";
|
|
6973
|
+
return "string";
|
|
6974
|
+
}
|
|
6975
|
+
return void 0;
|
|
6253
6976
|
}
|
|
6254
6977
|
|
|
6255
6978
|
// src/cli/index.ts
|
|
@@ -6300,6 +7023,7 @@ Options:
|
|
|
6300
7023
|
--yes Skip DML confirmation prompt
|
|
6301
7024
|
--allow-without-where Allow UPDATE/DELETE without WHERE
|
|
6302
7025
|
--dml-max-rows <n> Max affected rows for DML guard (default: 100)
|
|
7026
|
+
--continue-on-error Batch: keep executing after a statement error (read-only batch only)
|
|
6303
7027
|
-h, --help Show help
|
|
6304
7028
|
-v, --version Show version
|
|
6305
7029
|
`;
|
|
@@ -6340,6 +7064,7 @@ function parseArgs(argv) {
|
|
|
6340
7064
|
allowDml: false,
|
|
6341
7065
|
yes: false,
|
|
6342
7066
|
allowWithoutWhere: false,
|
|
7067
|
+
continueOnError: false,
|
|
6343
7068
|
dmlMaxRows: null,
|
|
6344
7069
|
userFormat: null,
|
|
6345
7070
|
arrayFormat: null,
|
|
@@ -6409,6 +7134,10 @@ function parseArgs(argv) {
|
|
|
6409
7134
|
out.allowWithoutWhere = true;
|
|
6410
7135
|
continue;
|
|
6411
7136
|
}
|
|
7137
|
+
if (a === "--continue-on-error") {
|
|
7138
|
+
out.continueOnError = true;
|
|
7139
|
+
continue;
|
|
7140
|
+
}
|
|
6412
7141
|
const v = argv[i + 1];
|
|
6413
7142
|
if (a === "-e" || a === "--execute") {
|
|
6414
7143
|
out.executeSql = v ?? "";
|
|
@@ -6751,6 +7480,50 @@ function toExitCodeFromError(err) {
|
|
|
6751
7480
|
if (msg.startsWith("AuthError:")) return 3;
|
|
6752
7481
|
return 1;
|
|
6753
7482
|
}
|
|
7483
|
+
function buildBatchStatementSummary(s) {
|
|
7484
|
+
const parts = [`[${s.index + 1}] ${s.type}`, s.status];
|
|
7485
|
+
if (s.tempTable) parts.push(`temp=${s.tempTable}`);
|
|
7486
|
+
if (s.rowCount !== void 0) parts.push(`rows=${s.rowCount}`);
|
|
7487
|
+
if (s.status === "success" && s.result?.type === "SELECT") parts.push(`rowCount=${s.result.rowCount}`);
|
|
7488
|
+
if (s.status === "success" && s.result && s.result.type !== "SELECT") {
|
|
7489
|
+
const r = s.result;
|
|
7490
|
+
if (r.type === "INSERT") parts.push(`inserted=${r.insertedCount}`);
|
|
7491
|
+
else if (r.type === "UPDATE") parts.push(`updated=${r.updatedCount}`);
|
|
7492
|
+
else if (r.type === "DELETE") parts.push(`deleted=${r.deletedCount}`);
|
|
7493
|
+
else if (r.type === "UPSERT") parts.push(`inserted=${r.insertedCount} updated=${r.updatedCount}`);
|
|
7494
|
+
else parts.push(`reordered=${r.reorderedParentCount}`);
|
|
7495
|
+
}
|
|
7496
|
+
if (s.status === "error" && s.error) parts.push(s.error.message);
|
|
7497
|
+
if (s.status === "skipped" && s.skippedReason) parts.push(`reason=${s.skippedReason}`);
|
|
7498
|
+
return parts.join(" ");
|
|
7499
|
+
}
|
|
7500
|
+
function buildBatchDmlConfirmMessage(analysis) {
|
|
7501
|
+
const lines = ["[DML Confirm] batch"];
|
|
7502
|
+
for (const s of analysis.statements) {
|
|
7503
|
+
if (!s.isDml) continue;
|
|
7504
|
+
const app = s.targetAppId !== null ? `APP${s.targetAppId}` : "-";
|
|
7505
|
+
lines.push(` [${s.index + 1}] ${s.statementType} app=${app} where=${s.hasWhere ? "yes" : "no"}`);
|
|
7506
|
+
}
|
|
7507
|
+
return lines.join("\n");
|
|
7508
|
+
}
|
|
7509
|
+
function writeBatchOutput(batch, opts) {
|
|
7510
|
+
const outputs = [];
|
|
7511
|
+
for (const s of batch.statements) {
|
|
7512
|
+
if (!opts.quiet) process.stderr.write(`${buildBatchStatementSummary(s)}
|
|
7513
|
+
`);
|
|
7514
|
+
if (s.status === "success" && s.result?.type === "SELECT") {
|
|
7515
|
+
outputs.push(buildOutput(s.result, opts.format, opts.noHeader, opts.pretty, opts.displayOptions));
|
|
7516
|
+
}
|
|
7517
|
+
}
|
|
7518
|
+
const output = outputs.join("\n\n");
|
|
7519
|
+
if (opts.outputPath) (0, import_fs2.writeFileSync)(opts.outputPath, `${output}
|
|
7520
|
+
`, "utf-8");
|
|
7521
|
+
else if (output) process.stdout.write(`${output}
|
|
7522
|
+
`);
|
|
7523
|
+
if (batch.ok) return 0;
|
|
7524
|
+
const firstError = batch.statements.find((s) => s.status === "error");
|
|
7525
|
+
return firstError?.error ? toExitCodeFromError(new Error(firstError.error.message)) : 1;
|
|
7526
|
+
}
|
|
6754
7527
|
function shouldExitOnEmpty(dryRun, exitOnEmpty, rowCount) {
|
|
6755
7528
|
if (dryRun) return false;
|
|
6756
7529
|
return exitOnEmpty && rowCount === 0;
|
|
@@ -6815,6 +7588,7 @@ function parseConsoleMetaCommand(line) {
|
|
|
6815
7588
|
if (!t.startsWith(":")) return { kind: "none" };
|
|
6816
7589
|
if (t === ":help") return { kind: "help" };
|
|
6817
7590
|
if (t === ":exit" || t === ":quit") return { kind: "exit" };
|
|
7591
|
+
if (t === ":run") return { kind: "run" };
|
|
6818
7592
|
if (t === ":clear") return { kind: "clear" };
|
|
6819
7593
|
if (t === ":last") return { kind: "show-last" };
|
|
6820
7594
|
if (t === ":buffer") return { kind: "show-buffer" };
|
|
@@ -6909,6 +7683,7 @@ function buildReplExecArgv(base, sql, dryRun, format) {
|
|
|
6909
7683
|
if (base.allowDml) argv.push("--yes");
|
|
6910
7684
|
if (base.allowDml) argv.push("--allow-dml");
|
|
6911
7685
|
if (base.allowWithoutWhere) argv.push("--allow-without-where");
|
|
7686
|
+
if (base.continueOnError) argv.push("--continue-on-error");
|
|
6912
7687
|
return argv;
|
|
6913
7688
|
}
|
|
6914
7689
|
function normalizeConsoleInputLine(line) {
|
|
@@ -6980,7 +7755,21 @@ async function confirmDmlInConsole(sql, opts, queue, defaultProfile = "dev") {
|
|
|
6980
7755
|
if (!opts.allowDml || opts.yes || opts.dryRun) return true;
|
|
6981
7756
|
try {
|
|
6982
7757
|
const normalized = normalizeSqlAppProfiles(sql, defaultProfile);
|
|
6983
|
-
const
|
|
7758
|
+
const statements = parseSqlStatements(normalized.normalizedSql);
|
|
7759
|
+
if (statements.length > 1) {
|
|
7760
|
+
const analysis = analyzeBatch(statements);
|
|
7761
|
+
if (!analysis.containsDml) return true;
|
|
7762
|
+
const message = buildBatchDmlConfirmMessage(analysis);
|
|
7763
|
+
if (queue) {
|
|
7764
|
+
process.stdout.write(`${message}
|
|
7765
|
+
Proceed? (yes/no): `);
|
|
7766
|
+
const input = await queue.next();
|
|
7767
|
+
if (input.kind !== "line") return false;
|
|
7768
|
+
return parseConfirmAnswer(input.line);
|
|
7769
|
+
}
|
|
7770
|
+
return await promptDmlConfirm(message);
|
|
7771
|
+
}
|
|
7772
|
+
const stmt = statements[0];
|
|
6984
7773
|
const stmtType = getStatementType(stmt);
|
|
6985
7774
|
if (!isDmlType(stmtType)) return true;
|
|
6986
7775
|
const compact = sql.replace(/\s+/g, " ").trim();
|
|
@@ -7092,17 +7881,47 @@ async function runConsole(base) {
|
|
|
7092
7881
|
const line = normalizeConsoleInputLine(input.line);
|
|
7093
7882
|
const t = line.trim();
|
|
7094
7883
|
emptyPromptSigintArmed = false;
|
|
7095
|
-
if (
|
|
7884
|
+
if (t.startsWith(":")) {
|
|
7096
7885
|
const meta = parseConsoleMetaCommand(t);
|
|
7097
7886
|
if (meta.kind === "none") {
|
|
7098
7887
|
} else if (meta.kind === "exit") {
|
|
7099
7888
|
return 0;
|
|
7889
|
+
} else if (meta.kind === "run") {
|
|
7890
|
+
const runDecision = decideRun(buffer);
|
|
7891
|
+
if (runDecision.kind === "error") {
|
|
7892
|
+
process.stderr.write(`${runDecision.message}
|
|
7893
|
+
`);
|
|
7894
|
+
continue;
|
|
7895
|
+
}
|
|
7896
|
+
const sql2 = runDecision.sql.trim();
|
|
7897
|
+
{
|
|
7898
|
+
const ok = await confirmDmlInConsole(sql2, {
|
|
7899
|
+
allowDml: base.allowDml,
|
|
7900
|
+
yes: base.yes,
|
|
7901
|
+
dryRun
|
|
7902
|
+
}, queue, profile ?? "dev");
|
|
7903
|
+
if (!ok) {
|
|
7904
|
+
process.stderr.write("DML was cancelled by user.\n");
|
|
7905
|
+
continue;
|
|
7906
|
+
}
|
|
7907
|
+
}
|
|
7908
|
+
buffer = "";
|
|
7909
|
+
lastSql = sql2;
|
|
7910
|
+
lastResolvedProfiles = formatResolvedAppProfiles(sql2, profile ?? "dev");
|
|
7911
|
+
history.push(sql2);
|
|
7912
|
+
appendHistory(sql2);
|
|
7913
|
+
const { code: code2, stdout: stdout2 } = await runWithArgvCapture(buildReplExecArgvWithProfile(base, sql2, dryRun, format, profile));
|
|
7914
|
+
lastOutput = stdout2;
|
|
7915
|
+
if (code2 !== 0) process.stderr.write(`(last exit code: ${code2})
|
|
7916
|
+
`);
|
|
7917
|
+
continue;
|
|
7100
7918
|
} else if (meta.kind === "help") {
|
|
7101
7919
|
process.stdout.write(
|
|
7102
7920
|
[
|
|
7103
7921
|
"console commands:",
|
|
7104
7922
|
" :help",
|
|
7105
7923
|
" :exit | :quit",
|
|
7924
|
+
" :run",
|
|
7106
7925
|
" :clear",
|
|
7107
7926
|
" :last",
|
|
7108
7927
|
" :buffer",
|
|
@@ -7250,10 +8069,22 @@ async function runConsole(base) {
|
|
|
7250
8069
|
continue;
|
|
7251
8070
|
}
|
|
7252
8071
|
}
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
if (
|
|
7256
|
-
|
|
8072
|
+
const decision = decideConsoleInput(buffer, line);
|
|
8073
|
+
if (decision.kind === "ignore") continue;
|
|
8074
|
+
if (decision.kind === "continue") {
|
|
8075
|
+
buffer = decision.buffer;
|
|
8076
|
+
continue;
|
|
8077
|
+
}
|
|
8078
|
+
if (decision.kind === "error") {
|
|
8079
|
+
buffer = "";
|
|
8080
|
+
process.stderr.write(`${decision.message}
|
|
8081
|
+
(input buffer cleared)
|
|
8082
|
+
`);
|
|
8083
|
+
continue;
|
|
8084
|
+
}
|
|
8085
|
+
if (decision.kind === "meta") continue;
|
|
8086
|
+
const isBatchExec = decision.kind === "execute-batch";
|
|
8087
|
+
const sql = isBatchExec ? decision.sql.trim() : decision.sql.replace(/;\s*$/, "").trim();
|
|
7257
8088
|
buffer = "";
|
|
7258
8089
|
if (!sql) continue;
|
|
7259
8090
|
{
|
|
@@ -7329,6 +8160,9 @@ async function run() {
|
|
|
7329
8160
|
let hasWhere = true;
|
|
7330
8161
|
let insertValuesCount = null;
|
|
7331
8162
|
let isDmlStatement = false;
|
|
8163
|
+
let isBatchSql = false;
|
|
8164
|
+
let batchContainsDml = false;
|
|
8165
|
+
let batchAnalysis = null;
|
|
7332
8166
|
if (args.diagRecordId === null) {
|
|
7333
8167
|
sql = args.executeSql;
|
|
7334
8168
|
if (!sql && args.filePath) sql = (0, import_fs2.readFileSync)(args.filePath, "utf-8");
|
|
@@ -7347,17 +8181,24 @@ async function run() {
|
|
|
7347
8181
|
return 2;
|
|
7348
8182
|
}
|
|
7349
8183
|
try {
|
|
7350
|
-
const
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
8184
|
+
const statements = parseSqlStatements(sql);
|
|
8185
|
+
if (statements.length > 1) {
|
|
8186
|
+
batchAnalysis = analyzeBatch(statements);
|
|
8187
|
+
isBatchSql = true;
|
|
8188
|
+
batchContainsDml = batchAnalysis.containsDml;
|
|
8189
|
+
} else {
|
|
8190
|
+
const stmt = parseSqlStatement(sql);
|
|
8191
|
+
parsedStmt = stmt;
|
|
8192
|
+
stmtType = getStatementType(stmt);
|
|
8193
|
+
isDmlStatement = isDmlType(stmtType);
|
|
8194
|
+
hasWhere = hasWhereClause(stmt);
|
|
8195
|
+
insertValuesCount = getInsertValuesCount(stmt);
|
|
8196
|
+
const supported = stmtType === "SELECT" || stmtType === "UNION" || stmtType === "WITH" || stmtType === "EXPLAIN" || stmtType === "SHOW_APPS" || stmtType === "DESCRIBE" || isDmlStatement;
|
|
8197
|
+
if (!supported) {
|
|
8198
|
+
process.stderr.write(`ArgumentError: unsupported statement type in CLI: ${stmtType}
|
|
7359
8199
|
`);
|
|
7360
|
-
|
|
8200
|
+
return 2;
|
|
8201
|
+
}
|
|
7361
8202
|
}
|
|
7362
8203
|
} catch (err) {
|
|
7363
8204
|
process.stderr.write(`${err instanceof Error ? err.message : String(err)}
|
|
@@ -7411,6 +8252,24 @@ async function run() {
|
|
|
7411
8252
|
process.stderr.write("ArgumentError: no APPxxx found in SQL and --app is not set.\n");
|
|
7412
8253
|
return 2;
|
|
7413
8254
|
}
|
|
8255
|
+
if (isBatchSql) {
|
|
8256
|
+
if (batchContainsDml && !allowDml) {
|
|
8257
|
+
process.stderr.write("ArgumentError: DML is disabled. Use --allow-dml to enable UPDATE/DELETE/INSERT/UPSERT/REORDER.\n");
|
|
8258
|
+
return 2;
|
|
8259
|
+
}
|
|
8260
|
+
if (args.dryRun) {
|
|
8261
|
+
const plans = buildBatchExplainPlans(sql);
|
|
8262
|
+
const out = [];
|
|
8263
|
+
plans.statements.forEach((p) => {
|
|
8264
|
+
if (p.index > 0) out.push("");
|
|
8265
|
+
out.push(`[${p.index + 1}] ${p.type}`);
|
|
8266
|
+
out.push(...p.plan);
|
|
8267
|
+
});
|
|
8268
|
+
process.stdout.write(`${out.join("\n")}
|
|
8269
|
+
`);
|
|
8270
|
+
return 0;
|
|
8271
|
+
}
|
|
8272
|
+
}
|
|
7414
8273
|
if (isDmlStatement) {
|
|
7415
8274
|
if (hasProfileSyntax && stmtType === "DELETE") {
|
|
7416
8275
|
process.stderr.write("ArgumentError: @profile is not supported for DELETE yet.\n");
|
|
@@ -7658,6 +8517,9 @@ async function run() {
|
|
|
7658
8517
|
getApps: () => defaultClient.getApps()
|
|
7659
8518
|
};
|
|
7660
8519
|
}
|
|
8520
|
+
if (!args.dryRun) {
|
|
8521
|
+
client = withRequestGate(client, getGlobalRequestGate(profile.query?.maxConcurrent));
|
|
8522
|
+
}
|
|
7661
8523
|
try {
|
|
7662
8524
|
if (isDmlStatement && !args.dryRun) {
|
|
7663
8525
|
const stmtAppId = parsedStmt && typeof parsedStmt === "object" && typeof parsedStmt.appId === "number" ? parsedStmt.appId : appIds[0];
|
|
@@ -7685,6 +8547,30 @@ async function run() {
|
|
|
7685
8547
|
return await promptDmlConfirm(`[DML Confirm] type=${operation} estimatedRows=${count}
|
|
7686
8548
|
query=${label}`);
|
|
7687
8549
|
};
|
|
8550
|
+
if (isBatchSql) {
|
|
8551
|
+
if (batchContainsDml && !yes && batchAnalysis) {
|
|
8552
|
+
const ok = await promptDmlConfirm(buildBatchDmlConfirmMessage(batchAnalysis));
|
|
8553
|
+
if (!ok) {
|
|
8554
|
+
process.stderr.write("DML was cancelled by user.\n");
|
|
8555
|
+
return 2;
|
|
8556
|
+
}
|
|
8557
|
+
}
|
|
8558
|
+
const batchResult = await executeBatch(sql, client, {
|
|
8559
|
+
maxRecords,
|
|
8560
|
+
fetchParallel,
|
|
8561
|
+
onLimitReached: onLimit,
|
|
8562
|
+
cacheContext,
|
|
8563
|
+
continueOnError: args.continueOnError,
|
|
8564
|
+
timeoutMs: timeout,
|
|
8565
|
+
confirm: batchContainsDml ? async (count, operation) => {
|
|
8566
|
+
if (count > dmlMaxRows) {
|
|
8567
|
+
throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed --dml-max-rows (${dmlMaxRows}).`);
|
|
8568
|
+
}
|
|
8569
|
+
return true;
|
|
8570
|
+
} : void 0
|
|
8571
|
+
});
|
|
8572
|
+
return writeBatchOutput(batchResult, { format, noHeader, pretty, displayOptions, outputPath, quiet });
|
|
8573
|
+
}
|
|
7688
8574
|
const result = args.dryRun ? await execute(`EXPLAIN ${sql}`, client, { maxRecords, onLimitReached: onLimit, cacheContext }) : await execute(sql, client, {
|
|
7689
8575
|
maxRecords,
|
|
7690
8576
|
fetchParallel,
|
|
@@ -7734,6 +8620,8 @@ if (isDirectCliRun()) {
|
|
|
7734
8620
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7735
8621
|
0 && (module.exports = {
|
|
7736
8622
|
HELP_TEXT,
|
|
8623
|
+
buildBatchDmlConfirmMessage,
|
|
8624
|
+
buildBatchStatementSummary,
|
|
7737
8625
|
buildOutput,
|
|
7738
8626
|
extractAppIds,
|
|
7739
8627
|
normalizeAppKey,
|