@rex0220/kintone-sql-tools 3.67.0 → 3.68.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cli/ksql.js +594 -39
- package/dist-engine/index.cjs +18 -14
- package/dist-engine/index.mjs +18 -14
- package/dist-engine/ksql-engine.umd.js +18 -14
- package/dist-engine/meta/bundle-baseline.json +10 -10
- package/dist-engine/meta/cjs.json +41 -14
- package/dist-engine/meta/esm.json +41 -14
- package/dist-engine/meta/umd.json +41 -14
- package/dist-mcp/ksql-mcp.js +605 -41
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
package/dist-cli/ksql.js
CHANGED
|
@@ -475,7 +475,7 @@ var Lexer = class {
|
|
|
475
475
|
// ヘルパー
|
|
476
476
|
// ----------------------------------------------------------
|
|
477
477
|
makeToken(kind, value, pos) {
|
|
478
|
-
return { kind, value, pos };
|
|
478
|
+
return { kind, value, pos, end: this.pos };
|
|
479
479
|
}
|
|
480
480
|
};
|
|
481
481
|
function isIdentStart(ch) {
|
|
@@ -1082,6 +1082,8 @@ var Parser = class {
|
|
|
1082
1082
|
this.allowRelativeDateFunctions = false;
|
|
1083
1083
|
/** WITH 句で定義された CTE 名のセット(parseTableRef で参照) */
|
|
1084
1084
|
this.cteNames = /* @__PURE__ */ new Set();
|
|
1085
|
+
/** dialect 1 の裸名で宣言された一時テーブル名(参照時に # 付きへ正規化) */
|
|
1086
|
+
this.bareTempTableNames = /* @__PURE__ */ new Set();
|
|
1085
1087
|
/** パース中に出現した一時テーブル参照(#name)のトークン。単文 API での拒否に使う */
|
|
1086
1088
|
this.tempTableRefs = [];
|
|
1087
1089
|
/** GROUP BY を読む前に作る B124 候補 leaf の診断位置。AST 公開型へ位置情報を足さない。 */
|
|
@@ -1120,7 +1122,12 @@ var Parser = class {
|
|
|
1120
1122
|
}
|
|
1121
1123
|
/** 複文(`;` 区切り)をパースする。空文はスキップする */
|
|
1122
1124
|
parseStatements() {
|
|
1125
|
+
return this.parseStatementsWithRanges().statements;
|
|
1126
|
+
}
|
|
1127
|
+
/** 複文と、原文上の文ごとの文字範囲を同時に返す。 */
|
|
1128
|
+
parseStatementsWithRanges() {
|
|
1123
1129
|
const stmts = [];
|
|
1130
|
+
const statementRanges = [];
|
|
1124
1131
|
while (true) {
|
|
1125
1132
|
while (this.peek().kind === ";" /* SEMICOLON */) this.advance();
|
|
1126
1133
|
if (this.peek().kind === "EOF" /* EOF */) break;
|
|
@@ -1136,9 +1143,11 @@ var Parser = class {
|
|
|
1136
1143
|
if (after.kind !== ";" /* SEMICOLON */ && after.kind !== "EOF" /* EOF */) {
|
|
1137
1144
|
throw new ParseError("\u6587\u306E\u533A\u5207\u308A\u306B\u306F ; \u304C\u5FC5\u8981\u3067\u3059", after);
|
|
1138
1145
|
}
|
|
1146
|
+
const lastTok = this.prev();
|
|
1147
|
+
statementRanges.push({ start: startTok.pos, end: lastTok.end ?? after.pos });
|
|
1139
1148
|
}
|
|
1140
1149
|
this.expect("EOF" /* EOF */);
|
|
1141
|
-
return stmts;
|
|
1150
|
+
return { statements: stmts, statementRanges };
|
|
1142
1151
|
}
|
|
1143
1152
|
// ----------------------------------------------------------
|
|
1144
1153
|
// Statement ディスパッチ
|
|
@@ -1177,6 +1186,13 @@ var Parser = class {
|
|
|
1177
1186
|
if (upper === "DROP") return this.parseDropTempTable();
|
|
1178
1187
|
if (upper === "DECLARE") return this.parseDeclareVariable();
|
|
1179
1188
|
if (upper === "VALIDATE") return this.parseValidate();
|
|
1189
|
+
if (upper === "EXIT") return this.parseExit();
|
|
1190
|
+
if (upper === "MERGE") {
|
|
1191
|
+
if (!this.capabilities.dialect1) {
|
|
1192
|
+
throw new ParseError("MERGE \u306B\u306F -- @ksql dialect: 1 \u306E\u5BA3\u8A00\u304C\u5FC5\u8981\u3067\u3059", tok);
|
|
1193
|
+
}
|
|
1194
|
+
return this.parseMergeAsUpsert();
|
|
1195
|
+
}
|
|
1180
1196
|
if (upper === "GENERATE_SERIES") {
|
|
1181
1197
|
throw new ParseError(
|
|
1182
1198
|
"GENERATE_SERIES \u306F WITH \u306E CTE \u672C\u4F53\u306B\u66F8\u3044\u3066\u304F\u3060\u3055\u3044\u3002\u4F8B: WITH s AS (GENERATE_SERIES(1, 5)) SELECT generate_series FROM s",
|
|
@@ -1195,7 +1211,7 @@ var Parser = class {
|
|
|
1195
1211
|
break;
|
|
1196
1212
|
}
|
|
1197
1213
|
throw new ParseError(
|
|
1198
|
-
"SELECT / INSERT / UPDATE / DELETE / REORDER / VALIDATE / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE / SET / DECLARE / ASSERT \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
|
|
1214
|
+
"SELECT / INSERT / UPDATE / DELETE / REORDER / VALIDATE / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE / SET / DECLARE / ASSERT / EXIT \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
|
|
1199
1215
|
tok
|
|
1200
1216
|
);
|
|
1201
1217
|
}
|
|
@@ -1323,6 +1339,7 @@ var Parser = class {
|
|
|
1323
1339
|
this.advance();
|
|
1324
1340
|
this.expectSoftKeyword("TEMP", "CREATE \u306E\u5F8C\u306B\u306F TEMP TABLE \u304C\u5FC5\u8981\u3067\u3059\uFF08\u4F8B: CREATE TEMP TABLE #temp AS SELECT ...\uFF09");
|
|
1325
1341
|
this.expectSoftKeyword("TABLE", "CREATE TEMP \u306E\u5F8C\u306B\u306F TABLE \u304C\u5FC5\u8981\u3067\u3059");
|
|
1342
|
+
const bareName = this.isDialect1BareTempTableName();
|
|
1326
1343
|
const name = this.parseTempTableName();
|
|
1327
1344
|
this.expect("AS" /* AS */, "CREATE TEMP TABLE \u306B\u306F AS SELECT \u304C\u5FC5\u8981\u3067\u3059");
|
|
1328
1345
|
const tok = this.peek();
|
|
@@ -1334,13 +1351,16 @@ var Parser = class {
|
|
|
1334
1351
|
} else {
|
|
1335
1352
|
throw new ParseError("CREATE TEMP TABLE ... AS \u306E\u5F8C\u306B\u306F SELECT / WITH \u304C\u5FC5\u8981\u3067\u3059", tok);
|
|
1336
1353
|
}
|
|
1354
|
+
if (bareName !== null) this.bareTempTableNames.add(bareName);
|
|
1337
1355
|
return { type: "CREATE_TEMP_TABLE", name, query };
|
|
1338
1356
|
}
|
|
1339
1357
|
parseDropTempTable() {
|
|
1340
1358
|
this.advance();
|
|
1341
1359
|
this.expectSoftKeyword("TEMP", "DROP \u306E\u5F8C\u306B\u306F TEMP TABLE \u304C\u5FC5\u8981\u3067\u3059\uFF08\u4F8B: DROP TEMP TABLE #temp\uFF09");
|
|
1342
1360
|
this.expectSoftKeyword("TABLE", "DROP TEMP \u306E\u5F8C\u306B\u306F TABLE \u304C\u5FC5\u8981\u3067\u3059");
|
|
1361
|
+
const bareName = this.isDialect1BareTempTableName();
|
|
1343
1362
|
const name = this.parseTempTableName();
|
|
1363
|
+
if (bareName !== null) this.bareTempTableNames.delete(bareName);
|
|
1344
1364
|
return { type: "DROP_TEMP_TABLE", name };
|
|
1345
1365
|
}
|
|
1346
1366
|
expectSoftKeyword(word, msg) {
|
|
@@ -1362,8 +1382,16 @@ var Parser = class {
|
|
|
1362
1382
|
this.advance();
|
|
1363
1383
|
return tok.value;
|
|
1364
1384
|
}
|
|
1385
|
+
if (this.capabilities.dialect1 && (tok.kind === "IDENT" /* IDENT */ || tok.kind === "BIDENT" /* BIDENT */)) {
|
|
1386
|
+
this.advance();
|
|
1387
|
+
return `#${tok.value}`;
|
|
1388
|
+
}
|
|
1365
1389
|
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);
|
|
1366
1390
|
}
|
|
1391
|
+
isDialect1BareTempTableName() {
|
|
1392
|
+
const tok = this.peek();
|
|
1393
|
+
return this.capabilities.dialect1 && (tok.kind === "IDENT" /* IDENT */ || tok.kind === "BIDENT" /* BIDENT */) && !tok.value.startsWith("#") ? tok.value : null;
|
|
1394
|
+
}
|
|
1367
1395
|
parseShow() {
|
|
1368
1396
|
this.advance();
|
|
1369
1397
|
if (!this.consume("APPS" /* APPS */)) {
|
|
@@ -1691,6 +1719,41 @@ var Parser = class {
|
|
|
1691
1719
|
// ----------------------------------------------------------
|
|
1692
1720
|
parseAssert() {
|
|
1693
1721
|
this.expect("ASSERT" /* ASSERT */);
|
|
1722
|
+
const warnTok = this.peek();
|
|
1723
|
+
const warn = this.isSoftKeyword("WARN");
|
|
1724
|
+
if (warn) {
|
|
1725
|
+
this.requireDialect1(warnTok);
|
|
1726
|
+
this.advance();
|
|
1727
|
+
}
|
|
1728
|
+
const condition = this.parseAssertCondition();
|
|
1729
|
+
const message = this.parseFlowMessage();
|
|
1730
|
+
return {
|
|
1731
|
+
type: "ASSERT",
|
|
1732
|
+
...condition,
|
|
1733
|
+
...warn ? { warn: true } : {},
|
|
1734
|
+
...message !== void 0 ? { message } : {}
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
/** EXIT SUCCESS IF <ASSERT と同じ条件>, '<message>' */
|
|
1738
|
+
parseExit() {
|
|
1739
|
+
const exitTok = this.advance();
|
|
1740
|
+
this.requireDialect1(exitTok);
|
|
1741
|
+
if (!this.isSoftKeyword("SUCCESS")) {
|
|
1742
|
+
throw new ParseError("EXIT \u306E\u5F8C\u306B\u306F SUCCESS \u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
1743
|
+
}
|
|
1744
|
+
this.advance();
|
|
1745
|
+
if (this.peek().kind !== "IF" /* IF */ && !this.isSoftKeyword("IF")) {
|
|
1746
|
+
throw new ParseError("EXIT SUCCESS \u306E\u5F8C\u306B\u306F IF \u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
1747
|
+
}
|
|
1748
|
+
this.advance();
|
|
1749
|
+
const condition = this.parseAssertCondition();
|
|
1750
|
+
if (!this.consume("," /* COMMA */)) {
|
|
1751
|
+
throw new ParseError("EXIT SUCCESS IF \u306B\u306F\u672B\u5C3E\u306E\u30E1\u30C3\u30BB\u30FC\u30B8\u6587\u5B57\u5217\u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
1752
|
+
}
|
|
1753
|
+
const message = this.expect("STRING" /* STRING */, "EXIT SUCCESS IF \u306E\u30E1\u30C3\u30BB\u30FC\u30B8\u306B\u306F\u6587\u5B57\u5217\u30EA\u30C6\u30E9\u30EB\u304C\u5FC5\u8981\u3067\u3059");
|
|
1754
|
+
return { type: "EXIT", ...condition, message: message.value };
|
|
1755
|
+
}
|
|
1756
|
+
parseAssertCondition() {
|
|
1694
1757
|
const condStart = this.pos;
|
|
1695
1758
|
const left = this.parseAssertOperand();
|
|
1696
1759
|
const opTok = this.peek();
|
|
@@ -1703,7 +1766,6 @@ var Parser = class {
|
|
|
1703
1766
|
const high = this.parseAssertOperand();
|
|
1704
1767
|
this.rejectAssertCompound();
|
|
1705
1768
|
return {
|
|
1706
|
-
type: "ASSERT",
|
|
1707
1769
|
left,
|
|
1708
1770
|
op: "BETWEEN",
|
|
1709
1771
|
right: null,
|
|
@@ -1722,7 +1784,6 @@ var Parser = class {
|
|
|
1722
1784
|
const right = this.parseAssertOperand();
|
|
1723
1785
|
this.rejectAssertCompound();
|
|
1724
1786
|
return {
|
|
1725
|
-
type: "ASSERT",
|
|
1726
1787
|
left,
|
|
1727
1788
|
op,
|
|
1728
1789
|
right,
|
|
@@ -1731,6 +1792,17 @@ var Parser = class {
|
|
|
1731
1792
|
text: this.renderTokenRange(condStart, this.pos)
|
|
1732
1793
|
};
|
|
1733
1794
|
}
|
|
1795
|
+
/** ASSERT の dialect 1 メッセージ。カンマが無ければ既存形式。 */
|
|
1796
|
+
parseFlowMessage() {
|
|
1797
|
+
if (!this.consume("," /* COMMA */)) return void 0;
|
|
1798
|
+
this.requireDialect1(this.prev());
|
|
1799
|
+
return this.expect("STRING" /* STRING */, "ASSERT \u306E\u30E1\u30C3\u30BB\u30FC\u30B8\u306B\u306F\u6587\u5B57\u5217\u30EA\u30C6\u30E9\u30EB\u304C\u5FC5\u8981\u3067\u3059").value;
|
|
1800
|
+
}
|
|
1801
|
+
requireDialect1(tok) {
|
|
1802
|
+
if (!this.capabilities.dialect1) {
|
|
1803
|
+
throw new ParseError("\u3053\u306E\u69CB\u6587\u306B\u306F -- @ksql dialect: 1 \u306E\u5BA3\u8A00\u304C\u5FC5\u8981\u3067\u3059", tok);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1734
1806
|
/** ASSERT のオペランド: 文字列 / スカラーサブクエリ / 数値算術式 */
|
|
1735
1807
|
parseAssertOperand() {
|
|
1736
1808
|
const tok = this.peek();
|
|
@@ -3121,6 +3193,12 @@ var Parser = class {
|
|
|
3121
3193
|
);
|
|
3122
3194
|
}
|
|
3123
3195
|
const name = this.parseTableName();
|
|
3196
|
+
if (this.capabilities.dialect1 && this.bareTempTableNames.has(name)) {
|
|
3197
|
+
const normalizedName = `#${name}`;
|
|
3198
|
+
this.tempTableRefs.push({ ...this.prev(), value: normalizedName });
|
|
3199
|
+
const alias2 = this.consume("AS" /* AS */) ? this.parseTableAliasName() : this.tryParseImplicitAlias();
|
|
3200
|
+
return { appId: 0, alias: alias2, cteName: normalizedName };
|
|
3201
|
+
}
|
|
3124
3202
|
if (nameTok.kind === "IDENT" /* IDENT */ && name.startsWith("#")) {
|
|
3125
3203
|
this.tempTableRefs.push(this.prev());
|
|
3126
3204
|
const alias2 = this.consume("AS" /* AS */) ? this.parseTableAliasName() : this.tryParseImplicitAlias();
|
|
@@ -3161,6 +3239,7 @@ var Parser = class {
|
|
|
3161
3239
|
if (k === "IDENT" /* IDENT */ || k === "BIDENT" /* BIDENT */) {
|
|
3162
3240
|
if (k === "IDENT" /* IDENT */ && this.peek().value.toUpperCase() === "VALIDATE" && this.peekAt(1).kind === "IDENT" /* IDENT */ && this.peekAt(1).value.toUpperCase() === "ONLY") return null;
|
|
3163
3241
|
if (k === "IDENT" /* IDENT */ && this.peek().value.toUpperCase() === "CHECK" && this.peekAt(1).kind === "WHEN" /* WHEN */) return null;
|
|
3242
|
+
if (k === "IDENT" /* IDENT */ && this.peek().value.toUpperCase() === "KEY" && this.peekAt(1).kind === "(" /* LPAREN */) return null;
|
|
3164
3243
|
return this.parseTableAliasName();
|
|
3165
3244
|
}
|
|
3166
3245
|
return null;
|
|
@@ -4061,6 +4140,15 @@ var Parser = class {
|
|
|
4061
4140
|
};
|
|
4062
4141
|
}
|
|
4063
4142
|
parseOnDuplicate() {
|
|
4143
|
+
if (this.capabilities.dialect1 && this.consumeSoftKeyword("KEY")) {
|
|
4144
|
+
this.expect("(" /* LPAREN */, "KEY \u306E\u5F8C\u306B\u306F (\u30AD\u30FC\u30D5\u30A3\u30FC\u30EB\u30C9) \u304C\u5FC5\u8981\u3067\u3059");
|
|
4145
|
+
const keyFields2 = this.parseIdentList();
|
|
4146
|
+
this.expect(")" /* RPAREN */);
|
|
4147
|
+
if (keyFields2.length === 0) {
|
|
4148
|
+
throw new ParseError("KEY \u306B\u306F\u30AD\u30FC\u30D5\u30A3\u30FC\u30EB\u30C9\u304C\u6700\u4F4E 1 \u3064\u5FC5\u8981\u3067\u3059", this.prev());
|
|
4149
|
+
}
|
|
4150
|
+
return keyFields2;
|
|
4151
|
+
}
|
|
4064
4152
|
this.expectKeyword("ON" /* ON */, "UPSERT \u306B\u306F ON DUPLICATE (\u30AD\u30FC\u30D5\u30A3\u30FC\u30EB\u30C9) \u304C\u5FC5\u8981\u3067\u3059");
|
|
4065
4153
|
if (!this.consume("DUPLICATE" /* DUPLICATE */)) {
|
|
4066
4154
|
throw new ParseError("ON \u306E\u5F8C\u306B\u306F DUPLICATE \u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
@@ -4073,6 +4161,238 @@ var Parser = class {
|
|
|
4073
4161
|
}
|
|
4074
4162
|
return keyFields;
|
|
4075
4163
|
}
|
|
4164
|
+
parseMergeAsUpsert() {
|
|
4165
|
+
const mergeToken = this.advance();
|
|
4166
|
+
this.expect("INTO" /* INTO */, "MERGE \u306E\u5F8C\u306B\u306F INTO \u304C\u5FC5\u8981\u3067\u3059");
|
|
4167
|
+
this.rejectTempTableDml();
|
|
4168
|
+
const targetName = this.parseIdentifier();
|
|
4169
|
+
const { appId, subtableCode } = extractTableRef(targetName, this.prev());
|
|
4170
|
+
if (subtableCode) {
|
|
4171
|
+
throw new ParseError("MERGE \u306F\u307E\u3060\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u4EEE\u60F3\u30C6\u30FC\u30D6\u30EB\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093", this.prev());
|
|
4172
|
+
}
|
|
4173
|
+
this.expect("AS" /* AS */, "MERGE \u306E\u30BF\u30FC\u30B2\u30C3\u30C8\u306B\u306F AS alias \u304C\u5FC5\u8981\u3067\u3059");
|
|
4174
|
+
const targetAlias = this.parseTableAliasName();
|
|
4175
|
+
this.expectSoftKeyword("USING", "MERGE \u306B\u306F USING <source> AS alias \u304C\u5FC5\u8981\u3067\u3059");
|
|
4176
|
+
const sourceStart = this.pos;
|
|
4177
|
+
const source = this.parseTableRef();
|
|
4178
|
+
const explicitSourceAlias = this.tokens.slice(sourceStart, this.pos).some((token) => token.kind === "AS" /* AS */);
|
|
4179
|
+
if (!explicitSourceAlias || source.alias === null) {
|
|
4180
|
+
throw new ParseError("MERGE \u306E USING \u30BD\u30FC\u30B9\u306B\u306F AS alias \u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
4181
|
+
}
|
|
4182
|
+
const sourceAlias = source.alias;
|
|
4183
|
+
this.expect("ON" /* ON */, "MERGE \u306B\u306F ON t.key = s.key \u306E\u5358\u4E00\u30AD\u30FC\u7B49\u5024\u304C\u5FC5\u8981\u3067\u3059");
|
|
4184
|
+
const left = this.parseMergeQualifiedField("MERGE \u306E ON \u5DE6\u8FBA\u306F targetAlias.key \u306E\u5F62\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
|
|
4185
|
+
if (!this.consume("=" /* EQ */)) {
|
|
4186
|
+
throw new ParseError(
|
|
4187
|
+
"MERGE \u306E ON \u306F\u5358\u4E00\u30AD\u30FC\u306E\u7B49\u5024\uFF08t.key = s.key\uFF09\u306E\u307F\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u3059\u3002\u8907\u6570\u6761\u4EF6\u3084\u975E\u7B49\u5024\u306F\u9023\u7D50\u30AD\u30FC\u30D5\u30A3\u30FC\u30EB\u30C9\u3067\u7F6E\u304D\u63DB\u3048\u3066\u304F\u3060\u3055\u3044",
|
|
4188
|
+
this.peek()
|
|
4189
|
+
);
|
|
4190
|
+
}
|
|
4191
|
+
const right = this.parseMergeQualifiedField("MERGE \u306E ON \u53F3\u8FBA\u306F sourceAlias.key \u306E\u5F62\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
|
|
4192
|
+
if (left.alias.toLowerCase() !== targetAlias.toLowerCase() || right.alias.toLowerCase() !== sourceAlias.toLowerCase()) {
|
|
4193
|
+
throw new ParseError(
|
|
4194
|
+
`MERGE \u306E ON \u306F ${targetAlias}.key = ${sourceAlias}.key \u306E\u5225\u540D\u4FEE\u98FE\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044`,
|
|
4195
|
+
mergeToken
|
|
4196
|
+
);
|
|
4197
|
+
}
|
|
4198
|
+
if (this.peek().kind === "AND" /* AND */ || this.peek().kind === "OR" /* OR */) {
|
|
4199
|
+
throw new ParseError(
|
|
4200
|
+
"MERGE \u306E ON \u306F\u5358\u4E00\u30AD\u30FC\u306E\u7B49\u5024\u306E\u307F\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u3059\u3002\u8907\u6570\u6761\u4EF6\u306F\u9023\u7D50\u30AD\u30FC\u30D5\u30A3\u30FC\u30EB\u30C9\u3067\u7F6E\u304D\u63DB\u3048\u3066\u304F\u3060\u3055\u3044",
|
|
4201
|
+
this.peek()
|
|
4202
|
+
);
|
|
4203
|
+
}
|
|
4204
|
+
let matched = null;
|
|
4205
|
+
let insertFields = null;
|
|
4206
|
+
let insertValues = null;
|
|
4207
|
+
while (this.peek().kind === "WHEN" /* WHEN */) {
|
|
4208
|
+
const whenToken = this.advance();
|
|
4209
|
+
if (this.consume("NOT" /* NOT */)) {
|
|
4210
|
+
this.expectSoftKeyword("MATCHED", "WHEN NOT \u306E\u5F8C\u306B\u306F MATCHED \u304C\u5FC5\u8981\u3067\u3059");
|
|
4211
|
+
if (insertFields !== null) throw new ParseError("WHEN NOT MATCHED \u53E5\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059", whenToken);
|
|
4212
|
+
this.expect("THEN" /* THEN */, "WHEN NOT MATCHED \u306E\u5F8C\u306B\u306F THEN \u304C\u5FC5\u8981\u3067\u3059");
|
|
4213
|
+
this.expect("INSERT" /* INSERT */, "WHEN NOT MATCHED THEN \u306E\u5F8C\u306B\u306F INSERT \u304C\u5FC5\u8981\u3067\u3059");
|
|
4214
|
+
this.expect("(" /* LPAREN */, "MERGE INSERT \u306B\u306F\u5217\u30EA\u30B9\u30C8\u304C\u5FC5\u8981\u3067\u3059");
|
|
4215
|
+
insertFields = this.parseIdentList();
|
|
4216
|
+
this.expect(")" /* RPAREN */);
|
|
4217
|
+
this.expect("VALUES" /* VALUES */, "MERGE INSERT \u306E\u5217\u30EA\u30B9\u30C8\u306E\u5F8C\u306B\u306F VALUES \u304C\u5FC5\u8981\u3067\u3059");
|
|
4218
|
+
this.expect("(" /* LPAREN */, "MERGE INSERT VALUES \u306F ( \u3067\u59CB\u3081\u3066\u304F\u3060\u3055\u3044");
|
|
4219
|
+
insertValues = this.parseMergeValueList();
|
|
4220
|
+
this.expect(")" /* RPAREN */);
|
|
4221
|
+
if (insertFields.length !== insertValues.length) {
|
|
4222
|
+
throw new ParseError("MERGE INSERT \u306E\u5217\u6570\u3068 VALUES \u306E\u5024\u6570\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093", whenToken);
|
|
4223
|
+
}
|
|
4224
|
+
} else {
|
|
4225
|
+
this.expectSoftKeyword("MATCHED", "WHEN \u306E\u5F8C\u306B\u306F MATCHED \u307E\u305F\u306F NOT MATCHED \u304C\u5FC5\u8981\u3067\u3059");
|
|
4226
|
+
if (matched !== null) throw new ParseError("WHEN MATCHED \u53E5\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059", whenToken);
|
|
4227
|
+
this.expect("THEN" /* THEN */, "WHEN MATCHED \u306E\u5F8C\u306B\u306F THEN \u304C\u5FC5\u8981\u3067\u3059");
|
|
4228
|
+
this.expect("UPDATE" /* UPDATE */, "WHEN MATCHED THEN \u306E\u5F8C\u306B\u306F UPDATE \u304C\u5FC5\u8981\u3067\u3059");
|
|
4229
|
+
this.expect("SET" /* SET */, "WHEN MATCHED THEN UPDATE \u306E\u5F8C\u306B\u306F SET \u304C\u5FC5\u8981\u3067\u3059");
|
|
4230
|
+
matched = this.parseMergeAssignments(targetAlias);
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4233
|
+
if (matched === null || insertFields === null || insertValues === null) {
|
|
4234
|
+
throw new ParseError(
|
|
4235
|
+
"WHEN MATCHED / WHEN NOT MATCHED \u306E\u4E21\u53E5\u304C\u5FC5\u8981\u3067\u3059\uFF08\u66F4\u65B0\u306E\u307F\u306F UPDATE ... FROM\u3001\u633F\u5165\u306E\u307F\u306F INSERT ... SELECT \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\uFF09",
|
|
4236
|
+
this.peek()
|
|
4237
|
+
);
|
|
4238
|
+
}
|
|
4239
|
+
if (!insertFields.some((field) => field.toLowerCase() === left.field.toLowerCase())) {
|
|
4240
|
+
throw new ParseError(
|
|
4241
|
+
`MERGE \u306E ON \u30AD\u30FC ${left.field} \u306F INSERT \u5217\u30EA\u30B9\u30C8\u306B\u542B\u3081\u3066\u304F\u3060\u3055\u3044`,
|
|
4242
|
+
mergeToken
|
|
4243
|
+
);
|
|
4244
|
+
}
|
|
4245
|
+
const expressions = /* @__PURE__ */ new Map();
|
|
4246
|
+
insertFields.forEach((field, index) => expressions.set(field.toLowerCase(), insertValues[index]));
|
|
4247
|
+
for (const assignment of matched) {
|
|
4248
|
+
const key = assignment.field.toLowerCase();
|
|
4249
|
+
const existing = expressions.get(key);
|
|
4250
|
+
if (existing !== void 0 && !this.mergeExpressionsEqual(existing, assignment.value, sourceAlias)) {
|
|
4251
|
+
throw new ParseError(
|
|
4252
|
+
`MERGE \u306E\u5217 ${assignment.field} \u306F\u4E21\u53E5\u306E\u5F0F\u304C\u4E00\u81F4\u3059\u308B\u5834\u5408\u306E\u307F MERGE \u3092 UPSERT \u3078\u6B63\u898F\u5316\u3067\u304D\u307E\u3059`,
|
|
4253
|
+
mergeToken
|
|
4254
|
+
);
|
|
4255
|
+
}
|
|
4256
|
+
if (existing === void 0) {
|
|
4257
|
+
insertFields.push(assignment.field);
|
|
4258
|
+
insertValues.push(assignment.value);
|
|
4259
|
+
expressions.set(key, assignment.value);
|
|
4260
|
+
}
|
|
4261
|
+
}
|
|
4262
|
+
const columns = insertValues.map((value) => this.mergeValueToSelectColumn(value, sourceAlias, mergeToken));
|
|
4263
|
+
const normalizedSource = source.cteName !== null ? { ...source, alias: null } : { ...source, alias: `APP${source.appId}${source.subtableCode ? `$${source.subtableCode}` : ""}` };
|
|
4264
|
+
const select = {
|
|
4265
|
+
type: "SELECT",
|
|
4266
|
+
distinct: false,
|
|
4267
|
+
columns,
|
|
4268
|
+
from: normalizedSource,
|
|
4269
|
+
joins: [],
|
|
4270
|
+
where: null,
|
|
4271
|
+
groupBy: [],
|
|
4272
|
+
having: null,
|
|
4273
|
+
orderMode: "CANONICAL",
|
|
4274
|
+
orderBy: [],
|
|
4275
|
+
limit: null,
|
|
4276
|
+
offset: null
|
|
4277
|
+
};
|
|
4278
|
+
const checkGroups = this.parseCheckGroups();
|
|
4279
|
+
const validation = this.parseDmlControlSuffix();
|
|
4280
|
+
return {
|
|
4281
|
+
type: "UPSERT_SELECT",
|
|
4282
|
+
appId,
|
|
4283
|
+
fields: insertFields,
|
|
4284
|
+
select,
|
|
4285
|
+
keyFields: [left.field],
|
|
4286
|
+
...checkGroups,
|
|
4287
|
+
...validation
|
|
4288
|
+
};
|
|
4289
|
+
}
|
|
4290
|
+
parseMergeQualifiedField(message) {
|
|
4291
|
+
const token = this.peek();
|
|
4292
|
+
const path = this.parseFieldPath();
|
|
4293
|
+
const ref = this.splitQualifiedField(path);
|
|
4294
|
+
if (ref.alias === null) throw new ParseError(message, token);
|
|
4295
|
+
return { alias: ref.alias, field: ref.field };
|
|
4296
|
+
}
|
|
4297
|
+
parseMergeAssignments(targetAlias) {
|
|
4298
|
+
const assignments = [];
|
|
4299
|
+
do {
|
|
4300
|
+
const token = this.peek();
|
|
4301
|
+
const path = this.parseFieldPath();
|
|
4302
|
+
const ref = this.splitQualifiedField(path);
|
|
4303
|
+
if (ref.alias !== null && ref.alias.toLowerCase() !== targetAlias.toLowerCase()) {
|
|
4304
|
+
throw new ParseError(`MERGE UPDATE SET \u306E\u5DE6\u8FBA\u306F target alias ${targetAlias} \u3067\u4FEE\u98FE\u3057\u3066\u304F\u3060\u3055\u3044`, token);
|
|
4305
|
+
}
|
|
4306
|
+
this.expect("=" /* EQ */);
|
|
4307
|
+
assignments.push({ field: ref.field, value: this.parseAssignmentValue() });
|
|
4308
|
+
} while (this.consume("," /* COMMA */));
|
|
4309
|
+
return assignments;
|
|
4310
|
+
}
|
|
4311
|
+
parseMergeValueList() {
|
|
4312
|
+
const values = [];
|
|
4313
|
+
if (this.peek().kind === ")" /* RPAREN */) return values;
|
|
4314
|
+
do
|
|
4315
|
+
values.push(this.parseAssignmentValue());
|
|
4316
|
+
while (this.consume("," /* COMMA */));
|
|
4317
|
+
return values;
|
|
4318
|
+
}
|
|
4319
|
+
mergeExpressionsEqual(left, right, sourceAlias) {
|
|
4320
|
+
return this.mergeNormalizedValueEqual(
|
|
4321
|
+
this.normalizeMergeValue(left, sourceAlias, true),
|
|
4322
|
+
this.normalizeMergeValue(right, sourceAlias, true)
|
|
4323
|
+
);
|
|
4324
|
+
}
|
|
4325
|
+
normalizeMergeValue(value, sourceAlias, compareLiteralValues = false) {
|
|
4326
|
+
if (Array.isArray(value)) {
|
|
4327
|
+
return value.map((item) => this.normalizeMergeValue(item, sourceAlias, compareLiteralValues));
|
|
4328
|
+
}
|
|
4329
|
+
if (value === null || typeof value !== "object") return value;
|
|
4330
|
+
const obj = value;
|
|
4331
|
+
if (compareLiteralValues && obj["type"] === "NUMBER") {
|
|
4332
|
+
return { type: "NUMBER", value: obj["value"] };
|
|
4333
|
+
}
|
|
4334
|
+
if (obj["type"] === "SOURCE_FIELD") {
|
|
4335
|
+
if (String(obj["alias"]).toLowerCase() !== sourceAlias.toLowerCase()) return { type: "INVALID_SOURCE" };
|
|
4336
|
+
return { type: "FIELD_REF", field: obj["field"] };
|
|
4337
|
+
}
|
|
4338
|
+
if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") {
|
|
4339
|
+
const ref = this.splitQualifiedField(obj["field"]);
|
|
4340
|
+
if (ref.alias !== null && ref.alias.toLowerCase() !== sourceAlias.toLowerCase()) return { type: "INVALID_SOURCE" };
|
|
4341
|
+
return { ...obj, field: ref.field };
|
|
4342
|
+
}
|
|
4343
|
+
if (obj["type"] === "FIELD" && typeof obj["tableAlias"] === "string") {
|
|
4344
|
+
if (obj["tableAlias"].toLowerCase() !== sourceAlias.toLowerCase()) return { type: "INVALID_SOURCE" };
|
|
4345
|
+
return { ...obj, tableAlias: null };
|
|
4346
|
+
}
|
|
4347
|
+
return Object.fromEntries(Object.entries(obj).map(([key, child]) => [
|
|
4348
|
+
key,
|
|
4349
|
+
this.normalizeMergeValue(child, sourceAlias, compareLiteralValues)
|
|
4350
|
+
]));
|
|
4351
|
+
}
|
|
4352
|
+
mergeNormalizedValueEqual(left, right) {
|
|
4353
|
+
if (left === right) return true;
|
|
4354
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
4355
|
+
return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((item, index) => this.mergeNormalizedValueEqual(item, right[index]));
|
|
4356
|
+
}
|
|
4357
|
+
if (left === null || right === null || typeof left !== "object" || typeof right !== "object") return false;
|
|
4358
|
+
const leftObj = left;
|
|
4359
|
+
const rightObj = right;
|
|
4360
|
+
const leftKeys = Object.keys(leftObj);
|
|
4361
|
+
const rightKeys = Object.keys(rightObj);
|
|
4362
|
+
return leftKeys.length === rightKeys.length && leftKeys.every((key) => Object.prototype.hasOwnProperty.call(rightObj, key) && this.mergeNormalizedValueEqual(leftObj[key], rightObj[key]));
|
|
4363
|
+
}
|
|
4364
|
+
mergeValueToSelectColumn(value, sourceAlias, token) {
|
|
4365
|
+
const normalized = this.normalizeMergeValue(value, sourceAlias);
|
|
4366
|
+
if (this.mergeValueContainsInvalidSource(normalized)) {
|
|
4367
|
+
throw new ParseError(`MERGE \u306E\u5F0F\u306F source alias ${sourceAlias} \u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u3060\u3051\u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044`, token);
|
|
4368
|
+
}
|
|
4369
|
+
const expr = normalized;
|
|
4370
|
+
switch (expr["type"]) {
|
|
4371
|
+
case "FIELD_REF":
|
|
4372
|
+
return { type: "FIELD", field: String(expr["field"]), alias: null };
|
|
4373
|
+
case "STRING":
|
|
4374
|
+
return { type: "LITERAL_COL", value: String(expr["value"]), alias: null };
|
|
4375
|
+
case "NUMBER":
|
|
4376
|
+
return { type: "ARITH_COL", expr, alias: null };
|
|
4377
|
+
case "ARITH":
|
|
4378
|
+
return { type: "ARITH_COL", expr, alias: null };
|
|
4379
|
+
case "STRING_FUNC":
|
|
4380
|
+
return { type: "STRFUNC_COL", expr, alias: null };
|
|
4381
|
+
case "CASE_VALUE":
|
|
4382
|
+
return { type: "CASE_COL", expr: expr["expr"], alias: null };
|
|
4383
|
+
default:
|
|
4384
|
+
throw new ParseError(
|
|
4385
|
+
"MERGE \u306E\u4EE3\u5165\u5F0F\u306F\u30BD\u30FC\u30B9\u30D5\u30A3\u30FC\u30EB\u30C9\u30FB\u30EA\u30C6\u30E9\u30EB\u30FB\u7B97\u8853\u5F0F\u30FB\u6587\u5B57\u5217\u95A2\u6570\u30FBCASE \u306E\u3044\u305A\u308C\u304B\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
4386
|
+
token
|
|
4387
|
+
);
|
|
4388
|
+
}
|
|
4389
|
+
}
|
|
4390
|
+
mergeValueContainsInvalidSource(value) {
|
|
4391
|
+
if (Array.isArray(value)) return value.some((item) => this.mergeValueContainsInvalidSource(item));
|
|
4392
|
+
if (value === null || typeof value !== "object") return false;
|
|
4393
|
+
const obj = value;
|
|
4394
|
+
return obj["type"] === "INVALID_SOURCE" || Object.values(obj).some((item) => this.mergeValueContainsInvalidSource(item));
|
|
4395
|
+
}
|
|
4076
4396
|
isUpsertApplyBranchStart() {
|
|
4077
4397
|
return this.peek().kind === "ON" /* ON */ && (this.peekAt(1).kind === "INSERT" /* INSERT */ || this.peekAt(1).kind === "UPDATE" /* UPDATE */);
|
|
4078
4398
|
}
|
|
@@ -4889,7 +5209,7 @@ function isDmlType(type) {
|
|
|
4889
5209
|
return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER" || type === "IMPORT";
|
|
4890
5210
|
}
|
|
4891
5211
|
function isReadOnlyType(type) {
|
|
4892
|
-
return type === "SELECT" || type === "VALIDATE" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE" || type === "SET_VARIABLE" || type === "DECLARE_VARIABLE" || type === "ASSERT";
|
|
5212
|
+
return type === "SELECT" || type === "VALIDATE" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE" || type === "SET_VARIABLE" || type === "DECLARE_VARIABLE" || type === "ASSERT" || type === "EXIT";
|
|
4893
5213
|
}
|
|
4894
5214
|
function writesKintone(stmt) {
|
|
4895
5215
|
return isDmlType(stmt.type) && !("validateOnly" in stmt && stmt.validateOnly === true);
|
|
@@ -8259,6 +8579,7 @@ function validateStatement(stmt) {
|
|
|
8259
8579
|
case "SET_VARIABLE":
|
|
8260
8580
|
case "DECLARE_VARIABLE":
|
|
8261
8581
|
case "ASSERT":
|
|
8582
|
+
case "EXIT":
|
|
8262
8583
|
validateNestedSelects(stmt);
|
|
8263
8584
|
return;
|
|
8264
8585
|
case "UPDATE":
|
|
@@ -16134,6 +16455,147 @@ function toFlatString(value) {
|
|
|
16134
16455
|
}
|
|
16135
16456
|
}
|
|
16136
16457
|
|
|
16458
|
+
// src/core/diagnostics.ts
|
|
16459
|
+
var DiagnosticCodes = {
|
|
16460
|
+
HEADER_UNKNOWN_KEY: "KSQL1001",
|
|
16461
|
+
HEADER_DUPLICATE_KEY: "KSQL1002",
|
|
16462
|
+
HEADER_INVALID_NAME: "KSQL1003",
|
|
16463
|
+
HEADER_INVALID_DEPENDS_ON: "KSQL1004",
|
|
16464
|
+
HEADER_INVALID_TIMEOUT: "KSQL1005",
|
|
16465
|
+
HEADER_INVALID_DIALECT: "KSQL1006",
|
|
16466
|
+
LOGICAL_APP_UNRESOLVED: "KSQL1101",
|
|
16467
|
+
LEX_ERROR: "KSQL1201",
|
|
16468
|
+
PARSE_ERROR: "KSQL1202"
|
|
16469
|
+
};
|
|
16470
|
+
function sourceLocationAt(source, offset) {
|
|
16471
|
+
const target = Math.max(0, Math.min(offset, source.length));
|
|
16472
|
+
let line = 1;
|
|
16473
|
+
let column = 1;
|
|
16474
|
+
for (let i = 0; i < target; i++) {
|
|
16475
|
+
const ch = source[i];
|
|
16476
|
+
if (ch === "\r") {
|
|
16477
|
+
if (source[i + 1] === "\n" && i + 1 < target) i++;
|
|
16478
|
+
line++;
|
|
16479
|
+
column = 1;
|
|
16480
|
+
} else if (ch === "\n") {
|
|
16481
|
+
line++;
|
|
16482
|
+
column = 1;
|
|
16483
|
+
} else {
|
|
16484
|
+
column++;
|
|
16485
|
+
}
|
|
16486
|
+
}
|
|
16487
|
+
return { line, column };
|
|
16488
|
+
}
|
|
16489
|
+
function diagnosticAt(source, offset, diagnostic2) {
|
|
16490
|
+
return { ...diagnostic2, ...sourceLocationAt(source, offset) };
|
|
16491
|
+
}
|
|
16492
|
+
|
|
16493
|
+
// src/core/scriptHeader.ts
|
|
16494
|
+
var HEADER_LINE_RE = /^(\s*)--\s*@ksql\s+([^:\s]+)\s*:\s*(.*)$/i;
|
|
16495
|
+
function parseScriptHeader(source) {
|
|
16496
|
+
const meta = { name: null, dependsOn: [], timeout: null, dialect: 0 };
|
|
16497
|
+
const diagnostics = [];
|
|
16498
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16499
|
+
let hasDirectives = false;
|
|
16500
|
+
let offset = source.charCodeAt(0) === 65279 ? 1 : 0;
|
|
16501
|
+
let headerEnd = offset;
|
|
16502
|
+
while (offset < source.length) {
|
|
16503
|
+
const lineEnd = findLineEnd(source, offset);
|
|
16504
|
+
const line = source.slice(offset, lineEnd.contentEnd);
|
|
16505
|
+
if (!/^\s*--/.test(line)) break;
|
|
16506
|
+
headerEnd = lineEnd.next;
|
|
16507
|
+
const match = HEADER_LINE_RE.exec(line);
|
|
16508
|
+
if (match) {
|
|
16509
|
+
hasDirectives = true;
|
|
16510
|
+
const rawKey = match[2];
|
|
16511
|
+
const key = rawKey.toLowerCase();
|
|
16512
|
+
const rawValue = match[3];
|
|
16513
|
+
const commentAt = rawValue.indexOf("#");
|
|
16514
|
+
const valuePart = commentAt < 0 ? rawValue : rawValue.slice(0, commentAt);
|
|
16515
|
+
const leading = valuePart.match(/^\s*/)?.[0].length ?? 0;
|
|
16516
|
+
const value = valuePart.trim();
|
|
16517
|
+
const valueOffset = offset + match.index + match[0].length - rawValue.length + leading;
|
|
16518
|
+
if (!isHeaderKey(key)) {
|
|
16519
|
+
diagnostics.push(diagnosticAt(source, valueOffset, {
|
|
16520
|
+
severity: "warning",
|
|
16521
|
+
code: DiagnosticCodes.HEADER_UNKNOWN_KEY,
|
|
16522
|
+
message: `Unknown @ksql header key "${rawKey}" was ignored.`
|
|
16523
|
+
}));
|
|
16524
|
+
} else if (seen.has(key)) {
|
|
16525
|
+
diagnostics.push(diagnosticAt(source, valueOffset, {
|
|
16526
|
+
severity: "warning",
|
|
16527
|
+
code: DiagnosticCodes.HEADER_DUPLICATE_KEY,
|
|
16528
|
+
message: `Duplicate @ksql header key "${key}" was ignored; the first value is retained.`
|
|
16529
|
+
}));
|
|
16530
|
+
} else {
|
|
16531
|
+
seen.add(key);
|
|
16532
|
+
applyHeaderValue(meta, key, value, source, valueOffset, diagnostics);
|
|
16533
|
+
}
|
|
16534
|
+
}
|
|
16535
|
+
offset = lineEnd.next;
|
|
16536
|
+
}
|
|
16537
|
+
return { meta, diagnostics, hasDirectives, headerEnd };
|
|
16538
|
+
}
|
|
16539
|
+
function isHeaderKey(value) {
|
|
16540
|
+
return value === "name" || value === "depends_on" || value === "timeout" || value === "dialect";
|
|
16541
|
+
}
|
|
16542
|
+
function applyHeaderValue(meta, key, value, source, valueOffset, diagnostics) {
|
|
16543
|
+
if (key === "name") {
|
|
16544
|
+
if (!value) {
|
|
16545
|
+
diagnostics.push(diagnosticAt(source, valueOffset, {
|
|
16546
|
+
severity: "error",
|
|
16547
|
+
code: DiagnosticCodes.HEADER_INVALID_NAME,
|
|
16548
|
+
message: "@ksql name must not be empty."
|
|
16549
|
+
}));
|
|
16550
|
+
} else {
|
|
16551
|
+
meta.name = value;
|
|
16552
|
+
}
|
|
16553
|
+
return;
|
|
16554
|
+
}
|
|
16555
|
+
if (key === "depends_on") {
|
|
16556
|
+
const dependencies = value.split(",").map((item) => item.trim());
|
|
16557
|
+
if (!value || dependencies.some((item) => !item)) {
|
|
16558
|
+
diagnostics.push(diagnosticAt(source, valueOffset, {
|
|
16559
|
+
severity: "error",
|
|
16560
|
+
code: DiagnosticCodes.HEADER_INVALID_DEPENDS_ON,
|
|
16561
|
+
message: "@ksql depends_on must be a comma-separated list without empty items."
|
|
16562
|
+
}));
|
|
16563
|
+
} else {
|
|
16564
|
+
meta.dependsOn = dependencies;
|
|
16565
|
+
}
|
|
16566
|
+
return;
|
|
16567
|
+
}
|
|
16568
|
+
if (key === "timeout") {
|
|
16569
|
+
if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(Number(value))) {
|
|
16570
|
+
diagnostics.push(diagnosticAt(source, valueOffset, {
|
|
16571
|
+
severity: "error",
|
|
16572
|
+
code: DiagnosticCodes.HEADER_INVALID_TIMEOUT,
|
|
16573
|
+
message: "@ksql timeout must be a positive integer."
|
|
16574
|
+
}));
|
|
16575
|
+
} else {
|
|
16576
|
+
meta.timeout = Number(value);
|
|
16577
|
+
}
|
|
16578
|
+
return;
|
|
16579
|
+
}
|
|
16580
|
+
if (value !== "0" && value !== "1") {
|
|
16581
|
+
diagnostics.push(diagnosticAt(source, valueOffset, {
|
|
16582
|
+
severity: "error",
|
|
16583
|
+
code: DiagnosticCodes.HEADER_INVALID_DIALECT,
|
|
16584
|
+
message: "@ksql dialect must be 0 or 1."
|
|
16585
|
+
}));
|
|
16586
|
+
} else {
|
|
16587
|
+
meta.dialect = Number(value);
|
|
16588
|
+
}
|
|
16589
|
+
}
|
|
16590
|
+
function findLineEnd(source, start) {
|
|
16591
|
+
let i = start;
|
|
16592
|
+
while (i < source.length && source[i] !== "\r" && source[i] !== "\n") i++;
|
|
16593
|
+
const contentEnd = i;
|
|
16594
|
+
if (source[i] === "\r" && source[i + 1] === "\n") i += 2;
|
|
16595
|
+
else if (i < source.length) i++;
|
|
16596
|
+
return { contentEnd, next: i };
|
|
16597
|
+
}
|
|
16598
|
+
|
|
16137
16599
|
// src/core/dmlPrevalidation.ts
|
|
16138
16600
|
function collectDmlPrevalidationSnapshotFields(fieldIndex) {
|
|
16139
16601
|
return [
|
|
@@ -18155,6 +18617,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
18155
18617
|
throw new Error("ArgumentError: DECLARE variable requires a batch.");
|
|
18156
18618
|
case "ASSERT":
|
|
18157
18619
|
return executeAssert(stmt, client, options, cacheContext);
|
|
18620
|
+
case "EXIT":
|
|
18621
|
+
throw new Error("ArgumentError: EXIT SUCCESS IF \u306F\u30D0\u30C3\u30C1\u5C02\u7528\u3067\u3059");
|
|
18158
18622
|
}
|
|
18159
18623
|
}
|
|
18160
18624
|
var EXISTING_VALIDATION_COLUMNS = [
|
|
@@ -18422,7 +18886,16 @@ var BatchTimeoutError = class extends Error {
|
|
|
18422
18886
|
};
|
|
18423
18887
|
async function executeBatch(sql, client, options = {}) {
|
|
18424
18888
|
resolveRecursiveCteLimits(options);
|
|
18425
|
-
const
|
|
18889
|
+
const header = parseScriptHeader(sql);
|
|
18890
|
+
const headerError = header.diagnostics.find((diagnostic2) => diagnostic2.severity === "error");
|
|
18891
|
+
if (header.hasDirectives && headerError) {
|
|
18892
|
+
throw new Error(`${headerError.code}: ${headerError.message} (${headerError.line}:${headerError.column})`);
|
|
18893
|
+
}
|
|
18894
|
+
const statements = parseSqlBatch(
|
|
18895
|
+
header.hasDirectives ? sql.slice(header.headerEnd) : sql,
|
|
18896
|
+
options.enableImport === true,
|
|
18897
|
+
header.hasDirectives && header.meta.dialect === 1
|
|
18898
|
+
);
|
|
18426
18899
|
const analysis = analyzeBatch(statements);
|
|
18427
18900
|
statements.forEach((statement) => assertApplyExecutionScope("phase15b", statement));
|
|
18428
18901
|
if (options.allowApplyMutation !== true && statements.some(
|
|
@@ -18464,7 +18937,7 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
18464
18937
|
const base = { index: i, type: info.statementType };
|
|
18465
18938
|
if (aborted) {
|
|
18466
18939
|
results.push({ ...base, status: "skipped", skippedReason: aborted });
|
|
18467
|
-
failed.add(i);
|
|
18940
|
+
if (aborted !== "exit") failed.add(i);
|
|
18468
18941
|
continue;
|
|
18469
18942
|
}
|
|
18470
18943
|
const brokenDep = info.dependsOn.find((d) => failed.has(d));
|
|
@@ -18502,24 +18975,29 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
18502
18975
|
info.statementType !== "SELECT" && info.statementType !== "UNION" && info.statementType !== "WITH" || statementContainsOuterJoin(statements[i])
|
|
18503
18976
|
);
|
|
18504
18977
|
const cursorScope = wrapClientWithCursorScope(statementClient);
|
|
18978
|
+
const boundOptions = bindStatementEvaluationContext(stmtOptions);
|
|
18979
|
+
const statementContext = {
|
|
18980
|
+
stmt: statements[i],
|
|
18981
|
+
info,
|
|
18982
|
+
client: cursorScope.client,
|
|
18983
|
+
options: boundOptions,
|
|
18984
|
+
cacheContext,
|
|
18985
|
+
tempTables,
|
|
18986
|
+
variables,
|
|
18987
|
+
relativeDateVariables,
|
|
18988
|
+
clock: statementEvaluationContext(boundOptions)
|
|
18989
|
+
};
|
|
18505
18990
|
const outcome = await runWithDeadline(
|
|
18506
|
-
executeBatchStatement(
|
|
18507
|
-
statements[i],
|
|
18508
|
-
info,
|
|
18509
|
-
cursorScope.client,
|
|
18510
|
-
stmtOptions,
|
|
18511
|
-
cacheContext,
|
|
18512
|
-
tempTables,
|
|
18513
|
-
variables,
|
|
18514
|
-
relativeDateVariables
|
|
18515
|
-
),
|
|
18991
|
+
executeBatchStatement(statementContext),
|
|
18516
18992
|
remaining,
|
|
18517
18993
|
cursorScope.closeActive
|
|
18518
18994
|
);
|
|
18519
18995
|
if (outcome.result) {
|
|
18520
18996
|
outcome.result = attachSearchAbortWarning(outcome.result, searchAbortCollector);
|
|
18521
18997
|
}
|
|
18522
|
-
|
|
18998
|
+
const { exitTriggered, ...statementOutcome } = outcome;
|
|
18999
|
+
results.push({ ...base, status: "success", ...statementOutcome });
|
|
19000
|
+
if (exitTriggered) aborted = "exit";
|
|
18523
19001
|
} catch (e) {
|
|
18524
19002
|
results.push({
|
|
18525
19003
|
...base,
|
|
@@ -18541,7 +19019,7 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
18541
19019
|
}
|
|
18542
19020
|
metrics.elapsedMs = Date.now() - startedAt;
|
|
18543
19021
|
return {
|
|
18544
|
-
ok: results.every((r) => r.status === "success"),
|
|
19022
|
+
ok: results.every((r) => r.status === "success" || r.skippedReason === "exit"),
|
|
18545
19023
|
statementCount: statements.length,
|
|
18546
19024
|
statements: results,
|
|
18547
19025
|
analysis,
|
|
@@ -18557,8 +19035,18 @@ function statementHasApplyMutation(statement) {
|
|
|
18557
19035
|
}
|
|
18558
19036
|
return statement.type === "UPSERT" && statement.validateOnly !== true && Boolean(statement.onInsertApplyBlocks?.length || statement.onUpdateApplyBlocks?.length);
|
|
18559
19037
|
}
|
|
18560
|
-
async function executeBatchStatement(
|
|
18561
|
-
|
|
19038
|
+
async function executeBatchStatement(context) {
|
|
19039
|
+
const {
|
|
19040
|
+
stmt,
|
|
19041
|
+
info,
|
|
19042
|
+
client,
|
|
19043
|
+
options,
|
|
19044
|
+
cacheContext,
|
|
19045
|
+
tempTables,
|
|
19046
|
+
variables,
|
|
19047
|
+
relativeDateVariables,
|
|
19048
|
+
clock
|
|
19049
|
+
} = context;
|
|
18562
19050
|
if (stmt.type === "SET_VARIABLE") {
|
|
18563
19051
|
const resolvedStmt2 = resolveBatchVariableReferences(stmt, variables);
|
|
18564
19052
|
validateStatementStatic(resolvedStmt2);
|
|
@@ -18598,7 +19086,7 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
18598
19086
|
throw e;
|
|
18599
19087
|
}
|
|
18600
19088
|
} else {
|
|
18601
|
-
variables.set(stmt.name, evaluateScalarExpr(resolvedStmt2.expr,
|
|
19089
|
+
variables.set(stmt.name, evaluateScalarExpr(resolvedStmt2.expr, clock));
|
|
18602
19090
|
}
|
|
18603
19091
|
return {};
|
|
18604
19092
|
}
|
|
@@ -18616,7 +19104,7 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
18616
19104
|
} else {
|
|
18617
19105
|
const value = evaluateScalarExpr(
|
|
18618
19106
|
stmt.default,
|
|
18619
|
-
|
|
19107
|
+
clock
|
|
18620
19108
|
);
|
|
18621
19109
|
variables.set(stmt.name, {
|
|
18622
19110
|
type: "string",
|
|
@@ -18713,8 +19201,12 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
18713
19201
|
}
|
|
18714
19202
|
}
|
|
18715
19203
|
if (resolvedStmt.type === "ASSERT") {
|
|
18716
|
-
await executeAssert(resolvedStmt, client, options, cacheContext, tempTables);
|
|
18717
|
-
return {};
|
|
19204
|
+
const result = await executeAssert(resolvedStmt, client, options, cacheContext, tempTables);
|
|
19205
|
+
return result.warning !== void 0 ? { result } : {};
|
|
19206
|
+
}
|
|
19207
|
+
if (resolvedStmt.type === "EXIT") {
|
|
19208
|
+
const result = await executeExit(resolvedStmt, client, options, cacheContext, tempTables);
|
|
19209
|
+
return { result, ...result.exited ? { exitTriggered: true } : {} };
|
|
18718
19210
|
}
|
|
18719
19211
|
if (info.tempTablesReferenced.length > 0) {
|
|
18720
19212
|
if (resolvedStmt.type === "SELECT" || resolvedStmt.type === "UNION") {
|
|
@@ -18838,9 +19330,9 @@ function safeJsonStringify(v) {
|
|
|
18838
19330
|
return String(v);
|
|
18839
19331
|
}
|
|
18840
19332
|
}
|
|
18841
|
-
function parseSqlBatch(sql, enableImport = false) {
|
|
19333
|
+
function parseSqlBatch(sql, enableImport = false, dialect1 = false) {
|
|
18842
19334
|
const tokens = new Lexer(sql).tokenize();
|
|
18843
|
-
return new Parser(tokens, { import: enableImport }).parseStatements();
|
|
19335
|
+
return new Parser(tokens, { import: enableImport, dialect1 }).parseStatements();
|
|
18844
19336
|
}
|
|
18845
19337
|
function parseRelativeDateVariableValue(name, value) {
|
|
18846
19338
|
try {
|
|
@@ -19026,6 +19518,31 @@ var ScalarSubqueryError = class extends Error {
|
|
|
19026
19518
|
}
|
|
19027
19519
|
};
|
|
19028
19520
|
async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
19521
|
+
const evaluation = await evaluateAssertCondition(stmt, client, options, cacheContext, tempTables);
|
|
19522
|
+
if (!evaluation.passed) {
|
|
19523
|
+
if (stmt.warn === true) {
|
|
19524
|
+
return {
|
|
19525
|
+
type: "ASSERT",
|
|
19526
|
+
condition: stmt.text,
|
|
19527
|
+
passed: false,
|
|
19528
|
+
warning: stmt.message ?? `assertion failed: ${stmt.text} (actual: ${evaluation.actual}).`
|
|
19529
|
+
};
|
|
19530
|
+
}
|
|
19531
|
+
const suffix = stmt.message !== void 0 ? ` ${stmt.message}` : "";
|
|
19532
|
+
throw new AssertError(`assertion failed: ${stmt.text} (actual: ${evaluation.actual}).${suffix}`);
|
|
19533
|
+
}
|
|
19534
|
+
return { type: "ASSERT", condition: stmt.text };
|
|
19535
|
+
}
|
|
19536
|
+
async function executeExit(stmt, client, options, cacheContext, tempTables) {
|
|
19537
|
+
const evaluation = await evaluateAssertCondition(stmt, client, options, cacheContext, tempTables);
|
|
19538
|
+
return {
|
|
19539
|
+
type: "EXIT",
|
|
19540
|
+
condition: stmt.text,
|
|
19541
|
+
exited: evaluation.passed,
|
|
19542
|
+
message: stmt.message
|
|
19543
|
+
};
|
|
19544
|
+
}
|
|
19545
|
+
async function evaluateAssertCondition(stmt, client, options, cacheContext, tempTables) {
|
|
19029
19546
|
const left = await evalAssertOperand(stmt.left, client, options, cacheContext, tempTables);
|
|
19030
19547
|
const semantics = stmt.left.type === "NUMBER" || stmt.left.type === "ARITH" ? syntheticSemantics("number") : syntheticSemantics("string");
|
|
19031
19548
|
if (stmt.op === "BETWEEN") {
|
|
@@ -19034,19 +19551,16 @@ async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
|
19034
19551
|
}
|
|
19035
19552
|
const low = await evalAssertOperand(stmt.low, client, options, cacheContext, tempTables);
|
|
19036
19553
|
const high = await evalAssertOperand(stmt.high, client, options, cacheContext, tempTables);
|
|
19037
|
-
|
|
19038
|
-
|
|
19039
|
-
|
|
19040
|
-
|
|
19554
|
+
return {
|
|
19555
|
+
passed: compareScalarValues(">=", left, low, semantics) && compareScalarValues("<=", left, high, semantics),
|
|
19556
|
+
actual: left
|
|
19557
|
+
};
|
|
19041
19558
|
}
|
|
19042
19559
|
if (stmt.right === null) {
|
|
19043
19560
|
throw new Error("ArgumentError: malformed ASSERT statement.");
|
|
19044
19561
|
}
|
|
19045
19562
|
const right = await evalAssertOperand(stmt.right, client, options, cacheContext, tempTables);
|
|
19046
|
-
|
|
19047
|
-
throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
|
|
19048
|
-
}
|
|
19049
|
-
return { type: "ASSERT", condition: stmt.text };
|
|
19563
|
+
return { passed: compareScalarValues(stmt.op, left, right, semantics), actual: left };
|
|
19050
19564
|
}
|
|
19051
19565
|
async function evalAssertOperand(operand, client, options, cacheContext, tempTables) {
|
|
19052
19566
|
switch (operand.type) {
|
|
@@ -26380,7 +26894,16 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
|
|
|
26380
26894
|
});
|
|
26381
26895
|
const invocationCacheContext = createInvocationCacheContext(cacheContext);
|
|
26382
26896
|
try {
|
|
26383
|
-
const
|
|
26897
|
+
const header = parseScriptHeader(sql);
|
|
26898
|
+
const headerError = header.diagnostics.find((diagnostic2) => diagnostic2.severity === "error");
|
|
26899
|
+
if (header.hasDirectives && headerError) {
|
|
26900
|
+
throw new Error(`${headerError.code}: ${headerError.message} (${headerError.line}:${headerError.column})`);
|
|
26901
|
+
}
|
|
26902
|
+
const statements = parseSqlBatch(
|
|
26903
|
+
header.hasDirectives ? sql.slice(header.headerEnd) : sql,
|
|
26904
|
+
enableImport,
|
|
26905
|
+
header.hasDirectives && header.meta.dialect === 1
|
|
26906
|
+
);
|
|
26384
26907
|
const analysis = analyzeBatch(statements);
|
|
26385
26908
|
const normalizedInjectedVariables = validateDeclaredBatchVariables(statements, injectedVariables);
|
|
26386
26909
|
const relativeDateVariables = prepareRelativeDateVariables(statements, normalizedInjectedVariables);
|
|
@@ -26575,8 +27098,8 @@ function buildBatchStatementPlan(stmt, info, capabilities, orderPlans, plainGrou
|
|
|
26575
27098
|
}
|
|
26576
27099
|
if (stmt.type === "ASSERT") {
|
|
26577
27100
|
const lines = [
|
|
26578
|
-
`ASSERT ${stmt.text}`,
|
|
26579
|
-
" check: \u5B9F\u884C\u6642\u306B\u6761\u4EF6\u8A55\u4FA1\uFF08\u4E0D\u6210\u7ACB\u306F AssertError \u3067\u30D0\u30C3\u30C1\u505C\u6B62\u3001\u4EE5\u964D\u306E\u6587\u306F skipped\uFF09"
|
|
27101
|
+
`ASSERT${stmt.warn === true ? " WARN" : ""} ${stmt.text}${stmt.message !== void 0 ? `, '${stmt.message.replace(/'/g, "''")}'` : ""}`,
|
|
27102
|
+
stmt.warn === true ? " check: \u5B9F\u884C\u6642\u306B\u6761\u4EF6\u8A55\u4FA1\uFF08\u4E0D\u6210\u7ACB\u306F\u8B66\u544A\u3068\u3057\u3066\u8A18\u9332\u3057\u3001\u5F8C\u7D9A\u6587\u3092\u7D9A\u884C\uFF09" : " check: \u5B9F\u884C\u6642\u306B\u6761\u4EF6\u8A55\u4FA1\uFF08\u4E0D\u6210\u7ACB\u306F AssertError \u3067\u30D0\u30C3\u30C1\u505C\u6B62\u3001\u4EE5\u964D\u306E\u6587\u306F skipped\uFF09"
|
|
26580
27103
|
];
|
|
26581
27104
|
const subqueries = [stmt.left, stmt.right, stmt.low, stmt.high].filter(
|
|
26582
27105
|
(o) => o !== null && o.type === "SCALAR_SUBQUERY"
|
|
@@ -26598,6 +27121,31 @@ function buildBatchStatementPlan(stmt, info, capabilities, orderPlans, plainGrou
|
|
|
26598
27121
|
});
|
|
26599
27122
|
return lines;
|
|
26600
27123
|
}
|
|
27124
|
+
if (stmt.type === "EXIT") {
|
|
27125
|
+
const lines = [
|
|
27126
|
+
`EXIT SUCCESS IF ${stmt.text}, '${stmt.message.replace(/'/g, "''")}'`,
|
|
27127
|
+
" check: \u5B9F\u884C\u6642\u306B\u6761\u4EF6\u8A55\u4FA1\uFF08\u6210\u7ACB\u6642\u306F\u6B63\u5E38\u7D42\u4E86\u3057\u3001\u4EE5\u964D\u306E\u6587\u306F skippedReason: exit\uFF09"
|
|
27128
|
+
];
|
|
27129
|
+
const subqueries = [stmt.left, stmt.right, stmt.low, stmt.high].filter(
|
|
27130
|
+
(o) => o !== null && o.type === "SCALAR_SUBQUERY"
|
|
27131
|
+
);
|
|
27132
|
+
subqueries.forEach((sq, i) => {
|
|
27133
|
+
lines.push(subqueries.length > 1 ? ` subquery[${i + 1}]:` : " subquery:");
|
|
27134
|
+
const subInfo = hasTempTableRef(sq.query) ? info : { ...info, tempTablesReferenced: [] };
|
|
27135
|
+
lines.push(...buildPlanForBatchQuery(
|
|
27136
|
+
sq.query,
|
|
27137
|
+
subInfo,
|
|
27138
|
+
capabilities,
|
|
27139
|
+
orderPlans,
|
|
27140
|
+
plainGroupByPlans,
|
|
27141
|
+
collector,
|
|
27142
|
+
"main",
|
|
27143
|
+
tempSchemaLedger,
|
|
27144
|
+
explainContext
|
|
27145
|
+
).map((line) => ` ${line}`));
|
|
27146
|
+
});
|
|
27147
|
+
return lines;
|
|
27148
|
+
}
|
|
26601
27149
|
if (stmt.type === "UPDATE" && (stmt.applyBlocks?.length ?? 0) > 0) {
|
|
26602
27150
|
return buildExplainPlan(
|
|
26603
27151
|
stmt,
|
|
@@ -28085,6 +28633,9 @@ function toMutationSummary(result) {
|
|
|
28085
28633
|
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
28086
28634
|
};
|
|
28087
28635
|
}
|
|
28636
|
+
if (result.type === "EXIT") {
|
|
28637
|
+
return { condition: result.condition, exited: result.exited, message: result.message };
|
|
28638
|
+
}
|
|
28088
28639
|
return { reorderedParentCount: result.reorderedParentCount };
|
|
28089
28640
|
}
|
|
28090
28641
|
function buildBatchEnvelope(batch, options = {}) {
|
|
@@ -28142,6 +28693,10 @@ function buildBatchEnvelope(batch, options = {}) {
|
|
|
28142
28693
|
...s.result.deletedRows ? { deletedRows: s.result.deletedRows } : {},
|
|
28143
28694
|
...s.result.diagnostic ? { diagnostic: s.result.diagnostic } : {}
|
|
28144
28695
|
});
|
|
28696
|
+
} else if (s.status === "success" && s.result?.type === "ASSERT") {
|
|
28697
|
+
entry.condition = s.result.condition;
|
|
28698
|
+
if (s.result.passed !== void 0) entry.passed = s.result.passed;
|
|
28699
|
+
if (s.result.warning !== void 0) entry.warning = s.result.warning;
|
|
28145
28700
|
} else if (s.status === "success" && s.result && s.result.type !== "SELECT" && s.result.type !== "ASSERT") {
|
|
28146
28701
|
Object.assign(entry, toMutationSummary(s.result));
|
|
28147
28702
|
}
|