@rex0220/kintone-sql-tools 3.68.0 → 3.69.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 CHANGED
@@ -259,6 +259,44 @@ Options:
259
259
  6. Windows で `ksql --help` 実行時にエディタが開いてしまう
260
260
  - `.js` 関連付けの影響の可能性があります。`ksql.cmd --help` または `node dist-cli/ksql.js --help` で確認してください。
261
261
 
262
+ ## 公式 API(プログラムから使う)
263
+
264
+ npm パッケージは 2 つのサブパスを **semver 対象の公開 API** として提供します。
265
+ ここに載る export のシグネチャ・挙動の互換性は semver(破壊的変更=メジャー、追加=マイナー)で管理します。
266
+
267
+ | サブパス | 用途 | 主な export |
268
+ |---|---|---|
269
+ | `@rex0220/kintone-sql-tools/engine` | **read-only** のクエリ実行(ダッシュボード等)。書込 API は構造的に遮断 | `runQuery` / `runBatch` / `explainQuery` / `createReadonlyKintoneClient` / `KsqlEngineError` / `version` |
270
+ | `@rex0220/kintone-sql-tools/flow` | **Flow dialect 1**(→ [言語リファレンス §27](docs/ksql_language_reference.md))のスクリプト解析・検証・**文単位実行**(バッチランナー向け・書込可能) | `parseScript` / `validateScript` / `explainScript` / `createExecutionContext` / `executeStatement` / `disposeExecutionContext` / `createKintoneClient` / `version` |
271
+
272
+ `/flow` の典型的な使い方(1 文ずつ実行して結果で継続判断する):
273
+
274
+ ```ts
275
+ import { parseScript, createExecutionContext, executeStatement, disposeExecutionContext, createKintoneClient } from "@rex0220/kintone-sql-tools/flow";
276
+
277
+ const client = createKintoneClient({ baseUrl, auth: { type: "apiToken", apiToken } });
278
+ const { statements, meta, diagnostics } = parseScript(source, { apps: { 受注: 100 } });
279
+ const ctx = createExecutionContext({ client, script: source, apps: { 受注: 100, 顧客マスタ: 200 }, asOf: new Date("2026-08-01T00:00:00+09:00"), timezone: "Asia/Tokyo" });
280
+ try {
281
+ for (const stmt of statements) {
282
+ const result = await executeStatement(stmt, ctx);
283
+ // result で ASSERT 違反 / EXIT 成立 / skipped を判別して継続を判断する
284
+ }
285
+ } finally {
286
+ await disposeExecutionContext(ctx);
287
+ }
288
+ ```
289
+
290
+ ### エンジンバージョン × dialect 対応表
291
+
292
+ | エンジン | dialect 0(既定・宣言なし) | dialect 1(`-- @ksql dialect: 1`) |
293
+ |---|---|---|
294
+ | 〜 v3.67.0 | ✅ | —(未実装) |
295
+ | v3.68.0 | ✅ | 解析のみ(エンジン内部 API。実行できる出荷面なし・実験的) |
296
+ | v3.69.0 〜 | ✅ | ✅ CLI / MCP / プラグイン / `/flow` で実行可 |
297
+
298
+ dialect は後方互換で管理します: dialect 0 のスクリプトはどのエンジン版でも挙動不変・破壊的変更は dialect 番号の繰り上げでのみ導入します。変更履歴は [CHANGELOG.md](CHANGELOG.md) を参照してください。
299
+
262
300
  ## 機密情報の取り扱い
263
301
 
264
302
  - token / password は直書きせず、環境変数または `env:` 参照を推奨します。
package/dist-cli/ksql.js CHANGED
@@ -677,6 +677,75 @@ function resolveGroupingSpec(stmt, resolve2) {
677
677
  };
678
678
  }
679
679
 
680
+ // src/core/asOfClock.ts
681
+ var AS_OF_FUNCTION_NAMES = [
682
+ "NOW",
683
+ "TODAY",
684
+ "MONTH_START",
685
+ "NEXT_MONTH_START"
686
+ ];
687
+ var AS_OF_VARIABLE_PREFIX = "\0as-of:";
688
+ function asOfVariableName(name) {
689
+ return `${AS_OF_VARIABLE_PREFIX}${name}`;
690
+ }
691
+ function asOfFunctionNameFromVariable(name) {
692
+ if (!name.startsWith(AS_OF_VARIABLE_PREFIX)) return null;
693
+ const candidate = name.slice(AS_OF_VARIABLE_PREFIX.length);
694
+ return isAsOfFunctionName(candidate) ? candidate : null;
695
+ }
696
+ function isAsOfFunctionName(name) {
697
+ return AS_OF_FUNCTION_NAMES.includes(name);
698
+ }
699
+ function createAsOfClock(asOf = /* @__PURE__ */ new Date(), timezone) {
700
+ if (!(asOf instanceof Date) || !Number.isFinite(asOf.getTime())) {
701
+ throw new Error("ArgumentError: asOf must be a valid Date.");
702
+ }
703
+ let formatter;
704
+ try {
705
+ formatter = new Intl.DateTimeFormat("en-CA", {
706
+ ...timezone === void 0 ? {} : { timeZone: timezone },
707
+ year: "numeric",
708
+ month: "2-digit",
709
+ day: "2-digit"
710
+ });
711
+ formatter.format(asOf);
712
+ } catch {
713
+ throw new Error(`ArgumentError: invalid IANA timezone: ${timezone ?? ""}.`);
714
+ }
715
+ const parts = formatter.formatToParts(asOf);
716
+ const year = partNumber(parts, "year");
717
+ const month = partNumber(parts, "month");
718
+ const day = partNumber(parts, "day");
719
+ const today = `${pad4(year)}-${pad2(month)}-${pad2(day)}`;
720
+ const monthStart = `${pad4(year)}-${pad2(month)}-01`;
721
+ const nextYear = month === 12 ? year + 1 : year;
722
+ const nextMonth = month === 12 ? 1 : month + 1;
723
+ return {
724
+ asOf: new Date(asOf.getTime()),
725
+ ...timezone === void 0 ? {} : { timezone },
726
+ values: {
727
+ NOW: asOf.toISOString(),
728
+ TODAY: today,
729
+ MONTH_START: monthStart,
730
+ NEXT_MONTH_START: `${pad4(nextYear)}-${pad2(nextMonth)}-01`
731
+ }
732
+ };
733
+ }
734
+ function partNumber(parts, type) {
735
+ const value = parts.find((part) => part.type === type)?.value;
736
+ const parsed = Number(value);
737
+ if (!Number.isInteger(parsed)) {
738
+ throw new Error(`InternalError: Intl.DateTimeFormat did not return ${type}.`);
739
+ }
740
+ return parsed;
741
+ }
742
+ function pad2(value) {
743
+ return String(value).padStart(2, "0");
744
+ }
745
+ function pad4(value) {
746
+ return String(value).padStart(4, "0");
747
+ }
748
+
680
749
  // src/core/aggregateExpression.ts
681
750
  function quote(value) {
682
751
  return `'${value.replace(/'/g, "''")}'`;
@@ -1095,6 +1164,19 @@ var Parser = class {
1095
1164
  this.activeCteDefinition = null;
1096
1165
  this.provisionalRecursiveCte = null;
1097
1166
  this.allowSelectArithVariable = false;
1167
+ if (capabilities.dialect1) {
1168
+ for (let index = 0; index + 1 < tokens.length; index++) {
1169
+ const token = tokens[index];
1170
+ if (token.kind !== "VARIABLE" /* VARIABLE */ || tokens[index + 1].kind !== "(" /* LPAREN */) continue;
1171
+ const name = token.value.slice(1).toUpperCase();
1172
+ if (!isAsOfFunctionName(name)) {
1173
+ throw new ParseError(
1174
+ `\u4F7F\u7528\u53EF\u80FD\u306A as-of \u95A2\u6570\u306F ${AS_OF_FUNCTION_NAMES.map((item) => `@${item}`).join("/")} \u3067\u3059`,
1175
+ token
1176
+ );
1177
+ }
1178
+ }
1179
+ }
1098
1180
  }
1099
1181
  // ----------------------------------------------------------
1100
1182
  // 公開 API
@@ -1264,6 +1346,10 @@ var Parser = class {
1264
1346
  parseScalarExpr(context, allowScalarSubquery) {
1265
1347
  const tok = this.peek();
1266
1348
  if (tok.kind === "VARIABLE" /* VARIABLE */) {
1349
+ if (this.peekAt(1).kind === "(" /* LPAREN */ && this.capabilities.dialect1) {
1350
+ this.advance();
1351
+ return this.finishVariableReference(tok);
1352
+ }
1267
1353
  throw new ParseError(`${context} \u306E\u53F3\u8FBA\u3067\u306F\u4ED6\u306E\u5909\u6570\u3092\u53C2\u7167\u3067\u304D\u307E\u305B\u3093`, tok);
1268
1354
  }
1269
1355
  if (tok.kind === "NULL" /* NULL */) {
@@ -1803,12 +1889,28 @@ var Parser = class {
1803
1889
  throw new ParseError("\u3053\u306E\u69CB\u6587\u306B\u306F -- @ksql dialect: 1 \u306E\u5BA3\u8A00\u304C\u5FC5\u8981\u3067\u3059", tok);
1804
1890
  }
1805
1891
  }
1892
+ /** VARIABLE + `()` is the dialect-1 as-of call syntax; a bare VARIABLE stays unchanged. */
1893
+ finishVariableReference(tok) {
1894
+ const ordinary = { type: "VARIABLE", name: tok.value.slice(1).toLowerCase() };
1895
+ if (this.peek().kind !== "(" /* LPAREN */) return ordinary;
1896
+ if (!this.capabilities.dialect1) return ordinary;
1897
+ const name = tok.value.slice(1).toUpperCase();
1898
+ if (!isAsOfFunctionName(name)) {
1899
+ throw new ParseError(
1900
+ `\u4F7F\u7528\u53EF\u80FD\u306A as-of \u95A2\u6570\u306F ${AS_OF_FUNCTION_NAMES.map((item) => `@${item}`).join("/")} \u3067\u3059`,
1901
+ tok
1902
+ );
1903
+ }
1904
+ this.advance();
1905
+ this.expect(")" /* RPAREN */, `@${name} \u306F\u5F15\u6570\u306A\u3057\u306E () \u3067\u547C\u3073\u51FA\u3057\u3066\u304F\u3060\u3055\u3044`);
1906
+ return { type: "VARIABLE", name: asOfVariableName(name) };
1907
+ }
1806
1908
  /** ASSERT のオペランド: 文字列 / スカラーサブクエリ / 数値算術式 */
1807
1909
  parseAssertOperand() {
1808
1910
  const tok = this.peek();
1809
1911
  if (tok.kind === "VARIABLE" /* VARIABLE */) {
1810
1912
  this.advance();
1811
- return { type: "VARIABLE", name: tok.value.slice(1).toLowerCase() };
1913
+ return this.finishVariableReference(tok);
1812
1914
  }
1813
1915
  if (tok.kind === "STRING" /* STRING */) {
1814
1916
  this.advance();
@@ -2182,7 +2284,7 @@ var Parser = class {
2182
2284
  }
2183
2285
  if (tok.kind === "VARIABLE" /* VARIABLE */) {
2184
2286
  this.advance();
2185
- args.push({ type: "VARIABLE", name: tok.value.slice(1).toLowerCase() });
2287
+ args.push(this.finishVariableReference(tok));
2186
2288
  continue;
2187
2289
  }
2188
2290
  let sign = "";
@@ -2267,15 +2369,21 @@ var Parser = class {
2267
2369
  return this.withAliasDisplay({ type: "SCALAR_VALUE_COL", expr, alias: parsedAlias2?.alias ?? null }, parsedAlias2);
2268
2370
  }
2269
2371
  if (this.peek().kind === "VARIABLE" /* VARIABLE */) {
2270
- const variable = this.advance();
2271
- if (!this.consume("AS" /* AS */)) {
2272
- throw new ParseError("SELECT \u5217\u306E\u30D0\u30C3\u30C1\u5909\u6570\u306B\u306F AS alias \u304C\u5FC5\u8981\u3067\u3059", this.peek());
2372
+ const variable = this.finishVariableReference(this.advance());
2373
+ const asOfFunction = asOfFunctionNameFromVariable(variable.name);
2374
+ let parsedAlias2 = null;
2375
+ if (asOfFunction === null) {
2376
+ if (!this.consume("AS" /* AS */)) {
2377
+ throw new ParseError("SELECT \u5217\u306E\u30D0\u30C3\u30C1\u5909\u6570\u306B\u306F AS alias \u304C\u5FC5\u8981\u3067\u3059", this.peek());
2378
+ }
2379
+ parsedAlias2 = this.parseAliasName();
2380
+ } else if (this.consume("AS" /* AS */)) {
2381
+ parsedAlias2 = this.parseAliasName();
2273
2382
  }
2274
- const parsedAlias2 = this.parseAliasName();
2275
2383
  return this.withAliasDisplay({
2276
2384
  type: "VARIABLE_COL",
2277
- name: variable.value.slice(1).toLowerCase(),
2278
- alias: parsedAlias2.alias
2385
+ name: variable.name,
2386
+ alias: parsedAlias2?.alias ?? null
2279
2387
  }, parsedAlias2);
2280
2388
  }
2281
2389
  const windowFunc = this.tryWindowFunc();
@@ -2611,7 +2719,7 @@ var Parser = class {
2611
2719
  }
2612
2720
  if (this.peek().kind === "VARIABLE" /* VARIABLE */) {
2613
2721
  const tok = this.advance();
2614
- return { type: "VARIABLE", name: tok.value.slice(1).toLowerCase() };
2722
+ return this.finishVariableReference(tok);
2615
2723
  }
2616
2724
  const aggFunc = this.tryAggregateFunc();
2617
2725
  if (aggFunc !== null) {
@@ -2771,7 +2879,7 @@ var Parser = class {
2771
2879
  }
2772
2880
  if (tok.kind === "VARIABLE" /* VARIABLE */) {
2773
2881
  this.advance();
2774
- return { type: "VARIABLE", name: tok.value.slice(1).toLowerCase() };
2882
+ return this.finishVariableReference(tok);
2775
2883
  }
2776
2884
  if (tok.kind === "CASE" /* CASE */) {
2777
2885
  if (!allowCase) throw new ParseError("\u3053\u306E\u30B9\u30AB\u30E9\u30FC\u5024\u5F0F\u3067\u306F CASE \u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
@@ -2871,7 +2979,7 @@ var Parser = class {
2871
2979
  }
2872
2980
  if (this.allowSelectArithVariable && tok.kind === "VARIABLE" /* VARIABLE */) {
2873
2981
  this.advance();
2874
- return { type: "VARIABLE", name: tok.value.slice(1).toLowerCase() };
2982
+ return this.finishVariableReference(tok);
2875
2983
  }
2876
2984
  if (tok.kind === "IDENT" /* IDENT */ || tok.kind === "BIDENT" /* BIDENT */) {
2877
2985
  this.advance();
@@ -3479,7 +3587,7 @@ var Parser = class {
3479
3587
  }
3480
3588
  if (tok.kind === "VARIABLE" /* VARIABLE */) {
3481
3589
  this.advance();
3482
- return { type: "VARIABLE", name: tok.value.slice(1).toLowerCase() };
3590
+ return this.finishVariableReference(tok);
3483
3591
  }
3484
3592
  throw new ParseError(
3485
3593
  "KLIKE / NOT KLIKE \u306E\u53F3\u8FBA\u306B\u306F\u6587\u5B57\u5217\u30EA\u30C6\u30E9\u30EB\u307E\u305F\u306F\u30D0\u30C3\u30C1\u5909\u6570\u304C\u5FC5\u8981\u3067\u3059",
@@ -3672,7 +3780,7 @@ var Parser = class {
3672
3780
  const tok = this.peek();
3673
3781
  if (tok.kind === "VARIABLE" /* VARIABLE */) {
3674
3782
  this.advance();
3675
- return { type: "VARIABLE", name: tok.value.slice(1).toLowerCase() };
3783
+ return this.finishVariableReference(tok);
3676
3784
  }
3677
3785
  if (tok.kind === "STRING" /* STRING */) {
3678
3786
  this.advance();
@@ -3728,6 +3836,9 @@ var Parser = class {
3728
3836
  "VARIABLE" /* VARIABLE */,
3729
3837
  "IN / NOT IN \u306E\u5F8C\u306B\u306F (\u5024\u30EA\u30B9\u30C8\u307E\u305F\u306F SELECT) \u304B\u914D\u5217\u5909\u6570\u304C\u5FC5\u8981\u3067\u3059"
3730
3838
  );
3839
+ if (this.peek().kind === "(" /* LPAREN */ && this.capabilities.dialect1) {
3840
+ return { type: "IN_LIST", values: [this.finishVariableReference(variable)] };
3841
+ }
3731
3842
  return { type: "VARIABLE_IN_LIST", name: variable.value.slice(1).toLowerCase() };
3732
3843
  }
3733
3844
  parseInListOrSubquery() {
@@ -3759,7 +3870,7 @@ var Parser = class {
3759
3870
  const sign = tok.kind === "-" /* MINUS */ ? "-" : "+";
3760
3871
  values.push(makeNumberLiteral(`${sign}${number.value}`));
3761
3872
  } else if (tok.kind === "VARIABLE" /* VARIABLE */) {
3762
- values.push({ type: "VARIABLE", name: tok.value.slice(1).toLowerCase() });
3873
+ values.push(this.finishVariableReference(tok));
3763
3874
  } else if (tok.kind === "LOGINUSER" /* LOGINUSER */) {
3764
3875
  if (values.length > 0) {
3765
3876
  throw new ParseError(mixedLoginUserMessage, tok);
@@ -9282,6 +9393,7 @@ function collectVariableRefs(node, refs, inWhere = false) {
9282
9393
  const obj = node;
9283
9394
  const type = obj["type"];
9284
9395
  if ((type === "VARIABLE" || type === "VARIABLE_COL" || type === "VARIABLE_IN_LIST") && typeof obj["name"] === "string") {
9396
+ if (asOfFunctionNameFromVariable(obj["name"]) !== null) return;
9285
9397
  refs.push({
9286
9398
  name: obj["name"],
9287
9399
  kind: type === "VARIABLE" ? "scalar" : type === "VARIABLE_COL" ? "select-column" : "array-in-list",
@@ -16465,7 +16577,14 @@ var DiagnosticCodes = {
16465
16577
  HEADER_INVALID_DIALECT: "KSQL1006",
16466
16578
  LOGICAL_APP_UNRESOLVED: "KSQL1101",
16467
16579
  LEX_ERROR: "KSQL1201",
16468
- PARSE_ERROR: "KSQL1202"
16580
+ PARSE_ERROR: "KSQL1202",
16581
+ DIALECT1_REQUIRED: "KSQL1203",
16582
+ UPDATE_KEY_COMPOSITE: "KSQL1301",
16583
+ UPDATE_KEY_FIELD_TYPE: "KSQL1302",
16584
+ UPDATE_KEY_NOT_UNIQUE: "KSQL1303",
16585
+ SUBTABLE_DML_FORBIDDEN: "KSQL1304",
16586
+ BARE_INSERT_NOT_IDEMPOTENT: "KSQL1305",
16587
+ SERVER_TIME_FUNCTION_NOT_AS_OF: "KSQL1306"
16469
16588
  };
16470
16589
  function sourceLocationAt(source, offset) {
16471
16590
  const target = Math.max(0, Math.min(offset, source.length));
@@ -16596,6 +16715,86 @@ function findLineEnd(source, start) {
16596
16715
  return { contentEnd, next: i };
16597
16716
  }
16598
16717
 
16718
+ // src/core/sql.ts
16719
+ function parseSqlStatements(sql, capabilities = {}) {
16720
+ const tokens = new Lexer(sql).tokenize();
16721
+ const statements = new Parser(tokens, capabilities).parseStatements();
16722
+ statements.forEach(validateStatementStatic);
16723
+ return statements;
16724
+ }
16725
+ function parseSqlStatementsForScript(sql, capabilities = {}) {
16726
+ const header = parseScriptHeader(sql);
16727
+ const headerError = header.diagnostics.find((diagnostic2) => diagnostic2.severity === "error");
16728
+ if (header.hasDirectives && headerError) {
16729
+ throw new Error(`${headerError.code}: ${headerError.message} (${headerError.line}:${headerError.column})`);
16730
+ }
16731
+ const scriptSql = header.hasDirectives ? sql.slice(header.headerEnd) : sql;
16732
+ const scriptCapabilities = header.hasDirectives ? { ...capabilities, dialect1: header.meta.dialect === 1 } : capabilities;
16733
+ return {
16734
+ statements: parseSqlStatements(scriptSql, scriptCapabilities),
16735
+ meta: header.meta
16736
+ };
16737
+ }
16738
+
16739
+ // src/core/dialect1Validation.ts
16740
+ var DIALECT1_SERVER_TIME_FUNCTION_WARNING = "bare \u306E\u6642\u523B\u4F9D\u5B58\u95A2\u6570\u306F kintone \u30B5\u30FC\u30D0\u30FC\u8A55\u4FA1\u306E\u305F\u3081 as-of \u306E\u5BFE\u8C61\u5916\u3067\u3059\u3002\u518D\u73FE\u6027\u304C\u5FC5\u8981\u306A\u3089 @ \u4ED8\u304D\u95A2\u6570\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002";
16741
+ function validateDialect1UpdateKey(statement, fieldInfos) {
16742
+ if (fieldInfos === void 0) {
16743
+ return statement.keyFields.length === 1 ? [] : [{
16744
+ code: DiagnosticCodes.UPDATE_KEY_COMPOSITE,
16745
+ severity: "error",
16746
+ message: "dialect 1 \u306E UPSERT / MERGE \u306E\u30AD\u30FC\u306F\u5358\u4E00\u30D5\u30A3\u30FC\u30EB\u30C9\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002\u8907\u5408\u30AD\u30FC\u306E\u4EE3\u308F\u308A\u306B\u3001\u9023\u7D50\u30AD\u30FC\u30D5\u30A3\u30FC\u30EB\u30C9\uFF08\u4F8B: \u9867\u5BA2\u30B3\u30FC\u30C9_\u5E74\u6708\uFF09\u3092\u30A2\u30D7\u30EA\u5074\u306B\u7528\u610F\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
16747
+ }];
16748
+ }
16749
+ if (statement.keyFields.length !== 1) return [];
16750
+ const key = statement.keyFields[0];
16751
+ const field = fieldInfos.find((candidate) => candidate.code === key);
16752
+ const issues = [];
16753
+ if (field === void 0 || field.fieldType !== "SINGLE_LINE_TEXT" && field.fieldType !== "NUMBER") {
16754
+ issues.push({
16755
+ code: DiagnosticCodes.UPDATE_KEY_FIELD_TYPE,
16756
+ severity: "error",
16757
+ message: field === void 0 ? `UPSERT / MERGE \u306E\u30AD\u30FC\u300C${key}\u300D\u304C APP${statement.appId} \u306E\u30D5\u30A9\u30FC\u30E0\u306B\u5B58\u5728\u3057\u307E\u305B\u3093\u3002\u91CD\u8907\u7981\u6B62\u3092\u8A2D\u5B9A\u3057\u305F\u6587\u5B57\u5217\uFF081\u884C\uFF09\u307E\u305F\u306F\u6570\u5024\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u30AD\u30FC\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002` : `UPSERT / MERGE \u306E\u30AD\u30FC\u300C${key}\u300D\u306E\u578B ${field.fieldType} \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002\u91CD\u8907\u7981\u6B62\u3092\u8A2D\u5B9A\u3057\u305F\u6587\u5B57\u5217\uFF081\u884C\uFF09\u307E\u305F\u306F\u6570\u5024\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u30AD\u30FC\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
16758
+ });
16759
+ }
16760
+ if (field?.isUnique === false) {
16761
+ issues.push({
16762
+ code: DiagnosticCodes.UPDATE_KEY_NOT_UNIQUE,
16763
+ severity: "error",
16764
+ message: `UPSERT / MERGE \u306E\u30AD\u30FC\u300C${key}\u300D\u306F\u91CD\u8907\u7981\u6B62\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002\u30A2\u30D7\u30EA\u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u8A2D\u5B9A\u3067\u300C\u5024\u306E\u91CD\u8907\u3092\u7981\u6B62\u3059\u308B\u300D\u3092\u6709\u52B9\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
16765
+ });
16766
+ } else if (field !== void 0 && field.isUnique === void 0) {
16767
+ issues.push({
16768
+ code: DiagnosticCodes.UPDATE_KEY_NOT_UNIQUE,
16769
+ severity: "warning",
16770
+ message: `UPSERT / MERGE \u306E\u30AD\u30FC\u300C${key}\u300D\u306E\u91CD\u8907\u7981\u6B62\u8A2D\u5B9A\u3092 schema resolver \u304B\u3089\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002isUnique \u3092\u8FD4\u3059 resolver \u3092\u4F7F\u7528\u3057\u3001\u30A2\u30D7\u30EA\u5074\u3067\u300C\u5024\u306E\u91CD\u8907\u3092\u7981\u6B62\u3059\u308B\u300D\u304C\u6709\u52B9\u304B\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
16771
+ });
16772
+ }
16773
+ return issues;
16774
+ }
16775
+ function statementHasBareServerTimeFunctionInWhere(statement) {
16776
+ let found = false;
16777
+ const visit = (node) => {
16778
+ if (found || node === null || typeof node !== "object") return;
16779
+ if (Array.isArray(node)) {
16780
+ node.forEach(visit);
16781
+ return;
16782
+ }
16783
+ const value = node;
16784
+ const where = value["where"];
16785
+ if (where !== null && typeof where === "object") {
16786
+ const names = serverOnlyFunctionOccurrencesInWhere(where);
16787
+ if (names.some((name) => name === "TODAY" || name === "NOW" || isRelativeDateFunctionName(name))) {
16788
+ found = true;
16789
+ return;
16790
+ }
16791
+ }
16792
+ Object.values(value).forEach(visit);
16793
+ };
16794
+ visit(statement);
16795
+ return found;
16796
+ }
16797
+
16599
16798
  // src/core/dmlPrevalidation.ts
16600
16799
  function collectDmlPrevalidationSnapshotFields(fieldIndex) {
16601
16800
  return [
@@ -18885,17 +19084,10 @@ var BatchTimeoutError = class extends Error {
18885
19084
  }
18886
19085
  };
18887
19086
  async function executeBatch(sql, client, options = {}) {
19087
+ const asOfClock = createAsOfClock(options.asOf ?? /* @__PURE__ */ new Date(), options.timezone);
18888
19088
  resolveRecursiveCteLimits(options);
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
- );
19089
+ const { statements, meta } = parseSqlStatementsForScript(sql, { import: options.enableImport === true });
19090
+ const dialect1Warnings = meta.dialect === 1 && statements.some(statementHasBareServerTimeFunctionInWhere) ? [DIALECT1_SERVER_TIME_FUNCTION_WARNING] : [];
18899
19091
  const analysis = analyzeBatch(statements);
18900
19092
  statements.forEach((statement) => assertApplyExecutionScope("phase15b", statement));
18901
19093
  if (options.allowApplyMutation !== true && statements.some(
@@ -18929,6 +19121,9 @@ async function executeBatch(sql, client, options = {}) {
18929
19121
  try {
18930
19122
  const tempTables = /* @__PURE__ */ new Map();
18931
19123
  const variables = /* @__PURE__ */ new Map();
19124
+ for (const [name, value] of Object.entries(asOfClock.values)) {
19125
+ variables.set(asOfVariableName(name), { type: "string", value });
19126
+ }
18932
19127
  const results = [];
18933
19128
  const failed = /* @__PURE__ */ new Set();
18934
19129
  let aborted = null;
@@ -18985,7 +19180,8 @@ async function executeBatch(sql, client, options = {}) {
18985
19180
  tempTables,
18986
19181
  variables,
18987
19182
  relativeDateVariables,
18988
- clock: statementEvaluationContext(boundOptions)
19183
+ clock: statementEvaluationContext(boundOptions),
19184
+ dialect: meta.dialect
18989
19185
  };
18990
19186
  const outcome = await runWithDeadline(
18991
19187
  executeBatchStatement(statementContext),
@@ -18994,6 +19190,9 @@ async function executeBatch(sql, client, options = {}) {
18994
19190
  );
18995
19191
  if (outcome.result) {
18996
19192
  outcome.result = attachSearchAbortWarning(outcome.result, searchAbortCollector);
19193
+ if (dialect1Warnings.length > 0 && statementHasBareServerTimeFunctionInWhere(statements[i]) && outcome.result.type === "SELECT") {
19194
+ outcome.result = mergeSelectWarnings(outcome.result, dialect1Warnings);
19195
+ }
18997
19196
  }
18998
19197
  const { exitTriggered, ...statementOutcome } = outcome;
18999
19198
  results.push({ ...base, status: "success", ...statementOutcome });
@@ -19023,7 +19222,8 @@ async function executeBatch(sql, client, options = {}) {
19023
19222
  statementCount: statements.length,
19024
19223
  statements: results,
19025
19224
  analysis,
19026
- metrics
19225
+ metrics,
19226
+ ...dialect1Warnings.length > 0 ? { warnings: dialect1Warnings } : {}
19027
19227
  };
19028
19228
  } finally {
19029
19229
  releaseMetadataCacheScope(cacheContext);
@@ -19045,7 +19245,8 @@ async function executeBatchStatement(context) {
19045
19245
  tempTables,
19046
19246
  variables,
19047
19247
  relativeDateVariables,
19048
- clock
19248
+ clock,
19249
+ dialect
19049
19250
  } = context;
19050
19251
  if (stmt.type === "SET_VARIABLE") {
19051
19252
  const resolvedStmt2 = resolveBatchVariableReferences(stmt, variables);
@@ -19117,6 +19318,18 @@ async function executeBatchStatement(context) {
19117
19318
  assertApplyScope("phase15b", resolvedStmt);
19118
19319
  assertApplyExecutionScope("phase15b", resolvedStmt);
19119
19320
  validateStatementStatic(resolvedStmt);
19321
+ if (dialect === 1 && (resolvedStmt.type === "INSERT" || resolvedStmt.type === "UPDATE" || resolvedStmt.type === "DELETE") && resolvedStmt.subtableCode) {
19322
+ throw new Error(
19323
+ "ArgumentError: dialect 1 \u3067\u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u4EEE\u60F3\u30C6\u30FC\u30D6\u30EB\u3078\u306E DML \u306F\u3067\u304D\u307E\u305B\u3093\u3002SELECT \u306F\u53EF\u80FD\u3067\u3059\u3002\u89AA\u30A2\u30D7\u30EA\u3092\u5BFE\u8C61\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
19324
+ );
19325
+ }
19326
+ if (dialect === 1 && (resolvedStmt.type === "UPSERT" || resolvedStmt.type === "UPSERT_SELECT")) {
19327
+ const staticIssue = validateDialect1UpdateKey(resolvedStmt)[0];
19328
+ if (staticIssue) throw new Error(`ArgumentError: ${staticIssue.message}`);
19329
+ const fieldInfos = await getFieldsCached(resolvedStmt.appId, client, cacheContext);
19330
+ const schemaIssue = validateDialect1UpdateKey(resolvedStmt, fieldInfos)[0];
19331
+ if (schemaIssue) throw new Error(`ArgumentError: ${schemaIssue.message}`);
19332
+ }
19120
19333
  await assertRelativeDateExecutionPlan(resolvedStmt, client, cacheContext);
19121
19334
  if (resolvedStmt.type === "VALIDATE") {
19122
19335
  const result = await executeExistingRecordValidationCore(
@@ -19377,6 +19590,8 @@ function evaluateScalarExpr(expr, evaluationContext = {}) {
19377
19590
  }
19378
19591
  return { type: "number", value, raw: String(value) };
19379
19592
  }
19593
+ case "VARIABLE":
19594
+ throw new Error(`InternalError: unresolved variable @${expr.name} reached scalar evaluation.`);
19380
19595
  }
19381
19596
  }
19382
19597
  function resolveBatchVariableReferences(node, variables) {
@@ -19412,7 +19627,7 @@ function resolveBatchVariableReferencesInternal(node, variables, numericArithmet
19412
19627
  raw: numericArithmeticOperand === "AGG_ARITH" ? `@${obj["name"]}` : value.raw ?? String(value.value)
19413
19628
  } : { type: "STRING", value: value.value, fromVariable: true };
19414
19629
  }
19415
- if (obj["type"] === "VARIABLE_COL" && typeof obj["name"] === "string" && typeof obj["alias"] === "string") {
19630
+ if (obj["type"] === "VARIABLE_COL" && typeof obj["name"] === "string" && (typeof obj["alias"] === "string" || obj["alias"] === null)) {
19416
19631
  const value = variables.get(obj["name"]);
19417
19632
  if (value === void 0) throw new Error(`ParseError: variable @${obj["name"]} is not defined in this batch.`);
19418
19633
  if (value.type === "array") throw new Error(`ParseError: array variable @${obj["name"]} cannot be used as a SELECT column.`);
@@ -26894,16 +27109,7 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
26894
27109
  });
26895
27110
  const invocationCacheContext = createInvocationCacheContext(cacheContext);
26896
27111
  try {
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
- );
27112
+ const { statements, meta } = parseSqlStatementsForScript(sql, { import: enableImport });
26907
27113
  const analysis = analyzeBatch(statements);
26908
27114
  const normalizedInjectedVariables = validateDeclaredBatchVariables(statements, injectedVariables);
26909
27115
  const relativeDateVariables = prepareRelativeDateVariables(statements, normalizedInjectedVariables);
@@ -26984,10 +27190,16 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
26984
27190
  ), cursorMaxActive)
26985
27191
  ];
26986
27192
  const metadataPlan = explainMetadataLines(whereAnalysis);
27193
+ const dialect1Estimate = meta.dialect === 1 ? buildDialect1ApiEstimateLines(
27194
+ planStmt,
27195
+ analysis.statements[i],
27196
+ maxRecords,
27197
+ dmlMaxRows
27198
+ ) : [];
26987
27199
  plans.push({
26988
27200
  index: i,
26989
27201
  type: analysis.statements[i].statementType,
26990
- plan: statementPlan.length === 0 ? metadataPlan : [statementPlan[0], ...metadataPlan, ...statementPlan.slice(1)]
27202
+ plan: statementPlan.length === 0 ? [...metadataPlan, ...dialect1Estimate] : [statementPlan[0], ...metadataPlan, ...statementPlan.slice(1), ...dialect1Estimate]
26991
27203
  });
26992
27204
  fetchStatements.push({
26993
27205
  index: i,
@@ -27012,6 +27224,63 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
27012
27224
  releaseMetadataCacheScope(invocationCacheContext);
27013
27225
  }
27014
27226
  }
27227
+ function buildDialect1ApiEstimateLines(statement, analysis, maxRecords, dmlMaxRows) {
27228
+ const lines = [" estimated API consumption (dialect 1):"];
27229
+ const sources = collectPhysicalExplainSources(statement);
27230
+ if ((statement.type === "UPDATE" || statement.type === "DELETE") && !sources.includes(`APP${statement.appId}`)) {
27231
+ sources.unshift(`APP${statement.appId}`);
27232
+ }
27233
+ const maxReadRequests = Math.ceil(maxRecords / 500);
27234
+ for (const source of sources) {
27235
+ lines.push(
27236
+ ` read ${source}: \u4E0D\u660E\uFF08\u4E0A\u9650 maxRecords=${maxRecords} \u3068\u4EEE\u5B9A: \u6700\u5927 ${maxReadRequests} \u56DE\u3001500 \u4EF6/\u56DE\uFF09`
27237
+ );
27238
+ }
27239
+ const metadataApps = [.../* @__PURE__ */ new Set([
27240
+ ...analysis.appIds,
27241
+ ...analysis.targetAppId === null ? [] : [analysis.targetAppId]
27242
+ ])];
27243
+ lines.push(
27244
+ ` metadata: GET form fields \xD7 ${metadataApps.length} \u30A2\u30D7\u30EA\uFF08\u30AD\u30E3\u30C3\u30B7\u30E5\u6E08\u307F\u306F\u8FFD\u52A0 0 \u56DE\uFF09`
27245
+ );
27246
+ if (statement.type === "UPSERT" || statement.type === "UPSERT_SELECT") {
27247
+ if (statement.type === "UPSERT") {
27248
+ lines.push(
27249
+ ` UPSERT pre-read: ${Math.ceil(statement.values.length / UPSERT_IN_CHUNK_SIZE)} \u56DE\uFF08${statement.values.length} \u884C\u3001${UPSERT_IN_CHUNK_SIZE} \u30AD\u30FC/\u56DE\uFF09`
27250
+ );
27251
+ } else {
27252
+ lines.push(
27253
+ ` UPSERT pre-read: \u4E0D\u660E\uFF08\u4E0A\u9650 dmlMaxRows=${dmlMaxRows} \u3068\u4EEE\u5B9A: \u6700\u5927 ${Math.ceil(dmlMaxRows / UPSERT_IN_CHUNK_SIZE)} \u56DE\u3001${UPSERT_IN_CHUNK_SIZE} \u30AD\u30FC/\u56DE\uFF09`
27254
+ );
27255
+ }
27256
+ }
27257
+ const knownRows = statement.type === "INSERT" || statement.type === "UPSERT" ? statement.values.length : null;
27258
+ if (statement.type === "INSERT" || statement.type === "INSERT_SELECT" || statement.type === "UPSERT" || statement.type === "UPSERT_SELECT" || statement.type === "UPDATE" || statement.type === "DELETE") {
27259
+ lines.push(knownRows === null ? ` write: \u4E0D\u660E\uFF08\u4E0A\u9650 dmlMaxRows=${dmlMaxRows} \u3068\u4EEE\u5B9A: \u6700\u5927 ${Math.ceil(dmlMaxRows / 100)} \u56DE\u3001100 \u4EF6/HTTP \u30EA\u30AF\u30A8\u30B9\u30C8\uFF09` : ` write: ${Math.ceil(knownRows / 100)} \u56DE\uFF08${knownRows} \u884C\u3001100 \u4EF6/HTTP \u30EA\u30AF\u30A8\u30B9\u30C8\uFF09`);
27260
+ lines.push(" reference: bulkRequest \u306F\u672A\u5B9F\u88C5\u3002\u66F8\u8FBC\u30B5\u30D6\u30EA\u30AF\u30A8\u30B9\u30C8\u6570\u306F HTTP \u66F8\u8FBC\u56DE\u6570\u3068\u540C\u3058");
27261
+ }
27262
+ return lines;
27263
+ }
27264
+ function collectPhysicalExplainSources(statement) {
27265
+ const sources = [];
27266
+ const visit = (node) => {
27267
+ if (Array.isArray(node)) {
27268
+ node.forEach(visit);
27269
+ return;
27270
+ }
27271
+ if (node === null || typeof node !== "object") return;
27272
+ const value = node;
27273
+ if (typeof value["appId"] === "number" && Object.prototype.hasOwnProperty.call(value, "alias") && Object.prototype.hasOwnProperty.call(value, "cteName") && value["cteName"] === null && value["appId"] > 0) {
27274
+ const app = `APP${value["appId"]}`;
27275
+ const subtable = typeof value["subtableCode"] === "string" ? `$${value["subtableCode"]}` : "";
27276
+ const alias = typeof value["alias"] === "string" && value["alias"] !== app ? ` AS ${value["alias"]}` : "";
27277
+ sources.push(`${app}${subtable}${alias}`);
27278
+ }
27279
+ Object.values(value).forEach(visit);
27280
+ };
27281
+ visit(statement);
27282
+ return sources;
27283
+ }
27015
27284
  function buildBatchStatementPlan(stmt, info, capabilities, orderPlans, plainGroupByPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, collector = { sources: [] }, tempSchemaLedger = /* @__PURE__ */ new Map(), createdSchema, explainContext = defaultRecursiveExplainContext()) {
27016
27285
  if (stmt.type === "CREATE_TEMP_TABLE") {
27017
27286
  return [
@@ -28491,20 +28760,6 @@ var OperationCancelledError = class extends Error {
28491
28760
  }
28492
28761
  };
28493
28762
 
28494
- // src/core/sql.ts
28495
- function parseSqlStatement(sql, capabilities = {}) {
28496
- const tokens = new Lexer(sql).tokenize();
28497
- const stmt = new Parser(tokens, capabilities).parse();
28498
- validateStatementStatic(stmt);
28499
- return stmt;
28500
- }
28501
- function parseSqlStatements(sql, capabilities = {}) {
28502
- const tokens = new Lexer(sql).tokenize();
28503
- const statements = new Parser(tokens, capabilities).parseStatements();
28504
- statements.forEach(validateStatementStatic);
28505
- return statements;
28506
- }
28507
-
28508
28763
  // src/core/displayFormat.ts
28509
28764
  var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
28510
28765
  var DATETIME_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
@@ -30217,7 +30472,7 @@ function toParseInput(sql) {
30217
30472
  }
30218
30473
  function tryParseStatements(sql) {
30219
30474
  try {
30220
- const stmts = new Parser(new Lexer(toParseInput(sql)).tokenize()).parseStatements();
30475
+ const { statements: stmts } = parseSqlStatementsForScript(toParseInput(sql));
30221
30476
  return {
30222
30477
  kind: "ok",
30223
30478
  count: stmts.length,
@@ -30232,7 +30487,11 @@ function tryParseStatements(sql) {
30232
30487
  if (e instanceof ParseError) {
30233
30488
  return { kind: "fail", continuable: e.token.kind === "EOF" /* EOF */, message: e.message };
30234
30489
  }
30235
- throw e;
30490
+ return {
30491
+ kind: "fail",
30492
+ continuable: false,
30493
+ message: e instanceof Error ? e.message : String(e)
30494
+ };
30236
30495
  }
30237
30496
  }
30238
30497
 
@@ -31421,7 +31680,7 @@ async function confirmDmlInConsole(sql, opts, queue, defaultProfile = "dev", res
31421
31680
  if (!opts.allowDml || opts.yes || opts.dryRun) return true;
31422
31681
  try {
31423
31682
  const normalized = normalizeSqlAppProfiles(sql, defaultProfile, resolutionContext);
31424
- const statements = parseSqlStatements(normalized.normalizedSql);
31683
+ const { statements } = parseSqlStatementsForScript(normalized.normalizedSql);
31425
31684
  if (statements.length > 1) {
31426
31685
  const analysis = analyzeBatch(statements);
31427
31686
  if (!analysis.containsDml) return true;
@@ -31871,7 +32130,7 @@ async function run() {
31871
32130
  }
31872
32131
  const importEnabled = Object.keys(args.importCsv).length > 0 || Object.keys(args.importJson).length > 0;
31873
32132
  try {
31874
- const statements = parseSqlStatements(sql, { import: importEnabled });
32133
+ const { statements } = parseSqlStatementsForScript(sql, { import: importEnabled });
31875
32134
  parsedStatements = statements;
31876
32135
  const hasApply = (statement) => statement.type === "UPDATE" || statement.type === "INSERT" ? (statement.applyBlocks?.length ?? 0) > 0 : statement.type === "UPSERT" ? (statement.onInsertApplyBlocks?.length ?? 0) > 0 || (statement.onUpdateApplyBlocks?.length ?? 0) > 0 : false;
31877
32136
  containsApplyStatement = statements.some(hasApply);
@@ -31892,7 +32151,7 @@ async function run() {
31892
32151
  isBatchSql = true;
31893
32152
  batchContainsDml = batchAnalysis.containsDml;
31894
32153
  } else {
31895
- const stmt = parseSqlStatement(sql, { import: importEnabled });
32154
+ const stmt = statements[0];
31896
32155
  parsedStmt = stmt;
31897
32156
  stmtType = getStatementType(stmt);
31898
32157
  isDmlStatement = writesKintone(stmt);