@rex0220/kintone-sql-tools 2.3.0 → 2.5.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 CHANGED
@@ -599,13 +599,14 @@ var Parser = class {
599
599
  const upper = tok.value.toUpperCase();
600
600
  if (upper === "CREATE") return this.parseCreateTempTable();
601
601
  if (upper === "DROP") return this.parseDropTempTable();
602
+ if (upper === "DECLARE") return this.parseDeclareVariable();
602
603
  break;
603
604
  }
604
605
  default:
605
606
  break;
606
607
  }
607
608
  throw new ParseError(
608
- "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",
609
+ "SELECT / INSERT / UPDATE / DELETE / REORDER / 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",
609
610
  tok
610
611
  );
611
612
  }
@@ -616,19 +617,32 @@ var Parser = class {
616
617
  this.expect("SET" /* SET */);
617
618
  const variable = this.expect("VARIABLE" /* VARIABLE */, "SET \u306E\u5F8C\u306B\u306F\u5909\u6570\u540D\uFF08\u4F8B: @name\uFF09\u304C\u5FC5\u8981\u3067\u3059");
618
619
  this.expect("=" /* EQ */);
619
- const expr = this.parseScalarExpr();
620
+ const expr = this.parseScalarExpr("SET", true);
620
621
  return { type: "SET_VARIABLE", name: variable.value.slice(1).toLowerCase(), expr };
621
622
  }
622
- /** SET RHS 専用。既存式パーサーで構文を読み、フィールド参照を明示的に拒否する。 */
623
- parseScalarExpr() {
623
+ parseDeclareVariable() {
624
+ this.advance();
625
+ const variable = this.expect("VARIABLE" /* VARIABLE */, "DECLARE \u306E\u5F8C\u306B\u306F\u5909\u6570\u540D\uFF08\u4F8B: @name\uFF09\u304C\u5FC5\u8981\u3067\u3059");
626
+ this.expect("=" /* EQ */);
627
+ const expr = this.parseScalarExpr("DECLARE", false);
628
+ if (expr.type === "SCALAR_SUBQUERY") {
629
+ throw new ParseError("DECLARE \u306E\u65E2\u5B9A\u5024\u306B\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", this.peek());
630
+ }
631
+ return { type: "DECLARE_VARIABLE", name: variable.value.slice(1).toLowerCase(), default: expr };
632
+ }
633
+ /** SET / DECLARE RHS 専用。既存式パーサーで構文を読み、フィールド参照を明示的に拒否する。 */
634
+ parseScalarExpr(context, allowScalarSubquery) {
624
635
  const tok = this.peek();
625
636
  if (tok.kind === "VARIABLE" /* VARIABLE */) {
626
- 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);
637
+ throw new ParseError(`${context} \u306E\u53F3\u8FBA\u3067\u306F\u4ED6\u306E\u5909\u6570\u3092\u53C2\u7167\u3067\u304D\u307E\u305B\u3093`, tok);
627
638
  }
628
639
  if (tok.kind === "NULL" /* NULL */) {
629
- throw new ParseError("SET \u306E\u53F3\u8FBA\u3067 NULL \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\uFF08Phase 1a\uFF09", tok);
640
+ throw new ParseError(`${context} \u306E\u53F3\u8FBA\u3067 NULL \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`, tok);
630
641
  }
631
642
  if (tok.kind === "(" /* LPAREN */ && this.peekAt(1).kind === "SELECT" /* SELECT */) {
643
+ if (!allowScalarSubquery) {
644
+ throw new ParseError("DECLARE \u306E\u65E2\u5B9A\u5024\u306B\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
645
+ }
632
646
  this.advance();
633
647
  const query = this.parseSelect();
634
648
  this.expect(")" /* RPAREN */);
@@ -652,7 +666,7 @@ var Parser = class {
652
666
  }
653
667
  if (tok.kind === "LOGINUSER" /* LOGINUSER */) {
654
668
  throw new ParseError(
655
- "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",
669
+ `${context} \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`,
656
670
  tok
657
671
  );
658
672
  }
@@ -660,22 +674,22 @@ var Parser = class {
660
674
  return this.parseSqlValue();
661
675
  }
662
676
  const expr = this.parseArithAddSub();
663
- this.rejectNonScalarExpr(expr, tok);
677
+ this.rejectNonScalarExpr(expr, tok, context);
664
678
  if (expr.type === "NUMBER" || expr.type === "STRING_FUNC" || expr.type === "ARITH") return expr;
665
- 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);
679
+ throw new ParseError(`${context} \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);
666
680
  }
667
- rejectNonScalarExpr(node, tok) {
681
+ rejectNonScalarExpr(node, tok, context) {
668
682
  if (node.type === "STRING" || node.type === "NUMBER") return;
669
683
  if (node.type === "FIELD_REF" || node.type === "AGG_REF") {
670
- 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);
684
+ throw new ParseError(`${context} \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);
671
685
  }
672
686
  if (node.type === "ARITH" || node.type === "AGG_ARITH") {
673
- this.rejectNonScalarExpr(node.left, tok);
674
- this.rejectNonScalarExpr(node.right, tok);
687
+ this.rejectNonScalarExpr(node.left, tok, context);
688
+ this.rejectNonScalarExpr(node.right, tok, context);
675
689
  return;
676
690
  }
677
691
  if (node.type === "STRING_FUNC") {
678
- for (const arg of node.args) this.rejectNonScalarExpr(arg, tok);
692
+ for (const arg of node.args) this.rejectNonScalarExpr(arg, tok, context);
679
693
  }
680
694
  }
681
695
  // ----------------------------------------------------------
@@ -2301,7 +2315,7 @@ function isDmlType(type) {
2301
2315
  return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
2302
2316
  }
2303
2317
  function isReadOnlyType(type) {
2304
- 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";
2318
+ 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 === "DECLARE_VARIABLE" || type === "ASSERT";
2305
2319
  }
2306
2320
  function hasWhereClause(stmt) {
2307
2321
  if (!stmt || typeof stmt !== "object") return false;
@@ -2375,8 +2389,9 @@ function analyzeBatch(statements) {
2375
2389
  }
2376
2390
  if (statements.length === 1) {
2377
2391
  const t = statements[0].type;
2378
- if (t === "SET_VARIABLE") {
2379
- throw new BatchAnalysisError("ArgumentError: SET variable requires a batch.", 0);
2392
+ if (t === "SET_VARIABLE" || t === "DECLARE_VARIABLE") {
2393
+ const verb = t === "SET_VARIABLE" ? "SET" : "DECLARE";
2394
+ throw new BatchAnalysisError(`ArgumentError: ${verb} variable requires a batch.`, 0);
2380
2395
  }
2381
2396
  if (t === "CREATE_TEMP_TABLE" || t === "DROP_TEMP_TABLE") {
2382
2397
  const verb = t === "CREATE_TEMP_TABLE" ? "CREATE TEMP TABLE" : "DROP TEMP TABLE";
@@ -2410,7 +2425,7 @@ function analyzeBatch(statements) {
2410
2425
  }
2411
2426
  def.referencedBy.push(index);
2412
2427
  }
2413
- if (stmt.type === "SET_VARIABLE") {
2428
+ if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
2414
2429
  if (variableDefs.has(stmt.name)) {
2415
2430
  throw new BatchAnalysisError(`ParseError: variable @${stmt.name} is already defined.`, index);
2416
2431
  }
@@ -2507,6 +2522,40 @@ function analyzeBatch(statements) {
2507
2522
  };
2508
2523
  }
2509
2524
 
2525
+ // src/core/batchVariables.ts
2526
+ var VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
2527
+ function normalizeBatchVariableName(name) {
2528
+ if (!VARIABLE_NAME_RE.test(name)) {
2529
+ throw new Error(
2530
+ `ArgumentError: invalid variable name "${name}". Use a name without @ matching [A-Za-z_][A-Za-z0-9_]{0,63}.`
2531
+ );
2532
+ }
2533
+ return name.toLowerCase();
2534
+ }
2535
+ function normalizeBatchVariables(input) {
2536
+ const normalized = /* @__PURE__ */ Object.create(null);
2537
+ for (const [rawName, value] of Object.entries(input ?? {})) {
2538
+ const name = normalizeBatchVariableName(rawName);
2539
+ if (Object.prototype.hasOwnProperty.call(normalized, name)) {
2540
+ throw new Error(`ArgumentError: variable "${rawName}" is specified more than once.`);
2541
+ }
2542
+ normalized[name] = value;
2543
+ }
2544
+ return normalized;
2545
+ }
2546
+ function validateDeclaredBatchVariables(statements, input) {
2547
+ const normalized = normalizeBatchVariables(input);
2548
+ const declared = new Set(
2549
+ statements.filter((stmt) => stmt.type === "DECLARE_VARIABLE").map((stmt) => stmt.name)
2550
+ );
2551
+ for (const name of Object.keys(normalized)) {
2552
+ if (!declared.has(name)) {
2553
+ throw new Error(`ArgumentError: injected variable @${name} is not declared.`);
2554
+ }
2555
+ }
2556
+ return normalized;
2557
+ }
2558
+
2510
2559
  // src/core/scalarCompare.ts
2511
2560
  function compareScalarValues(op, leftStr, rightStr) {
2512
2561
  if (op === "=") return leftStr === rightStr;
@@ -3459,52 +3508,91 @@ function resolveFieldRef(row, field) {
3459
3508
  }
3460
3509
 
3461
3510
  // src/engine/evalWhere.ts
3462
- function evalWhere(expr, row) {
3511
+ function evalWhere(expr, row, resolveFieldType) {
3463
3512
  switch (expr.type) {
3464
3513
  case "BINARY":
3465
- return evalBinary(expr, row);
3514
+ return evalBinary(expr, row, resolveFieldType);
3466
3515
  case "NULL_CHECK":
3467
3516
  return evalNullCheck(expr, row);
3468
3517
  case "LOGICAL":
3469
- return evalLogical(expr, row);
3518
+ return evalLogical(expr, row, resolveFieldType);
3470
3519
  case "NOT":
3471
- return !evalWhere(expr.expr, row);
3520
+ return !evalWhere(expr.expr, row, resolveFieldType);
3472
3521
  case "GROUP":
3473
- return evalWhere(expr.expr, row);
3522
+ return evalWhere(expr.expr, row, resolveFieldType);
3474
3523
  case "EXISTS": {
3475
3524
  const exists = expr.resolved;
3476
3525
  return expr.not ? !exists : exists;
3477
3526
  }
3478
3527
  }
3479
3528
  }
3480
- function evalBinary(expr, row) {
3481
- const left = resolveField(expr.left, row);
3482
- return evalOp(expr.op, left, expr.right, row);
3529
+ function evalBinary(expr, row, resolveFieldType) {
3530
+ const left = resolveField(expr.left, row, resolveFieldType);
3531
+ const fieldType = expr.left.type === "FIELD" ? resolveFieldType?.(expr.left) : void 0;
3532
+ return evalOp(expr.op, left, expr.right, row, fieldType, resolveFieldType);
3483
3533
  }
3484
- function evalOp(op, leftStr, right, row) {
3534
+ function evalOp(op, leftStr, right, row, fieldType, resolveFieldType) {
3485
3535
  if (op === "IN" || op === "NOT_IN") {
3536
+ let values = null;
3486
3537
  if (right.type === "IN_LIST") {
3487
3538
  assertResolvedInListValues2(right.values);
3488
- const contains = right.values.some((v) => leftStr === String(v.value));
3489
- return op === "IN" ? contains : !contains;
3539
+ values = new Set(right.values.map((v) => String(v.value)));
3490
3540
  }
3491
3541
  if (right.type === "SUBQUERY_IN_LIST") {
3492
- const contains = right.resolved.has(leftStr);
3493
- return op === "IN" ? contains : !contains;
3542
+ values = right.resolved;
3494
3543
  }
3495
- return op === "NOT_IN";
3544
+ if (values === null) return op === "NOT_IN";
3545
+ const contains = typedInContains(leftStr, values, fieldType);
3546
+ return op === "IN" ? contains : !contains;
3496
3547
  }
3497
3548
  if (op === "LIKE") {
3498
- const pattern = resolveValue(right, row);
3549
+ const pattern = resolveValue(right, row, resolveFieldType);
3499
3550
  return matchLike(leftStr, pattern);
3500
3551
  }
3501
3552
  if (op === "NOT_LIKE") {
3502
- const pattern = resolveValue(right, row);
3553
+ const pattern = resolveValue(right, row, resolveFieldType);
3503
3554
  return !matchLike(leftStr, pattern);
3504
3555
  }
3505
- const rightStr = resolveValue(right, row);
3556
+ const rightStr = resolveValue(right, row, resolveFieldType);
3506
3557
  return compareScalarValues(op, leftStr, rightStr);
3507
3558
  }
3559
+ var STRING_ARRAY_FIELD_TYPES = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
3560
+ var OBJECT_ARRAY_FIELD_TYPES = /* @__PURE__ */ new Set([
3561
+ "USER_SELECT",
3562
+ "ORGANIZATION_SELECT",
3563
+ "GROUP_SELECT",
3564
+ "STATUS_ASSIGNEE"
3565
+ ]);
3566
+ var SINGLE_OBJECT_FIELD_TYPES = /* @__PURE__ */ new Set(["CREATOR", "MODIFIER"]);
3567
+ function typedInContains(leftStr, values, fieldType) {
3568
+ const fallback = () => values.has(leftStr);
3569
+ if (fieldType === void 0) return fallback();
3570
+ let parsed;
3571
+ if (STRING_ARRAY_FIELD_TYPES.has(fieldType) || OBJECT_ARRAY_FIELD_TYPES.has(fieldType) || SINGLE_OBJECT_FIELD_TYPES.has(fieldType)) {
3572
+ try {
3573
+ parsed = JSON.parse(leftStr);
3574
+ } catch {
3575
+ return fallback();
3576
+ }
3577
+ } else {
3578
+ return fallback();
3579
+ }
3580
+ if (STRING_ARRAY_FIELD_TYPES.has(fieldType)) {
3581
+ if (!Array.isArray(parsed) || !parsed.every((item) => typeof item === "string")) {
3582
+ return fallback();
3583
+ }
3584
+ return parsed.some((item) => values.has(item));
3585
+ }
3586
+ if (OBJECT_ARRAY_FIELD_TYPES.has(fieldType)) {
3587
+ if (!Array.isArray(parsed) || !parsed.every(hasStringCode)) return fallback();
3588
+ return parsed.some((item) => values.has(item.code));
3589
+ }
3590
+ if (!hasStringCode(parsed)) return fallback();
3591
+ return values.has(parsed.code);
3592
+ }
3593
+ function hasStringCode(value) {
3594
+ return value !== null && typeof value === "object" && !Array.isArray(value) && typeof value.code === "string";
3595
+ }
3508
3596
  function assertResolvedInListValues2(values) {
3509
3597
  const unresolved = values.find((item) => item.type === "VARIABLE");
3510
3598
  if (unresolved?.type === "VARIABLE") {
@@ -3515,20 +3603,20 @@ function evalNullCheck(expr, row) {
3515
3603
  const val = resolveField(expr.field, row);
3516
3604
  return expr.not ? val !== "" : val === "";
3517
3605
  }
3518
- function evalLogical(expr, row) {
3606
+ function evalLogical(expr, row, resolveFieldType) {
3519
3607
  if (expr.op === "AND") {
3520
- return evalWhere(expr.left, row) && evalWhere(expr.right, row);
3608
+ return evalWhere(expr.left, row, resolveFieldType) && evalWhere(expr.right, row, resolveFieldType);
3521
3609
  }
3522
- return evalWhere(expr.left, row) || evalWhere(expr.right, row);
3610
+ return evalWhere(expr.left, row, resolveFieldType) || evalWhere(expr.right, row, resolveFieldType);
3523
3611
  }
3524
- function resolveField(field, row) {
3612
+ function resolveField(field, row, resolveFieldType) {
3525
3613
  if (field.type === "FUNC_FIELD") return evalStringFunc(field.expr, row);
3526
3614
  if (field.type === "ARITH_FIELD") return String(evalArithExpr(field.expr, row));
3527
- if (field.type === "CASE_FIELD") return evalCaseWhen(field.expr, row);
3615
+ if (field.type === "CASE_FIELD") return evalCaseWhen(field.expr, row, resolveFieldType);
3528
3616
  const key = field.tableAlias ? `${field.tableAlias}.${field.field}` : field.field;
3529
3617
  return resolveFieldRef(row, key);
3530
3618
  }
3531
- function resolveValue(value, row) {
3619
+ function resolveValue(value, row, resolveFieldType) {
3532
3620
  switch (value.type) {
3533
3621
  case "VARIABLE":
3534
3622
  throw new Error(`ParseError: unresolved batch variable @${value.name}.`);
@@ -3551,14 +3639,14 @@ function resolveValue(value, row) {
3551
3639
  if (value.expr.type === "STRING_FUNC") return evalStringFunc(value.expr, row);
3552
3640
  return String(evalArithExpr(value.expr, row));
3553
3641
  case "CASE_VALUE":
3554
- return evalCaseWhen(value.expr, row);
3642
+ return evalCaseWhen(value.expr, row, resolveFieldType);
3555
3643
  case "ARRAY":
3556
3644
  return value.elements.map((e) => e.value).join(",");
3557
3645
  }
3558
3646
  }
3559
- function evalCaseWhen(expr, row) {
3647
+ function evalCaseWhen(expr, row, resolveFieldType) {
3560
3648
  for (const branch of expr.branches) {
3561
- if (evalWhere(branch.condition, row)) {
3649
+ if (evalWhere(branch.condition, row, resolveFieldType)) {
3562
3650
  return evalCaseResult(branch.result, row);
3563
3651
  }
3564
3652
  }
@@ -4225,9 +4313,9 @@ function applyJoin(leftRows, rightRows, join2) {
4225
4313
  }
4226
4314
  return result;
4227
4315
  }
4228
- function applyFilter(rows, where) {
4316
+ function applyFilter(rows, where, resolveFieldType) {
4229
4317
  if (where === null) return rows;
4230
- return rows.filter((row) => evalWhere(where, row));
4318
+ return rows.filter((row) => evalWhere(where, row, resolveFieldType));
4231
4319
  }
4232
4320
  function hasAggregateColumns(columns) {
4233
4321
  return columns.some(
@@ -4353,9 +4441,9 @@ function aggregateSyntheticName2(func, distinct, arg) {
4353
4441
  const argStr = aggregateArgLabel(arg);
4354
4442
  return distinct ? `${func}(DISTINCT ${argStr})` : `${func}(${argStr})`;
4355
4443
  }
4356
- function applyHaving(rows, having) {
4444
+ function applyHaving(rows, having, resolveFieldType) {
4357
4445
  if (having === null) return rows;
4358
- return rows.filter((row) => evalWhere(having, row));
4446
+ return rows.filter((row) => evalWhere(having, row, resolveFieldType));
4359
4447
  }
4360
4448
  function applyDistinct(rows, columns) {
4361
4449
  if (rows.length === 0) return rows;
@@ -4480,7 +4568,7 @@ function applyLimit(rows, limit, offset) {
4480
4568
  if (limit === null) return rows.slice(start);
4481
4569
  return rows.slice(start, start + limit);
4482
4570
  }
4483
- function project(rows, columns, scalarCache) {
4571
+ function project(rows, columns, scalarCache, resolveFieldType) {
4484
4572
  if (columns.length === 1 && columns[0].type === "WILDCARD") {
4485
4573
  const projected2 = rows.map((row) => stripParentShortcutColumns(row));
4486
4574
  const cols = projected2.length > 0 ? Object.keys(projected2[0]) : [];
@@ -4541,7 +4629,7 @@ function project(rows, columns, scalarCache) {
4541
4629
  }
4542
4630
  case "CASE_COL": {
4543
4631
  const key = outputKeys?.[colIdx] ?? col.alias ?? "case";
4544
- out[key] = evalCaseWhen(col.expr, row);
4632
+ out[key] = evalCaseWhen(col.expr, row, resolveFieldType);
4545
4633
  if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
4546
4634
  break;
4547
4635
  }
@@ -4677,7 +4765,15 @@ function resolveAggInStringFuncExpr(expr, rows) {
4677
4765
  };
4678
4766
  }
4679
4767
  function runFullScan(input) {
4680
- const { stmt, tables, scalarCache, optionOrders, sortKinds } = input;
4768
+ const {
4769
+ stmt,
4770
+ tables,
4771
+ scalarCache,
4772
+ optionOrders,
4773
+ sortKinds,
4774
+ fieldTypeResolver,
4775
+ havingFieldTypeResolver
4776
+ } = input;
4681
4777
  let rows = [];
4682
4778
  const mainAlias = stmt.from.alias;
4683
4779
  const mainRecords = tables.get(mainAlias) ?? tables.get(null) ?? [];
@@ -4688,17 +4784,17 @@ function runFullScan(input) {
4688
4784
  const rightRows = rightRecords.map((r) => flatten(r, rightAlias));
4689
4785
  rows = applyJoin(rows, rightRows, join2);
4690
4786
  }
4691
- rows = applyFilter(rows, stmt.where);
4787
+ rows = applyFilter(rows, stmt.where, fieldTypeResolver);
4692
4788
  if (stmt.groupBy.length > 0 || hasAggregateColumns(stmt.columns)) {
4693
4789
  rows = applyGroupBy(rows, stmt.groupBy, stmt.columns);
4694
4790
  }
4695
- rows = applyHaving(rows, stmt.having);
4791
+ rows = applyHaving(rows, stmt.having, havingFieldTypeResolver);
4696
4792
  if (stmt.distinct) {
4697
4793
  rows = applyDistinct(rows, stmt.columns);
4698
4794
  }
4699
4795
  rows = applyOrderBy(rows, stmt.orderBy, optionOrders, sortKinds);
4700
4796
  rows = applyLimit(rows, stmt.limit, stmt.offset);
4701
- return project(rows, stmt.columns, scalarCache);
4797
+ return project(rows, stmt.columns, scalarCache, fieldTypeResolver);
4702
4798
  }
4703
4799
 
4704
4800
  // src/converter/subtableAdapter.ts
@@ -4838,6 +4934,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
4838
4934
  throw new Error("ArgumentError: DROP TEMP TABLE requires a batch (temp tables are batch-scoped).");
4839
4935
  case "SET_VARIABLE":
4840
4936
  throw new Error("ArgumentError: SET variable requires a batch.");
4937
+ case "DECLARE_VARIABLE":
4938
+ throw new Error("ArgumentError: DECLARE variable requires a batch.");
4841
4939
  case "ASSERT":
4842
4940
  return executeAssert(stmt, client, options, cacheContext);
4843
4941
  }
@@ -4851,6 +4949,8 @@ var BatchTimeoutError = class extends Error {
4851
4949
  async function executeBatch(sql, client, options = {}) {
4852
4950
  const statements = parseSqlBatch(sql);
4853
4951
  const analysis = analyzeBatch(statements);
4952
+ const injectedVariables = validateDeclaredBatchVariables(statements, options.variables);
4953
+ const batchOptions = { ...options, variables: injectedVariables };
4854
4954
  if (options.continueOnError && analysis.containsDml) {
4855
4955
  throw new Error("ArgumentError: continueOnError is not allowed for batches containing DML.");
4856
4956
  }
@@ -4895,16 +4995,16 @@ async function executeBatch(sql, client, options = {}) {
4895
4995
  }
4896
4996
  try {
4897
4997
  const remaining = deadline !== null ? deadline - Date.now() : null;
4898
- const userConfirm = options.confirm;
4998
+ const userConfirm = batchOptions.confirm;
4899
4999
  const stmtOptions = userConfirm ? {
4900
- ...options,
5000
+ ...batchOptions,
4901
5001
  confirm: (count, operation) => userConfirm(count, operation, {
4902
5002
  statementIndex: i,
4903
5003
  statementCount: statements.length,
4904
5004
  statementType: info.statementType,
4905
5005
  targetAppId: info.targetAppId
4906
5006
  })
4907
- } : options;
5007
+ } : batchOptions;
4908
5008
  const outcome = await runWithDeadline(
4909
5009
  executeBatchStatement(statements[i], info, countedClient, stmtOptions, cacheContext, tempTables, variables),
4910
5010
  remaining
@@ -4917,7 +5017,7 @@ async function executeBatch(sql, client, options = {}) {
4917
5017
  aborted = "timeout";
4918
5018
  } else if (e instanceof AssertError) {
4919
5019
  aborted = "assertion";
4920
- } else if (info.statementType === "SET_VARIABLE") {
5020
+ } else if (info.statementType === "SET_VARIABLE" || info.statementType === "DECLARE_VARIABLE") {
4921
5021
  aborted = "fail-fast";
4922
5022
  } else if (!options.continueOnError) {
4923
5023
  aborted = "fail-fast";
@@ -4957,6 +5057,16 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
4957
5057
  }
4958
5058
  return {};
4959
5059
  }
5060
+ if (stmt.type === "DECLARE_VARIABLE") {
5061
+ const injected = options.variables ?? {};
5062
+ if (Object.prototype.hasOwnProperty.call(injected, stmt.name)) {
5063
+ variables.set(stmt.name, { type: "string", value: injected[stmt.name] });
5064
+ } else {
5065
+ const value = evaluateScalarExpr(stmt.default);
5066
+ variables.set(stmt.name, { type: "string", value: String(value.value) });
5067
+ }
5068
+ return {};
5069
+ }
4960
5070
  const resolvedStmt = resolveVariableRefs(stmt, variables);
4961
5071
  if (resolvedStmt.type === "CREATE_TEMP_TABLE") {
4962
5072
  const materializeOptions = {
@@ -5217,6 +5327,7 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache) {
5217
5327
  if (isNoFromSelect(stmt)) {
5218
5328
  return executeNoFromSelect(stmt);
5219
5329
  }
5330
+ await resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache);
5220
5331
  const mode = resolveSelectMode(stmt);
5221
5332
  await validateSelectFieldCodes(stmt, mode, client, cacheContext);
5222
5333
  if (mode === "SIMPLE") {
@@ -5275,6 +5386,8 @@ function executeNoFromSelect(stmt) {
5275
5386
  }
5276
5387
  async function executeSimpleSelect(stmt, client, options, cacheContext) {
5277
5388
  const params = selectToKintoneParams(stmt);
5389
+ const typedInFieldTypes = await loadTypedInFieldTypes(stmt, client, cacheContext);
5390
+ const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
5278
5391
  const maxRecords = options.maxRecords ?? 1e4;
5279
5392
  const warnings = /* @__PURE__ */ new Set();
5280
5393
  const onLimit = options.onLimitReached ?? "error";
@@ -5311,7 +5424,12 @@ async function executeSimpleSelect(stmt, client, options, cacheContext) {
5311
5424
  rows = applyOrderBy(rows, stmt.orderBy, optionOrders, sortKinds);
5312
5425
  rows = applyLimit(rows, stmt.limit, stmt.offset);
5313
5426
  }
5314
- const { rows: projected, columns } = project(rows, stmt.columns);
5427
+ const { rows: projected, columns } = project(
5428
+ rows,
5429
+ stmt.columns,
5430
+ void 0,
5431
+ fieldTypeResolvers.row
5432
+ );
5315
5433
  return { type: "SELECT", rows: projected, columns, rowCount: projected.length, warnings: [...warnings] };
5316
5434
  }
5317
5435
  async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
@@ -5384,6 +5502,108 @@ async function loadNumericPushdownFieldTypes(stmt, client, cacheContext) {
5384
5502
  const entries = await Promise.all([...appIds].map(async (appId) => [appId, await getFieldTypeMap(appId, client, cacheContext)]));
5385
5503
  return new Map(entries);
5386
5504
  }
5505
+ function collectTypedInFieldRefs(expr, out) {
5506
+ if (expr === null) return;
5507
+ switch (expr.type) {
5508
+ case "BINARY":
5509
+ if ((expr.op === "IN" || expr.op === "NOT_IN") && expr.left.type === "FIELD") {
5510
+ out.push(expr.left);
5511
+ }
5512
+ if (expr.left.type === "CASE_FIELD") collectCaseTypedInFieldRefs(expr.left.expr, out);
5513
+ if (expr.right.type === "CASE_VALUE") collectCaseTypedInFieldRefs(expr.right.expr, out);
5514
+ return;
5515
+ case "LOGICAL":
5516
+ collectTypedInFieldRefs(expr.left, out);
5517
+ collectTypedInFieldRefs(expr.right, out);
5518
+ return;
5519
+ case "NOT":
5520
+ case "GROUP":
5521
+ collectTypedInFieldRefs(expr.expr, out);
5522
+ return;
5523
+ case "NULL_CHECK":
5524
+ case "EXISTS":
5525
+ return;
5526
+ }
5527
+ }
5528
+ function collectCaseTypedInFieldRefs(expr, out) {
5529
+ for (const branch of expr.branches) collectTypedInFieldRefs(branch.condition, out);
5530
+ }
5531
+ function collectSelectTypedInFieldRefs(stmt) {
5532
+ const refs = [];
5533
+ collectTypedInFieldRefs(stmt.where, refs);
5534
+ collectTypedInFieldRefs(stmt.having, refs);
5535
+ for (const column of stmt.columns) {
5536
+ if (column.type === "CASE_COL") collectCaseTypedInFieldRefs(column.expr, refs);
5537
+ }
5538
+ return refs;
5539
+ }
5540
+ function findTableForAlias(stmt, alias) {
5541
+ return [stmt.from, ...stmt.joins.map((join2) => join2.table)].find((table) => table.alias === alias);
5542
+ }
5543
+ function physicalSelectTables(stmt) {
5544
+ return [stmt.from, ...stmt.joins.map((join2) => join2.table)].filter((table) => table.cteName === null);
5545
+ }
5546
+ async function loadTypedInFieldTypes(stmt, client, cacheContext) {
5547
+ const refs = collectSelectTypedInFieldRefs(stmt);
5548
+ if (refs.length === 0) return /* @__PURE__ */ new Map();
5549
+ const appIds = /* @__PURE__ */ new Set();
5550
+ const physicalTables = physicalSelectTables(stmt);
5551
+ for (const ref of refs) {
5552
+ if (ref.tableAlias !== null) {
5553
+ if (ref.tableAlias === "_p" && stmt.from.subtableCode && stmt.from.cteName === null) {
5554
+ appIds.add(stmt.from.appId);
5555
+ continue;
5556
+ }
5557
+ const table = findTableForAlias(stmt, ref.tableAlias);
5558
+ if (table && table.cteName === null) appIds.add(table.appId);
5559
+ continue;
5560
+ }
5561
+ if (stmt.joins.length === 0) {
5562
+ if (stmt.from.cteName === null) appIds.add(stmt.from.appId);
5563
+ continue;
5564
+ }
5565
+ for (const table of physicalTables) appIds.add(table.appId);
5566
+ }
5567
+ const entries = await Promise.all([...appIds].map(async (appId) => [appId, await getFieldTypeMap(appId, client, cacheContext)]));
5568
+ return new Map(entries);
5569
+ }
5570
+ function fieldCodeForTypeLookup(table, field) {
5571
+ if (table.subtableCode && field.startsWith("_p.")) return field.slice(3);
5572
+ return field;
5573
+ }
5574
+ function buildSelectFieldTypeResolvers(stmt, fieldTypesByApp) {
5575
+ const tables = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
5576
+ const physicalTables = tables.filter((table) => table.cteName === null);
5577
+ const outputAliases = new Set(
5578
+ stmt.columns.map((column) => "alias" in column ? column.alias : null).filter((alias) => alias !== null)
5579
+ );
5580
+ const row = (field) => {
5581
+ if (field.tableAlias !== null) {
5582
+ if (field.tableAlias === "_p" && stmt.from.subtableCode && stmt.from.cteName === null) {
5583
+ return fieldTypesByApp.get(stmt.from.appId)?.get(field.field);
5584
+ }
5585
+ const table2 = tables.find((candidate) => candidate.alias === field.tableAlias);
5586
+ if (!table2 || table2.cteName !== null) return void 0;
5587
+ return fieldTypesByApp.get(table2.appId)?.get(fieldCodeForTypeLookup(table2, field.field));
5588
+ }
5589
+ if (stmt.joins.length === 0) {
5590
+ if (stmt.from.cteName !== null) return void 0;
5591
+ return fieldTypesByApp.get(stmt.from.appId)?.get(fieldCodeForTypeLookup(stmt.from, field.field));
5592
+ }
5593
+ if (tables.some((table2) => table2.cteName !== null)) return void 0;
5594
+ const matches = physicalTables.filter(
5595
+ (table2) => fieldTypesByApp.get(table2.appId)?.has(fieldCodeForTypeLookup(table2, field.field))
5596
+ );
5597
+ if (matches.length !== 1) return void 0;
5598
+ const table = matches[0];
5599
+ return fieldTypesByApp.get(table.appId)?.get(fieldCodeForTypeLookup(table, field.field));
5600
+ };
5601
+ const having = (field) => {
5602
+ if (field.tableAlias === null && outputAliases.has(field.field)) return void 0;
5603
+ return row(field);
5604
+ };
5605
+ return { row, having };
5606
+ }
5387
5607
  async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache) {
5388
5608
  const maxRecords = options.maxRecords ?? 1e4;
5389
5609
  const warnings = /* @__PURE__ */ new Set();
@@ -5392,7 +5612,11 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
5392
5612
  resolveSubqueries(stmt.where, client, options, cacheContext, cteCache),
5393
5613
  resolveSubqueries(stmt.having, client, options, cacheContext, cteCache)
5394
5614
  ]);
5395
- const pushdownFieldTypes = await loadNumericPushdownFieldTypes(stmt, client, cacheContext);
5615
+ const [pushdownFieldTypes, typedInFieldTypes] = await Promise.all([
5616
+ loadNumericPushdownFieldTypes(stmt, client, cacheContext),
5617
+ loadTypedInFieldTypes(stmt, client, cacheContext)
5618
+ ]);
5619
+ const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
5396
5620
  const mainPushDown = extractMainSafePushdown(
5397
5621
  stmt,
5398
5622
  pushdownFieldTypes.get(stmt.from.appId)
@@ -5481,7 +5705,15 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
5481
5705
  }));
5482
5706
  const scalarCache = await scalarCachePromise;
5483
5707
  const { optionOrders, sortKinds } = await orderByMetaPromise;
5484
- const { rows, columns } = runFullScan({ tables, stmt, scalarCache, optionOrders, sortKinds });
5708
+ const { rows, columns } = runFullScan({
5709
+ tables,
5710
+ stmt,
5711
+ scalarCache,
5712
+ optionOrders,
5713
+ sortKinds,
5714
+ fieldTypeResolver: fieldTypeResolvers.row,
5715
+ havingFieldTypeResolver: fieldTypeResolvers.having
5716
+ });
5485
5717
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
5486
5718
  }
5487
5719
  async function executeUnion(stmt, client, options, cacheContext) {
@@ -5637,8 +5869,11 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
5637
5869
  const parallel = options.fetchParallel ?? 1;
5638
5870
  await Promise.all([
5639
5871
  resolveSubqueries(stmt.where, client, options, cacheContext, cteCache),
5640
- resolveSubqueries(stmt.having, client, options, cacheContext, cteCache)
5872
+ resolveSubqueries(stmt.having, client, options, cacheContext, cteCache),
5873
+ resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache)
5641
5874
  ]);
5875
+ const typedInFieldTypes = await loadTypedInFieldTypes(stmt, client, cacheContext);
5876
+ const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
5642
5877
  const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
5643
5878
  const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
5644
5879
  scalarCachePromise.catch(() => {
@@ -5693,7 +5928,15 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
5693
5928
  await Promise.all(joinFetches);
5694
5929
  const scalarCache = await scalarCachePromise;
5695
5930
  const { optionOrders, sortKinds } = await orderByMetaPromise;
5696
- const { rows, columns } = runFullScan({ tables, stmt, scalarCache, optionOrders, sortKinds });
5931
+ const { rows, columns } = runFullScan({
5932
+ tables,
5933
+ stmt,
5934
+ scalarCache,
5935
+ optionOrders,
5936
+ sortKinds,
5937
+ fieldTypeResolver: fieldTypeResolvers.row,
5938
+ havingFieldTypeResolver: fieldTypeResolvers.having
5939
+ });
5697
5940
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
5698
5941
  }
5699
5942
  function processRowToKintoneRecord(row) {
@@ -6190,6 +6433,16 @@ async function executeUpsert(stmt, client, options, cacheContext) {
6190
6433
  updatedCount: toUpdate.length
6191
6434
  };
6192
6435
  }
6436
+ async function buildSubtableFieldTypeResolver(appId, typedInRefs, client, cacheContext) {
6437
+ if (typedInRefs.length === 0) return void 0;
6438
+ const fieldTypes = await getFieldTypeMap(appId, client, cacheContext);
6439
+ return (field) => {
6440
+ if (field.tableAlias !== null && field.tableAlias !== "_p") return void 0;
6441
+ if (field.tableAlias === "_p") return fieldTypes.get(field.field);
6442
+ const code = field.field.startsWith("_p.") ? field.field.slice(3) : field.field;
6443
+ return fieldTypes.get(code);
6444
+ };
6445
+ }
6193
6446
  async function executeInsertSubtable(stmt, client, options, _cacheContext) {
6194
6447
  const subtableCode = stmt.subtableCode;
6195
6448
  const pidIndex = stmt.fields.indexOf("_pid");
@@ -6230,11 +6483,24 @@ async function executeInsertSubtable(stmt, client, options, _cacheContext) {
6230
6483
  }
6231
6484
  return { type: "INSERT", createdIds: [], insertedCount: stmt.values.length };
6232
6485
  }
6233
- async function executeUpdateSubtable(stmt, client, options, _cacheContext) {
6486
+ async function executeUpdateSubtable(stmt, client, options, cacheContext) {
6234
6487
  const subtableCode = stmt.subtableCode;
6235
6488
  if (!hasRidCondition(stmt.where)) {
6236
6489
  throw new Error("\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB UPDATE \u306B\u306F _rid \u6761\u4EF6\u304C\u5FC5\u9808\u3067\u3059");
6237
6490
  }
6491
+ const typedInRefs = [];
6492
+ collectTypedInFieldRefs(stmt.where, typedInRefs);
6493
+ for (const assignment of stmt.assignments) {
6494
+ if (assignment.value.type === "CASE_VALUE") {
6495
+ collectCaseTypedInFieldRefs(assignment.value.expr, typedInRefs);
6496
+ }
6497
+ }
6498
+ const resolveFieldType = await buildSubtableFieldTypeResolver(
6499
+ stmt.appId,
6500
+ typedInRefs,
6501
+ client,
6502
+ cacheContext
6503
+ );
6238
6504
  const parents = await fetchAll(
6239
6505
  client.getRecords,
6240
6506
  stmt.appId,
@@ -6243,7 +6509,7 @@ async function executeUpdateSubtable(stmt, client, options, _cacheContext) {
6243
6509
  { maxRecords: options.maxRecords ?? 1e4, parallel: options.fetchParallel ?? 1 }
6244
6510
  );
6245
6511
  const expanded = expandRowsForSubtableDml(parents, subtableCode);
6246
- const targets = expanded.filter((r) => evalWhere(stmt.where, r.flat));
6512
+ const targets = expanded.filter((r) => evalWhere(stmt.where, r.flat, resolveFieldType));
6247
6513
  if (options.confirm) {
6248
6514
  const ok = await options.confirm(targets.length, "UPDATE");
6249
6515
  if (!ok) throw new OperationCancelledError("UPDATE", targets.length);
@@ -6263,7 +6529,7 @@ async function executeUpdateSubtable(stmt, client, options, _cacheContext) {
6263
6529
  if (a.field.startsWith("_")) {
6264
6530
  throw new Error(`\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB UPDATE \u3067\u30B7\u30B9\u30C6\u30E0\u5217\u300C${a.field}\u300D\u306F\u66F4\u65B0\u3067\u304D\u307E\u305B\u3093`);
6265
6531
  }
6266
- updates[a.field] = { value: evalAssignmentValueForSubtable(a.value, t.flat) };
6532
+ updates[a.field] = { value: evalAssignmentValueForSubtable(a.value, t.flat, resolveFieldType) };
6267
6533
  }
6268
6534
  byRid.set(t.rowId, updates);
6269
6535
  }
@@ -6285,11 +6551,19 @@ async function executeUpdateSubtable(stmt, client, options, _cacheContext) {
6285
6551
  }
6286
6552
  return { type: "UPDATE", updatedCount: targets.length };
6287
6553
  }
6288
- async function executeDeleteSubtable(stmt, client, options, _cacheContext) {
6554
+ async function executeDeleteSubtable(stmt, client, options, cacheContext) {
6289
6555
  const subtableCode = stmt.subtableCode;
6290
6556
  if (!hasRidCondition(stmt.where)) {
6291
6557
  throw new Error("\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB DELETE \u306B\u306F _rid \u6761\u4EF6\u304C\u5FC5\u9808\u3067\u3059");
6292
6558
  }
6559
+ const typedInRefs = [];
6560
+ collectTypedInFieldRefs(stmt.where, typedInRefs);
6561
+ const resolveFieldType = await buildSubtableFieldTypeResolver(
6562
+ stmt.appId,
6563
+ typedInRefs,
6564
+ client,
6565
+ cacheContext
6566
+ );
6293
6567
  const parents = await fetchAll(
6294
6568
  client.getRecords,
6295
6569
  stmt.appId,
@@ -6298,7 +6572,7 @@ async function executeDeleteSubtable(stmt, client, options, _cacheContext) {
6298
6572
  { maxRecords: options.maxRecords ?? 1e4, parallel: options.fetchParallel ?? 1 }
6299
6573
  );
6300
6574
  const expanded = expandRowsForSubtableDml(parents, subtableCode);
6301
- const targets = expanded.filter((r) => evalWhere(stmt.where, r.flat));
6575
+ const targets = expanded.filter((r) => evalWhere(stmt.where, r.flat, resolveFieldType));
6302
6576
  if (options.confirm) {
6303
6577
  const ok = await options.confirm(targets.length, "DELETE");
6304
6578
  if (!ok) throw new OperationCancelledError("DELETE", targets.length);
@@ -6413,11 +6687,11 @@ function buildSubtableReorderPutParams(appId, parentId, revision, subtableCode,
6413
6687
  ]
6414
6688
  };
6415
6689
  }
6416
- function evalAssignmentValueForSubtable(value, row) {
6690
+ function evalAssignmentValueForSubtable(value, row, resolveFieldType) {
6417
6691
  if (value.type === "STRING") return value.value;
6418
6692
  if (value.type === "NUMBER") return String(value.value);
6419
6693
  if (value.type === "ARITH") return String(evalArithExpr(value, row));
6420
- if (value.type === "CASE_VALUE") return evalCaseWhen(value.expr, row);
6694
+ if (value.type === "CASE_VALUE") return evalCaseWhen(value.expr, row, resolveFieldType);
6421
6695
  throw new Error(`${value.type} \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB UPDATE \u306E\u5024\u3068\u3057\u3066\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`);
6422
6696
  }
6423
6697
  function valueToString(value) {
@@ -6449,7 +6723,15 @@ function hasRidCondition(where) {
6449
6723
  return false;
6450
6724
  }
6451
6725
  }
6452
- async function executeReorder(stmt, client, options, _cacheContext) {
6726
+ async function executeReorder(stmt, client, options, cacheContext) {
6727
+ const typedInRefs = [];
6728
+ collectTypedInFieldRefs(stmt.where, typedInRefs);
6729
+ const resolveFieldType = await buildSubtableFieldTypeResolver(
6730
+ stmt.appId,
6731
+ typedInRefs,
6732
+ client,
6733
+ cacheContext
6734
+ );
6453
6735
  const parents = await fetchAll(
6454
6736
  client.getRecords,
6455
6737
  stmt.appId,
@@ -6458,7 +6740,7 @@ async function executeReorder(stmt, client, options, _cacheContext) {
6458
6740
  { maxRecords: options.maxRecords ?? 1e4, parallel: options.fetchParallel ?? 1 }
6459
6741
  );
6460
6742
  const expanded = expandRowsForSubtableDml(parents, stmt.subtableCode);
6461
- const targetParentIds = stmt.all ? new Set(parents.map((p) => String(p["$id"]?.value ?? "")).filter((id) => id !== "")) : new Set(expanded.filter((r) => stmt.where && evalWhere(stmt.where, r.flat)).map((r) => r.parentId));
6743
+ const targetParentIds = stmt.all ? new Set(parents.map((p) => String(p["$id"]?.value ?? "")).filter((id) => id !== "")) : new Set(expanded.filter((r) => stmt.where && evalWhere(stmt.where, r.flat, resolveFieldType)).map((r) => r.parentId));
6462
6744
  if (options.confirm) {
6463
6745
  const ok = await options.confirm(targetParentIds.size, "UPDATE");
6464
6746
  if (!ok) throw new OperationCancelledError("UPDATE", targetParentIds.size);
@@ -6603,6 +6885,16 @@ async function resolveSubqueries(where, client, options, cacheContext, cteCache)
6603
6885
  collectSubqueryTasks(where, client, options, cacheContext, tasks, cteCache);
6604
6886
  await Promise.all(tasks);
6605
6887
  }
6888
+ async function resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache) {
6889
+ const tasks = [];
6890
+ for (const column of stmt.columns) {
6891
+ if (column.type !== "CASE_COL") continue;
6892
+ for (const branch of column.expr.branches) {
6893
+ tasks.push(resolveSubqueries(branch.condition, client, options, cacheContext, cteCache));
6894
+ }
6895
+ }
6896
+ await Promise.all(tasks);
6897
+ }
6606
6898
  function runSubquery(query, client, options, cacheContext, cteCache) {
6607
6899
  if (cteCache !== void 0 && cteCache.size > 0) {
6608
6900
  return executeQueryWithCte(query, client, options, cteCache, cacheContext);
@@ -6682,9 +6974,10 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
6682
6974
  pending.forEach(([i], idx) => cache.set(i, values[idx]));
6683
6975
  return cache;
6684
6976
  }
6685
- function buildBatchExplainPlans(sql) {
6977
+ function buildBatchExplainPlans(sql, injectedVariables) {
6686
6978
  const statements = parseSqlBatch(sql);
6687
6979
  const analysis = analyzeBatch(statements);
6980
+ validateDeclaredBatchVariables(statements, injectedVariables);
6688
6981
  const variables = /* @__PURE__ */ new Map();
6689
6982
  return {
6690
6983
  statementCount: statements.length,
@@ -6695,7 +6988,7 @@ function buildBatchExplainPlans(sql) {
6695
6988
  type: analysis.statements[i].statementType,
6696
6989
  plan: buildBatchStatementPlan(planStmt, analysis.statements[i])
6697
6990
  };
6698
- if (stmt.type === "SET_VARIABLE") {
6991
+ if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
6699
6992
  variables.set(stmt.name, { type: "string", value: `@${stmt.name}` });
6700
6993
  }
6701
6994
  return result;
@@ -6732,6 +7025,12 @@ function buildBatchStatementPlan(stmt, info) {
6732
7025
  " 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"
6733
7026
  ];
6734
7027
  }
7028
+ if (stmt.type === "DECLARE_VARIABLE") {
7029
+ return [
7030
+ `DECLARE @${stmt.name} = <default scalar expression>`,
7031
+ " value: \u5916\u90E8\u6CE8\u5165\u304C\u3042\u308C\u3070\u63A1\u7528\u3001\u306A\u3051\u308C\u3070\u65E2\u5B9A\u5024\u3092\u5B9F\u884C\u6642\u306B1\u56DE\u8A55\u4FA1\uFF08\u5024\u306F\u975E\u516C\u958B\uFF09"
7032
+ ];
7033
+ }
6735
7034
  if (stmt.type === "SHOW_APPS") return ["SHOW APPS\uFF08\u30A2\u30D7\u30EA\u4E00\u89A7\u306E\u53D6\u5F97\uFF09"];
6736
7035
  if (stmt.type === "DESCRIBE") return [`DESCRIBE APP${stmt.appId}\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u5B9A\u7FA9\u306E\u53D6\u5F97\uFF09`];
6737
7036
  if (stmt.type === "EXPLAIN") return buildPlanForBatchQuery(stmt.query, info);
@@ -7525,6 +7824,42 @@ function clampInt(v, min, max) {
7525
7824
  return Math.max(min, Math.min(max, Math.trunc(v)));
7526
7825
  }
7527
7826
 
7827
+ // src/core/formFieldInfo.ts
7828
+ function flattenFormFieldProperties(properties) {
7829
+ const out = [];
7830
+ for (const field of Object.values(properties)) {
7831
+ out.push({
7832
+ code: field.code,
7833
+ label: field.label,
7834
+ fieldType: field.type,
7835
+ optionOrder: toOptionOrderMap(field.options),
7836
+ sortKind: detectSortKind(field.type, field.format)
7837
+ });
7838
+ if (field.fields) out.push(...flattenFormFieldProperties(field.fields));
7839
+ }
7840
+ return out;
7841
+ }
7842
+ function toOptionOrderMap(options) {
7843
+ if (!options || typeof options !== "object") return void 0;
7844
+ const order = {};
7845
+ let hasAny = false;
7846
+ for (const [label, meta] of Object.entries(options)) {
7847
+ const n = Number(meta?.index);
7848
+ if (!Number.isFinite(n)) continue;
7849
+ order[label] = n;
7850
+ hasAny = true;
7851
+ }
7852
+ return hasAny ? order : void 0;
7853
+ }
7854
+ function detectSortKind(fieldType, calcFormat) {
7855
+ if (fieldType === "NUMBER" || fieldType === "RECORD_NUMBER") return "number";
7856
+ if (fieldType === "CALC") {
7857
+ if (calcFormat === "NUMBER" || calcFormat === "NUMBER_DIGIT") return "number";
7858
+ return "string";
7859
+ }
7860
+ return void 0;
7861
+ }
7862
+
7528
7863
  // src/cli/nodeKintoneClient.ts
7529
7864
  function createNodeKintoneClient(baseUrl, tokenResolver) {
7530
7865
  const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
@@ -7703,36 +8038,10 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
7703
8038
  { method: "GET" },
7704
8039
  appId
7705
8040
  );
7706
- return Object.values(res.properties).map((f) => ({
7707
- code: f.code,
7708
- label: f.label,
7709
- fieldType: f.type,
7710
- optionOrder: toOptionOrderMap(f.options),
7711
- sortKind: detectSortKind(f.type, f.format)
7712
- }));
8041
+ return flattenFormFieldProperties(res.properties);
7713
8042
  }
7714
8043
  };
7715
8044
  }
7716
- function toOptionOrderMap(options) {
7717
- if (!options || typeof options !== "object") return void 0;
7718
- const order = {};
7719
- let hasAny = false;
7720
- for (const [label, meta] of Object.entries(options)) {
7721
- const n = Number(meta?.index);
7722
- if (!Number.isFinite(n)) continue;
7723
- order[label] = n;
7724
- hasAny = true;
7725
- }
7726
- return hasAny ? order : void 0;
7727
- }
7728
- function detectSortKind(fieldType, calcFormat) {
7729
- if (fieldType === "NUMBER" || fieldType === "RECORD_NUMBER") return "number";
7730
- if (fieldType === "CALC") {
7731
- if (calcFormat === "NUMBER" || calcFormat === "NUMBER_DIGIT") return "number";
7732
- return "string";
7733
- }
7734
- return void 0;
7735
- }
7736
8045
 
7737
8046
  // src/node/appProfiles.ts
7738
8047
  var import_fs = require("fs");
@@ -8193,6 +8502,7 @@ Options:
8193
8502
  -f, --file <path> Execute SQL file
8194
8503
  --console Start interactive console mode
8195
8504
  --dry-run Parse and show execution plan only
8505
+ --var <name=value> Override a DECLARE variable (repeatable; not for secrets)
8196
8506
  --format <type> Output format: table | json | jsonl | csv | markdown | md
8197
8507
  (batch + json: prints one JSON envelope for the whole batch)
8198
8508
  --max-records <n> Max records to fetch (default: 500)
@@ -8262,6 +8572,8 @@ function parseArgs(argv) {
8262
8572
  password: null,
8263
8573
  token: null,
8264
8574
  tokenMap: {},
8575
+ // `__proto__` も有効な変数名なので prototype のない辞書で保持する。
8576
+ variables: /* @__PURE__ */ Object.create(null),
8265
8577
  tokenFile: null,
8266
8578
  app: null,
8267
8579
  diagRecordId: null,
@@ -8356,6 +8668,19 @@ function parseArgs(argv) {
8356
8668
  continue;
8357
8669
  }
8358
8670
  const v = argv[i + 1];
8671
+ if (a === "--var") {
8672
+ const raw = v ?? "";
8673
+ const eq = raw.indexOf("=");
8674
+ if (eq < 0) throw new Error("ArgumentError: --var must use name=value.");
8675
+ const rawName = raw.slice(0, eq);
8676
+ const name = normalizeBatchVariableName(rawName);
8677
+ if (Object.prototype.hasOwnProperty.call(out.variables, name)) {
8678
+ throw new Error(`ArgumentError: variable "${rawName}" is specified more than once.`);
8679
+ }
8680
+ out.variables[name] = raw.slice(eq + 1);
8681
+ i++;
8682
+ continue;
8683
+ }
8359
8684
  if (a === "-e" || a === "--execute") {
8360
8685
  out.executeSql = v ?? "";
8361
8686
  i++;
@@ -8912,6 +9237,7 @@ function buildReplExecArgv(base, sql, dryRun, format) {
8912
9237
  const argv = ["-e", sql];
8913
9238
  if (dryRun) argv.push("--dry-run");
8914
9239
  if (format) argv.push("--format", format);
9240
+ for (const [name, value] of Object.entries(base.variables)) argv.push("--var", `${name}=${value}`);
8915
9241
  pushOpt(argv, "--config", base.configPath);
8916
9242
  pushOpt(argv, "--profile", base.profile);
8917
9243
  pushOpt(argv, "--base-url", base.baseUrl);
@@ -9535,6 +9861,10 @@ async function run() {
9535
9861
  attachmentFormat: args.attachmentFormat ?? profile.output?.attachmentFormat ?? "full"
9536
9862
  };
9537
9863
  const appIds = sql ? extractAppIds(sql) : [];
9864
+ if (!isBatchSql && Object.keys(args.variables).length > 0) {
9865
+ process.stderr.write("ArgumentError: --var requires a batch containing DECLARE.\n");
9866
+ return 2;
9867
+ }
9538
9868
  const defaultApp = args.app ?? envInt2("KSQL_APP") ?? profile.app ?? null;
9539
9869
  if (appIds.length === 0 && defaultApp !== null) appIds.push(defaultApp);
9540
9870
  const allowNoFromSelect = isNoFromSelectStatement(parsedStmt) || stmtType === "SHOW_APPS";
@@ -9550,7 +9880,7 @@ async function run() {
9550
9880
  if (args.dryRun) {
9551
9881
  let plans;
9552
9882
  try {
9553
- plans = buildBatchExplainPlans(sql);
9883
+ plans = buildBatchExplainPlans(sql, args.variables);
9554
9884
  } catch (err) {
9555
9885
  const restored = sourceSql && sqlDiagnosticContext ? restoreSqlContextError(err, sourceSql, {
9556
9886
  bindings: sqlDiagnosticContext.appBindingByMappedApp,
@@ -9867,6 +10197,7 @@ query=${label}`);
9867
10197
  continueOnError: args.continueOnError,
9868
10198
  tempTableMaxRows,
9869
10199
  timeoutMs: timeout,
10200
+ variables: args.variables,
9870
10201
  confirm: batchContainsDml ? async (count, operation) => {
9871
10202
  if (count > dmlMaxRows) {
9872
10203
  throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed --dml-max-rows (${dmlMaxRows}).`);