@rex0220/kintone-sql-tools 1.13.2 → 2.1.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.
@@ -31096,6 +31096,7 @@ var Lexer = class {
31096
31096
  if (opTok) return opTok;
31097
31097
  if (isIdentStart(ch)) return this.readIdentOrKeyword(start);
31098
31098
  if (ch === "#") return this.readHashIdent(start);
31099
+ if (ch === "@") return this.readVariable(start);
31099
31100
  throw new LexError(
31100
31101
  `\u4E88\u671F\u3057\u306A\u3044\u6587\u5B57 \u300C${ch}\u300D \u3067\u3059`,
31101
31102
  this.pos,
@@ -31275,6 +31276,23 @@ var Lexer = class {
31275
31276
  }
31276
31277
  return this.makeToken("IDENT" /* IDENT */, value, start);
31277
31278
  }
31279
+ /** バッチ変数: @[A-Za-z_][A-Za-z0-9_]{0,63} */
31280
+ readVariable(start) {
31281
+ this.pos++;
31282
+ const first = this.input[this.pos] ?? "";
31283
+ if (!/[A-Za-z_]/.test(first)) {
31284
+ throw new LexError("\u300C@\u300D \u306E\u76F4\u5F8C\u306B\u306F\u82F1\u5B57\u307E\u305F\u306F _ \u3067\u59CB\u307E\u308B\u5909\u6570\u540D\u304C\u5FC5\u8981\u3067\u3059", start, this.input);
31285
+ }
31286
+ this.pos++;
31287
+ while (this.pos < this.input.length && /[A-Za-z0-9_]/.test(this.input[this.pos])) {
31288
+ this.pos++;
31289
+ }
31290
+ const nameLength = this.pos - start - 1;
31291
+ if (nameLength > 64) {
31292
+ throw new LexError("\u5909\u6570\u540D\u306F 64 \u6587\u5B57\u4EE5\u5185\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044", start, this.input);
31293
+ }
31294
+ return this.makeToken("VARIABLE" /* VARIABLE */, this.input.slice(start, this.pos), start);
31295
+ }
31278
31296
  // ----------------------------------------------------------
31279
31297
  // 空白・コメントをスキップ
31280
31298
  // ----------------------------------------------------------
@@ -31486,6 +31504,8 @@ var Parser = class {
31486
31504
  return this.parseDescribe();
31487
31505
  case "EXPLAIN" /* EXPLAIN */:
31488
31506
  return this.parseExplain();
31507
+ case "SET" /* SET */:
31508
+ return this.parseSetVariable();
31489
31509
  case "ASSERT" /* ASSERT */:
31490
31510
  return this.parseAssert();
31491
31511
  case "IDENT" /* IDENT */: {
@@ -31498,11 +31518,65 @@ var Parser = class {
31498
31518
  break;
31499
31519
  }
31500
31520
  throw new ParseError(
31501
- "SELECT / INSERT / UPDATE / DELETE / REORDER / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE / ASSERT \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
31521
+ "SELECT / INSERT / UPDATE / DELETE / REORDER / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE / SET / ASSERT \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
31502
31522
  tok
31503
31523
  );
31504
31524
  }
31505
31525
  // ----------------------------------------------------------
31526
+ // SET @name = <ScalarExpr>
31527
+ // ----------------------------------------------------------
31528
+ parseSetVariable() {
31529
+ this.expect("SET" /* SET */);
31530
+ const variable = this.expect("VARIABLE" /* VARIABLE */, "SET \u306E\u5F8C\u306B\u306F\u5909\u6570\u540D\uFF08\u4F8B: @name\uFF09\u304C\u5FC5\u8981\u3067\u3059");
31531
+ this.expect("=" /* EQ */);
31532
+ const expr = this.parseScalarExpr();
31533
+ return { type: "SET_VARIABLE", name: variable.value.slice(1).toLowerCase(), expr };
31534
+ }
31535
+ /** SET RHS 専用。既存式パーサーで構文を読み、フィールド参照を明示的に拒否する。 */
31536
+ parseScalarExpr() {
31537
+ const tok = this.peek();
31538
+ if (tok.kind === "VARIABLE" /* VARIABLE */) {
31539
+ throw new ParseError("SET \u306E\u53F3\u8FBA\u3067\u306F\u4ED6\u306E\u5909\u6570\u3092\u53C2\u7167\u3067\u304D\u307E\u305B\u3093\uFF08Phase 1a\uFF09", tok);
31540
+ }
31541
+ if (tok.kind === "NULL" /* NULL */) {
31542
+ throw new ParseError("SET \u306E\u53F3\u8FBA\u3067 NULL \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\uFF08Phase 1a\uFF09", tok);
31543
+ }
31544
+ if (tok.kind === "(" /* LPAREN */ && this.peekAt(1).kind === "SELECT" /* SELECT */) {
31545
+ throw new ParseError("SET \u306E\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u4EE3\u5165\u306F Phase 1b \u3067\u5BFE\u5FDC\u4E88\u5B9A\u3067\u3059", tok);
31546
+ }
31547
+ if (tok.kind === "STRING" /* STRING */) {
31548
+ this.advance();
31549
+ return { type: "STRING", value: tok.value };
31550
+ }
31551
+ if (tok.kind === "LOGINUSER" /* LOGINUSER */) {
31552
+ throw new ParseError(
31553
+ "SET \u306E\u53F3\u8FBA\u3067 LOGINUSER() \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\uFF08\u5B9F\u884C\u74B0\u5883\u5171\u901A\u306E\u30ED\u30B0\u30A4\u30F3\u30E6\u30FC\u30B6\u30FC\u89E3\u6C7A\u306F\u672A\u5BFE\u5FDC\u3067\u3059\uFF09",
31554
+ tok
31555
+ );
31556
+ }
31557
+ if (tok.kind === "TODAY" /* TODAY */ || tok.kind === "NOW" /* NOW */) {
31558
+ return this.parseSqlValue();
31559
+ }
31560
+ const expr = this.parseArithAddSub();
31561
+ this.rejectNonScalarExpr(expr, tok);
31562
+ if (expr.type === "NUMBER" || expr.type === "STRING_FUNC" || expr.type === "ARITH") return expr;
31563
+ throw new ParseError("SET \u306E\u53F3\u8FBA\u306B\u306F\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u3092\u542B\u307E\u306A\u3044\u30B9\u30AB\u30E9\u30FC\u5F0F\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044", tok);
31564
+ }
31565
+ rejectNonScalarExpr(node, tok) {
31566
+ if (node.type === "STRING" || node.type === "NUMBER") return;
31567
+ if (node.type === "FIELD_REF" || node.type === "AGG_REF") {
31568
+ throw new ParseError("SET \u306E\u53F3\u8FBA\u3067\u306F\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u30FB\u96C6\u8A08\u95A2\u6570\u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
31569
+ }
31570
+ if (node.type === "ARITH" || node.type === "AGG_ARITH") {
31571
+ this.rejectNonScalarExpr(node.left, tok);
31572
+ this.rejectNonScalarExpr(node.right, tok);
31573
+ return;
31574
+ }
31575
+ if (node.type === "STRING_FUNC") {
31576
+ for (const arg of node.args) this.rejectNonScalarExpr(arg, tok);
31577
+ }
31578
+ }
31579
+ // ----------------------------------------------------------
31506
31580
  // CREATE TEMP TABLE / DROP TEMP TABLE(バッチ内一時テーブル)
31507
31581
  // CREATE / DROP / TEMP / TABLE は予約語にしない(ソフトキーワード)
31508
31582
  // ----------------------------------------------------------
@@ -31646,6 +31720,10 @@ var Parser = class {
31646
31720
  /** ASSERT のオペランド: 文字列 / スカラーサブクエリ / 数値算術式 */
31647
31721
  parseAssertOperand() {
31648
31722
  const tok = this.peek();
31723
+ if (tok.kind === "VARIABLE" /* VARIABLE */) {
31724
+ this.advance();
31725
+ return { type: "VARIABLE", name: tok.value.slice(1).toLowerCase() };
31726
+ }
31649
31727
  if (tok.kind === "STRING" /* STRING */) {
31650
31728
  this.advance();
31651
31729
  return { type: "STRING", value: tok.value };
@@ -32553,6 +32631,10 @@ var Parser = class {
32553
32631
  // 右辺の値
32554
32632
  parseSqlValue() {
32555
32633
  const tok = this.peek();
32634
+ if (tok.kind === "VARIABLE" /* VARIABLE */) {
32635
+ this.advance();
32636
+ return { type: "VARIABLE", name: tok.value.slice(1).toLowerCase() };
32637
+ }
32556
32638
  if (tok.kind === "STRING" /* STRING */) {
32557
32639
  this.advance();
32558
32640
  return { type: "STRING", value: tok.value };
@@ -32615,8 +32697,10 @@ var Parser = class {
32615
32697
  values.push({ type: "STRING", value: tok.value });
32616
32698
  } else if (tok.kind === "NUMBER" /* NUMBER */) {
32617
32699
  values.push({ type: "NUMBER", value: Number(tok.value) });
32700
+ } else if (tok.kind === "VARIABLE" /* VARIABLE */) {
32701
+ values.push({ type: "VARIABLE", name: tok.value.slice(1).toLowerCase() });
32618
32702
  } else {
32619
- throw new ParseError("IN \u30EA\u30B9\u30C8\u306B\u306F\u6587\u5B57\u5217\u307E\u305F\u306F\u6570\u5024\u304C\u5FC5\u8981\u3067\u3059", tok);
32703
+ throw new ParseError("IN \u30EA\u30B9\u30C8\u306B\u306F\u6587\u5B57\u5217\u3001\u6570\u5024\u3001\u307E\u305F\u306F\u30D0\u30C3\u30C1\u5909\u6570\u304C\u5FC5\u8981\u3067\u3059", tok);
32620
32704
  }
32621
32705
  } while (this.consume("," /* COMMA */));
32622
32706
  return values;
@@ -32848,6 +32932,7 @@ var Parser = class {
32848
32932
  */
32849
32933
  parseAssignmentValue() {
32850
32934
  const tok = this.peek();
32935
+ if (tok.kind === "VARIABLE" /* VARIABLE */) return this.parseSqlValue();
32851
32936
  if (tok.kind === "STRING" /* STRING */) return this.parseSqlValue();
32852
32937
  if (tok.kind === "TODAY" /* TODAY */ || tok.kind === "NOW" /* NOW */ || tok.kind === "LOGINUSER" /* LOGINUSER */) return this.parseSqlValue();
32853
32938
  if (tok.kind === "IN" /* IN */) return this.parseSqlValue();
@@ -33109,7 +33194,7 @@ function isDmlType(type) {
33109
33194
  return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
33110
33195
  }
33111
33196
  function isReadOnlyType(type) {
33112
- return type === "SELECT" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE" || type === "ASSERT";
33197
+ return type === "SELECT" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE" || type === "SET_VARIABLE" || type === "ASSERT";
33113
33198
  }
33114
33199
  function hasWhereClause(stmt) {
33115
33200
  if (!stmt || typeof stmt !== "object") return false;
@@ -33130,6 +33215,7 @@ function getInsertValuesCount(stmt) {
33130
33215
 
33131
33216
  // src/core/batch.ts
33132
33217
  var MAX_TEMP_TABLES = 16;
33218
+ var MAX_BATCH_VARIABLES = 64;
33133
33219
  var BatchAnalysisError = class extends Error {
33134
33220
  constructor(message, statementIndex) {
33135
33221
  super(message);
@@ -33150,12 +33236,29 @@ function collectRefs(node, tempRefs, appIds) {
33150
33236
  for (const v of Object.values(obj)) collectRefs(v, tempRefs, appIds);
33151
33237
  }
33152
33238
  }
33239
+ function collectVariableRefs(node, refs) {
33240
+ if (Array.isArray(node)) {
33241
+ for (const v of node) collectVariableRefs(v, refs);
33242
+ return;
33243
+ }
33244
+ if (node !== null && typeof node === "object") {
33245
+ const obj = node;
33246
+ if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") {
33247
+ refs.add(obj["name"]);
33248
+ return;
33249
+ }
33250
+ for (const v of Object.values(obj)) collectVariableRefs(v, refs);
33251
+ }
33252
+ }
33153
33253
  function analyzeBatch(statements) {
33154
33254
  if (statements.length === 0) {
33155
33255
  throw new BatchAnalysisError("ArgumentError: SQL is empty.", 0);
33156
33256
  }
33157
33257
  if (statements.length === 1) {
33158
33258
  const t = statements[0].type;
33259
+ if (t === "SET_VARIABLE") {
33260
+ throw new BatchAnalysisError("ArgumentError: SET variable requires a batch.", 0);
33261
+ }
33159
33262
  if (t === "CREATE_TEMP_TABLE" || t === "DROP_TEMP_TABLE") {
33160
33263
  const verb = t === "CREATE_TEMP_TABLE" ? "CREATE TEMP TABLE" : "DROP TEMP TABLE";
33161
33264
  throw new BatchAnalysisError(
@@ -33167,6 +33270,8 @@ function analyzeBatch(statements) {
33167
33270
  const defined = /* @__PURE__ */ new Map();
33168
33271
  const createdOrder = [];
33169
33272
  const results = [];
33273
+ const variableDefs = /* @__PURE__ */ new Map();
33274
+ const variableOrder = [];
33170
33275
  statements.forEach((stmt, index) => {
33171
33276
  const statementType = getStatementType(stmt);
33172
33277
  const created = [];
@@ -33174,6 +33279,31 @@ function analyzeBatch(statements) {
33174
33279
  const refs = /* @__PURE__ */ new Set();
33175
33280
  const stmtAppIds = /* @__PURE__ */ new Set();
33176
33281
  const dependsOn = /* @__PURE__ */ new Set();
33282
+ const variableRefs = /* @__PURE__ */ new Set();
33283
+ collectVariableRefs(stmt, variableRefs);
33284
+ for (const name of variableRefs) {
33285
+ const def = variableDefs.get(name);
33286
+ if (def === void 0) {
33287
+ throw new BatchAnalysisError(
33288
+ `ParseError: variable @${name} is not defined before statement ${index + 1}.`,
33289
+ index
33290
+ );
33291
+ }
33292
+ def.referencedBy.push(index);
33293
+ }
33294
+ if (stmt.type === "SET_VARIABLE") {
33295
+ if (variableDefs.has(stmt.name)) {
33296
+ throw new BatchAnalysisError(`ParseError: variable @${stmt.name} is already defined.`, index);
33297
+ }
33298
+ variableDefs.set(stmt.name, { index, referencedBy: [] });
33299
+ variableOrder.push(stmt.name);
33300
+ if (variableOrder.length > MAX_BATCH_VARIABLES) {
33301
+ throw new BatchAnalysisError(
33302
+ `ParseError: batch exceeds ${MAX_BATCH_VARIABLES} variables.`,
33303
+ index
33304
+ );
33305
+ }
33306
+ }
33177
33307
  if (stmt.type === "CREATE_TEMP_TABLE") {
33178
33308
  collectRefs(stmt.query, refs, stmtAppIds);
33179
33309
  } else if (stmt.type === "DROP_TEMP_TABLE") {
@@ -33243,11 +33373,17 @@ function analyzeBatch(statements) {
33243
33373
  });
33244
33374
  });
33245
33375
  const containsDml = results.some((r) => r.isDml);
33376
+ const variables = variableOrder.map((name) => ({
33377
+ name,
33378
+ referencedBy: [...variableDefs.get(name).referencedBy]
33379
+ }));
33246
33380
  return {
33247
33381
  statementCount: statements.length,
33248
33382
  isReadOnlyBatch: !containsDml && results.every((r) => r.isReadOnly),
33249
33383
  containsDml,
33250
33384
  tempTables: createdOrder,
33385
+ variables,
33386
+ warnings: variables.filter((v) => v.referencedBy.length === 0).map((v) => `variable @${v.name} is never used.`),
33251
33387
  statements: results
33252
33388
  };
33253
33389
  }
@@ -33308,6 +33444,29 @@ function negateOp(op) {
33308
33444
  }
33309
33445
  }
33310
33446
 
33447
+ // src/core/like.ts
33448
+ function likePatternHasWildcard(pattern) {
33449
+ return pattern.includes("%") || pattern.includes("_");
33450
+ }
33451
+ function isLike(where) {
33452
+ return where.type === "BINARY" && (where.op === "LIKE" || where.op === "NOT_LIKE");
33453
+ }
33454
+ function whereHasLike(where) {
33455
+ if (where === null) return false;
33456
+ if (isLike(where)) return true;
33457
+ switch (where.type) {
33458
+ case "LOGICAL":
33459
+ return whereHasLike(where.left) || whereHasLike(where.right);
33460
+ case "NOT":
33461
+ case "GROUP":
33462
+ return whereHasLike(where.expr);
33463
+ case "BINARY":
33464
+ case "NULL_CHECK":
33465
+ case "EXISTS":
33466
+ return false;
33467
+ }
33468
+ }
33469
+
33311
33470
  // src/converter/whereToKintone.ts
33312
33471
  function whereToKintone(expr) {
33313
33472
  switch (expr.type) {
@@ -33326,6 +33485,11 @@ function whereToKintone(expr) {
33326
33485
  }
33327
33486
  }
33328
33487
  function convertBinary(expr) {
33488
+ if (isLike(expr)) {
33489
+ throw new KintoneQueryError(
33490
+ "LIKE / NOT LIKE \u306F kintone \u30AF\u30A8\u30EA\u306B\u5909\u63DB\u3067\u304D\u307E\u305B\u3093\uFF08\u5E38\u306B JS \u8A55\u4FA1\u304C\u5FC5\u8981\u3067\u3059\uFF09"
33491
+ );
33492
+ }
33329
33493
  const left = convertField(expr.left);
33330
33494
  const op = convertOp(expr.op);
33331
33495
  const right = convertValue(expr.right, expr.op);
@@ -33390,6 +33554,8 @@ function convertField(field) {
33390
33554
  }
33391
33555
  function convertValue(value, op) {
33392
33556
  switch (value.type) {
33557
+ case "VARIABLE":
33558
+ throw new KintoneQueryError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
33393
33559
  case "STRING":
33394
33560
  return convertString(value);
33395
33561
  case "NUMBER":
@@ -33420,11 +33586,18 @@ function convertInList(v, op) {
33420
33586
  if (op !== "IN" && op !== "NOT_IN") {
33421
33587
  throw new KintoneQueryError("IN_LIST \u306F IN / NOT IN \u6F14\u7B97\u5B50\u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059");
33422
33588
  }
33589
+ assertResolvedInListValues(v.values);
33423
33590
  const values = v.values.map(
33424
33591
  (item) => item.type === "STRING" ? convertString(item) : String(item.value)
33425
33592
  ).join(",");
33426
33593
  return `(${values})`;
33427
33594
  }
33595
+ function assertResolvedInListValues(values) {
33596
+ const unresolved = values.find((item) => item.type === "VARIABLE");
33597
+ if (unresolved?.type === "VARIABLE") {
33598
+ throw new KintoneQueryError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${unresolved.name} \u304C\u3042\u308A\u307E\u3059`);
33599
+ }
33600
+ }
33428
33601
  function quoteIdentifier(name) {
33429
33602
  if (/^[\w$\u3000-\u9FFF]+$/u.test(name)) {
33430
33603
  return name;
@@ -33451,22 +33624,22 @@ function resolveSelectMode(stmt) {
33451
33624
  if (stmt.columns.some(
33452
33625
  (c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL" || c.type === "SCALAR_SUBQUERY_COL" || c.type === "STRFUNC_COL" && hasAggregateInStringFuncExpr(c.expr)
33453
33626
  )) return "FULL_SCAN";
33454
- if (hasWhereFunc(stmt.where)) return "FULL_SCAN";
33627
+ if (whereRequiresJsEval(stmt.where)) return "FULL_SCAN";
33455
33628
  if (stmt.orderBy.some((o) => o.key.type !== "FIELD_NAME")) return "FULL_SCAN";
33456
33629
  return "SIMPLE";
33457
33630
  }
33458
- function hasWhereFunc(where) {
33631
+ function whereRequiresJsEval(where) {
33459
33632
  if (where === null) return false;
33460
33633
  switch (where.type) {
33461
33634
  case "BINARY":
33462
- return isFunc(where.left) || where.right.type === "ARITH_VALUE" || where.right.type === "CASE_VALUE" || where.right.type === "SUBQUERY_IN_LIST" || where.right.type === "SCALAR_SUBQUERY";
33635
+ return isFunc(where.left) || where.right.type === "ARITH_VALUE" || where.right.type === "CASE_VALUE" || where.right.type === "SUBQUERY_IN_LIST" || where.right.type === "SCALAR_SUBQUERY" || isLike(where);
33463
33636
  case "NULL_CHECK":
33464
33637
  return isFunc(where.field);
33465
33638
  case "LOGICAL":
33466
- return hasWhereFunc(where.left) || hasWhereFunc(where.right);
33639
+ return whereRequiresJsEval(where.left) || whereRequiresJsEval(where.right);
33467
33640
  case "NOT":
33468
33641
  case "GROUP":
33469
- return hasWhereFunc(where.expr);
33642
+ return whereRequiresJsEval(where.expr);
33470
33643
  case "EXISTS":
33471
33644
  return true;
33472
33645
  }
@@ -33502,7 +33675,7 @@ function selectToKintoneParams(stmt) {
33502
33675
  }
33503
33676
  function selectToFetchAllParams(stmt, appId) {
33504
33677
  const queryParts = [];
33505
- if (stmt.where !== null && stmt.joins.length === 0 && !hasWhereFunc(stmt.where)) {
33678
+ if (stmt.where !== null && stmt.joins.length === 0 && !whereRequiresJsEval(stmt.where)) {
33506
33679
  queryParts.push(whereToKintone(stmt.where));
33507
33680
  }
33508
33681
  return {
@@ -34168,15 +34341,17 @@ function evalBinary(expr, row) {
34168
34341
  return evalOp(expr.op, left, expr.right, row);
34169
34342
  }
34170
34343
  function evalOp(op, leftStr, right, row) {
34171
- if (op === "IN") {
34172
- if (right.type === "IN_LIST") return right.values.some((v) => leftStr === String(v.value));
34173
- if (right.type === "SUBQUERY_IN_LIST") return right.resolved.has(leftStr);
34174
- return false;
34175
- }
34176
- if (op === "NOT_IN") {
34177
- if (right.type === "IN_LIST") return right.values.every((v) => leftStr !== String(v.value));
34178
- if (right.type === "SUBQUERY_IN_LIST") return !right.resolved.has(leftStr);
34179
- return true;
34344
+ if (op === "IN" || op === "NOT_IN") {
34345
+ if (right.type === "IN_LIST") {
34346
+ assertResolvedInListValues2(right.values);
34347
+ const contains = right.values.some((v) => leftStr === String(v.value));
34348
+ return op === "IN" ? contains : !contains;
34349
+ }
34350
+ if (right.type === "SUBQUERY_IN_LIST") {
34351
+ const contains = right.resolved.has(leftStr);
34352
+ return op === "IN" ? contains : !contains;
34353
+ }
34354
+ return op === "NOT_IN";
34180
34355
  }
34181
34356
  if (op === "LIKE") {
34182
34357
  const pattern = resolveValue(right, row);
@@ -34206,6 +34381,12 @@ function evalOp(op, leftStr, right, row) {
34206
34381
  return numeric ? leftNum <= rightNum : leftStr <= rightStr;
34207
34382
  }
34208
34383
  }
34384
+ function assertResolvedInListValues2(values) {
34385
+ const unresolved = values.find((item) => item.type === "VARIABLE");
34386
+ if (unresolved?.type === "VARIABLE") {
34387
+ throw new Error(`ParseError: unresolved batch variable @${unresolved.name}.`);
34388
+ }
34389
+ }
34209
34390
  function evalNullCheck(expr, row) {
34210
34391
  const val = resolveField(expr.field, row);
34211
34392
  return expr.not ? val !== "" : val === "";
@@ -34225,6 +34406,8 @@ function resolveField(field, row) {
34225
34406
  }
34226
34407
  function resolveValue(value, row) {
34227
34408
  switch (value.type) {
34409
+ case "VARIABLE":
34410
+ throw new Error(`ParseError: unresolved batch variable @${value.name}.`);
34228
34411
  case "STRING":
34229
34412
  return value.value;
34230
34413
  case "NUMBER":
@@ -34240,6 +34423,8 @@ function resolveValue(value, row) {
34240
34423
  case "SCALAR_SUBQUERY":
34241
34424
  return value.resolved;
34242
34425
  case "ARITH_VALUE":
34426
+ if (value.expr.type === "FIELD_REF") return resolveFieldRef(row, value.expr.field);
34427
+ if (value.expr.type === "STRING_FUNC") return evalStringFunc(value.expr, row);
34243
34428
  return String(evalArithExpr(value.expr, row));
34244
34429
  case "CASE_VALUE":
34245
34430
  return evalCaseWhen(value.expr, row);
@@ -34283,7 +34468,7 @@ function resolveKintoneFunc(name) {
34283
34468
  var likeRegexCache = /* @__PURE__ */ new Map();
34284
34469
  var LIKE_REGEX_CACHE_MAX = 200;
34285
34470
  function matchLike(value, pattern) {
34286
- if (!pattern.includes("%") && !pattern.includes("_")) {
34471
+ if (!likePatternHasWildcard(pattern)) {
34287
34472
  return value.includes(pattern);
34288
34473
  }
34289
34474
  let regex = likeRegexCache.get(pattern);
@@ -34308,6 +34493,12 @@ function matchLike(value, pattern) {
34308
34493
  }
34309
34494
 
34310
34495
  // src/converter/dmlToKintone.ts
34496
+ function assertDmlWhereIsSafe(where) {
34497
+ if (!whereHasLike(where)) return;
34498
+ throw new DmlConvertError(
34499
+ "UPDATE / DELETE \u306E WHERE \u306B LIKE / NOT LIKE \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002LIKE \u306F kSQL \u306E\u610F\u5473\u8AD6\u306B\u5F93\u3063\u3066 JS \u3067\u8A55\u4FA1\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u304C\u3001\u89AA\u30EC\u30B3\u30FC\u30C9 DML \u306B\u306F JS \u8A55\u4FA1\u7D4C\u8DEF\u304C\u306A\u3044\u305F\u3081\u3001\u5B89\u5168\u4E0A\u62D2\u5426\u3057\u307E\u3057\u305F\u3002SELECT \u3067\u5BFE\u8C61\u30EC\u30B3\u30FC\u30C9\u756A\u53F7\u3092\u78BA\u8A8D\u3057\u3001IN \u307E\u305F\u306F\u5B8C\u5168\u4E00\u81F4\u3067\u5BFE\u8C61\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
34500
+ );
34501
+ }
34311
34502
  var USER_TYPES = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
34312
34503
  var ARRAY_TYPES = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
34313
34504
  function insertToPostBatches(stmt, fieldTypes = /* @__PURE__ */ new Map()) {
@@ -34332,6 +34523,7 @@ function buildInsertRecord(fields, row, fieldTypes) {
34332
34523
  return record2;
34333
34524
  }
34334
34525
  function updateToGetQuery(stmt) {
34526
+ assertDmlWhereIsSafe(stmt.where);
34335
34527
  return {
34336
34528
  app: stmt.appId,
34337
34529
  query: whereToKintone(stmt.where),
@@ -34360,6 +34552,7 @@ function hasArithAssignment(stmt) {
34360
34552
  );
34361
34553
  }
34362
34554
  function updateToGetQueryForArith(stmt) {
34555
+ assertDmlWhereIsSafe(stmt.where);
34363
34556
  const refFields = /* @__PURE__ */ new Set();
34364
34557
  for (const { value } of stmt.assignments) {
34365
34558
  if (value.type === "ARITH") {
@@ -34505,6 +34698,7 @@ function resolveArithOperand(operand, raw) {
34505
34698
  return n;
34506
34699
  }
34507
34700
  function deleteToGetQuery(stmt) {
34701
+ assertDmlWhereIsSafe(stmt.where);
34508
34702
  return {
34509
34703
  app: stmt.appId,
34510
34704
  query: whereToKintone(stmt.where),
@@ -34577,6 +34771,8 @@ function evalCaseWhenValue(expr, row, fieldType) {
34577
34771
  }
34578
34772
  function toKintoneValue(value, fieldType) {
34579
34773
  switch (value.type) {
34774
+ case "VARIABLE":
34775
+ throw new DmlConvertError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
34580
34776
  case "STRING":
34581
34777
  return convertString2(value.value, fieldType);
34582
34778
  case "NUMBER":
@@ -34789,6 +34985,7 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
34789
34985
  function extractTableCondition(where, tableAlias) {
34790
34986
  switch (where.type) {
34791
34987
  case "BINARY":
34988
+ if (isLike(where)) return null;
34792
34989
  if (!isSingleTableField(where.left, tableAlias)) return null;
34793
34990
  if (!isPushDownableRight(where.right)) return null;
34794
34991
  return where;
@@ -34828,7 +35025,7 @@ function isPushDownableRight(value) {
34828
35025
  function referencesOnlyTable(expr, tableAlias) {
34829
35026
  switch (expr.type) {
34830
35027
  case "BINARY":
34831
- return isSingleTableField(expr.left, tableAlias) && isPushDownableRight(expr.right);
35028
+ return !isLike(expr) && isSingleTableField(expr.left, tableAlias) && isPushDownableRight(expr.right);
34832
35029
  case "NULL_CHECK":
34833
35030
  return isSingleTableField(expr.field, tableAlias);
34834
35031
  case "LOGICAL":
@@ -35451,6 +35648,10 @@ async function executeStatement(sql, client, options) {
35451
35648
  return executeParsedStatement(stmt, client, options, cacheContext);
35452
35649
  }
35453
35650
  async function executeParsedStatement(stmt, client, options, cacheContext) {
35651
+ const unresolved = findVariableRef(stmt);
35652
+ if (unresolved !== null) {
35653
+ throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
35654
+ }
35454
35655
  switch (stmt.type) {
35455
35656
  case "SELECT":
35456
35657
  return executeSelect(stmt, client, options, cacheContext);
@@ -35483,6 +35684,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
35483
35684
  throw new Error("ArgumentError: CREATE TEMP TABLE requires a batch (temp tables are batch-scoped).");
35484
35685
  case "DROP_TEMP_TABLE":
35485
35686
  throw new Error("ArgumentError: DROP TEMP TABLE requires a batch (temp tables are batch-scoped).");
35687
+ case "SET_VARIABLE":
35688
+ throw new Error("ArgumentError: SET variable requires a batch.");
35486
35689
  case "ASSERT":
35487
35690
  return executeAssert(stmt, client, options, cacheContext);
35488
35691
  }
@@ -35513,6 +35716,7 @@ async function executeBatch(sql, client, options = {}) {
35513
35716
  const deadline = options.timeoutMs != null ? startedAt + options.timeoutMs : null;
35514
35717
  const cacheContext = options.cacheContext ?? "default";
35515
35718
  const tempTables = /* @__PURE__ */ new Map();
35719
+ const variables = /* @__PURE__ */ new Map();
35516
35720
  const results = [];
35517
35721
  const failed = /* @__PURE__ */ new Set();
35518
35722
  let aborted2 = null;
@@ -35550,7 +35754,7 @@ async function executeBatch(sql, client, options = {}) {
35550
35754
  })
35551
35755
  } : options;
35552
35756
  const outcome = await runWithDeadline(
35553
- executeBatchStatement(statements[i], info, countedClient, stmtOptions, cacheContext, tempTables),
35757
+ executeBatchStatement(statements[i], info, countedClient, stmtOptions, cacheContext, tempTables, variables),
35554
35758
  remaining
35555
35759
  );
35556
35760
  results.push({ ...base, status: "success", ...outcome });
@@ -35561,6 +35765,8 @@ async function executeBatch(sql, client, options = {}) {
35561
35765
  aborted2 = "timeout";
35562
35766
  } else if (e instanceof AssertError) {
35563
35767
  aborted2 = "assertion";
35768
+ } else if (info.statementType === "SET_VARIABLE") {
35769
+ aborted2 = "fail-fast";
35564
35770
  } else if (!options.continueOnError) {
35565
35771
  aborted2 = "fail-fast";
35566
35772
  }
@@ -35575,44 +35781,49 @@ async function executeBatch(sql, client, options = {}) {
35575
35781
  metrics
35576
35782
  };
35577
35783
  }
35578
- async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables) {
35579
- if (stmt.type === "CREATE_TEMP_TABLE") {
35784
+ async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables, variables) {
35785
+ if (stmt.type === "SET_VARIABLE") {
35786
+ variables.set(stmt.name, evaluateScalarExpr(stmt.expr));
35787
+ return {};
35788
+ }
35789
+ const resolvedStmt = resolveVariableRefs(stmt, variables);
35790
+ if (resolvedStmt.type === "CREATE_TEMP_TABLE") {
35580
35791
  const materializeOptions = {
35581
35792
  ...options,
35582
35793
  maxRecords: options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
35583
35794
  onLimitReached: "error"
35584
35795
  };
35585
- const result = await runSelectLike(stmt.query, client, materializeOptions, cacheContext, tempTables);
35586
- tempTables.set(stmt.name, result.rows);
35587
- return { tempTable: stmt.name, rowCount: result.rows.length };
35796
+ const result = await runSelectLike(resolvedStmt.query, client, materializeOptions, cacheContext, tempTables);
35797
+ tempTables.set(resolvedStmt.name, result.rows);
35798
+ return { tempTable: resolvedStmt.name, rowCount: result.rows.length };
35588
35799
  }
35589
35800
  if (stmt.type === "DROP_TEMP_TABLE") {
35590
35801
  tempTables.delete(stmt.name);
35591
35802
  return { tempTable: stmt.name };
35592
35803
  }
35593
35804
  if (stmt.type === "EXPLAIN") {
35594
- return { result: await executeParsedStatement(stmt, client, options, cacheContext) };
35805
+ return { result: await executeParsedStatement(resolvedStmt, client, options, cacheContext) };
35595
35806
  }
35596
- if (stmt.type === "ASSERT") {
35597
- await executeAssert(stmt, client, options, cacheContext, tempTables);
35807
+ if (resolvedStmt.type === "ASSERT") {
35808
+ await executeAssert(resolvedStmt, client, options, cacheContext, tempTables);
35598
35809
  return {};
35599
35810
  }
35600
35811
  if (info.tempTablesReferenced.length > 0) {
35601
- if (stmt.type === "SELECT" || stmt.type === "UNION") {
35602
- return { result: await executeQueryWithCte(stmt, client, options, tempTables, cacheContext) };
35812
+ if (resolvedStmt.type === "SELECT" || resolvedStmt.type === "UNION") {
35813
+ return { result: await executeQueryWithCte(resolvedStmt, client, options, tempTables, cacheContext) };
35603
35814
  }
35604
- if (stmt.type === "WITH") {
35605
- return { result: await executeWith(stmt, client, options, cacheContext, tempTables) };
35815
+ if (resolvedStmt.type === "WITH") {
35816
+ return { result: await executeWith(resolvedStmt, client, options, cacheContext, tempTables) };
35606
35817
  }
35607
- if (stmt.type === "INSERT_SELECT") {
35608
- return { result: await executeInsertSelect(stmt, client, options, cacheContext, tempTables) };
35818
+ if (resolvedStmt.type === "INSERT_SELECT") {
35819
+ return { result: await executeInsertSelect(resolvedStmt, client, options, cacheContext, tempTables) };
35609
35820
  }
35610
- if (stmt.type === "UPSERT_SELECT") {
35611
- return { result: await executeUpsertSelect(stmt, client, options, cacheContext, tempTables) };
35821
+ if (resolvedStmt.type === "UPSERT_SELECT") {
35822
+ return { result: await executeUpsertSelect(resolvedStmt, client, options, cacheContext, tempTables) };
35612
35823
  }
35613
35824
  throw new Error(`ArgumentError: temp table references in ${stmt.type} are not supported yet.`);
35614
35825
  }
35615
- return { result: await executeParsedStatement(stmt, client, options, cacheContext) };
35826
+ return { result: await executeParsedStatement(resolvedStmt, client, options, cacheContext) };
35616
35827
  }
35617
35828
  async function runSelectLike(query, client, options, cacheContext, tempTables) {
35618
35829
  if (query.type === "WITH") {
@@ -35673,6 +35884,62 @@ function parseSqlBatch(sql) {
35673
35884
  const tokens = new Lexer(sql).tokenize();
35674
35885
  return new Parser(tokens).parseStatements();
35675
35886
  }
35887
+ function evaluateScalarExpr(expr) {
35888
+ switch (expr.type) {
35889
+ case "STRING":
35890
+ return { type: "string", value: expr.value };
35891
+ case "NUMBER":
35892
+ return { type: "number", value: expr.value };
35893
+ case "KINTONE_FUNC":
35894
+ return { type: "string", value: resolveKintoneFunc(expr.name) };
35895
+ case "STRING_FUNC":
35896
+ return { type: "string", value: evalStringFunc(expr, {}) };
35897
+ case "ARITH": {
35898
+ const value = evalArithExpr(expr, {});
35899
+ if (!Number.isFinite(value)) {
35900
+ throw new Error("ArgumentError: SET scalar arithmetic produced a non-finite number.");
35901
+ }
35902
+ return { type: "number", value };
35903
+ }
35904
+ }
35905
+ }
35906
+ function resolveVariableRefs(node, variables) {
35907
+ if (Array.isArray(node)) {
35908
+ return node.map((v) => resolveVariableRefs(v, variables));
35909
+ }
35910
+ if (node !== null && typeof node === "object") {
35911
+ const obj = node;
35912
+ if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") {
35913
+ const value = variables.get(obj["name"]);
35914
+ if (value === void 0) {
35915
+ throw new Error(`ParseError: variable @${obj["name"]} is not defined in this batch.`);
35916
+ }
35917
+ return value.type === "number" ? { type: "NUMBER", value: value.value } : { type: "STRING", value: value.value };
35918
+ }
35919
+ return Object.fromEntries(
35920
+ Object.entries(obj).map(([key, value]) => [key, resolveVariableRefs(value, variables)])
35921
+ );
35922
+ }
35923
+ return node;
35924
+ }
35925
+ function findVariableRef(node) {
35926
+ if (Array.isArray(node)) {
35927
+ for (const value of node) {
35928
+ const found = findVariableRef(value);
35929
+ if (found !== null) return found;
35930
+ }
35931
+ return null;
35932
+ }
35933
+ if (node !== null && typeof node === "object") {
35934
+ const obj = node;
35935
+ if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") return obj["name"];
35936
+ for (const value of Object.values(obj)) {
35937
+ const found = findVariableRef(value);
35938
+ if (found !== null) return found;
35939
+ }
35940
+ }
35941
+ return null;
35942
+ }
35676
35943
  var AssertError = class extends Error {
35677
35944
  constructor(message) {
35678
35945
  super(`AssertError: ${message}`);
@@ -35703,6 +35970,8 @@ async function executeAssert(stmt, client, options, cacheContext, tempTables) {
35703
35970
  }
35704
35971
  async function evalAssertOperand(operand, client, options, cacheContext, tempTables) {
35705
35972
  switch (operand.type) {
35973
+ case "VARIABLE":
35974
+ throw new Error(`ParseError: unresolved batch variable @${operand.name}.`);
35706
35975
  case "NUMBER":
35707
35976
  return String(operand.value);
35708
35977
  case "STRING":
@@ -37204,13 +37473,21 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
37204
37473
  function buildBatchExplainPlans(sql) {
37205
37474
  const statements = parseSqlBatch(sql);
37206
37475
  const analysis = analyzeBatch(statements);
37476
+ const variables = /* @__PURE__ */ new Map();
37207
37477
  return {
37208
37478
  statementCount: statements.length,
37209
- statements: statements.map((stmt, i) => ({
37210
- index: i,
37211
- type: analysis.statements[i].statementType,
37212
- plan: buildBatchStatementPlan(stmt, analysis.statements[i])
37213
- }))
37479
+ statements: statements.map((stmt, i) => {
37480
+ const planStmt = stmt.type === "SET_VARIABLE" ? stmt : resolveVariableRefs(stmt, variables);
37481
+ const result = {
37482
+ index: i,
37483
+ type: analysis.statements[i].statementType,
37484
+ plan: buildBatchStatementPlan(planStmt, analysis.statements[i])
37485
+ };
37486
+ if (stmt.type === "SET_VARIABLE") {
37487
+ variables.set(stmt.name, { type: "string", value: `@${stmt.name}` });
37488
+ }
37489
+ return result;
37490
+ })
37214
37491
  };
37215
37492
  }
37216
37493
  function buildBatchStatementPlan(stmt, info) {
@@ -37228,6 +37505,12 @@ function buildBatchStatementPlan(stmt, info) {
37228
37505
  " \u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u30B9\u30C8\u30A2\u306E\u89E3\u653E\u306E\u307F\uFF08kintone \u30A2\u30AF\u30BB\u30B9\u306A\u3057\uFF09"
37229
37506
  ];
37230
37507
  }
37508
+ if (stmt.type === "SET_VARIABLE") {
37509
+ return [
37510
+ `SET @${stmt.name} = <scalar expression>`,
37511
+ " value: \u5B9F\u884C\u6642\u306B1\u56DE\u8A55\u4FA1\uFF08\u30D0\u30C3\u30C1\u5185\u5B9A\u6570\u30FB\u7D50\u679C\u30E1\u30BF\u30C7\u30FC\u30BF\u306B\u306F\u975E\u516C\u958B\uFF09"
37512
+ ];
37513
+ }
37231
37514
  if (stmt.type === "SHOW_APPS") return ["SHOW APPS\uFF08\u30A2\u30D7\u30EA\u4E00\u89A7\u306E\u53D6\u5F97\uFF09"];
37232
37515
  if (stmt.type === "DESCRIBE") return [`DESCRIBE APP${stmt.appId}\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u5B9A\u7FA9\u306E\u53D6\u5F97\uFF09`];
37233
37516
  if (stmt.type === "EXPLAIN") return buildPlanForBatchQuery(stmt.query, info);
@@ -37387,8 +37670,10 @@ function collectFullScanReasons(stmt) {
37387
37670
  r.push("\u96C6\u8A08\u95A2\u6570\uFF08COUNT / SUM \u7B49\uFF09\u3042\u308A");
37388
37671
  if (stmt.columns.some((c) => c.type === "SCALAR_SUBQUERY_COL"))
37389
37672
  r.push("SELECT \u5217\u306B\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA");
37390
- if (hasWhereFunc(stmt.where))
37673
+ if (whereRequiresJsEval(stmt.where))
37391
37674
  r.push("WHERE \u53E5\u306B JS \u8A55\u4FA1\u304C\u5FC5\u8981\u306A\u5F0F");
37675
+ if (whereHasLike(stmt.where))
37676
+ r.push("LIKE \u306F\u5E38\u306B JS \u8A55\u4FA1\u306E\u305F\u3081\u5168\u4EF6\u53D6\u5F97");
37392
37677
  if (stmt.orderBy.some((o) => o.key.type !== "FIELD_NAME"))
37393
37678
  r.push("ORDER BY \u306B\u5F0F");
37394
37679
  return r;
@@ -39661,7 +39946,7 @@ Options:
39661
39946
  -h, --help Show help
39662
39947
  `);
39663
39948
  }
39664
- var SERVER_VERSION = true ? "1.13.2" : "0.0.0-dev";
39949
+ var SERVER_VERSION = true ? "2.1.0" : "0.0.0-dev";
39665
39950
  function createServer(args) {
39666
39951
  const server = new McpServer({
39667
39952
  name: "ksql-mcp",