@rex0220/kintone-sql-tools 3.0.0 → 3.2.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.
@@ -31029,6 +31029,7 @@ var KEYWORDS = /* @__PURE__ */ new Map([
31029
31029
  ["LTRIM", "LTRIM" /* LTRIM */],
31030
31030
  ["RTRIM", "RTRIM" /* RTRIM */],
31031
31031
  ["LENGTH", "LENGTH" /* LENGTH */],
31032
+ ["LENGTH_CHAR", "LENGTH_CHAR" /* LENGTH_CHAR */],
31032
31033
  ["SUBSTRING", "SUBSTRING" /* SUBSTRING */],
31033
31034
  ["SUBSTR", "SUBSTR" /* SUBSTR */],
31034
31035
  ["CONCAT", "CONCAT" /* CONCAT */],
@@ -31041,6 +31042,7 @@ var KEYWORDS = /* @__PURE__ */ new Map([
31041
31042
  ["LEAST", "LEAST" /* LEAST */],
31042
31043
  ["LPAD", "LPAD" /* LPAD */],
31043
31044
  ["RPAD", "RPAD" /* RPAD */],
31045
+ ["TRANSLATE", "TRANSLATE" /* TRANSLATE */],
31044
31046
  ["CAST", "CAST" /* CAST */],
31045
31047
  ["CONVERT", "CONVERT" /* CONVERT */],
31046
31048
  ["FORMAT", "FORMAT" /* FORMAT */],
@@ -31400,10 +31402,12 @@ var FUNC_CALL_PREFIX_KINDS = /* @__PURE__ */ new Set([
31400
31402
  "LTRIM" /* LTRIM */,
31401
31403
  "RTRIM" /* RTRIM */,
31402
31404
  "LENGTH" /* LENGTH */,
31405
+ "LENGTH_CHAR" /* LENGTH_CHAR */,
31403
31406
  "SUBSTRING" /* SUBSTRING */,
31404
31407
  "SUBSTR" /* SUBSTR */,
31405
31408
  "CONCAT" /* CONCAT */,
31406
31409
  "REPLACE" /* REPLACE */,
31410
+ "TRANSLATE" /* TRANSLATE */,
31407
31411
  "COALESCE" /* COALESCE */,
31408
31412
  "NULLIF" /* NULLIF */,
31409
31413
  "ISNULL" /* ISNULL */,
@@ -31455,6 +31459,7 @@ var ParseError = class extends Error {
31455
31459
  var Parser = class {
31456
31460
  constructor(tokens) {
31457
31461
  this.tokens = tokens;
31462
+ this.allowUnaryPlusNumber = false;
31458
31463
  this.pos = 0;
31459
31464
  /** WITH 句で定義された CTE 名のセット(parseTableRef で参照) */
31460
31465
  this.cteNames = /* @__PURE__ */ new Set();
@@ -32223,8 +32228,16 @@ var Parser = class {
32223
32228
  this.expect(")" /* RPAREN */);
32224
32229
  return expr;
32225
32230
  }
32231
+ if (this.allowUnaryPlusNumber && this.peek().kind === "+" /* PLUS */) {
32232
+ this.advance();
32233
+ const number4 = this.expect("NUMBER" /* NUMBER */, "\u5358\u9805 + \u306E\u76F4\u5F8C\u306B\u306F\u6570\u5024\u30EA\u30C6\u30E9\u30EB\u304C\u5FC5\u8981\u3067\u3059");
32234
+ return { type: "NUMBER", value: Number(number4.value) };
32235
+ }
32226
32236
  if (this.peek().kind === "-" /* MINUS */) {
32227
32237
  this.advance();
32238
+ if (this.peek().kind === "-" /* MINUS */ || this.peek().kind === "+" /* PLUS */) {
32239
+ throw new ParseError("\u5358\u9805\u7B26\u53F7\u3092\u91CD\u306D\u3066\u6307\u5B9A\u3059\u308B\u3053\u3068\u306F\u3067\u304D\u307E\u305B\u3093", this.peek());
32240
+ }
32228
32241
  const operand = this.parseArithPrimary();
32229
32242
  if (operand.type === "NUMBER") return { type: "NUMBER", value: -operand.value };
32230
32243
  return { type: "ARITH", left: { type: "NUMBER", value: 0 }, op: "-", right: operand };
@@ -32334,10 +32347,12 @@ var Parser = class {
32334
32347
  ["LTRIM" /* LTRIM */]: "LTRIM",
32335
32348
  ["RTRIM" /* RTRIM */]: "RTRIM",
32336
32349
  ["LENGTH" /* LENGTH */]: "LENGTH",
32350
+ ["LENGTH_CHAR" /* LENGTH_CHAR */]: "LENGTH_CHAR",
32337
32351
  ["SUBSTRING" /* SUBSTRING */]: "SUBSTRING",
32338
32352
  ["SUBSTR" /* SUBSTR */]: "SUBSTRING",
32339
32353
  ["CONCAT" /* CONCAT */]: "CONCAT",
32340
32354
  ["REPLACE" /* REPLACE */]: "REPLACE",
32355
+ ["TRANSLATE" /* TRANSLATE */]: "TRANSLATE",
32341
32356
  ["COALESCE" /* COALESCE */]: "COALESCE",
32342
32357
  ["NULLIF" /* NULLIF */]: "NULLIF",
32343
32358
  ["ISNULL" /* ISNULL */]: "ISNULL",
@@ -33098,6 +33113,11 @@ var Parser = class {
33098
33113
  } else if (this.peek().kind === "IF" /* IF */) {
33099
33114
  const expr = this.parseIfExpr();
33100
33115
  row.push({ type: "CASE_VALUE", expr });
33116
+ } else if (this.peek().kind === "-" /* MINUS */ || this.peek().kind === "+" /* PLUS */) {
33117
+ const sign = this.advance();
33118
+ const number4 = this.expect("NUMBER" /* NUMBER */, "INSERT \u306E\u5358\u9805\u7B26\u53F7\u306E\u76F4\u5F8C\u306B\u306F\u6570\u5024\u30EA\u30C6\u30E9\u30EB\u304C\u5FC5\u8981\u3067\u3059");
33119
+ const value = Number(number4.value);
33120
+ row.push({ type: "NUMBER", value: sign.kind === "-" /* MINUS */ ? -value : value });
33101
33121
  } else {
33102
33122
  const tok = this.advance();
33103
33123
  if (tok.kind === "STRING" /* STRING */) {
@@ -33127,6 +33147,12 @@ var Parser = class {
33127
33147
  const { appId, subtableCode } = extractTableRef(name, this.prev());
33128
33148
  this.expect("SET" /* SET */);
33129
33149
  const assignments = this.parseAssignments();
33150
+ if (subtableCode && assignments.some((a) => a.value.type === "STRING_FUNC")) {
33151
+ throw new ParseError(
33152
+ "\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB UPDATE SET \u3067\u306F\u6587\u5B57\u5217\u95A2\u6570\u3092\u76F4\u63A5\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093",
33153
+ this.prev()
33154
+ );
33155
+ }
33130
33156
  let from = null;
33131
33157
  if (this.consume("FROM" /* FROM */)) {
33132
33158
  const table = this.parseTableRef();
@@ -33170,7 +33196,14 @@ var Parser = class {
33170
33196
  from.targetFilter = decomposed.targetFilter;
33171
33197
  } else if (assignments.some((a) => a.value.type === "SOURCE_FIELD")) {
33172
33198
  throw new ParseError(
33173
- "SET \u306E\u5024\u306B\u306F\u30EA\u30C6\u30E9\u30EB\u30FB\u7B97\u8853\u5F0F\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u306E\u307F\u306F\u4E0D\u53EF\uFF09",
33199
+ "SET \u306E\u5024\u306B\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u3092\u5358\u72EC\u3067\u6307\u5B9A\u3059\u308B\u3053\u3068\u306F\u3067\u304D\u307E\u305B\u3093",
33200
+ whereTok
33201
+ );
33202
+ } else if (assignments.some(
33203
+ (a) => a.value.type === "STRING_FUNC" && this.nodeContainsAnyQualifier(a.value)
33204
+ )) {
33205
+ throw new ParseError(
33206
+ "UPDATE SET \u306E\u6587\u5B57\u5217\u95A2\u6570\u3067\u306F\u66F4\u65B0\u5148\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u4FEE\u98FE\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044",
33174
33207
  whereTok
33175
33208
  );
33176
33209
  }
@@ -33237,6 +33270,12 @@ var Parser = class {
33237
33270
  }
33238
33271
  validateUpdateFromAssignments(assignments, sourceAlias, tok) {
33239
33272
  for (const assignment of assignments) {
33273
+ if (assignment.value.type === "STRING_FUNC") {
33274
+ throw new ParseError(
33275
+ "UPDATE ... FROM \u306E SET \u3067\u306F\u6587\u5B57\u5217\u95A2\u6570\u3092\u76F4\u63A5\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093",
33276
+ tok
33277
+ );
33278
+ }
33240
33279
  if (assignment.value.type === "SOURCE_FIELD") {
33241
33280
  if (assignment.value.alias.toLowerCase() !== sourceAlias.toLowerCase()) {
33242
33281
  throw new ParseError(`UPDATE ... FROM \u306E SET \u53C2\u7167\u306F\u30BD\u30FC\u30B9 alias ${sourceAlias} \u3067\u4FEE\u98FE\u3057\u3066\u304F\u3060\u3055\u3044`, tok);
@@ -33376,9 +33415,17 @@ var Parser = class {
33376
33415
  this.expect(")" /* RPAREN */);
33377
33416
  return { type: "SCALAR_SUBQUERY", query };
33378
33417
  }
33379
- const node = this.parseArithAddSub();
33418
+ const previousAllowUnaryPlusNumber = this.allowUnaryPlusNumber;
33419
+ this.allowUnaryPlusNumber = true;
33420
+ let node;
33421
+ try {
33422
+ node = this.parseArithAddSub();
33423
+ } finally {
33424
+ this.allowUnaryPlusNumber = previousAllowUnaryPlusNumber;
33425
+ }
33380
33426
  if (node.type === "NUMBER") return node;
33381
33427
  if (node.type === "ARITH") return node;
33428
+ if (node.type === "STRING_FUNC") return node;
33382
33429
  if (node.type === "FIELD_REF") {
33383
33430
  const dot = node.field.indexOf(".");
33384
33431
  if (dot > 0 && dot < node.field.length - 1) {
@@ -33386,7 +33433,7 @@ var Parser = class {
33386
33433
  }
33387
33434
  }
33388
33435
  throw new ParseError(
33389
- "SET \u306E\u5024\u306B\u306F\u30EA\u30C6\u30E9\u30EB\u30FB\u7B97\u8853\u5F0F\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u306E\u307F\u306F\u4E0D\u53EF\uFF09",
33436
+ "SET \u306E\u5024\u306B\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u3092\u5358\u72EC\u3067\u6307\u5B9A\u3059\u308B\u3053\u3068\u306F\u3067\u304D\u307E\u305B\u3093",
33390
33437
  tok
33391
33438
  );
33392
33439
  }
@@ -35399,6 +35446,43 @@ function applyRoundOp(op, num, digits) {
35399
35446
  if (digits > 0) return String(parseFloat(raw.toFixed(digits)));
35400
35447
  return String(raw);
35401
35448
  }
35449
+ function isHighSurrogate(codeUnit) {
35450
+ return codeUnit >= 55296 && codeUnit <= 56319;
35451
+ }
35452
+ function isLowSurrogate(codeUnit) {
35453
+ return codeUnit >= 56320 && codeUnit <= 57343;
35454
+ }
35455
+ function splitsSurrogatePair(value, index) {
35456
+ return index > 0 && index < value.length && isHighSurrogate(value.charCodeAt(index - 1)) && isLowSurrogate(value.charCodeAt(index));
35457
+ }
35458
+ function normalizeSliceIndex(index, length) {
35459
+ if (Number.isNaN(index) || index === Number.NEGATIVE_INFINITY) return 0;
35460
+ if (index === Number.POSITIVE_INFINITY) return length;
35461
+ const integer2 = Math.trunc(index);
35462
+ return integer2 < 0 ? Math.max(length + integer2, 0) : Math.min(integer2, length);
35463
+ }
35464
+ function sliceSafePrefix(value, budget) {
35465
+ let end = Math.min(Math.max(0, budget), value.length);
35466
+ if (splitsSurrogatePair(value, end)) end -= 1;
35467
+ return value.slice(0, end);
35468
+ }
35469
+ function sliceSafeSuffix(value, budget) {
35470
+ let start = Math.max(0, value.length - budget);
35471
+ if (splitsSurrogatePair(value, start)) start += 1;
35472
+ return value.slice(start);
35473
+ }
35474
+ function sliceSafeRange(value, rawStart, rawEnd) {
35475
+ let start = normalizeSliceIndex(rawStart, value.length);
35476
+ let end = normalizeSliceIndex(rawEnd, value.length);
35477
+ if (end <= start) return "";
35478
+ if (splitsSurrogatePair(value, start)) start += 1;
35479
+ if (splitsSurrogatePair(value, end)) end -= 1;
35480
+ return value.slice(start, Math.max(start, end));
35481
+ }
35482
+ function makeSafePadding(pad, gap) {
35483
+ const repeated = pad.repeat(Math.ceil(gap / pad.length));
35484
+ return sliceSafePrefix(repeated, gap);
35485
+ }
35402
35486
  function evalStringFunc(expr, row) {
35403
35487
  const args = expr.args.map((a) => evalStringFuncArg(a, row));
35404
35488
  switch (expr.func) {
@@ -35414,23 +35498,26 @@ function evalStringFunc(expr, row) {
35414
35498
  return (args[0] ?? "").trimEnd();
35415
35499
  case "LENGTH":
35416
35500
  return String((args[0] ?? "").length);
35501
+ case "LENGTH_CHAR":
35502
+ assertArity("LENGTH_CHAR", args, 1, 1);
35503
+ return String([...args[0] ?? ""].length);
35417
35504
  case "SUBSTRING": {
35418
35505
  const str = args[0] ?? "";
35419
35506
  const start = Math.max(0, Number(args[1] ?? "1") - 1);
35420
35507
  const len = args[2] !== void 0 ? Number(args[2]) : void 0;
35421
- return len !== void 0 ? str.slice(start, start + len) : str.slice(start);
35508
+ return sliceSafeRange(str, start, len !== void 0 ? start + len : str.length);
35422
35509
  }
35423
35510
  case "LEFT": {
35424
35511
  assertArity("LEFT", args, 2, 2);
35425
35512
  const str = args[0];
35426
35513
  const n = Math.trunc(Number(args[1]));
35427
- return Number.isNaN(n) || n <= 0 ? "" : str.slice(0, n);
35514
+ return Number.isNaN(n) || n <= 0 ? "" : sliceSafePrefix(str, n);
35428
35515
  }
35429
35516
  case "RIGHT": {
35430
35517
  assertArity("RIGHT", args, 2, 2);
35431
35518
  const str = args[0];
35432
35519
  const n = Math.trunc(Number(args[1]));
35433
- return Number.isNaN(n) || n <= 0 ? "" : str.slice(Math.max(0, str.length - n));
35520
+ return Number.isNaN(n) || n <= 0 ? "" : sliceSafeSuffix(str, n);
35434
35521
  }
35435
35522
  case "INSTR":
35436
35523
  assertArity("INSTR", args, 2, 2);
@@ -35441,10 +35528,11 @@ function evalStringFunc(expr, row) {
35441
35528
  const str = args[0];
35442
35529
  const n = Math.trunc(Number(args[1]));
35443
35530
  if (Number.isNaN(n) || n <= 0) return "";
35444
- if (str.length >= n) return str.slice(0, n);
35531
+ if (str.length >= n) return sliceSafePrefix(str, n);
35445
35532
  const pad = args[2] ?? " ";
35446
35533
  if (pad === "") return str;
35447
- return expr.func === "LPAD" ? str.padStart(n, pad) : str.padEnd(n, pad);
35534
+ const padding = makeSafePadding(pad, n - str.length);
35535
+ return expr.func === "LPAD" ? padding + str : str + padding;
35448
35536
  }
35449
35537
  case "GREATEST":
35450
35538
  case "LEAST":
@@ -35458,6 +35546,21 @@ function evalStringFunc(expr, row) {
35458
35546
  const to = args[2] ?? "";
35459
35547
  return from === "" ? str : str.split(from).join(to);
35460
35548
  }
35549
+ case "TRANSLATE": {
35550
+ assertArity("TRANSLATE", args, 3, 3);
35551
+ const from = [...args[1]];
35552
+ const to = [...args[2]];
35553
+ if (from.length !== to.length) {
35554
+ throw new Error(
35555
+ `ArgumentError: TRANSLATE \u306E from \u3068 to \u306F\u540C\u3058\u6587\u5B57\u6570\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\uFF08from=${from.length}, to=${to.length}\uFF09`
35556
+ );
35557
+ }
35558
+ const map2 = /* @__PURE__ */ new Map();
35559
+ from.forEach((ch, i) => {
35560
+ if (!map2.has(ch)) map2.set(ch, to[i]);
35561
+ });
35562
+ return [...args[0]].map((ch) => map2.get(ch) ?? ch).join("");
35563
+ }
35461
35564
  case "COALESCE":
35462
35565
  return args.find((a) => a !== "") ?? "";
35463
35566
  case "NULLIF":
@@ -35695,6 +35798,7 @@ function evalOp(op, leftStr, right, row, fieldType, resolveFieldType, semantics
35695
35798
  }
35696
35799
  var NUMERIC_STRING_FUNCTIONS = /* @__PURE__ */ new Set([
35697
35800
  "LENGTH",
35801
+ "LENGTH_CHAR",
35698
35802
  "INSTR",
35699
35803
  "ROUND",
35700
35804
  "FLOOR",
@@ -35942,7 +36046,7 @@ function updateToPutBatches(stmt, ids, fieldTypes = /* @__PURE__ */ new Map()) {
35942
36046
  function buildUpdateRecord(assignments, fieldTypes) {
35943
36047
  const record2 = {};
35944
36048
  for (const { field, value } of assignments) {
35945
- if (value.type === "ARITH" || value.type === "CASE_VALUE" || value.type === "SOURCE_FIELD") continue;
36049
+ if (value.type === "ARITH" || value.type === "CASE_VALUE" || value.type === "STRING_FUNC" || value.type === "SOURCE_FIELD") continue;
35946
36050
  record2[field] = { value: toKintoneValue(value, fieldTypes.get(field)) };
35947
36051
  }
35948
36052
  return record2;
@@ -35952,12 +36056,19 @@ function hasArithAssignment(stmt) {
35952
36056
  (a) => a.value.type === "ARITH" || a.value.type === "CASE_VALUE"
35953
36057
  );
35954
36058
  }
36059
+ function hasRowDependentAssignment(stmt) {
36060
+ return stmt.assignments.some(
36061
+ (a) => a.value.type === "ARITH" || a.value.type === "CASE_VALUE" || a.value.type === "STRING_FUNC"
36062
+ );
36063
+ }
35955
36064
  function updateToGetQueryForArith(stmt) {
35956
36065
  assertDmlWhereIsSafe(stmt.where);
35957
36066
  const refFields = /* @__PURE__ */ new Set();
35958
36067
  for (const { value } of stmt.assignments) {
35959
36068
  if (value.type === "ARITH") {
35960
36069
  collectArithFields2(value, refFields);
36070
+ } else if (value.type === "STRING_FUNC") {
36071
+ collectStringFuncFields2(value, refFields);
35961
36072
  } else if (value.type === "CASE_VALUE") {
35962
36073
  collectCaseFields(value.expr, refFields);
35963
36074
  }
@@ -36044,6 +36155,8 @@ function updateToPutBatchesArith(stmt, records, fieldTypes = /* @__PURE__ */ new
36044
36155
  for (const { field, value } of stmt.assignments) {
36045
36156
  if (value.type === "ARITH") {
36046
36157
  record2[field] = { value: String(evalArith(value, raw)) };
36158
+ } else if (value.type === "STRING_FUNC") {
36159
+ record2[field] = { value: evalStringFunc(value, row) };
36047
36160
  } else if (value.type === "CASE_VALUE") {
36048
36161
  record2[field] = { value: evalCaseWhenValue(value.expr, row, fieldTypes.get(field)) };
36049
36162
  } else if (value.type === "SOURCE_FIELD") {
@@ -36092,6 +36205,8 @@ function updateFromToPutBatches(stmt, matched, fieldTypes = /* @__PURE__ */ new
36092
36205
  throw new DmlConvertError(`\u6570\u5024\u30D5\u30A3\u30FC\u30EB\u30C9 ${field} \u306B\u5909\u63DB\u3067\u304D\u306A\u3044\u5024\u3067\u3059: ${raw}`);
36093
36206
  }
36094
36207
  record2[field] = { value: toKintoneValue({ type: "STRING", value: raw }, fieldType) };
36208
+ } else if (value.type === "STRING_FUNC") {
36209
+ throw new DmlConvertError("UPDATE ... FROM \u306E SET \u3067\u306F\u6587\u5B57\u5217\u95A2\u6570\u3092\u76F4\u63A5\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093");
36095
36210
  } else if (value.type === "ARITH") {
36096
36211
  record2[field] = { value: String(evalArith(value, target)) };
36097
36212
  } else if (value.type === "CASE_VALUE") {
@@ -36543,7 +36658,7 @@ var KORDER_NATIVE_FIELD_TYPES = /* @__PURE__ */ new Set([
36543
36658
  "CREATOR",
36544
36659
  "MODIFIER"
36545
36660
  ]);
36546
- function planKorderNative(input) {
36661
+ function planKorder(input) {
36547
36662
  const { stmt } = input;
36548
36663
  const reasons = [];
36549
36664
  if (stmt.orderMode !== "KINTONE_NATIVE") reasons.push("KORDER_MODE_REQUIRED");
@@ -36573,29 +36688,120 @@ function planKorderNative(input) {
36573
36688
  reasons.push(`KORDER_TYPE_UNSUPPORTED(field=${name}, type=${semantics.fieldType})`);
36574
36689
  }
36575
36690
  }
36576
- if (stmt.limit === null || stmt.limit < 0 || stmt.limit > 500) {
36691
+ if (stmt.limit === null || !Number.isSafeInteger(stmt.limit) || stmt.limit < 0) {
36577
36692
  reasons.push(`KORDER_LIMIT_INVALID(limit=${String(stmt.limit)})`);
36578
36693
  }
36579
- if (stmt.limit !== null && stmt.limit > input.maxRecords) {
36580
- reasons.push(`KORDER_LIMIT_EXCEEDS_MAX_RECORDS(limit=${stmt.limit}, maxRecords=${input.maxRecords})`);
36581
- }
36582
36694
  const offset = stmt.offset ?? 0;
36583
- if (offset < 0 || offset > 1e4) reasons.push(`KORDER_OFFSET_INVALID(offset=${offset})`);
36695
+ if (!Number.isSafeInteger(offset) || offset < 0) {
36696
+ reasons.push(`KORDER_OFFSET_INVALID(offset=${offset})`);
36697
+ }
36698
+ const scanRows = stmt.limit === null ? Number.NaN : offset + stmt.limit;
36699
+ if (stmt.limit !== null && !Number.isSafeInteger(scanRows)) {
36700
+ reasons.push(`KORDER_SCAN_ROWS_INVALID(offset=${offset}, limit=${stmt.limit})`);
36701
+ }
36584
36702
  const unique = [...new Set(reasons)];
36585
36703
  if (unique.length > 0) {
36586
36704
  throw new Error(
36587
36705
  `ArgumentError: KORDER BY cannot be executed (mode=KINTONE_NATIVE; ${unique.join(", ")}). Use ORDER BY for canonical local ordering or simplify the query.`
36588
36706
  );
36589
36707
  }
36708
+ const native = stmt.limit <= 500 && offset <= 1e4 && stmt.limit <= input.maxRecords;
36709
+ if (!native && scanRows > input.maxRecords) {
36710
+ throw new Error(
36711
+ `ArgumentError: KORDER BY cannot be executed (mode=KINTONE_NATIVE; KORDER_SCAN_ROWS_EXCEEDS_MAX_RECORDS(scanRows=${scanRows}, maxRecords=${input.maxRecords})). Use ORDER BY for canonical local ordering, raise maxRecords, or reduce LIMIT/OFFSET.`
36712
+ );
36713
+ }
36590
36714
  return {
36591
- kind: "KORDER_NATIVE",
36715
+ kind: native ? "KORDER_NATIVE" : "KORDER_CURSOR",
36592
36716
  requiresCompleteInput: false,
36593
36717
  localOrderBy: false,
36594
36718
  applyLocalOffsetLimit: false,
36595
- reasonCodes: []
36719
+ reasonCodes: [],
36720
+ scanRows
36596
36721
  };
36597
36722
  }
36598
36723
 
36724
+ // src/core/errors/cursorErrors.ts
36725
+ var CursorCapacityError = class extends Error {
36726
+ constructor(host, limit, waitMs) {
36727
+ super(`CursorCapacityError: host=${host} \u306E active cursor \u4E0A\u9650 ${limit} \u306B ${waitMs}ms \u4EE5\u5185\u3067\u7A7A\u304D\u304C\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002`);
36728
+ this.name = "CursorCapacityError";
36729
+ }
36730
+ };
36731
+ var CursorCreateOutcomeUnknownError = class extends Error {
36732
+ constructor(cause) {
36733
+ super("CursorCreateOutcomeUnknownError: Create Cursor \u306E\u6210\u5426\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002\u81EA\u52D5\u518D\u8A66\u884C\u305B\u305A\u3001\u6700\u592710\u5206+\u5B89\u5168\u4F59\u88D5\u306E\u9593\u306F\u67A0\u3092\u9694\u96E2\u3057\u307E\u3059\u3002");
36734
+ this.name = "CursorCreateOutcomeUnknownError";
36735
+ this.cause = cause;
36736
+ }
36737
+ };
36738
+ var CursorCleanupWarning = class extends Error {
36739
+ constructor(cause) {
36740
+ const detail = cause instanceof Error ? cause.message : String(cause);
36741
+ super(`CursorCleanupWarning: Cursor \u306E\u89E3\u653E\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002\u7D50\u679C\u306F\u6709\u52B9\u3067\u3059\u304C\u3001\u6700\u592710\u5206+\u5B89\u5168\u4F59\u88D5\u306E\u9593\u306F\u67A0\u3092\u9694\u96E2\u3057\u307E\u3059\u3002\u8A73\u7D30: ${detail}`);
36742
+ this.name = "CursorCleanupWarning";
36743
+ this.cause = cause;
36744
+ }
36745
+ };
36746
+
36747
+ // src/core/optimization/korderCursorExecutor.ts
36748
+ async function executeKorderCursor(input) {
36749
+ const handle = await input.client.openCursor({
36750
+ app: input.app,
36751
+ fields: input.fields.length > 0 ? input.fields : void 0,
36752
+ query: input.query,
36753
+ size: 500
36754
+ });
36755
+ const records = [];
36756
+ let seen = 0;
36757
+ let primaryError;
36758
+ let cleanupWarning;
36759
+ try {
36760
+ if (handle.totalCount > input.offset) {
36761
+ while (records.length < input.limit) {
36762
+ const page = await handle.nextPage();
36763
+ for (const record2 of page.records) {
36764
+ if (seen < input.offset) seen += 1;
36765
+ else if (records.length < input.limit) records.push(record2);
36766
+ else break;
36767
+ }
36768
+ if (!page.next) break;
36769
+ }
36770
+ }
36771
+ } catch (error51) {
36772
+ primaryError = error51;
36773
+ throw error51;
36774
+ } finally {
36775
+ try {
36776
+ await handle.close();
36777
+ } catch (cleanupError) {
36778
+ if (primaryError && primaryError instanceof Error) {
36779
+ Object.defineProperty(primaryError, "cursorCleanupError", {
36780
+ value: cleanupError,
36781
+ configurable: true
36782
+ });
36783
+ } else {
36784
+ cleanupWarning = new CursorCleanupWarning(cleanupError).message;
36785
+ }
36786
+ }
36787
+ }
36788
+ return { records, cleanupWarning };
36789
+ }
36790
+
36791
+ // src/converter/korderCursorQuery.ts
36792
+ function buildKorderCursorQuery(stmt) {
36793
+ const parts = [];
36794
+ if (stmt.where) parts.push(whereToKintone(stmt.where));
36795
+ const order = stmt.orderBy.map((item) => {
36796
+ if (item.key.type !== "FIELD_NAME") {
36797
+ throw new Error("ArgumentError: KORDER cursor key must be a direct field.");
36798
+ }
36799
+ return `${item.key.name} ${item.direction === "ASC" ? "asc" : "desc"}`;
36800
+ });
36801
+ parts.push(`order by ${order.join(", ")}`);
36802
+ return parts.join(" ");
36803
+ }
36804
+
36599
36805
  // src/engine/process.ts
36600
36806
  function flatten(record2, alias) {
36601
36807
  const row = {};
@@ -36892,6 +37098,7 @@ function compareSortKeys(a, b, meta3) {
36892
37098
  }
36893
37099
  var NUMERIC_ORDER_FUNCTIONS = /* @__PURE__ */ new Set([
36894
37100
  "LENGTH",
37101
+ "LENGTH_CHAR",
36895
37102
  "INSTR",
36896
37103
  "ROUND",
36897
37104
  "FLOOR",
@@ -37736,6 +37943,15 @@ function createEmptyMetrics() {
37736
37943
  fieldCalls: 0,
37737
37944
  appsCalls: 0,
37738
37945
  processStatusCalls: 0,
37946
+ cursorCreateCalls: 0,
37947
+ cursorGetCalls: 0,
37948
+ cursorDeleteCalls: 0,
37949
+ cursorRecordsScanned: 0,
37950
+ cursorActiveCurrent: 0,
37951
+ cursorActivePeak: 0,
37952
+ cursorCleanupFailures: 0,
37953
+ cursorCreateOutcomeUnknown: 0,
37954
+ cursorQuarantinedCurrent: 0,
37739
37955
  fetchedRows: 0,
37740
37956
  elapsedMs: 0
37741
37957
  };
@@ -37748,6 +37964,48 @@ function wrapClientWithMetrics(client, metrics) {
37748
37964
  metrics.fetchedRows += res.records.length;
37749
37965
  return res;
37750
37966
  },
37967
+ openCursor: async (params) => {
37968
+ metrics.cursorCreateCalls += 1;
37969
+ let handle;
37970
+ try {
37971
+ handle = await client.openCursor(params);
37972
+ } catch (error51) {
37973
+ if (error51 instanceof Error && error51.name === "CursorCreateOutcomeUnknownError") {
37974
+ metrics.cursorCreateOutcomeUnknown += 1;
37975
+ metrics.cursorQuarantinedCurrent += 1;
37976
+ }
37977
+ throw error51;
37978
+ }
37979
+ metrics.cursorActiveCurrent += 1;
37980
+ metrics.cursorActivePeak = Math.max(metrics.cursorActivePeak, metrics.cursorActiveCurrent);
37981
+ let released = false;
37982
+ const markReleased = () => {
37983
+ if (released) return;
37984
+ released = true;
37985
+ metrics.cursorActiveCurrent -= 1;
37986
+ };
37987
+ return {
37988
+ totalCount: handle.totalCount,
37989
+ nextPage: async () => {
37990
+ metrics.cursorGetCalls += 1;
37991
+ const page = await handle.nextPage();
37992
+ metrics.cursorRecordsScanned += page.records.length;
37993
+ if (!page.next) markReleased();
37994
+ return page;
37995
+ },
37996
+ close: async () => {
37997
+ if (!released) metrics.cursorDeleteCalls += 1;
37998
+ try {
37999
+ await handle.close();
38000
+ markReleased();
38001
+ } catch (error51) {
38002
+ metrics.cursorCleanupFailures += 1;
38003
+ metrics.cursorQuarantinedCurrent += 1;
38004
+ throw error51;
38005
+ }
38006
+ }
38007
+ };
38008
+ },
37751
38009
  postRecords: (params) => {
37752
38010
  metrics.postCalls += 1;
37753
38011
  return client.postRecords(params);
@@ -37787,6 +38045,37 @@ function wrapClientWithSearchAbort(client, collector, failClosed) {
37787
38045
  }
37788
38046
  };
37789
38047
  }
38048
+ function wrapClientWithCursorScope(client) {
38049
+ const active = /* @__PURE__ */ new Set();
38050
+ return {
38051
+ client: {
38052
+ ...client,
38053
+ openCursor: async (params) => {
38054
+ const handle = await client.openCursor(params);
38055
+ active.add(handle);
38056
+ const remove = () => active.delete(handle);
38057
+ return {
38058
+ totalCount: handle.totalCount,
38059
+ async nextPage() {
38060
+ const page = await handle.nextPage();
38061
+ if (!page.next) remove();
38062
+ return page;
38063
+ },
38064
+ async close() {
38065
+ try {
38066
+ await handle.close();
38067
+ } finally {
38068
+ remove();
38069
+ }
38070
+ }
38071
+ };
38072
+ }
38073
+ },
38074
+ closeActive: async () => {
38075
+ await Promise.all([...active].map((handle) => handle.close().catch(() => void 0)));
38076
+ }
38077
+ };
38078
+ }
37790
38079
  function isSelectLikeStatement(stmt) {
37791
38080
  return stmt.type === "SELECT" || stmt.type === "UNION" || stmt.type === "WITH";
37792
38081
  }
@@ -37837,7 +38126,13 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
37837
38126
  case "DESCRIBE":
37838
38127
  return executeDescribe(stmt, client, cacheContext);
37839
38128
  case "EXPLAIN":
37840
- return executeExplain(stmt, client, cacheContext, options.maxRecords ?? 1e4);
38129
+ return executeExplain(
38130
+ stmt,
38131
+ client,
38132
+ cacheContext,
38133
+ options.maxRecords ?? 1e4,
38134
+ options.cursorMaxActive ?? 2
38135
+ );
37841
38136
  // 一時テーブルはバッチスコープのため単文実行では拒否する(executeBatch を使う)
37842
38137
  case "CREATE_TEMP_TABLE":
37843
38138
  throw new Error("ArgumentError: CREATE TEMP TABLE requires a batch (temp tables are batch-scoped).");
@@ -37944,9 +38239,11 @@ async function executeBatch(sql, client, options = {}) {
37944
38239
  searchAbortCollector,
37945
38240
  info.statementType !== "SELECT" && info.statementType !== "UNION" && info.statementType !== "WITH"
37946
38241
  );
38242
+ const cursorScope = wrapClientWithCursorScope(statementClient);
37947
38243
  const outcome = await runWithDeadline(
37948
- executeBatchStatement(statements[i], info, statementClient, stmtOptions, cacheContext, tempTables, variables),
37949
- remaining
38244
+ executeBatchStatement(statements[i], info, cursorScope.client, stmtOptions, cacheContext, tempTables, variables),
38245
+ remaining,
38246
+ cursorScope.closeActive
37950
38247
  );
37951
38248
  if (outcome.result) {
37952
38249
  outcome.result = attachSearchAbortWarning(outcome.result, searchAbortCollector);
@@ -38104,19 +38401,47 @@ async function runSelectLike(query, client, options, cacheContext, tempTables) {
38104
38401
  }
38105
38402
  return executeQueryWithCte(query, client, options, tempTables, cacheContext, true);
38106
38403
  }
38107
- async function runWithDeadline(work, remainingMs) {
38404
+ async function runWithDeadline(work, remainingMs, onTimeout) {
38108
38405
  if (remainingMs === null) return work;
38109
38406
  if (remainingMs <= 0) {
38407
+ if (onTimeout) await onTimeout();
38110
38408
  void work.catch(() => {
38111
38409
  });
38112
38410
  throw new BatchTimeoutError();
38113
38411
  }
38114
38412
  let timer;
38413
+ let timedOut = false;
38414
+ const guardedWork = work.then(
38415
+ (value) => timedOut ? new Promise(() => void 0) : value,
38416
+ (error51) => {
38417
+ if (timedOut) return new Promise(() => void 0);
38418
+ throw error51;
38419
+ }
38420
+ );
38115
38421
  try {
38116
38422
  return await Promise.race([
38117
- work,
38423
+ guardedWork,
38118
38424
  new Promise((_, reject) => {
38119
- timer = setTimeout(() => reject(new BatchTimeoutError()), remainingMs);
38425
+ timer = setTimeout(() => {
38426
+ timedOut = true;
38427
+ void (async () => {
38428
+ if (onTimeout) {
38429
+ let cleanupTimer;
38430
+ try {
38431
+ await Promise.race([
38432
+ onTimeout(),
38433
+ new Promise((resolve2) => {
38434
+ cleanupTimer = setTimeout(resolve2, 5e3);
38435
+ cleanupTimer.unref?.();
38436
+ })
38437
+ ]);
38438
+ } finally {
38439
+ if (cleanupTimer) clearTimeout(cleanupTimer);
38440
+ }
38441
+ }
38442
+ reject(new BatchTimeoutError());
38443
+ })();
38444
+ }, remainingMs);
38120
38445
  })
38121
38446
  ]);
38122
38447
  } catch (e) {
@@ -38476,7 +38801,7 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
38476
38801
  const staticMode = resolveSelectMode(stmt);
38477
38802
  const mode = whereCapability.capability === "EXACT_PUSHDOWN" ? staticMode : "FULL_SCAN";
38478
38803
  const orderMeta = await buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
38479
- const orderPlan = hasCanonicalOrder(stmt) ? (stmt.orderMode === "KINTONE_NATIVE" ? planKorderNative : planCanonicalOrder)({
38804
+ const orderPlan = hasCanonicalOrder(stmt) ? (stmt.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
38480
38805
  stmt,
38481
38806
  staticMode: mode,
38482
38807
  whereCapability: whereCapability.capability,
@@ -38490,7 +38815,7 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
38490
38815
  client,
38491
38816
  cacheContext
38492
38817
  );
38493
- const completeInputRequired = orderPlan?.kind === "CANONICAL_REST_TOP_N" || orderPlan?.kind === "KORDER_NATIVE" ? requiresCompleteInput({ ...stmt, orderBy: [] }) : requiresCompleteInput(stmt);
38818
+ const completeInputRequired = orderPlan?.kind === "CANONICAL_REST_TOP_N" || orderPlan?.kind === "KORDER_NATIVE" || orderPlan?.kind === "KORDER_CURSOR" ? requiresCompleteInput({ ...stmt, orderBy: [] }) : requiresCompleteInput(stmt);
38494
38819
  const truncateWasDisabled = completeInputRequired && options.onLimitReached === "truncate";
38495
38820
  const effectiveOptions = truncateWasDisabled ? { ...options, onLimitReached: "error" } : options;
38496
38821
  try {
@@ -38591,12 +38916,23 @@ async function executeSimpleSelect(stmt, client, options, cacheContext, orderPla
38591
38916
  const warnings = /* @__PURE__ */ new Set();
38592
38917
  const onLimit2 = options.onLimitReached ?? "error";
38593
38918
  const parallel = options.fetchParallel ?? 1;
38594
- const useRestWindow = stmt.orderBy.length > 0 ? orderPlan?.kind === "CANONICAL_REST_TOP_N" || orderPlan?.kind === "KORDER_NATIVE" : stmt.limit !== null && stmt.limit <= 500;
38919
+ const useRestWindow = stmt.orderBy.length > 0 ? orderPlan?.kind === "CANONICAL_REST_TOP_N" || orderPlan?.kind === "KORDER_NATIVE" || orderPlan?.kind === "KORDER_CURSOR" : stmt.limit !== null && stmt.limit <= 500;
38595
38920
  const needed = stmt.limit === null ? null : (stmt.offset ?? 0) + stmt.limit;
38596
38921
  const stopAfter = stmt.orderBy.length === 0 && needed !== null && needed <= maxRecords2 && !whereHasKlike(stmt.where) ? needed : void 0;
38597
38922
  let records;
38598
- if (orderPlan?.kind === "KORDER_NATIVE" && stmt.limit === 0) {
38923
+ if ((orderPlan?.kind === "KORDER_NATIVE" || orderPlan?.kind === "KORDER_CURSOR") && stmt.limit === 0) {
38599
38924
  records = [];
38925
+ } else if (orderPlan?.kind === "KORDER_CURSOR") {
38926
+ const cursorResult = await executeKorderCursor({
38927
+ client,
38928
+ app: params.app,
38929
+ fields: params.fields,
38930
+ query: buildKorderCursorQuery(stmt),
38931
+ offset: stmt.offset ?? 0,
38932
+ limit: stmt.limit
38933
+ });
38934
+ records = cursorResult.records;
38935
+ if (cursorResult.cleanupWarning) warnings.add(cursorResult.cleanupWarning);
38600
38936
  } else if (useRestWindow) {
38601
38937
  const res = await client.getRecords({
38602
38938
  app: params.app,
@@ -38983,6 +39319,7 @@ function systemColumnMeta(field) {
38983
39319
  }
38984
39320
  var NUMBER_RETURNING_STRING_FUNCTIONS = /* @__PURE__ */ new Set([
38985
39321
  "LENGTH",
39322
+ "LENGTH_CHAR",
38986
39323
  "INSTR",
38987
39324
  "ROUND",
38988
39325
  "FLOOR",
@@ -39388,7 +39725,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
39388
39725
  }
39389
39726
  const orderMeta = await buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
39390
39727
  if (hasCanonicalOrder(stmt)) {
39391
- (stmt.orderMode === "KINTONE_NATIVE" ? planKorderNative : planCanonicalOrder)({
39728
+ (stmt.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
39392
39729
  stmt,
39393
39730
  staticMode: "FULL_SCAN",
39394
39731
  whereCapability: whereCapability.capability,
@@ -39950,6 +40287,28 @@ var NON_WRITABLE_FIELD_TYPES = /* @__PURE__ */ new Set([
39950
40287
  "CATEGORY",
39951
40288
  "REFERENCE_TABLE"
39952
40289
  ]);
40290
+ function assertWritableTopLevelDmlFields(appId, targetFields, fieldInfos) {
40291
+ const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
40292
+ for (const code of targetFields) {
40293
+ const info = infoByCode.get(code);
40294
+ if (!info) {
40295
+ throw new Error(`ArgumentError: DML target field ${code} does not exist.`);
40296
+ }
40297
+ if (info.inSubtable) {
40298
+ throw new Error(
40299
+ `ArgumentError: DML target field ${code} is inside a subtable. Use subtable DML syntax (for example, APP${appId}$\u30C6\u30FC\u30D6\u30EB).`
40300
+ );
40301
+ }
40302
+ if (info.writable === false || NON_WRITABLE_FIELD_TYPES.has(info.fieldType)) {
40303
+ throw new Error(`ArgumentError: DML target field ${code} is not writable (${info.fieldType}).`);
40304
+ }
40305
+ }
40306
+ }
40307
+ async function loadWritableTopLevelDmlFields(appId, targetFields, client, cacheContext) {
40308
+ const fieldInfos = await getFieldsCached(appId, client, cacheContext);
40309
+ assertWritableTopLevelDmlFields(appId, targetFields, fieldInfos);
40310
+ return fieldInfos;
40311
+ }
39953
40312
  async function executeDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber) {
39954
40313
  return (await prepareDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber)).result;
39955
40314
  }
@@ -39961,24 +40320,22 @@ var RejectLimitExceededError = class extends Error {
39961
40320
  }
39962
40321
  };
39963
40322
  async function prepareDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber) {
39964
- if (stmt.type === "UPDATE") {
39965
- await assertDmlWhereCapability(stmt, client, cacheContext);
39966
- }
39967
40323
  const operation = stmt.type === "UPDATE" ? "UPDATE" : stmt.type.startsWith("UPSERT") ? "UPSERT" : "INSERT";
39968
40324
  const payloadFields = stmt.type === "UPDATE" ? ["$id", ...stmt.assignments.map((a) => a.field)] : [...stmt.fields];
39969
40325
  if (new Set(payloadFields).size !== payloadFields.length) {
39970
40326
  throw new Error("ArgumentError: DML target fields contain duplicates.");
39971
40327
  }
39972
- const fieldInfos = await getFieldsCached(stmt.appId, client, cacheContext);
39973
- const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
39974
40328
  const targetFields = stmt.type === "UPDATE" ? stmt.assignments.map((a) => a.field) : stmt.fields;
39975
- for (const code of targetFields) {
39976
- const info = infoByCode.get(code);
39977
- if (!info) throw new Error(`ArgumentError: DML target field ${code} does not exist.`);
39978
- if (info.writable === false || NON_WRITABLE_FIELD_TYPES.has(info.fieldType)) {
39979
- throw new Error(`ArgumentError: DML target field ${code} is not writable (${info.fieldType}).`);
39980
- }
40329
+ const fieldInfos = await loadWritableTopLevelDmlFields(
40330
+ stmt.appId,
40331
+ targetFields,
40332
+ client,
40333
+ cacheContext
40334
+ );
40335
+ if (stmt.type === "UPDATE") {
40336
+ await assertDmlWhereCapability(stmt, client, cacheContext);
39981
40337
  }
40338
+ const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
39982
40339
  const candidates = await materializeValidationCandidates(stmt, operation, client, options, cacheContext, tempTables, infoByCode);
39983
40340
  const { errors, invalidRows, invalidRowNumbers } = validateDmlCandidates(
39984
40341
  candidates,
@@ -40147,7 +40504,7 @@ async function materializeUpdateValidationCandidates(stmt, client, options, cach
40147
40504
  await resolveSetSubqueries(stmt.assignments, client, options, cacheContext);
40148
40505
  const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
40149
40506
  let records;
40150
- if (hasArithAssignment(stmt)) {
40507
+ if (hasRowDependentAssignment(stmt)) {
40151
40508
  const getParams = updateToGetQueryForArith(stmt);
40152
40509
  const resolved = await fetchRecordsForSharedPlan(client.getRecords, getParams.app, getParams.query, [...getParams.fields], {
40153
40510
  maxRecords: options.maxRecords ?? 1e4,
@@ -40356,6 +40713,7 @@ async function executeInsert(stmt, client, options, cacheContext) {
40356
40713
  if (stmt.subtableCode) {
40357
40714
  return executeInsertSubtable(stmt, client, options, cacheContext);
40358
40715
  }
40716
+ await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
40359
40717
  const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
40360
40718
  const batches = insertToPostBatches(stmt, fieldTypes);
40361
40719
  const createdIds = [];
@@ -40370,6 +40728,7 @@ async function executeInsert(stmt, client, options, cacheContext) {
40370
40728
  };
40371
40729
  }
40372
40730
  async function executeInsertSelect(stmt, client, options, cacheContext, cteCache) {
40731
+ await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
40373
40732
  const selectResult = cteCache !== void 0 && cteCache.size > 0 ? await executeQueryWithCte(stmt.select, client, options, cteCache, cacheContext) : await executeSelect(stmt.select, client, options, cacheContext);
40374
40733
  const { rows, columns } = selectResult;
40375
40734
  if (columns.length !== stmt.fields.length) {
@@ -40404,17 +40763,24 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
40404
40763
  };
40405
40764
  }
40406
40765
  async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
40407
- await assertDmlWhereCapability(stmt, client, cacheContext);
40408
40766
  if (stmt.subtableCode) {
40767
+ await assertDmlWhereCapability(stmt, client, cacheContext);
40409
40768
  return executeUpdateSubtable(stmt, client, options, cacheContext);
40410
40769
  }
40770
+ await loadWritableTopLevelDmlFields(
40771
+ stmt.appId,
40772
+ stmt.assignments.map((assignment) => assignment.field),
40773
+ client,
40774
+ cacheContext
40775
+ );
40776
+ await assertDmlWhereCapability(stmt, client, cacheContext);
40411
40777
  if (stmt.from != null) {
40412
40778
  return executeUpdateFrom(stmt, stmt.from, client, options, cacheContext, tempTables);
40413
40779
  }
40414
40780
  const maxRecords2 = options.maxRecords ?? 1e4;
40415
40781
  await resolveSetSubqueries(stmt.assignments, client, options, cacheContext);
40416
40782
  const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
40417
- if (hasArithAssignment(stmt)) {
40783
+ if (hasRowDependentAssignment(stmt)) {
40418
40784
  const getParams2 = updateToGetQueryForArith(stmt);
40419
40785
  const resolved2 = await fetchRecordsForSharedPlan(
40420
40786
  client.getRecords,
@@ -40508,6 +40874,7 @@ async function executeDelete(stmt, client, options, cacheContext) {
40508
40874
  return { type: "DELETE", deletedCount: ids.length };
40509
40875
  }
40510
40876
  async function executeUpsert(stmt, client, options, cacheContext) {
40877
+ await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
40511
40878
  const toInsert = [];
40512
40879
  const toUpdate = [];
40513
40880
  const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
@@ -40934,6 +41301,7 @@ function evalOrderKeyForRow(key, row) {
40934
41301
  }
40935
41302
  }
40936
41303
  async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache) {
41304
+ await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
40937
41305
  const selectResult = cteCache !== void 0 && cteCache.size > 0 ? await executeQueryWithCte(stmt.select, client, options, cteCache, cacheContext) : await executeSelect(stmt.select, client, options, cacheContext);
40938
41306
  const { rows, columns } = selectResult;
40939
41307
  if (columns.length !== stmt.fields.length) {
@@ -41165,7 +41533,7 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
41165
41533
  const hasUnmaterializedSource = [select.from, ...select.joins.map((join) => join.table)].some((table) => table.cteName !== null);
41166
41534
  if (hasCanonicalOrder(select) && !hasUnmaterializedSource) {
41167
41535
  const mode = capability.capability === "EXACT_PUSHDOWN" ? resolveSelectMode(select) : "FULL_SCAN";
41168
- orderPlans.set(select, (select.orderMode === "KINTONE_NATIVE" ? planKorderNative : planCanonicalOrder)({
41536
+ orderPlans.set(select, (select.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
41169
41537
  stmt: select,
41170
41538
  staticMode: mode,
41171
41539
  whereCapability: capability.capability,
@@ -41195,7 +41563,7 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
41195
41563
  capabilities.set(inlined, capability);
41196
41564
  if (hasCanonicalOrder(inlined)) {
41197
41565
  const meta3 = await buildOrderByMetaForSelect(inlined, tracedClient, cacheContext);
41198
- orderPlans.set(inlined, (inlined.orderMode === "KINTONE_NATIVE" ? planKorderNative : planCanonicalOrder)({
41566
+ orderPlans.set(inlined, (inlined.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
41199
41567
  stmt: inlined,
41200
41568
  staticMode: capability.capability === "EXACT_PUSHDOWN" ? resolveSelectMode(inlined) : "FULL_SCAN",
41201
41569
  whereCapability: capability.capability,
@@ -41213,7 +41581,7 @@ function explainMetadataLines(analysis) {
41213
41581
  ...[...analysis.processStatusApps].sort((a, b) => a - b).map((appId) => ` metadata API: process status APP${appId}`)
41214
41582
  ];
41215
41583
  }
41216
- async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords2 = 1e4) {
41584
+ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords2 = 1e4, cursorMaxActive2 = 2) {
41217
41585
  const statements = parseSqlBatch(sql);
41218
41586
  const analysis = analyzeBatch(statements);
41219
41587
  validateDeclaredBatchVariables(statements, injectedVariables);
@@ -41224,12 +41592,12 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
41224
41592
  const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveVariableRefs(stmt.expr, variables) } : stmt : resolveVariableRefs(stmt, variables);
41225
41593
  validateKlikeStatement(planStmt);
41226
41594
  const whereAnalysis = await buildExplainWhereAnalysis(planStmt, client, cacheContext, maxRecords2);
41227
- const statementPlan = buildBatchStatementPlan(
41595
+ const statementPlan = addCursorConcurrency(buildBatchStatementPlan(
41228
41596
  planStmt,
41229
41597
  analysis.statements[i],
41230
41598
  whereAnalysis.capabilities,
41231
41599
  whereAnalysis.orderPlans
41232
- );
41600
+ ), cursorMaxActive2);
41233
41601
  const metadataPlan = explainMetadataLines(whereAnalysis);
41234
41602
  plans.push({
41235
41603
  index: i,
@@ -41335,11 +41703,14 @@ function buildPlanForBatchQuery(query, info, capabilities, orderPlans) {
41335
41703
  lines.push(" note: \u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u3078\u306E WHERE \u30D7\u30C3\u30B7\u30E5\u30C0\u30A6\u30F3\u306F\u884C\u308F\u308C\u306A\u3044");
41336
41704
  return lines;
41337
41705
  }
41338
- async function executeExplain(stmt, client, cacheContext, maxRecords2) {
41706
+ async function executeExplain(stmt, client, cacheContext, maxRecords2, cursorMaxActive2) {
41339
41707
  const analysis = await buildExplainWhereAnalysis(stmt.query, client, cacheContext, maxRecords2);
41340
41708
  const lines = [
41341
41709
  ...explainMetadataLines(analysis),
41342
- ...buildExplainPlan(stmt.query, void 0, analysis.capabilities, analysis.orderPlans)
41710
+ ...addCursorConcurrency(
41711
+ buildExplainPlan(stmt.query, void 0, analysis.capabilities, analysis.orderPlans),
41712
+ cursorMaxActive2
41713
+ )
41343
41714
  ];
41344
41715
  return {
41345
41716
  type: "SELECT",
@@ -41348,6 +41719,17 @@ async function executeExplain(stmt, client, cacheContext, maxRecords2) {
41348
41719
  rowCount: lines.length
41349
41720
  };
41350
41721
  }
41722
+ function addCursorConcurrency(lines, cursorMaxActive2) {
41723
+ const result = [];
41724
+ for (const line of lines) {
41725
+ result.push(line);
41726
+ if (line.trim() === "cursor page size: 500") {
41727
+ const indent = line.match(/^\s*/)?.[0] ?? "";
41728
+ result.push(`${indent}cursor concurrency: ${cursorMaxActive2} per domain (process-local)`);
41729
+ }
41730
+ }
41731
+ return result;
41732
+ }
41351
41733
  function buildExplainPlan(query, label, capabilities, orderPlans) {
41352
41734
  if (query.type === "UNION") return buildUnionPlan(query, capabilities, orderPlans);
41353
41735
  if (query.type === "WITH") return buildWithPlan(query, capabilities, orderPlans);
@@ -41377,6 +41759,11 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
41377
41759
  if (orderPlan.kind === "KORDER_NATIVE") {
41378
41760
  lines.push(" order semantics: kintone native (not kSQL canonical)");
41379
41761
  lines.push(" REST execution: single GET");
41762
+ } else if (orderPlan.kind === "KORDER_CURSOR") {
41763
+ lines.push(" order semantics: kintone native (not kSQL canonical)");
41764
+ lines.push(" fetch API: POST/GET/DELETE records/cursor.json");
41765
+ lines.push(" cursor page size: 500");
41766
+ lines.push(` scan rows: ${orderPlan.scanRows}`);
41380
41767
  }
41381
41768
  }
41382
41769
  if (orderPlan?.requiresCompleteInput ?? requiresCompleteInput(stmt)) {
@@ -41388,7 +41775,8 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
41388
41775
  if (mode === "SIMPLE") {
41389
41776
  const params = selectToKintoneParams(orderPlan?.kind === "CANONICAL_REST_TOP_N" ? withCanonicalRestTie(stmt) : stmt);
41390
41777
  lines.push(` app: APP${stmt.from.appId} (${stmt.from.appId})`);
41391
- lines.push(` kintone query: ${params.query || "(\u306A\u3057)"}`);
41778
+ const displayedQuery = orderPlan?.kind === "KORDER_CURSOR" ? buildKorderCursorQuery(stmt) : params.query;
41779
+ lines.push(` kintone query: ${displayedQuery || "(\u306A\u3057)"}`);
41392
41780
  lines.push(` fields: ${params.fields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : params.fields.join(", ")}`);
41393
41781
  } else {
41394
41782
  const pushdownPlan = buildKlikePushdownPlan(stmt);
@@ -41549,6 +41937,8 @@ function buildInsertSelectPlan(stmt, label, capabilities, orderPlans) {
41549
41937
  }
41550
41938
  function buildUpdatePlan(stmt, label, capabilities, orderPlans) {
41551
41939
  const isArith = hasArithAssignment(stmt);
41940
+ const isStringFunc = stmt.assignments.some((a) => a.value.type === "STRING_FUNC");
41941
+ const isRowDependent = hasRowDependentAssignment(stmt);
41552
41942
  const isSubq = stmt.assignments.some((a) => a.value.type === "SCALAR_SUBQUERY");
41553
41943
  const lines = [];
41554
41944
  if (label) lines.push(label);
@@ -41565,10 +41955,11 @@ function buildUpdatePlan(stmt, label, capabilities, orderPlans) {
41565
41955
  lines.push(` api: GET /k/v1/records.json \u2192 PUT /k/v1/records.json`);
41566
41956
  const setTypes = [];
41567
41957
  if (isArith) setTypes.push("\u7B97\u8853 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A08\u7B97\uFF09");
41958
+ if (isStringFunc) setTypes.push("\u6587\u5B57\u5217\u95A2\u6570 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A55\u4FA1\uFF09");
41568
41959
  if (isSubq) setTypes.push("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA SET");
41569
- if (!isArith && !isSubq) setTypes.push("\u5358\u7D14 SET");
41960
+ if (!isRowDependent && !isSubq) setTypes.push("\u5358\u7D14 SET");
41570
41961
  lines.push(` set type: ${setTypes.join(", ")}`);
41571
- if (isArith) {
41962
+ if (isRowDependent) {
41572
41963
  const refFields = collectArithRefFields(stmt);
41573
41964
  if (refFields.length > 0) {
41574
41965
  lines.push(` ref fields: ${refFields.join(", ")}\uFF08GET \u306B\u542B\u3081\u308B\uFF09`);
@@ -41654,6 +42045,7 @@ function collectArithRefFields(stmt) {
41654
42045
  const refs = /* @__PURE__ */ new Set();
41655
42046
  for (const { value } of stmt.assignments) {
41656
42047
  if (value.type === "ARITH") collectArithNodeRefs(value, refs);
42048
+ if (value.type === "STRING_FUNC") collectArithNodeRefs(value, refs);
41657
42049
  }
41658
42050
  return [...refs];
41659
42051
  }
@@ -41666,6 +42058,13 @@ function collectArithNodeRefs(node, out) {
41666
42058
  collectArithNodeRefs(node.left, out);
41667
42059
  collectArithNodeRefs(node.right, out);
41668
42060
  }
42061
+ if (node.type === "STRING_FUNC") {
42062
+ for (const arg of node.args) {
42063
+ if (arg.type !== "STRING" && arg.type !== "AGG_REF" && arg.type !== "AGG_ARITH") {
42064
+ collectArithNodeRefs(arg, out);
42065
+ }
42066
+ }
42067
+ }
41669
42068
  }
41670
42069
  function formatAssignment(a) {
41671
42070
  const v = a.value;
@@ -41673,6 +42072,7 @@ function formatAssignment(a) {
41673
42072
  if (v.type === "NUMBER") return `${a.field} = ${v.value}`;
41674
42073
  if (v.type === "ARITH") return `${a.field} = ${formatArithExprStr(v)}`;
41675
42074
  if (v.type === "CASE_VALUE") return `${a.field} = CASE WHEN ...`;
42075
+ if (v.type === "STRING_FUNC") return `${a.field} = ${v.func}(...)`;
41676
42076
  if (v.type === "SCALAR_SUBQUERY") return `${a.field} = (SELECT ...)`;
41677
42077
  if (v.type === "SOURCE_FIELD") return `${a.field} = ${v.alias}.${v.field}`;
41678
42078
  return `${a.field} = (${v.type})`;
@@ -41931,6 +42331,12 @@ function validateKsqlConfig(config2) {
41931
42331
  }
41932
42332
  const logicalApps = normalizeLogicalApps(profileName, profile2.logicalApps);
41933
42333
  if (logicalApps !== void 0) profile2.logicalApps = logicalApps;
42334
+ if (profile2.query?.cursorMaxActive !== void 0) {
42335
+ const value = profile2.query.cursorMaxActive;
42336
+ if (!Number.isSafeInteger(value) || value < 1 || value > 5) {
42337
+ throw argumentError(`query.cursorMaxActive for profile "${profileName}" must be an integer from 1 to 5.`);
42338
+ }
42339
+ }
41934
42340
  }
41935
42341
  return config2;
41936
42342
  }
@@ -42093,6 +42499,10 @@ var RequestGate = class {
42093
42499
  async runMutation(fn) {
42094
42500
  return this.withSlot(fn);
42095
42501
  }
42502
+ /** Cursor Create/Get/Delete: セマフォのみ。GETでも位置を進めるため再試行しない。 */
42503
+ async runCursorStep(fn) {
42504
+ return this.withSlot(fn);
42505
+ }
42096
42506
  async withSlot(fn) {
42097
42507
  await this.acquire();
42098
42508
  try {
@@ -42124,6 +42534,14 @@ var RequestGate = class {
42124
42534
  function withRequestGate(client, gate) {
42125
42535
  return {
42126
42536
  getRecords: (params) => gate.runReadOnly(() => client.getRecords(params)),
42537
+ openCursor: async (params) => {
42538
+ const handle = await gate.runCursorStep(() => client.openCursor(params));
42539
+ return {
42540
+ totalCount: handle.totalCount,
42541
+ nextPage: () => gate.runCursorStep(() => handle.nextPage()),
42542
+ close: () => gate.runCursorStep(() => handle.close())
42543
+ };
42544
+ },
42127
42545
  getApps: () => gate.runReadOnly(() => client.getApps()),
42128
42546
  getFields: (appId) => gate.runReadOnly(() => client.getFields(appId)),
42129
42547
  getProcessStatuses: (appId) => gate.runReadOnly(() => client.getProcessStatuses(appId)),
@@ -42238,7 +42656,218 @@ function normalizeProcessStatusStates(states) {
42238
42656
  });
42239
42657
  }
42240
42658
 
42659
+ // src/api/kintoneCursor.ts
42660
+ function isAlreadyReleasedCursorError(error51) {
42661
+ const shaped = error51;
42662
+ return shaped?.status === 404 && shaped.code === "GAIA_CN01";
42663
+ }
42664
+ async function deleteCursorWithConfirmation(deleteCursor, sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms)), isAlreadyReleased = isAlreadyReleasedCursorError) {
42665
+ try {
42666
+ await deleteCursor();
42667
+ return;
42668
+ } catch (firstError) {
42669
+ if (isAlreadyReleased(firstError)) return;
42670
+ }
42671
+ await sleep(250);
42672
+ try {
42673
+ await deleteCursor();
42674
+ } catch (confirmationError) {
42675
+ if (isAlreadyReleased(confirmationError)) return;
42676
+ throw confirmationError;
42677
+ }
42678
+ }
42679
+ async function withTimeout(promise2, timeoutMs) {
42680
+ let timer;
42681
+ const timeout2 = new Promise((_resolve, reject) => {
42682
+ timer = setTimeout(() => reject(new Error(`CursorCleanupTimeoutError: cleanup exceeded ${timeoutMs}ms.`)), timeoutMs);
42683
+ timer.unref?.();
42684
+ });
42685
+ try {
42686
+ return await Promise.race([promise2, timeout2]);
42687
+ } finally {
42688
+ if (timer) clearTimeout(timer);
42689
+ }
42690
+ }
42691
+ function createKintoneCursorHandle(totalCount, operations) {
42692
+ let released = false;
42693
+ let closing = false;
42694
+ let pageTail = Promise.resolve();
42695
+ let closePromise = null;
42696
+ const nextPage = () => {
42697
+ if (closing || released) return Promise.resolve({ records: [], next: false });
42698
+ const result = pageTail.then(async () => {
42699
+ if (closing || released) return { records: [], next: false };
42700
+ const page = await operations.get();
42701
+ if (!page.next) {
42702
+ released = true;
42703
+ operations.onReleased?.();
42704
+ }
42705
+ return page;
42706
+ });
42707
+ pageTail = result.then(() => void 0, () => void 0);
42708
+ return result;
42709
+ };
42710
+ const close = () => {
42711
+ if (released) return Promise.resolve();
42712
+ if (closePromise) return closePromise;
42713
+ closing = true;
42714
+ closePromise = pageTail.then(async () => {
42715
+ if (released) return;
42716
+ try {
42717
+ await withTimeout(
42718
+ deleteCursorWithConfirmation(
42719
+ operations.delete,
42720
+ operations.sleep,
42721
+ operations.isAlreadyReleasedError
42722
+ ),
42723
+ operations.cleanupTimeoutMs ?? 5e3
42724
+ );
42725
+ released = true;
42726
+ operations.onReleased?.();
42727
+ } catch (error51) {
42728
+ operations.onReleaseUnknown?.();
42729
+ throw error51;
42730
+ }
42731
+ });
42732
+ return closePromise;
42733
+ };
42734
+ return { totalCount, nextPage, close };
42735
+ }
42736
+
42737
+ // src/api/cursorLeaseManager.ts
42738
+ var DEFAULT_MAX_ACTIVE = 2;
42739
+ var MAX_ACTIVE = 5;
42740
+ var DEFAULT_WAIT_MS = 3e4;
42741
+ var DEFAULT_QUARANTINE_MS = 10 * 6e4 + 3e4;
42742
+ var CursorLeaseManager = class {
42743
+ constructor(host, options = {}) {
42744
+ this.host = host;
42745
+ this.active = 0;
42746
+ this.peak = 0;
42747
+ this.quarantined = 0;
42748
+ this.waiters = [];
42749
+ this.createTail = Promise.resolve();
42750
+ const maxActive = options.maxActive ?? DEFAULT_MAX_ACTIVE;
42751
+ if (!Number.isSafeInteger(maxActive) || maxActive < 1 || maxActive > MAX_ACTIVE) {
42752
+ throw new Error(`ArgumentError: cursorMaxActive must be an integer from 1 to ${MAX_ACTIVE}.`);
42753
+ }
42754
+ this.maxActive = maxActive;
42755
+ this.waitTimeoutMs = options.waitTimeoutMs ?? DEFAULT_WAIT_MS;
42756
+ this.quarantineMs = options.quarantineMs ?? DEFAULT_QUARANTINE_MS;
42757
+ }
42758
+ acquire() {
42759
+ if (this.active < this.maxActive) {
42760
+ this.active += 1;
42761
+ this.peak = Math.max(this.peak, this.active);
42762
+ return Promise.resolve(this.makeLease());
42763
+ }
42764
+ return new Promise((resolve2, reject) => {
42765
+ const waiter = {};
42766
+ waiter.resolve = resolve2;
42767
+ waiter.reject = reject;
42768
+ waiter.timer = setTimeout(() => {
42769
+ const index = this.waiters.indexOf(waiter);
42770
+ if (index >= 0) this.waiters.splice(index, 1);
42771
+ reject(new CursorCapacityError(this.host, this.maxActive, this.waitTimeoutMs));
42772
+ }, this.waitTimeoutMs);
42773
+ waiter.timer.unref?.();
42774
+ this.waiters.push(waiter);
42775
+ });
42776
+ }
42777
+ /**
42778
+ * 同一hostを共有する後続surfaceの設定を反映する。
42779
+ * 縮小時は既存leaseを強制終了せず、activeが新上限を下回るまで新規取得だけを止める。
42780
+ */
42781
+ setMaxActive(maxActive) {
42782
+ this.validateMaxActive(maxActive);
42783
+ if (this.maxActive === maxActive) return;
42784
+ this.maxActive = maxActive;
42785
+ this.dispatchWaiters();
42786
+ }
42787
+ async runCreate(fn) {
42788
+ const previous = this.createTail;
42789
+ let unlock;
42790
+ this.createTail = new Promise((resolve2) => {
42791
+ unlock = resolve2;
42792
+ });
42793
+ await previous;
42794
+ try {
42795
+ return await fn();
42796
+ } finally {
42797
+ unlock();
42798
+ }
42799
+ }
42800
+ snapshot() {
42801
+ return {
42802
+ active: this.active,
42803
+ peak: this.peak,
42804
+ quarantined: this.quarantined,
42805
+ waiting: this.waiters.length,
42806
+ limit: this.maxActive
42807
+ };
42808
+ }
42809
+ makeLease() {
42810
+ let done = false;
42811
+ return {
42812
+ release: () => {
42813
+ if (done) return;
42814
+ done = true;
42815
+ this.returnPermit();
42816
+ },
42817
+ quarantine: (durationMs = this.quarantineMs) => {
42818
+ if (done) return;
42819
+ done = true;
42820
+ this.quarantined += 1;
42821
+ const timer = setTimeout(() => {
42822
+ this.quarantined -= 1;
42823
+ this.returnPermit();
42824
+ }, durationMs);
42825
+ timer.unref?.();
42826
+ }
42827
+ };
42828
+ }
42829
+ returnPermit() {
42830
+ this.active -= 1;
42831
+ this.dispatchWaiters();
42832
+ }
42833
+ dispatchWaiters() {
42834
+ while (this.active < this.maxActive) {
42835
+ const waiter = this.waiters.shift();
42836
+ if (!waiter) return;
42837
+ clearTimeout(waiter.timer);
42838
+ this.active += 1;
42839
+ this.peak = Math.max(this.peak, this.active);
42840
+ waiter.resolve(this.makeLease());
42841
+ }
42842
+ }
42843
+ validateMaxActive(maxActive) {
42844
+ if (!Number.isSafeInteger(maxActive) || maxActive < 1 || maxActive > MAX_ACTIVE) {
42845
+ throw new Error(`ArgumentError: cursorMaxActive must be an integer from 1 to ${MAX_ACTIVE}.`);
42846
+ }
42847
+ }
42848
+ };
42849
+ var managers = /* @__PURE__ */ new Map();
42850
+ function getCursorLeaseManager(host, maxActive = DEFAULT_MAX_ACTIVE) {
42851
+ const key = host.toLowerCase();
42852
+ let manager = managers.get(key);
42853
+ if (!manager) {
42854
+ manager = new CursorLeaseManager(key, { maxActive });
42855
+ managers.set(key, manager);
42856
+ } else {
42857
+ manager.setMaxActive(maxActive);
42858
+ }
42859
+ return manager;
42860
+ }
42861
+
42241
42862
  // src/cli/nodeKintoneClient.ts
42863
+ var KintoneApiError = class extends Error {
42864
+ constructor(status, code, bodyText) {
42865
+ super(`kintone API error ${status}: ${bodyText}`);
42866
+ this.status = status;
42867
+ this.code = code;
42868
+ this.name = "KintoneApiError";
42869
+ }
42870
+ };
42242
42871
  var SEARCH_ABORTED_HEADER_VALUE = "Filter aborted because of too many search results";
42243
42872
  function createNodeKintoneClient(baseUrl, tokenResolver) {
42244
42873
  const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
@@ -42286,7 +42915,13 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
42286
42915
  if (tokenResolver.debug) {
42287
42916
  tokenResolver.log?.(`[debug] response status=${res.status} body=${bodyText}`);
42288
42917
  }
42289
- throw new Error(`kintone API error ${res.status}: ${bodyText}`);
42918
+ let code;
42919
+ try {
42920
+ const body = JSON.parse(bodyText);
42921
+ if (typeof body.code === "string") code = body.code;
42922
+ } catch {
42923
+ }
42924
+ throw new KintoneApiError(res.status, code, bodyText);
42290
42925
  }
42291
42926
  if (tokenResolver.debug) {
42292
42927
  tokenResolver.log?.(`[debug] response status=${res.status}`);
@@ -42353,6 +42988,48 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
42353
42988
  return response.searchAborted ? { ...response.body, searchAborted: true } : response.body;
42354
42989
  }
42355
42990
  },
42991
+ async openCursor(params) {
42992
+ const manager = getCursorLeaseManager(new URL(normalizedBaseUrl).host, tokenResolver.cursorMaxActive);
42993
+ const lease = await manager.acquire();
42994
+ let created;
42995
+ try {
42996
+ created = await manager.runCreate(() => requestJson(
42997
+ `${apiBasePath}/records/cursor.json`,
42998
+ {
42999
+ method: "POST",
43000
+ body: JSON.stringify({
43001
+ app: params.app,
43002
+ query: params.query,
43003
+ size: params.size,
43004
+ fields: params.fields && params.fields.length > 0 ? params.fields : void 0
43005
+ })
43006
+ },
43007
+ params.app
43008
+ ));
43009
+ } catch (error51) {
43010
+ if (error51 instanceof KintoneApiError) {
43011
+ lease.release();
43012
+ throw error51;
43013
+ }
43014
+ lease.quarantine();
43015
+ throw new CursorCreateOutcomeUnknownError(error51);
43016
+ }
43017
+ const cursorId = created.id;
43018
+ return createKintoneCursorHandle(Number(created.totalCount), {
43019
+ get: () => requestJson(
43020
+ `${apiBasePath}/records/cursor.json?id=${encodeURIComponent(cursorId)}`,
43021
+ { method: "GET" },
43022
+ params.app
43023
+ ),
43024
+ delete: () => requestJson(
43025
+ `${apiBasePath}/records/cursor.json`,
43026
+ { method: "DELETE", body: JSON.stringify({ id: cursorId }) },
43027
+ params.app
43028
+ ),
43029
+ onReleased: () => lease.release(),
43030
+ onReleaseUnknown: () => lease.quarantine()
43031
+ });
43032
+ },
42356
43033
  async postRecords(_params) {
42357
43034
  const res = await requestJson(
42358
43035
  `${apiBasePath}/records.json`,
@@ -42819,6 +43496,10 @@ async function createKsqlRuntime(serverOptions, input) {
42819
43496
  const onLimit2 = input.onLimit ?? envOnLimit("KSQL_ON_LIMIT") ?? profile2.query?.onLimit ?? "error";
42820
43497
  const timeout2 = input.timeout ?? envInt("KSQL_TIMEOUT") ?? profile2.query?.timeout ?? 3e4;
42821
43498
  const tempTableMaxRows2 = input.tempTableMaxRows ?? envInt("KSQL_TEMP_TABLE_MAX_ROWS") ?? profile2.query?.tempTableMaxRows;
43499
+ const cursorMaxActive2 = input.cursorMaxActive ?? envInt("KSQL_CURSOR_MAX_ACTIVE") ?? profile2.query?.cursorMaxActive ?? 2;
43500
+ if (!Number.isSafeInteger(cursorMaxActive2) || cursorMaxActive2 < 1 || cursorMaxActive2 > 5) {
43501
+ throw new Error("ArgumentError: cursorMaxActive must be an integer from 1 to 5.");
43502
+ }
42822
43503
  const appIds = extractAppIds(sql);
42823
43504
  const defaultApp = envInt("KSQL_APP") ?? profile2.app ?? null;
42824
43505
  if (appIds.length === 0 && defaultApp !== null) appIds.push(defaultApp);
@@ -42856,6 +43537,7 @@ async function createKsqlRuntime(serverOptions, input) {
42856
43537
  }
42857
43538
  profileClientMap.set(pName, createNodeKintoneClient(baseUrl, {
42858
43539
  guestSpaceId,
43540
+ cursorMaxActive: cursorMaxActive2,
42859
43541
  timeoutMs: timeout2,
42860
43542
  debug: input.debug,
42861
43543
  debugHeaders: input.debugHeaders,
@@ -42887,6 +43569,7 @@ async function createKsqlRuntime(serverOptions, input) {
42887
43569
  }
42888
43570
  profileClientMap.set(pName, createNodeKintoneClient(baseUrl, {
42889
43571
  guestSpaceId,
43572
+ cursorMaxActive: cursorMaxActive2,
42890
43573
  timeoutMs: timeout2,
42891
43574
  debug: input.debug,
42892
43575
  debugHeaders: input.debugHeaders,
@@ -42920,6 +43603,12 @@ async function createKsqlRuntime(serverOptions, input) {
42920
43603
  if (!routed) throw new Error(`AuthError: profile "${binding.profile}" is not resolved for APP${params.app}.`);
42921
43604
  return routed.getRecords({ ...params, app: binding.appId });
42922
43605
  },
43606
+ openCursor: (params) => {
43607
+ const binding = resolveRuntimeBinding(runtimeContext.sqlContext, params.app);
43608
+ const routed = runtimeContext.clientsByProfile.get(binding.profile);
43609
+ if (!routed) throw new Error(`AuthError: profile "${binding.profile}" is not resolved for APP${params.app}.`);
43610
+ return routed.openCursor({ ...params, app: binding.appId });
43611
+ },
42923
43612
  postRecords: (params) => {
42924
43613
  const binding = resolveRuntimeBinding(runtimeContext.sqlContext, params.app);
42925
43614
  const routed = runtimeContext.clientsByProfile.get(binding.profile);
@@ -42970,6 +43659,7 @@ async function createKsqlRuntime(serverOptions, input) {
42970
43659
  fetchParallel: fetchParallel2,
42971
43660
  onLimit: onLimit2,
42972
43661
  timeout: timeout2,
43662
+ cursorMaxActive: cursorMaxActive2,
42973
43663
  tempTableMaxRows: tempTableMaxRows2
42974
43664
  };
42975
43665
  }
@@ -43170,6 +43860,7 @@ function noOpClient() {
43170
43860
  };
43171
43861
  return {
43172
43862
  getRecords: fail,
43863
+ openCursor: fail,
43173
43864
  postRecords: fail,
43174
43865
  putRecords: fail,
43175
43866
  deleteRecords: fail,
@@ -43446,7 +44137,9 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43446
44137
  const runtime = needsAppMetadata ? await createRuntime(serverOptions, {
43447
44138
  sql: input.sql,
43448
44139
  sqlContext: normalized.sqlContext,
43449
- profile: input.profile
44140
+ profile: input.profile,
44141
+ maxRecords: input.maxRecords,
44142
+ cursorMaxActive: input.cursorMaxActive
43450
44143
  }) : null;
43451
44144
  const explainClient = runtime?.client ?? noOpClient();
43452
44145
  const explainCacheContext = runtime?.cacheContext ?? normalized.cacheContext;
@@ -43457,7 +44150,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43457
44150
  explainClient,
43458
44151
  void 0,
43459
44152
  explainCacheContext,
43460
- runtime?.maxRecords
44153
+ runtime?.maxRecords ?? input.maxRecords,
44154
+ runtime?.cursorMaxActive ?? input.cursorMaxActive ?? 2
43461
44155
  );
43462
44156
  return {
43463
44157
  ok: true,
@@ -43469,7 +44163,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43469
44163
  }
43470
44164
  const result = await executeSql(explainSql(explainSourceSql), explainClient, {
43471
44165
  cacheContext: explainCacheContext,
43472
- maxRecords: runtime?.maxRecords
44166
+ maxRecords: runtime?.maxRecords ?? input.maxRecords,
44167
+ cursorMaxActive: runtime?.cursorMaxActive ?? input.cursorMaxActive ?? 2
43473
44168
  });
43474
44169
  if (result.type !== "SELECT") {
43475
44170
  throw new Error(`ArgumentError: EXPLAIN returned unexpected result type ${result.type}.`);
@@ -43496,7 +44191,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43496
44191
  fetchParallel: input.fetchParallel,
43497
44192
  onLimit: validation.containsValidationOnly ? "error" : input.onLimit,
43498
44193
  timeout: input.timeout,
43499
- tempTableMaxRows: input.tempTableMaxRows
44194
+ tempTableMaxRows: input.tempTableMaxRows,
44195
+ cursorMaxActive: input.cursorMaxActive
43500
44196
  });
43501
44197
  const batchResult = await executeBatchSql(runtime2.sql, runtime2.client, {
43502
44198
  maxRecords: runtime2.maxRecords,
@@ -43511,6 +44207,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43511
44207
  // runtime.timeout は env / profile / 既定 30000ms を解決済みの値で、
43512
44208
  // HTTP クライアント側の per-request タイムアウトと同値になる
43513
44209
  timeoutMs: runtime2.timeout,
44210
+ cursorMaxActive: runtime2.cursorMaxActive ?? input.cursorMaxActive ?? 2,
43514
44211
  variables: input.variables
43515
44212
  });
43516
44213
  return { ...buildBatchEnvelope(batchResult, { maxTotalRecords: input.maxTotalRecords }) };
@@ -43540,13 +44237,15 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43540
44237
  maxRecords: input.maxRecords,
43541
44238
  fetchParallel: input.fetchParallel,
43542
44239
  onLimit: validation.containsValidationOnly ? "error" : input.onLimit,
43543
- timeout: input.timeout
44240
+ timeout: input.timeout,
44241
+ cursorMaxActive: input.cursorMaxActive
43544
44242
  });
43545
44243
  const result = await executeSql(runtime.sql, runtime.client, {
43546
44244
  maxRecords: runtime.maxRecords,
43547
44245
  fetchParallel: runtime.fetchParallel,
43548
44246
  onLimitReached: runtime.onLimit,
43549
- cacheContext: runtime.cacheContext
44247
+ cacheContext: runtime.cacheContext,
44248
+ cursorMaxActive: runtime.cursorMaxActive ?? input.cursorMaxActive ?? 2
43550
44249
  });
43551
44250
  if (result.type === "ASSERT") return toAssertPayload(result);
43552
44251
  if (result.type === "VALIDATION") return toDmlValidationPayload(result);
@@ -43590,7 +44289,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43590
44289
  fetchParallel: input.fetchParallel,
43591
44290
  onLimit: DEFAULT_ON_LIMIT,
43592
44291
  timeout: input.timeout,
43593
- tempTableMaxRows: input.tempTableMaxRows
44292
+ tempTableMaxRows: input.tempTableMaxRows,
44293
+ cursorMaxActive: input.cursorMaxActive
43594
44294
  });
43595
44295
  let totalAffected = staticInsertTotal;
43596
44296
  const batchResult = await executeBatchSql(runtime.sql, runtime.client, {
@@ -43602,6 +44302,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43602
44302
  tempTableMaxRows: runtime.tempTableMaxRows,
43603
44303
  // 合計タイムアウト(解決済みの runtime.timeout。per-request と同値)
43604
44304
  timeoutMs: runtime.timeout,
44305
+ cursorMaxActive: runtime.cursorMaxActive ?? input.cursorMaxActive ?? 2,
43605
44306
  variables: input.variables,
43606
44307
  confirm: async (count, operation) => {
43607
44308
  if (count > dmlMaxRows) {
@@ -43657,7 +44358,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43657
44358
  maxRecords: resolveMutateRuntimeMaxRecords(validation.statements, dmlMaxRows),
43658
44359
  fetchParallel: input.fetchParallel,
43659
44360
  onLimit: DEFAULT_ON_LIMIT,
43660
- timeout: input.timeout
44361
+ timeout: input.timeout,
44362
+ cursorMaxActive: input.cursorMaxActive
43661
44363
  });
43662
44364
  let result;
43663
44365
  try {
@@ -43666,6 +44368,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43666
44368
  fetchParallel: runtime.fetchParallel,
43667
44369
  onLimitReached: runtime.onLimit,
43668
44370
  cacheContext: runtime.cacheContext,
44371
+ cursorMaxActive: runtime.cursorMaxActive ?? input.cursorMaxActive ?? 2,
43669
44372
  confirm: async (count, operation) => {
43670
44373
  if (count > dmlMaxRows) {
43671
44374
  throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed dmlMaxRows (${dmlMaxRows}).`);
@@ -43838,6 +44541,7 @@ var fetchParallel = external_exports.number().int().min(1).max(10).describe("Num
43838
44541
  var onLimit = external_exports.enum(["error", "truncate"]).describe("Behavior when maxRecords is exceeded: 'error' rejects, 'truncate' returns the first maxRecords rows (default 'error'). Local ORDER BY plans require complete input and fail instead of returning a truncated top-N; REST top-N and KORDER_NATIVE do not fetch a partial candidate set. VALIDATE ONLY always overrides 'truncate' to 'error'.").optional();
43839
44542
  var tempTableMaxRows = external_exports.number().int().positive().describe("Per-temp-table cap on materialized rows for CREATE TEMP TABLE ... AS SELECT (default 10000). Overflow always errors \u2014 'truncate' never applies to temp tables, so downstream statements never see silently truncated data. Raising this increases memory use (up to 16 temp tables per batch); prefer narrowing the SELECT with WHERE.").optional();
43840
44543
  var timeout = external_exports.number().int().positive().describe("Request timeout in milliseconds. For multi-statement batches this also acts as the total batch deadline.").optional();
44544
+ var cursorMaxActive = external_exports.number().int().min(1).max(5).describe("Maximum active Cursor API handles per kintone host in this process (1-5, default 2). Later calls update the host limit; lowering it keeps existing cursors and delays new ones until active usage falls below the new limit. Create/Get are never automatically retried; capacity waits up to 30 seconds.").optional();
43841
44545
  var savedQueryName = external_exports.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/).describe("Saved query name (alphanumeric, '_' and '-', up to 64 chars).");
43842
44546
  var savedQueryTags = external_exports.array(external_exports.string().min(1)).describe("Tags for organizing saved queries.").optional();
43843
44547
  var validateInputSchema = external_exports.object({
@@ -43846,7 +44550,9 @@ var validateInputSchema = external_exports.object({
43846
44550
  });
43847
44551
  var explainInputSchema = external_exports.object({
43848
44552
  sql: external_exports.string().min(1).describe("kSQL text to explain. May contain multiple ;-separated statements (batch) and temp tables (#name)."),
43849
- profile
44553
+ profile,
44554
+ maxRecords,
44555
+ cursorMaxActive
43850
44556
  });
43851
44557
  var queryInputSchema = external_exports.object({
43852
44558
  sql: external_exports.string().min(1).describe("Read-only kSQL text. May contain multiple ;-separated statements (batch) with temp tables, e.g. CREATE TEMP TABLE #t AS SELECT ...; SELECT ... FROM #t;"),
@@ -43856,6 +44562,7 @@ var queryInputSchema = external_exports.object({
43856
44562
  onLimit,
43857
44563
  tempTableMaxRows,
43858
44564
  timeout,
44565
+ cursorMaxActive,
43859
44566
  continueOnError: external_exports.boolean().describe("Batch (multi-statement) only: keep executing subsequent statements after a runtime error (default false = fail-fast).").optional(),
43860
44567
  maxTotalRecords: external_exports.number().int().positive().describe("Batch (multi-statement) only: cap on total rows returned across all result sets (default: unlimited).").optional(),
43861
44568
  variables: external_exports.record(external_exports.string(), external_exports.string()).describe("Batch only: string values for variables declared with DECLARE. Keys omit @ and are case-insensitive.").optional()
@@ -43869,6 +44576,7 @@ var mutateInputSchema = external_exports.object({
43869
44576
  fetchParallel,
43870
44577
  tempTableMaxRows,
43871
44578
  timeout,
44579
+ cursorMaxActive,
43872
44580
  dmlTotalMaxRows: external_exports.number().int().positive().describe("Batch (multi-statement) only: cap on total affected rows across the whole batch (default: per-statement dmlMaxRows only). DML batches always run fail-fast.").optional(),
43873
44581
  variables: external_exports.record(external_exports.string(), external_exports.string()).describe("Batch only: string values for variables declared with DECLARE. Keys omit @ and are case-insensitive.").optional()
43874
44582
  });
@@ -43959,7 +44667,7 @@ Options:
43959
44667
  -h, --help Show help
43960
44668
  `);
43961
44669
  }
43962
- var SERVER_VERSION = true ? "3.0.0" : "0.0.0-dev";
44670
+ var SERVER_VERSION = true ? "3.2.0" : "0.0.0-dev";
43963
44671
  function createServer(args) {
43964
44672
  const server = new McpServer({
43965
44673
  name: "ksql-mcp",