@rex0220/kintone-sql-tools 2.17.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist-cli/ksql.js CHANGED
@@ -78,6 +78,7 @@ var KEYWORDS = /* @__PURE__ */ new Map([
78
78
  ["BY", "BY" /* BY */],
79
79
  ["HAVING", "HAVING" /* HAVING */],
80
80
  ["ORDER", "ORDER" /* ORDER */],
81
+ ["KORDER", "KORDER" /* KORDER */],
81
82
  ["ASC", "ASC" /* ASC */],
82
83
  ["DESC", "DESC" /* DESC */],
83
84
  ["LIMIT", "LIMIT" /* LIMIT */],
@@ -603,7 +604,7 @@ var Parser = class {
603
604
  case "WITH" /* WITH */:
604
605
  return this.parseWith();
605
606
  case "SELECT" /* SELECT */:
606
- return this.tryParseUnionChain(this.parseSelect());
607
+ return this.tryParseUnionChain(this.parseSelect(true));
607
608
  case "INSERT" /* INSERT */:
608
609
  return this.parseInsert();
609
610
  case "UPDATE" /* UPDATE */:
@@ -793,7 +794,7 @@ var Parser = class {
793
794
  }
794
795
  query = w;
795
796
  } else if (tok.kind === "SELECT" /* SELECT */) {
796
- const sel = this.parseSelect();
797
+ const sel = this.parseSelect(true);
797
798
  const chained = this.tryParseUnionChain(sel);
798
799
  query = chained;
799
800
  } else if (tok.kind === "INSERT" /* INSERT */) {
@@ -986,7 +987,7 @@ var Parser = class {
986
987
  // ----------------------------------------------------------
987
988
  // SELECT
988
989
  // ----------------------------------------------------------
989
- parseSelect() {
990
+ parseSelect(allowKorder = false) {
990
991
  this.expect("SELECT" /* SELECT */);
991
992
  const distinct = this.consume("DISTINCT" /* DISTINCT */);
992
993
  const columns = this.parseSelectColumns();
@@ -1003,7 +1004,19 @@ var Parser = class {
1003
1004
  having = this.parseWhereExpr();
1004
1005
  }
1005
1006
  }
1006
- const orderBy = this.consume("ORDER" /* ORDER */) ? (this.expect("BY" /* BY */), this.parseOrderBy()) : [];
1007
+ let orderMode = "CANONICAL";
1008
+ let orderBy = [];
1009
+ if (this.consume("ORDER" /* ORDER */)) {
1010
+ this.expect("BY" /* BY */);
1011
+ orderBy = this.parseOrderBy();
1012
+ } else if (this.consume("KORDER" /* KORDER */)) {
1013
+ if (!allowKorder) {
1014
+ throw new ParseError("KORDER BY \u306F\u5229\u7528\u8005\u3078\u7D50\u679C\u3092\u8FD4\u3059\u30C8\u30C3\u30D7\u30EC\u30D9\u30EB SELECT \u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059", this.prev());
1015
+ }
1016
+ orderMode = "KINTONE_NATIVE";
1017
+ this.expect("BY" /* BY */);
1018
+ orderBy = this.parseOrderBy();
1019
+ }
1007
1020
  const limit = this.consume("LIMIT" /* LIMIT */) ? this.parseUnsignedInt() : null;
1008
1021
  const offset = this.consume("OFFSET" /* OFFSET */) ? this.parseUnsignedInt() : null;
1009
1022
  const hasWindow = columns.some((column) => column.type === "WINDOW_COL");
@@ -1020,6 +1033,7 @@ var Parser = class {
1020
1033
  where,
1021
1034
  groupBy,
1022
1035
  having,
1036
+ orderMode,
1023
1037
  orderBy,
1024
1038
  limit,
1025
1039
  offset
@@ -1057,6 +1071,9 @@ var Parser = class {
1057
1071
  // ----------------------------------------------------------
1058
1072
  tryParseUnionChain(left) {
1059
1073
  if (this.peek().kind !== "UNION" /* UNION */) return left;
1074
+ if (left.type === "SELECT" && left.orderMode === "KINTONE_NATIVE") {
1075
+ throw new ParseError("KORDER BY \u306F UNION \u5206\u5C90\u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", this.peek());
1076
+ }
1060
1077
  this.advance();
1061
1078
  const all = this.consume("ALL" /* ALL */);
1062
1079
  const right = this.parseSelect();
@@ -2710,7 +2727,50 @@ function isReadOnlyStatement(stmt) {
2710
2727
  return !writesKintone(stmt) && (isReadOnlyType(stmt.type) || isDmlType(stmt.type));
2711
2728
  }
2712
2729
  function requiresCompleteInput(stmt) {
2713
- return isDmlType(stmt.type);
2730
+ if (isDmlType(stmt.type)) return true;
2731
+ switch (stmt.type) {
2732
+ case "SELECT":
2733
+ return selectRequiresCompleteInput(stmt);
2734
+ case "UNION":
2735
+ return unionRequiresCompleteInput(stmt);
2736
+ case "WITH":
2737
+ return stmt.ctes.some(
2738
+ (cte) => cte.query.type === "SELECT" && selectRequiresCompleteInput(cte.query) || cte.query.type === "UNION" && unionRequiresCompleteInput(cte.query)
2739
+ ) || (stmt.query.type === "SELECT" ? selectRequiresCompleteInput(stmt.query) : unionRequiresCompleteInput(stmt.query));
2740
+ case "CREATE_TEMP_TABLE":
2741
+ return stmt.query.type === "SELECT" ? selectRequiresCompleteInput(stmt.query) : stmt.query.type === "UNION" ? unionRequiresCompleteInput(stmt.query) : requiresCompleteInput(stmt.query);
2742
+ default:
2743
+ return false;
2744
+ }
2745
+ }
2746
+ function unionRequiresCompleteInput(stmt) {
2747
+ const left = stmt.left.type === "SELECT" ? selectRequiresCompleteInput(stmt.left) : unionRequiresCompleteInput(stmt.left);
2748
+ return left || selectRequiresCompleteInput(stmt.right);
2749
+ }
2750
+ function selectRequiresCompleteInput(stmt) {
2751
+ if (stmt.orderBy.length > 0) return true;
2752
+ if (stmt.columns.some(
2753
+ (column) => column.type === "WINDOW_COL" && column.orderBy.length > 0 || column.type === "SCALAR_SUBQUERY_COL" && selectRequiresCompleteInput(column.query) || column.type === "CASE_COL" && column.expr.branches.some(
2754
+ (branch) => whereRequiresCompleteInput(branch.condition)
2755
+ )
2756
+ )) return true;
2757
+ return whereRequiresCompleteInput(stmt.where) || whereRequiresCompleteInput(stmt.having);
2758
+ }
2759
+ function whereRequiresCompleteInput(where) {
2760
+ if (where === null) return false;
2761
+ switch (where.type) {
2762
+ case "BINARY":
2763
+ return (where.right.type === "SUBQUERY_IN_LIST" || where.right.type === "SCALAR_SUBQUERY") && selectRequiresCompleteInput(where.right.query);
2764
+ case "LOGICAL":
2765
+ return whereRequiresCompleteInput(where.left) || whereRequiresCompleteInput(where.right);
2766
+ case "NOT":
2767
+ case "GROUP":
2768
+ return whereRequiresCompleteInput(where.expr);
2769
+ case "EXISTS":
2770
+ return selectRequiresCompleteInput(where.query);
2771
+ case "NULL_CHECK":
2772
+ return false;
2773
+ }
2714
2774
  }
2715
2775
  function hasWhereClause(stmt) {
2716
2776
  if (!stmt || typeof stmt !== "object") return false;
@@ -3525,6 +3585,7 @@ function buildInlinedQuery(stmt) {
3525
3585
  where,
3526
3586
  groupBy: [],
3527
3587
  having: null,
3588
+ orderMode: "CANONICAL",
3528
3589
  orderBy: final.orderBy.length > 0 ? final.orderBy : cteBody.orderBy,
3529
3590
  limit: final.limit ?? cteBody.limit,
3530
3591
  offset: final.offset ?? cteBody.offset,
@@ -4119,6 +4180,72 @@ function analyzeBatch(statements) {
4119
4180
  };
4120
4181
  }
4121
4182
 
4183
+ // src/core/fieldSemantics.ts
4184
+ var STRING_FIELD_TYPES = /* @__PURE__ */ new Set([
4185
+ "SINGLE_LINE_TEXT",
4186
+ "MULTI_LINE_TEXT",
4187
+ "RICH_TEXT",
4188
+ "LINK",
4189
+ "DATE",
4190
+ "TIME",
4191
+ "DATETIME",
4192
+ "CREATED_TIME",
4193
+ "UPDATED_TIME",
4194
+ "CREATOR",
4195
+ "MODIFIER"
4196
+ ]);
4197
+ var OPTION_FIELD_TYPES = /* @__PURE__ */ new Set([
4198
+ "DROP_DOWN",
4199
+ "RADIO_BUTTON",
4200
+ "CHECK_BOX",
4201
+ "MULTI_SELECT",
4202
+ "STATUS"
4203
+ ]);
4204
+ function resolveFieldSemantics(source) {
4205
+ let compareMode;
4206
+ if (source.fieldType === "RECORD_NUMBER" || source.fieldType === "__ID__") {
4207
+ compareMode = "recordNumber";
4208
+ } else if (source.fieldType === "NUMBER") {
4209
+ compareMode = "number";
4210
+ } else if (source.fieldType === "CALC") {
4211
+ compareMode = source.sortKind === "number" ? "number" : "string";
4212
+ } else if (OPTION_FIELD_TYPES.has(source.fieldType)) {
4213
+ compareMode = "option";
4214
+ } else if (STRING_FIELD_TYPES.has(source.fieldType)) {
4215
+ compareMode = "string";
4216
+ } else {
4217
+ compareMode = "unsupported";
4218
+ }
4219
+ const optionOrder = source.optionOrder ? new Map(Object.entries(source.optionOrder)) : void 0;
4220
+ return {
4221
+ fieldType: source.fieldType,
4222
+ compareMode,
4223
+ inSubtable: source.inSubtable === true,
4224
+ requiresCollectionOperators: source.inSubtable === true || source.requiresCollectionOperators === true,
4225
+ ...optionOrder && optionOrder.size > 0 ? { optionOrder } : {}
4226
+ };
4227
+ }
4228
+ function syntheticSemantics(compareMode, fieldType = compareMode === "number" ? "KSQL_NUMBER" : "KSQL_STRING") {
4229
+ return { fieldType, compareMode, inSubtable: false, requiresCollectionOperators: false };
4230
+ }
4231
+ function withFieldSemanticSource(semantics, appId, fieldCode) {
4232
+ return { ...semantics, source: { appId, fieldCode } };
4233
+ }
4234
+ function fieldSemanticsEqual(left, right) {
4235
+ if (left === right) return true;
4236
+ if (!left || !right) return false;
4237
+ if (left.fieldType !== right.fieldType || left.compareMode !== right.compareMode || left.inSubtable !== right.inSubtable || left.requiresCollectionOperators !== right.requiresCollectionOperators) return false;
4238
+ if (left.source?.appId !== right.source?.appId || left.source?.fieldCode !== right.source?.fieldCode) return false;
4239
+ const a = left.optionOrder;
4240
+ const b = right.optionOrder;
4241
+ if (a === b) return true;
4242
+ if (!a || !b || a.size !== b.size) return false;
4243
+ for (const [key, value] of a) {
4244
+ if (b.get(key) !== value) return false;
4245
+ }
4246
+ return true;
4247
+ }
4248
+
4122
4249
  // src/core/batchVariables.ts
4123
4250
  var VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
4124
4251
  function normalizeBatchVariableName(name) {
@@ -4154,24 +4281,129 @@ function validateDeclaredBatchVariables(statements, input) {
4154
4281
  }
4155
4282
 
4156
4283
  // src/core/scalarCompare.ts
4157
- function compareScalarValues(op, leftStr, rightStr) {
4158
- if (op === "=") return leftStr === rightStr;
4159
- if (op === "!=" || op === "<>") return leftStr !== rightStr;
4160
- const rightNum = Number(rightStr);
4161
- if (leftStr === "" && rightStr !== "" && Number.isFinite(rightNum)) {
4162
- return op === "<" || op === "<=";
4163
- }
4164
- const leftNum = Number(leftStr);
4165
- const numeric = !Number.isNaN(leftNum) && !Number.isNaN(rightNum);
4284
+ function compareCodePointStrings(left, right) {
4285
+ const a = left[Symbol.iterator]();
4286
+ const b = right[Symbol.iterator]();
4287
+ while (true) {
4288
+ const av = a.next();
4289
+ const bv = b.next();
4290
+ if (av.done || bv.done) {
4291
+ if (av.done && bv.done) return 0;
4292
+ return av.done ? -1 : 1;
4293
+ }
4294
+ const ac = av.value.codePointAt(0) ?? 0;
4295
+ const bc = bv.value.codePointAt(0) ?? 0;
4296
+ if (ac < bc) return -1;
4297
+ if (ac > bc) return 1;
4298
+ }
4299
+ }
4300
+ function triCompare(left, right) {
4301
+ return left < right ? -1 : left > right ? 1 : 0;
4302
+ }
4303
+ function numberKey(value) {
4304
+ if (value === "") return { band: 0 };
4305
+ const numeric = Number(value);
4306
+ if (numeric === Number.NEGATIVE_INFINITY) return { band: 1 };
4307
+ if (Number.isFinite(numeric)) return { band: 2, value: numeric };
4308
+ if (numeric === Number.POSITIVE_INFINITY) return { band: 3 };
4309
+ if (value === "NaN") return { band: 4 };
4310
+ return { band: 5, value };
4311
+ }
4312
+ function compareNumbers(left, right) {
4313
+ const a = numberKey(left);
4314
+ const b = numberKey(right);
4315
+ if (a.band !== b.band) return a.band < b.band ? -1 : 1;
4316
+ if (a.band === 2 && b.band === 2) return triCompare(a.value, b.value);
4317
+ if (a.band === 5 && b.band === 5) return compareCodePointStrings(a.value, b.value);
4318
+ return 0;
4319
+ }
4320
+ function recordNumberKey(value, allowPrefix) {
4321
+ if (value === "") return { empty: true, normalizedId: "", display: value };
4322
+ const match = /^\d+$/.test(value) ? value : allowPrefix ? /-(\d+)$/.exec(value)?.[1] : void 0;
4323
+ if (match === void 0) {
4324
+ throw new Error(`ArgumentError: invalid ${allowPrefix ? "RECORD_NUMBER" : "$id"} value: ${value}`);
4325
+ }
4326
+ return {
4327
+ empty: false,
4328
+ normalizedId: match.replace(/^0+(?=\d)/, ""),
4329
+ display: value
4330
+ };
4331
+ }
4332
+ function compareRecordNumbers(left, right, allowPrefix) {
4333
+ const a = recordNumberKey(left, allowPrefix);
4334
+ const b = recordNumberKey(right, allowPrefix);
4335
+ if (a.empty || b.empty) return a.empty === b.empty ? 0 : a.empty ? -1 : 1;
4336
+ if (a.normalizedId.length !== b.normalizedId.length) {
4337
+ return a.normalizedId.length < b.normalizedId.length ? -1 : 1;
4338
+ }
4339
+ const idCmp = compareCodePointStrings(a.normalizedId, b.normalizedId);
4340
+ return idCmp !== 0 ? idCmp : compareCodePointStrings(a.display, b.display);
4341
+ }
4342
+ function parseOptionValues(value, fieldType) {
4343
+ if (value === "") return [];
4344
+ if (fieldType !== "CHECK_BOX" && fieldType !== "MULTI_SELECT") return [value];
4345
+ try {
4346
+ const parsed = JSON.parse(value);
4347
+ return Array.isArray(parsed) ? parsed.map((item) => String(item ?? "")) : [value];
4348
+ } catch {
4349
+ return [value];
4350
+ }
4351
+ }
4352
+ function optionVector(value, semantics) {
4353
+ const order = semantics.optionOrder ?? /* @__PURE__ */ new Map();
4354
+ const unique = [...new Set(parseOptionValues(value, semantics.fieldType))];
4355
+ const vector = unique.map((label) => {
4356
+ const rank = order.get(label);
4357
+ return rank === void 0 ? { knownBand: 1, rank: 0, label } : { knownBand: 0, rank, label };
4358
+ });
4359
+ vector.sort(compareOptionElement);
4360
+ return vector;
4361
+ }
4362
+ function compareOptionElement(left, right) {
4363
+ if (left.knownBand !== right.knownBand) return left.knownBand < right.knownBand ? -1 : 1;
4364
+ if (left.rank !== right.rank) return left.rank < right.rank ? -1 : 1;
4365
+ return compareCodePointStrings(left.label, right.label);
4366
+ }
4367
+ function compareOptions(left, right, semantics) {
4368
+ const a = optionVector(left, semantics);
4369
+ const b = optionVector(right, semantics);
4370
+ const length = Math.min(a.length, b.length);
4371
+ for (let index = 0; index < length; index++) {
4372
+ const cmp = compareOptionElement(a[index], b[index]);
4373
+ if (cmp !== 0) return cmp;
4374
+ }
4375
+ return a.length < b.length ? -1 : a.length > b.length ? 1 : 0;
4376
+ }
4377
+ function compareCanonicalValues(left, right, semantics) {
4378
+ switch (semantics.compareMode) {
4379
+ case "string":
4380
+ return compareCodePointStrings(left, right);
4381
+ case "number":
4382
+ return compareNumbers(left, right);
4383
+ case "recordNumber":
4384
+ return compareRecordNumbers(left, right, semantics.fieldType === "RECORD_NUMBER");
4385
+ case "option":
4386
+ return compareOptions(left, right, semantics);
4387
+ case "unsupported":
4388
+ throw new Error(`ArgumentError: values of type ${semantics.fieldType} cannot be compared.`);
4389
+ }
4390
+ }
4391
+ function compareScalarValues(op, left, right, semantics = syntheticSemantics("string")) {
4392
+ const cmp = compareCanonicalValues(left, right, semantics);
4166
4393
  switch (op) {
4394
+ case "=":
4395
+ return cmp === 0;
4396
+ case "!=":
4397
+ case "<>":
4398
+ return cmp !== 0;
4167
4399
  case ">":
4168
- return numeric ? leftNum > rightNum : leftStr > rightStr;
4400
+ return cmp > 0;
4169
4401
  case "<":
4170
- return numeric ? leftNum < rightNum : leftStr < rightStr;
4402
+ return cmp < 0;
4171
4403
  case ">=":
4172
- return numeric ? leftNum >= rightNum : leftStr >= rightStr;
4404
+ return cmp >= 0;
4173
4405
  case "<=":
4174
- return numeric ? leftNum <= rightNum : leftStr <= rightStr;
4406
+ return cmp <= 0;
4175
4407
  }
4176
4408
  }
4177
4409
  function selectScalarExtreme(values, extreme) {
@@ -4181,12 +4413,10 @@ function selectScalarExtreme(values, extreme) {
4181
4413
  const numeric = candidates.every((value) => !Number.isNaN(Number(value)));
4182
4414
  const compare = (left, right) => {
4183
4415
  if (numeric) {
4184
- const leftNum = Number(left);
4185
- const rightNum = Number(right);
4186
- if (leftNum < rightNum) return -1;
4187
- if (leftNum > rightNum) return 1;
4416
+ const numericCmp = triCompare(Number(left), Number(right));
4417
+ if (numericCmp !== 0) return numericCmp;
4188
4418
  }
4189
- return left < right ? -1 : left > right ? 1 : 0;
4419
+ return compareCodePointStrings(left, right);
4190
4420
  };
4191
4421
  return candidates.reduce((best, candidate) => {
4192
4422
  const cmp = compare(candidate, best);
@@ -4194,6 +4424,55 @@ function selectScalarExtreme(values, extreme) {
4194
4424
  });
4195
4425
  }
4196
4426
 
4427
+ // src/core/explainMetadata.ts
4428
+ function whereNeedsFieldMetadata(where) {
4429
+ if (where === null) return false;
4430
+ switch (where.type) {
4431
+ case "BINARY":
4432
+ return valueNeedsFieldMetadata(where.left);
4433
+ case "NULL_CHECK":
4434
+ return valueNeedsFieldMetadata(where.field);
4435
+ case "LOGICAL":
4436
+ return whereNeedsFieldMetadata(where.left) || whereNeedsFieldMetadata(where.right);
4437
+ case "NOT":
4438
+ case "GROUP":
4439
+ return whereNeedsFieldMetadata(where.expr);
4440
+ case "EXISTS":
4441
+ return false;
4442
+ }
4443
+ }
4444
+ function valueNeedsFieldMetadata(value) {
4445
+ if (Array.isArray(value)) return value.some(valueNeedsFieldMetadata);
4446
+ if (value === null || typeof value !== "object") return false;
4447
+ const item = value;
4448
+ if (item["type"] === "FIELD") return item["field"] !== "$id";
4449
+ if (item["type"] === "SELECT") return false;
4450
+ return Object.values(item).some(valueNeedsFieldMetadata);
4451
+ }
4452
+ function selectNeedsOwnMetadata(statement) {
4453
+ return whereNeedsFieldMetadata(statement.where) || statement.orderBy.length > 0 || statement.columns.some(
4454
+ (column) => column.type === "WINDOW_COL" && column.orderBy.length > 0
4455
+ );
4456
+ }
4457
+ function explainNeedsAppMetadata(statement) {
4458
+ const seen = /* @__PURE__ */ new Set();
4459
+ const visit = (node) => {
4460
+ if (node === null || typeof node !== "object") return false;
4461
+ if (seen.has(node)) return false;
4462
+ seen.add(node);
4463
+ if (Array.isArray(node)) return node.some(visit);
4464
+ const item = node;
4465
+ if (item["type"] === "SELECT" && selectNeedsOwnMetadata(node)) {
4466
+ return true;
4467
+ }
4468
+ if ((item["type"] === "UPDATE" || item["type"] === "DELETE") && whereNeedsFieldMetadata(node.where)) {
4469
+ return true;
4470
+ }
4471
+ return Object.values(item).some(visit);
4472
+ };
4473
+ return visit(statement);
4474
+ }
4475
+
4197
4476
  // src/engine/evalFunc.ts
4198
4477
  function evalArithExpr(expr, row) {
4199
4478
  if (expr.type === "NUMBER") return expr.value;
@@ -4458,34 +4737,35 @@ function resolveFieldRef(row, field) {
4458
4737
  }
4459
4738
 
4460
4739
  // src/engine/evalWhere.ts
4461
- function evalWhere(expr, row, resolveFieldType, appliedKlikes) {
4740
+ function evalWhere(expr, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2) {
4462
4741
  switch (expr.type) {
4463
4742
  case "BINARY":
4464
- return evalBinary(expr, row, resolveFieldType, appliedKlikes);
4743
+ return evalBinary(expr, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2);
4465
4744
  case "NULL_CHECK":
4466
4745
  return evalNullCheck(expr, row);
4467
4746
  case "LOGICAL":
4468
- return evalLogical(expr, row, resolveFieldType, appliedKlikes);
4747
+ return evalLogical(expr, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2);
4469
4748
  case "NOT":
4470
- return !evalWhere(expr.expr, row, resolveFieldType, appliedKlikes);
4749
+ return !evalWhere(expr.expr, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2);
4471
4750
  case "GROUP":
4472
- return evalWhere(expr.expr, row, resolveFieldType, appliedKlikes);
4751
+ return evalWhere(expr.expr, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2);
4473
4752
  case "EXISTS": {
4474
4753
  const exists = expr.resolved;
4475
4754
  return expr.not ? !exists : exists;
4476
4755
  }
4477
4756
  }
4478
4757
  }
4479
- function evalBinary(expr, row, resolveFieldType, appliedKlikes) {
4758
+ function evalBinary(expr, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2) {
4480
4759
  if (expr.op === "KLIKE" || expr.op === "NOT_KLIKE") {
4481
4760
  if (appliedKlikes?.has(expr)) return true;
4482
4761
  throw new Error("KLIKE / NOT KLIKE \u306F\u62BC\u3057\u4E0B\u3052\u6E08\u307F\u96C6\u5408\u306B\u542B\u307E\u308C\u306A\u3044\u305F\u3081 JavaScript \u5074\u3067\u306F\u8A55\u4FA1\u3067\u304D\u307E\u305B\u3093");
4483
4762
  }
4484
- const left = resolveField(expr.left, row, resolveFieldType);
4763
+ const left = resolveField(expr.left, row, resolveFieldType, resolveFieldSemantics2);
4485
4764
  const fieldType = expr.left.type === "FIELD" ? resolveFieldType?.(expr.left) : void 0;
4486
- return evalOp(expr.op, left, expr.right, row, fieldType, resolveFieldType);
4765
+ const semantics = semanticsForLeft(expr.left, fieldType, resolveFieldSemantics2);
4766
+ return evalOp(expr.op, left, expr.right, row, fieldType, resolveFieldType, semantics, resolveFieldSemantics2);
4487
4767
  }
4488
- function evalOp(op, leftStr, right, row, fieldType, resolveFieldType) {
4768
+ function evalOp(op, leftStr, right, row, fieldType, resolveFieldType, semantics = syntheticSemantics("string"), resolveFieldSemantics2) {
4489
4769
  if (op === "IN" || op === "NOT_IN") {
4490
4770
  let values = null;
4491
4771
  if (right.type === "IN_LIST") {
@@ -4510,8 +4790,53 @@ function evalOp(op, leftStr, right, row, fieldType, resolveFieldType) {
4510
4790
  if (op === "KLIKE" || op === "NOT_KLIKE") {
4511
4791
  throw new Error("KLIKE / NOT KLIKE \u306F JavaScript \u5074\u3067\u306F\u8A55\u4FA1\u3067\u304D\u307E\u305B\u3093\uFF08SIMPLE SELECT \u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059\uFF09");
4512
4792
  }
4513
- const rightStr = resolveValue(right, row, resolveFieldType);
4514
- return compareScalarValues(op, leftStr, rightStr);
4793
+ const rightStr = resolveValue(right, row, resolveFieldType, resolveFieldSemantics2);
4794
+ return compareScalarValues(op, leftStr, rightStr, semantics);
4795
+ }
4796
+ var NUMERIC_STRING_FUNCTIONS = /* @__PURE__ */ new Set([
4797
+ "LENGTH",
4798
+ "INSTR",
4799
+ "ROUND",
4800
+ "FLOOR",
4801
+ "CEIL",
4802
+ "TRUNCATE",
4803
+ "YEAR",
4804
+ "MONTH",
4805
+ "DAY",
4806
+ "DATEDIFF",
4807
+ "ABS",
4808
+ "MOD",
4809
+ "POWER",
4810
+ "SQRT"
4811
+ ]);
4812
+ function semanticsForLeft(left, fieldType, resolveSemantics) {
4813
+ if (left.type === "FIELD") {
4814
+ return resolveSemantics?.(left) ?? (fieldType ? resolveFieldSemantics({ fieldType }) : syntheticSemantics("string"));
4815
+ }
4816
+ if (left.type === "ARITH_FIELD") return syntheticSemantics("number");
4817
+ if (left.type === "FUNC_FIELD") {
4818
+ return syntheticSemantics(NUMERIC_STRING_FUNCTIONS.has(left.expr.func) ? "number" : "string");
4819
+ }
4820
+ if (left.type === "CASE_FIELD") {
4821
+ const results = [
4822
+ ...left.expr.branches.map((branch) => branch.result),
4823
+ ...left.expr.elseResult ? [left.expr.elseResult] : []
4824
+ ];
4825
+ const modes = results.map((result) => {
4826
+ if (result.type === "NUMBER" || result.type === "ARITH") return syntheticSemantics("number");
4827
+ if (result.type === "STRING_FUNC") {
4828
+ return syntheticSemantics(NUMERIC_STRING_FUNCTIONS.has(result.func) ? "number" : "string");
4829
+ }
4830
+ if (result.type === "FIELD_REF") {
4831
+ const dot = result.field.indexOf(".");
4832
+ const ref = dot > 0 ? { type: "FIELD", tableAlias: result.field.slice(0, dot), field: result.field.slice(dot + 1) } : { type: "FIELD", tableAlias: null, field: result.field };
4833
+ return resolveSemantics?.(ref) ?? syntheticSemantics("string");
4834
+ }
4835
+ return syntheticSemantics("string");
4836
+ });
4837
+ if (modes.length > 0 && modes.every((mode) => mode.compareMode === modes[0].compareMode)) return modes[0];
4838
+ }
4839
+ return syntheticSemantics("string");
4515
4840
  }
4516
4841
  var STRING_ARRAY_FIELD_TYPES = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
4517
4842
  var OBJECT_ARRAY_FIELD_TYPES = /* @__PURE__ */ new Set([
@@ -4562,20 +4887,20 @@ function evalNullCheck(expr, row) {
4562
4887
  const val = resolveField(expr.field, row);
4563
4888
  return expr.not ? val !== "" : val === "";
4564
4889
  }
4565
- function evalLogical(expr, row, resolveFieldType, appliedKlikes) {
4890
+ function evalLogical(expr, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2) {
4566
4891
  if (expr.op === "AND") {
4567
- return evalWhere(expr.left, row, resolveFieldType, appliedKlikes) && evalWhere(expr.right, row, resolveFieldType, appliedKlikes);
4892
+ return evalWhere(expr.left, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2) && evalWhere(expr.right, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2);
4568
4893
  }
4569
- return evalWhere(expr.left, row, resolveFieldType, appliedKlikes) || evalWhere(expr.right, row, resolveFieldType, appliedKlikes);
4894
+ return evalWhere(expr.left, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2) || evalWhere(expr.right, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2);
4570
4895
  }
4571
- function resolveField(field, row, resolveFieldType) {
4896
+ function resolveField(field, row, resolveFieldType, resolveFieldSemantics2) {
4572
4897
  if (field.type === "FUNC_FIELD") return evalStringFunc(field.expr, row);
4573
4898
  if (field.type === "ARITH_FIELD") return String(evalArithExpr(field.expr, row));
4574
- if (field.type === "CASE_FIELD") return evalCaseWhen(field.expr, row, resolveFieldType);
4899
+ if (field.type === "CASE_FIELD") return evalCaseWhen(field.expr, row, resolveFieldType, resolveFieldSemantics2);
4575
4900
  const key = field.tableAlias ? `${field.tableAlias}.${field.field}` : field.field;
4576
4901
  return resolveFieldRef(row, key);
4577
4902
  }
4578
- function resolveValue(value, row, resolveFieldType) {
4903
+ function resolveValue(value, row, resolveFieldType, resolveFieldSemantics2) {
4579
4904
  switch (value.type) {
4580
4905
  case "VARIABLE":
4581
4906
  throw new Error(`ParseError: unresolved batch variable @${value.name}.`);
@@ -4598,14 +4923,14 @@ function resolveValue(value, row, resolveFieldType) {
4598
4923
  if (value.expr.type === "STRING_FUNC") return evalStringFunc(value.expr, row);
4599
4924
  return String(evalArithExpr(value.expr, row));
4600
4925
  case "CASE_VALUE":
4601
- return evalCaseWhen(value.expr, row, resolveFieldType);
4926
+ return evalCaseWhen(value.expr, row, resolveFieldType, resolveFieldSemantics2);
4602
4927
  case "ARRAY":
4603
4928
  return value.elements.map((e) => e.value).join(",");
4604
4929
  }
4605
4930
  }
4606
- function evalCaseWhen(expr, row, resolveFieldType) {
4931
+ function evalCaseWhen(expr, row, resolveFieldType, resolveFieldSemantics2) {
4607
4932
  for (const branch of expr.branches) {
4608
- if (evalWhere(branch.condition, row, resolveFieldType)) {
4933
+ if (evalWhere(branch.condition, row, resolveFieldType, void 0, resolveFieldSemantics2)) {
4609
4934
  return evalCaseResult(branch.result, row);
4610
4935
  }
4611
4936
  }
@@ -5236,6 +5561,232 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
5236
5561
  };
5237
5562
  }
5238
5563
 
5564
+ // src/core/optimization/canonicalOrderPlanner.ts
5565
+ var REST_OFFSET_MAX = 1e4;
5566
+ var REST_LIMIT_MAX = 500;
5567
+ function fieldSemantics(item, semantics) {
5568
+ return item.key.type === "FIELD_NAME" ? semantics.get(item.key.name) : void 0;
5569
+ }
5570
+ function planCanonicalOrder(input) {
5571
+ const { stmt } = input;
5572
+ const reasons = [];
5573
+ const windowOrderBy = stmt.columns.flatMap(
5574
+ (column) => column.type === "WINDOW_COL" ? column.orderBy : []
5575
+ );
5576
+ const allOrderBy = [...stmt.orderBy, ...windowOrderBy];
5577
+ for (const item of allOrderBy) {
5578
+ if (item.key.type !== "FIELD_NAME") continue;
5579
+ const semantics = fieldSemantics(item, input.orderSemantics);
5580
+ if (!semantics) {
5581
+ reasons.push("ORDER_KEY_UNRESOLVED");
5582
+ continue;
5583
+ }
5584
+ if (semantics.fieldType === "KSQL_AMBIGUOUS") reasons.push("ORDER_KEY_AMBIGUOUS");
5585
+ else if (semantics.compareMode === "unsupported") reasons.push("ORDER_KEY_UNSUPPORTED");
5586
+ }
5587
+ if (reasons.includes("ORDER_KEY_AMBIGUOUS")) {
5588
+ throw new Error(
5589
+ "ArgumentError: ORDER BY key is an ambiguous column reference (reason=ORDER_KEY_AMBIGUOUS). Qualify the key with its table alias."
5590
+ );
5591
+ }
5592
+ if (reasons.includes("ORDER_KEY_UNSUPPORTED") || reasons.includes("ORDER_KEY_UNRESOLVED")) {
5593
+ const reason = reasons.includes("ORDER_KEY_UNSUPPORTED") ? "ORDER_KEY_UNSUPPORTED" : "ORDER_KEY_UNRESOLVED";
5594
+ throw new Error(`ArgumentError: ORDER BY key has no canonical comparison contract (reason=${reason}).`);
5595
+ }
5596
+ const allRestEquivalent = stmt.orderBy.length > 0 && windowOrderBy.length === 0 && stmt.orderBy.every(
5597
+ (item) => item.key.type === "FIELD_NAME" && item.key.name === "$id"
5598
+ );
5599
+ if (!allRestEquivalent) reasons.push("ORDER_KEY_NOT_REST_EQUIVALENT");
5600
+ if (input.whereCapability !== "EXACT_PUSHDOWN") reasons.push("WHERE_NOT_EXACT");
5601
+ if (input.staticMode !== "SIMPLE") reasons.push("QUERY_SHAPE_LOCAL");
5602
+ if (stmt.limit === null || stmt.limit < 0 || stmt.limit > REST_LIMIT_MAX) {
5603
+ reasons.push("LIMIT_NOT_REST_WINDOW");
5604
+ }
5605
+ if ((stmt.offset ?? 0) < 0 || (stmt.offset ?? 0) > REST_OFFSET_MAX) {
5606
+ reasons.push("OFFSET_NOT_REST_WINDOW");
5607
+ }
5608
+ if (stmt.limit !== null && stmt.limit > input.maxRecords) reasons.push("MAX_RECORDS_WINDOW");
5609
+ if (input.hasKlike) reasons.push("KLIKE_NOT_REST_WINDOW");
5610
+ if (reasons.length === 0) {
5611
+ return {
5612
+ kind: "CANONICAL_REST_TOP_N",
5613
+ requiresCompleteInput: false,
5614
+ localOrderBy: false,
5615
+ applyLocalOffsetLimit: false,
5616
+ reasonCodes: []
5617
+ };
5618
+ }
5619
+ return {
5620
+ kind: "CANONICAL_LOCAL",
5621
+ requiresCompleteInput: allOrderBy.length > 0,
5622
+ localOrderBy: stmt.orderBy.length > 0,
5623
+ applyLocalOffsetLimit: stmt.orderBy.length > 0,
5624
+ reasonCodes: [...new Set(reasons)]
5625
+ };
5626
+ }
5627
+
5628
+ // src/core/optimization/korderPlanner.ts
5629
+ var KORDER_NATIVE_FIELD_TYPES = /* @__PURE__ */ new Set([
5630
+ "RECORD_NUMBER",
5631
+ "SINGLE_LINE_TEXT",
5632
+ "NUMBER",
5633
+ "CALC",
5634
+ "DATE",
5635
+ "DATETIME",
5636
+ "TIME",
5637
+ "CREATED_TIME",
5638
+ "UPDATED_TIME",
5639
+ "DROP_DOWN",
5640
+ "RADIO_BUTTON",
5641
+ "STATUS",
5642
+ "LINK",
5643
+ "CREATOR",
5644
+ "MODIFIER"
5645
+ ]);
5646
+ function planKorder(input) {
5647
+ const { stmt } = input;
5648
+ const reasons = [];
5649
+ if (stmt.orderMode !== "KINTONE_NATIVE") reasons.push("KORDER_MODE_REQUIRED");
5650
+ if (stmt.from.cteName !== null || stmt.from.subtableCode || input.staticMode !== "SIMPLE") {
5651
+ reasons.push("KORDER_QUERY_SHAPE_UNSUPPORTED");
5652
+ }
5653
+ if (input.whereCapability !== "EXACT_PUSHDOWN") reasons.push("KORDER_WHERE_NOT_EXACT");
5654
+ if (input.hasKlike) reasons.push("KORDER_KLIKE_UNSUPPORTED");
5655
+ if (stmt.orderBy.length === 0) reasons.push("KORDER_KEY_REQUIRED");
5656
+ for (const item of stmt.orderBy) {
5657
+ if (item.key.type !== "FIELD_NAME") {
5658
+ reasons.push(`KORDER_KEY_NOT_DIRECT_FIELD(key=${item.key.type})`);
5659
+ continue;
5660
+ }
5661
+ const name = item.key.name;
5662
+ const semantics = input.orderSemantics.get(name);
5663
+ if (!semantics) {
5664
+ reasons.push(`KORDER_KEY_UNRESOLVED(field=${name})`);
5665
+ continue;
5666
+ }
5667
+ if (name === "$id") continue;
5668
+ if (!semantics.source || semantics.source.fieldCode !== name) {
5669
+ reasons.push(`KORDER_KEY_NOT_DIRECT_FIELD(field=${name})`);
5670
+ continue;
5671
+ }
5672
+ if (!KORDER_NATIVE_FIELD_TYPES.has(semantics.fieldType)) {
5673
+ reasons.push(`KORDER_TYPE_UNSUPPORTED(field=${name}, type=${semantics.fieldType})`);
5674
+ }
5675
+ }
5676
+ if (stmt.limit === null || !Number.isSafeInteger(stmt.limit) || stmt.limit < 0) {
5677
+ reasons.push(`KORDER_LIMIT_INVALID(limit=${String(stmt.limit)})`);
5678
+ }
5679
+ const offset = stmt.offset ?? 0;
5680
+ if (!Number.isSafeInteger(offset) || offset < 0) {
5681
+ reasons.push(`KORDER_OFFSET_INVALID(offset=${offset})`);
5682
+ }
5683
+ const scanRows = stmt.limit === null ? Number.NaN : offset + stmt.limit;
5684
+ if (stmt.limit !== null && !Number.isSafeInteger(scanRows)) {
5685
+ reasons.push(`KORDER_SCAN_ROWS_INVALID(offset=${offset}, limit=${stmt.limit})`);
5686
+ }
5687
+ const unique = [...new Set(reasons)];
5688
+ if (unique.length > 0) {
5689
+ throw new Error(
5690
+ `ArgumentError: KORDER BY cannot be executed (mode=KINTONE_NATIVE; ${unique.join(", ")}). Use ORDER BY for canonical local ordering or simplify the query.`
5691
+ );
5692
+ }
5693
+ const native = stmt.limit <= 500 && offset <= 1e4 && stmt.limit <= input.maxRecords;
5694
+ if (!native && scanRows > input.maxRecords) {
5695
+ throw new Error(
5696
+ `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.`
5697
+ );
5698
+ }
5699
+ return {
5700
+ kind: native ? "KORDER_NATIVE" : "KORDER_CURSOR",
5701
+ requiresCompleteInput: false,
5702
+ localOrderBy: false,
5703
+ applyLocalOffsetLimit: false,
5704
+ reasonCodes: [],
5705
+ scanRows
5706
+ };
5707
+ }
5708
+
5709
+ // src/core/errors/cursorErrors.ts
5710
+ var CursorCapacityError = class extends Error {
5711
+ constructor(host, limit, waitMs) {
5712
+ 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`);
5713
+ this.name = "CursorCapacityError";
5714
+ }
5715
+ };
5716
+ var CursorCreateOutcomeUnknownError = class extends Error {
5717
+ constructor(cause) {
5718
+ 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");
5719
+ this.name = "CursorCreateOutcomeUnknownError";
5720
+ this.cause = cause;
5721
+ }
5722
+ };
5723
+ var CursorCleanupWarning = class extends Error {
5724
+ constructor(cause) {
5725
+ const detail = cause instanceof Error ? cause.message : String(cause);
5726
+ 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}`);
5727
+ this.name = "CursorCleanupWarning";
5728
+ this.cause = cause;
5729
+ }
5730
+ };
5731
+
5732
+ // src/core/optimization/korderCursorExecutor.ts
5733
+ async function executeKorderCursor(input) {
5734
+ const handle = await input.client.openCursor({
5735
+ app: input.app,
5736
+ fields: input.fields.length > 0 ? input.fields : void 0,
5737
+ query: input.query,
5738
+ size: 500
5739
+ });
5740
+ const records = [];
5741
+ let seen = 0;
5742
+ let primaryError;
5743
+ let cleanupWarning;
5744
+ try {
5745
+ if (handle.totalCount > input.offset) {
5746
+ while (records.length < input.limit) {
5747
+ const page = await handle.nextPage();
5748
+ for (const record of page.records) {
5749
+ if (seen < input.offset) seen += 1;
5750
+ else if (records.length < input.limit) records.push(record);
5751
+ else break;
5752
+ }
5753
+ if (!page.next) break;
5754
+ }
5755
+ }
5756
+ } catch (error) {
5757
+ primaryError = error;
5758
+ throw error;
5759
+ } finally {
5760
+ try {
5761
+ await handle.close();
5762
+ } catch (cleanupError) {
5763
+ if (primaryError && primaryError instanceof Error) {
5764
+ Object.defineProperty(primaryError, "cursorCleanupError", {
5765
+ value: cleanupError,
5766
+ configurable: true
5767
+ });
5768
+ } else {
5769
+ cleanupWarning = new CursorCleanupWarning(cleanupError).message;
5770
+ }
5771
+ }
5772
+ }
5773
+ return { records, cleanupWarning };
5774
+ }
5775
+
5776
+ // src/converter/korderCursorQuery.ts
5777
+ function buildKorderCursorQuery(stmt) {
5778
+ const parts = [];
5779
+ if (stmt.where) parts.push(whereToKintone(stmt.where));
5780
+ const order = stmt.orderBy.map((item) => {
5781
+ if (item.key.type !== "FIELD_NAME") {
5782
+ throw new Error("ArgumentError: KORDER cursor key must be a direct field.");
5783
+ }
5784
+ return `${item.key.name} ${item.direction === "ASC" ? "asc" : "desc"}`;
5785
+ });
5786
+ parts.push(`order by ${order.join(", ")}`);
5787
+ return parts.join(" ");
5788
+ }
5789
+
5239
5790
  // src/engine/process.ts
5240
5791
  function flatten(record, alias) {
5241
5792
  const row = {};
@@ -5300,9 +5851,9 @@ function applyJoin(leftRows, rightRows, join2) {
5300
5851
  }
5301
5852
  return result;
5302
5853
  }
5303
- function applyFilter(rows, where, resolveFieldType, appliedKlikes) {
5854
+ function applyFilter(rows, where, resolveFieldType, appliedKlikes, resolveFieldSemantics2) {
5304
5855
  if (where === null) return rows;
5305
- return rows.filter((row) => evalWhere(where, row, resolveFieldType, appliedKlikes));
5856
+ return rows.filter((row) => evalWhere(where, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2));
5306
5857
  }
5307
5858
  function hasAggregateColumns(columns) {
5308
5859
  return columns.some(
@@ -5363,7 +5914,7 @@ function evalAggregate(func, distinct, arg, separator, rows, resolveAggSortKind)
5363
5914
  let strVal;
5364
5915
  if (arg.type === "FIELD_REF") {
5365
5916
  const raw = row[arg.field];
5366
- if (raw === void 0 || raw === "") continue;
5917
+ if (raw === void 0 || raw === "" && func !== "MIN" && func !== "MAX") continue;
5367
5918
  strVal = raw;
5368
5919
  } else {
5369
5920
  const n = evalArithExpr(arg, row);
@@ -5375,10 +5926,16 @@ function evalAggregate(func, distinct, arg, separator, rows, resolveAggSortKind)
5375
5926
  const eff = distinct ? [...new Set(strValues)] : strValues;
5376
5927
  if (func === "COUNT") return eff.length;
5377
5928
  if (func === "GROUP_CONCAT") return eff.join(separator ?? ",");
5378
- const sortKind = (func === "MIN" || func === "MAX") && arg.type === "FIELD_REF" ? resolveAggSortKind?.(toAggregateFieldRef(arg.field)) : void 0;
5379
- if (sortKind === "string") {
5380
- if (eff.length === 0) return "";
5381
- return func === "MAX" ? maxStringOf(eff) : minStringOf(eff);
5929
+ const comparison = (func === "MIN" || func === "MAX") && arg.type === "FIELD_REF" ? resolveAggSortKind?.(toAggregateFieldRef(arg.field)) : void 0;
5930
+ if (func === "MIN" || func === "MAX") {
5931
+ if (eff.length === 0) return 0;
5932
+ const semantics = typeof comparison === "string" ? syntheticSemantics(comparison) : comparison ?? (arg.type === "FIELD_REF" ? syntheticSemantics("string") : syntheticSemantics("number"));
5933
+ let result = eff[0];
5934
+ for (const candidate of eff.slice(1)) {
5935
+ const cmp = compareCanonicalValues(candidate, result, semantics);
5936
+ if (func === "MAX" && cmp > 0 || func === "MIN" && cmp < 0) result = candidate;
5937
+ }
5938
+ return result;
5382
5939
  }
5383
5940
  const nums = eff.map(Number);
5384
5941
  switch (func) {
@@ -5386,37 +5943,12 @@ function evalAggregate(func, distinct, arg, separator, rows, resolveAggSortKind)
5386
5943
  return nums.reduce((a, b) => a + b, 0);
5387
5944
  case "AVG":
5388
5945
  return nums.length === 0 ? 0 : nums.reduce((a, b) => a + b, 0) / nums.length;
5389
- // Math.max(...nums) は要素数が多いと RangeError になるためループで求める
5390
- case "MAX":
5391
- return nums.length === 0 ? 0 : maxOf(nums);
5392
- case "MIN":
5393
- return nums.length === 0 ? 0 : minOf(nums);
5394
5946
  }
5395
5947
  }
5396
5948
  function toAggregateFieldRef(field) {
5397
5949
  const dot = field.indexOf(".");
5398
5950
  return dot > 0 ? { type: "FIELD", tableAlias: field.slice(0, dot), field: field.slice(dot + 1) } : { type: "FIELD", tableAlias: null, field };
5399
5951
  }
5400
- function maxStringOf(values) {
5401
- let value = values[0];
5402
- for (const candidate of values) if (candidate > value) value = candidate;
5403
- return value;
5404
- }
5405
- function minStringOf(values) {
5406
- let value = values[0];
5407
- for (const candidate of values) if (candidate < value) value = candidate;
5408
- return value;
5409
- }
5410
- function maxOf(nums) {
5411
- let m = nums[0];
5412
- for (const n of nums) if (n > m) m = n;
5413
- return m;
5414
- }
5415
- function minOf(nums) {
5416
- let m = nums[0];
5417
- for (const n of nums) if (n < m) m = n;
5418
- return m;
5419
- }
5420
5952
  function evalAggArithExpr(node, rows, resolveAggSortKind) {
5421
5953
  if (node.type === "NUMBER") return node.value;
5422
5954
  if (node.type === "AGG_REF") return Number(evalAggregate(node.func, node.distinct, node.arg, node.separator, rows, resolveAggSortKind));
@@ -5448,9 +5980,9 @@ function aggregateSyntheticName2(func, distinct, arg) {
5448
5980
  const argStr = aggregateArgLabel(arg);
5449
5981
  return distinct ? `${func}(DISTINCT ${argStr})` : `${func}(${argStr})`;
5450
5982
  }
5451
- function applyHaving(rows, having, resolveFieldType) {
5983
+ function applyHaving(rows, having, resolveFieldType, resolveFieldSemantics2) {
5452
5984
  if (having === null) return rows;
5453
- return rows.filter((row) => evalWhere(having, row, resolveFieldType));
5985
+ return rows.filter((row) => evalWhere(having, row, resolveFieldType, void 0, resolveFieldSemantics2));
5454
5986
  }
5455
5987
  function applyDistinct(rows, columns) {
5456
5988
  if (rows.length === 0) return rows;
@@ -5502,27 +6034,37 @@ function buildDistinctKeyBuilder(rows, columns) {
5502
6034
  return JSON.stringify(values);
5503
6035
  };
5504
6036
  }
5505
- function applyOrderBy(rows, orderBy, optionOrders, sortKinds) {
6037
+ function applyOrderBy(rows, orderBy, optionOrders, sortKinds, fieldSemantics2) {
5506
6038
  if (orderBy.length === 0) return rows;
5507
- return sortDecoratedRows(rows, orderBy, optionOrders, sortKinds).rows.map((item) => item.row);
5508
- }
5509
- function sortDecoratedRows(rows, orderBy, optionOrders, sortKinds) {
5510
- const keyMeta = orderBy.map(({ key }) => ({
5511
- orderMap: key.type === "FIELD_NAME" ? optionOrders?.get(key.name) : void 0,
5512
- sortKind: key.type === "FIELD_NAME" ? sortKinds?.get(key.name) : void 0
5513
- }));
6039
+ return sortDecoratedRows(rows, orderBy, optionOrders, sortKinds, fieldSemantics2).rows.map((item) => item.row);
6040
+ }
6041
+ function sortDecoratedRows(rows, orderBy, optionOrders, sortKinds, fieldSemantics2) {
6042
+ const keyMeta = orderBy.map(({ key }) => {
6043
+ if (key.type === "ARITH_KEY") return { semantics: syntheticSemantics("number") };
6044
+ if (key.type === "FUNC_KEY") {
6045
+ return { semantics: syntheticSemantics(NUMERIC_ORDER_FUNCTIONS.has(key.expr.func) ? "number" : "string") };
6046
+ }
6047
+ const semantics = fieldSemantics2?.get(key.name);
6048
+ if (semantics) return { semantics };
6049
+ const orderMap = optionOrders?.get(key.name);
6050
+ if (orderMap) {
6051
+ return {
6052
+ semantics: {
6053
+ fieldType: "MULTI_SELECT",
6054
+ compareMode: "option",
6055
+ inSubtable: false,
6056
+ requiresCollectionOperators: false,
6057
+ optionOrder: orderMap
6058
+ }
6059
+ };
6060
+ }
6061
+ return { semantics: syntheticSemantics(sortKinds?.get(key.name) ?? "string") };
6062
+ });
5514
6063
  const decorated = rows.map((row) => ({
5515
6064
  row,
5516
6065
  keys: orderBy.map(({ key }, i) => {
5517
6066
  const s = evalOrderKey(key, row);
5518
- const n = Number(s);
5519
- const orderMap = keyMeta[i].orderMap;
5520
- return {
5521
- s,
5522
- n,
5523
- isNum: !Number.isNaN(n),
5524
- rank: orderMap ? minChoiceIndex(parseChoiceValues(s), orderMap) : 0
5525
- };
6067
+ return { s };
5526
6068
  })
5527
6069
  }));
5528
6070
  const compare = (a, b) => compareDecoratedRows(a, b, orderBy, keyMeta);
@@ -5537,15 +6079,24 @@ function compareDecoratedRows(a, b, orderBy, keyMeta) {
5537
6079
  return 0;
5538
6080
  }
5539
6081
  function compareSortKeys(a, b, meta) {
5540
- if (meta.orderMap) {
5541
- if (a.rank !== b.rank) return a.rank - b.rank;
5542
- return a.s.localeCompare(b.s, "ja");
5543
- }
5544
- if (meta.sortKind === "string") {
5545
- return a.s.localeCompare(b.s, "ja");
5546
- }
5547
- return a.isNum && b.isNum ? a.n - b.n : a.s.localeCompare(b.s, "ja");
5548
- }
6082
+ return compareCanonicalValues(a.s, b.s, meta.semantics);
6083
+ }
6084
+ var NUMERIC_ORDER_FUNCTIONS = /* @__PURE__ */ new Set([
6085
+ "LENGTH",
6086
+ "INSTR",
6087
+ "ROUND",
6088
+ "FLOOR",
6089
+ "CEIL",
6090
+ "TRUNCATE",
6091
+ "YEAR",
6092
+ "MONTH",
6093
+ "DAY",
6094
+ "DATEDIFF",
6095
+ "ABS",
6096
+ "MOD",
6097
+ "POWER",
6098
+ "SQRT"
6099
+ ]);
5549
6100
  function evalOrderKey(key, row) {
5550
6101
  switch (key.type) {
5551
6102
  case "FIELD_NAME":
@@ -5556,30 +6107,7 @@ function evalOrderKey(key, row) {
5556
6107
  return evalStringFunc(key.expr, row);
5557
6108
  }
5558
6109
  }
5559
- function parseChoiceValues(raw) {
5560
- const trimmed = raw.trim();
5561
- if (trimmed === "") return [""];
5562
- if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
5563
- try {
5564
- const arr = JSON.parse(trimmed);
5565
- if (Array.isArray(arr)) {
5566
- return arr.map((v) => String(v ?? ""));
5567
- }
5568
- } catch {
5569
- }
5570
- }
5571
- return [trimmed];
5572
- }
5573
- function minChoiceIndex(values, orderMap) {
5574
- let min = Number.MAX_SAFE_INTEGER;
5575
- for (const value of values) {
5576
- const idx = orderMap.get(value);
5577
- const rank = idx ?? Number.MAX_SAFE_INTEGER;
5578
- if (rank < min) min = rank;
5579
- }
5580
- return min;
5581
- }
5582
- function applyWindow(rows, columns, optionOrders, sortKinds) {
6110
+ function applyWindow(rows, columns, optionOrders, sortKinds, fieldSemantics2) {
5583
6111
  const windows = columns.filter((column) => column.type === "WINDOW_COL");
5584
6112
  if (rows.length === 0 || windows.length === 0) return rows;
5585
6113
  for (const window of windows) {
@@ -5591,7 +6119,7 @@ function applyWindow(rows, columns, optionOrders, sortKinds) {
5591
6119
  else partitions.set(key, [row]);
5592
6120
  }
5593
6121
  for (const partition of partitions.values()) {
5594
- const sortedResult = sortDecoratedRows(partition, window.orderBy, optionOrders, sortKinds);
6122
+ const sortedResult = sortDecoratedRows(partition, window.orderBy, optionOrders, sortKinds, fieldSemantics2);
5595
6123
  const sorted = sortedResult.rows;
5596
6124
  let rank = 1;
5597
6125
  let denseRank = 1;
@@ -5616,7 +6144,7 @@ function applyLimit(rows, limit, offset) {
5616
6144
  if (limit === null) return rows.slice(start);
5617
6145
  return rows.slice(start, start + limit);
5618
6146
  }
5619
- function project(rows, columns, scalarCache, resolveFieldType, sourceColumns) {
6147
+ function project(rows, columns, scalarCache, resolveFieldType, sourceColumns, resolveFieldSemantics2) {
5620
6148
  if (columns.length === 1 && columns[0].type === "WILDCARD") {
5621
6149
  const projected2 = rows.map((row) => stripParentShortcutColumns(row));
5622
6150
  const cols = projected2.length > 0 ? Object.keys(projected2[0]) : [...sourceColumns ?? []];
@@ -5680,7 +6208,7 @@ function project(rows, columns, scalarCache, resolveFieldType, sourceColumns) {
5680
6208
  }
5681
6209
  case "CASE_COL": {
5682
6210
  const key = outputKeys?.[colIdx] ?? col.alias ?? "case";
5683
- out[key] = evalCaseWhen(col.expr, row, resolveFieldType);
6211
+ out[key] = evalCaseWhen(col.expr, row, resolveFieldType, resolveFieldSemantics2);
5684
6212
  if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
5685
6213
  break;
5686
6214
  }
@@ -5835,6 +6363,26 @@ function resolveAggInStringFuncExpr(expr, rows, resolveAggSortKind) {
5835
6363
  args: expr.args.map((arg) => resolveAggInStringFuncArg(arg, rows, resolveAggSortKind))
5836
6364
  };
5837
6365
  }
6366
+ function deriveOutputOrderSemantics(columns) {
6367
+ const result = /* @__PURE__ */ new Map();
6368
+ for (const column of columns) {
6369
+ if (!("alias" in column) || !column.alias) continue;
6370
+ if (column.type === "ARITH_COL" || column.type === "ARITH_AGG_COL" || column.type === "WINDOW_COL") {
6371
+ result.set(column.alias, syntheticSemantics("number"));
6372
+ } else if (column.type === "AGGREGATE") {
6373
+ if (column.func === "COUNT" || column.func === "SUM" || column.func === "AVG") {
6374
+ result.set(column.alias, syntheticSemantics("number"));
6375
+ } else if (column.func === "GROUP_CONCAT") {
6376
+ result.set(column.alias, syntheticSemantics("string"));
6377
+ }
6378
+ } else if (column.type === "LITERAL_COL" || column.type === "CASE_COL" || column.type === "SCALAR_SUBQUERY_COL") {
6379
+ result.set(column.alias, syntheticSemantics("string"));
6380
+ } else if (column.type === "STRFUNC_COL") {
6381
+ result.set(column.alias, syntheticSemantics(NUMERIC_ORDER_FUNCTIONS.has(column.expr.func) ? "number" : "string"));
6382
+ }
6383
+ }
6384
+ return result;
6385
+ }
5838
6386
  function runFullScan(input) {
5839
6387
  const {
5840
6388
  stmt,
@@ -5842,12 +6390,17 @@ function runFullScan(input) {
5842
6390
  scalarCache,
5843
6391
  optionOrders,
5844
6392
  sortKinds,
6393
+ orderSemantics,
5845
6394
  fieldTypeResolver,
6395
+ fieldSemanticsResolver,
5846
6396
  havingFieldTypeResolver,
6397
+ havingFieldSemanticsResolver,
5847
6398
  aggregateSortKindResolver,
5848
6399
  appliedKlikes,
5849
6400
  sourceColumns
5850
6401
  } = input;
6402
+ const effectiveOrderSemantics = deriveOutputOrderSemantics(stmt.columns);
6403
+ for (const [key, value] of orderSemantics ?? []) effectiveOrderSemantics.set(key, value);
5851
6404
  let rows = [];
5852
6405
  const mainAlias = stmt.from.alias;
5853
6406
  const mainRecords = tables.get(mainAlias) ?? tables.get(null) ?? [];
@@ -5858,18 +6411,18 @@ function runFullScan(input) {
5858
6411
  const rightRows = rightRecords.map((r) => flatten(r, rightAlias));
5859
6412
  rows = applyJoin(rows, rightRows, join2);
5860
6413
  }
5861
- rows = applyFilter(rows, stmt.where, fieldTypeResolver, appliedKlikes);
6414
+ rows = applyFilter(rows, stmt.where, fieldTypeResolver, appliedKlikes, fieldSemanticsResolver);
5862
6415
  if (stmt.groupBy.length > 0 || hasAggregateColumns(stmt.columns)) {
5863
6416
  rows = applyGroupBy(rows, stmt.groupBy, stmt.columns, aggregateSortKindResolver);
5864
6417
  }
5865
- rows = applyHaving(rows, stmt.having, havingFieldTypeResolver);
5866
- rows = applyWindow(rows, stmt.columns, optionOrders, sortKinds);
6418
+ rows = applyHaving(rows, stmt.having, havingFieldTypeResolver, havingFieldSemanticsResolver);
6419
+ rows = applyWindow(rows, stmt.columns, optionOrders, sortKinds, effectiveOrderSemantics);
5867
6420
  if (stmt.distinct) {
5868
6421
  rows = applyDistinct(rows, stmt.columns);
5869
6422
  }
5870
- rows = applyOrderBy(rows, stmt.orderBy, optionOrders, sortKinds);
6423
+ rows = applyOrderBy(rows, stmt.orderBy, optionOrders, sortKinds, effectiveOrderSemantics);
5871
6424
  rows = applyLimit(rows, stmt.limit, stmt.offset);
5872
- return project(rows, stmt.columns, scalarCache, fieldTypeResolver, sourceColumns);
6425
+ return project(rows, stmt.columns, scalarCache, fieldTypeResolver, sourceColumns, fieldSemanticsResolver);
5873
6426
  }
5874
6427
 
5875
6428
  // src/converter/subtableAdapter.ts
@@ -6137,6 +6690,192 @@ function renderValidationValue(value) {
6137
6690
  return String(value);
6138
6691
  }
6139
6692
 
6693
+ // src/core/optimization/whereCapability.ts
6694
+ var RANGE_AND_EQUALITY = ["=", "!=", ">", "<", ">=", "<="];
6695
+ var EQUALITY_IN = ["=", "!=", "in", "not in"];
6696
+ var NATIVE_OPERATORS = /* @__PURE__ */ new Map([
6697
+ ["RECORD_NUMBER", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
6698
+ ["__ID__", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
6699
+ ["CREATOR", /* @__PURE__ */ new Set(["in", "not in"])],
6700
+ ["MODIFIER", /* @__PURE__ */ new Set(["in", "not in"])],
6701
+ ["CREATED_TIME", new Set(RANGE_AND_EQUALITY)],
6702
+ ["UPDATED_TIME", new Set(RANGE_AND_EQUALITY)],
6703
+ ["DATE", new Set(RANGE_AND_EQUALITY)],
6704
+ ["TIME", new Set(RANGE_AND_EQUALITY)],
6705
+ ["DATETIME", new Set(RANGE_AND_EQUALITY)],
6706
+ ["SINGLE_LINE_TEXT", /* @__PURE__ */ new Set(["=", "!=", "in", "not in", "like", "not like"])],
6707
+ ["LINK", /* @__PURE__ */ new Set(["=", "!=", "in", "not in", "like", "not like"])],
6708
+ ["NUMBER", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
6709
+ ["CALC", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
6710
+ ["MULTI_LINE_TEXT", /* @__PURE__ */ new Set(["like", "not like"])],
6711
+ ["RICH_TEXT", /* @__PURE__ */ new Set(["like", "not like"])],
6712
+ ["CHECK_BOX", /* @__PURE__ */ new Set(["in", "not in"])],
6713
+ ["RADIO_BUTTON", /* @__PURE__ */ new Set(["in", "not in"])],
6714
+ ["DROP_DOWN", /* @__PURE__ */ new Set(["in", "not in"])],
6715
+ ["MULTI_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
6716
+ ["FILE", /* @__PURE__ */ new Set(["like", "not like"])],
6717
+ ["USER_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
6718
+ ["ORGANIZATION_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
6719
+ ["GROUP_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
6720
+ ["STATUS", new Set(EQUALITY_IN)]
6721
+ ]);
6722
+ var LOCAL_SCALAR_TYPES = /* @__PURE__ */ new Set([
6723
+ "RECORD_NUMBER",
6724
+ "__ID__",
6725
+ "CREATOR",
6726
+ "MODIFIER",
6727
+ "CREATED_TIME",
6728
+ "UPDATED_TIME",
6729
+ "DATE",
6730
+ "TIME",
6731
+ "DATETIME",
6732
+ "SINGLE_LINE_TEXT",
6733
+ "LINK",
6734
+ "NUMBER",
6735
+ "CALC",
6736
+ "MULTI_LINE_TEXT",
6737
+ "RICH_TEXT",
6738
+ "RADIO_BUTTON",
6739
+ "DROP_DOWN",
6740
+ "STATUS",
6741
+ // 一時表・CTE・式列は kintone REST へは送らず、共有ローカル評価器で扱う。
6742
+ "KSQL_STRING",
6743
+ "KSQL_NUMBER",
6744
+ "KSQL_BOOLEAN"
6745
+ ]);
6746
+ var LOCAL_COLLECTION_TYPES = /* @__PURE__ */ new Set([
6747
+ "CHECK_BOX",
6748
+ "MULTI_SELECT",
6749
+ "FILE",
6750
+ "USER_SELECT",
6751
+ "ORGANIZATION_SELECT",
6752
+ "GROUP_SELECT",
6753
+ "STATUS_ASSIGNEE",
6754
+ "CATEGORY"
6755
+ ]);
6756
+ function nativeWhereOperatorsForType(fieldType) {
6757
+ return NATIVE_OPERATORS.get(fieldType) ?? /* @__PURE__ */ new Set();
6758
+ }
6759
+ function classifyWhereCapability(where, resolveField2) {
6760
+ if (where === null) {
6761
+ return { capability: "EXACT_PUSHDOWN", reasons: [{ code: "WHERE_EXACT" }] };
6762
+ }
6763
+ return classifyNode(where, resolveField2);
6764
+ }
6765
+ function classifyNode(where, resolveField2) {
6766
+ switch (where.type) {
6767
+ case "BINARY":
6768
+ return classifyBinary(where.op, where.left, where.right.type, resolveField2);
6769
+ case "NULL_CHECK":
6770
+ if (where.field.type !== "FIELD") return localExpression();
6771
+ return classifyLocalOnlyField(where.field, where.not ? "IS NOT NULL" : "IS NULL", resolveField2);
6772
+ case "EXISTS":
6773
+ return localExpression();
6774
+ case "GROUP":
6775
+ return classifyNode(where.expr, resolveField2);
6776
+ case "NOT": {
6777
+ const inner = classifyNode(where.expr, resolveField2);
6778
+ return inner.capability === "SUPERSET_PREFILTER" ? { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] } : inner;
6779
+ }
6780
+ case "LOGICAL": {
6781
+ const left = classifyNode(where.left, resolveField2);
6782
+ const right = classifyNode(where.right, resolveField2);
6783
+ return combineLogical(where.op, left, right);
6784
+ }
6785
+ }
6786
+ }
6787
+ function classifyBinary(op, left, rightType, resolveField2) {
6788
+ if (left.type !== "FIELD") return localExpression();
6789
+ const semantics = resolveField2(left);
6790
+ if (!semantics) {
6791
+ return unsupported("WHERE_FIELD_UNRESOLVED", left.field, void 0, normalizeOperator(op));
6792
+ }
6793
+ if (!hasLocalContract(semantics.fieldType, op)) {
6794
+ return unsupported("WHERE_OPERATOR_UNSUPPORTED", left.field, semantics.fieldType, normalizeOperator(op));
6795
+ }
6796
+ const nativeOp = normalizeOperator(op);
6797
+ const native = nativeWhereOperatorsForType(semantics.fieldType);
6798
+ const rightCanPush = rightType === "STRING" || rightType === "NUMBER" || rightType === "IN_LIST" || rightType === "KINTONE_FUNC";
6799
+ const structureAllows = !semantics.requiresCollectionOperators || nativeOp !== "=" && nativeOp !== "!=";
6800
+ const sqlLikeIsResidual = op === "LIKE" || op === "NOT_LIKE";
6801
+ if (rightCanPush && structureAllows && native.has(nativeOp) && !sqlLikeIsResidual) {
6802
+ return {
6803
+ capability: "EXACT_PUSHDOWN",
6804
+ reasons: [{
6805
+ code: "WHERE_EXACT",
6806
+ field: left.field,
6807
+ fieldType: semantics.fieldType,
6808
+ operator: nativeOp
6809
+ }]
6810
+ };
6811
+ }
6812
+ return {
6813
+ capability: "LOCAL_ONLY",
6814
+ reasons: [{
6815
+ code: "WHERE_RESIDUAL",
6816
+ field: left.field,
6817
+ fieldType: semantics.fieldType,
6818
+ operator: nativeOp
6819
+ }]
6820
+ };
6821
+ }
6822
+ function classifyLocalOnlyField(field, operator, resolveField2) {
6823
+ const semantics = resolveField2(field);
6824
+ if (!semantics) return unsupported("WHERE_FIELD_UNRESOLVED", field.field, void 0, operator);
6825
+ if (!LOCAL_SCALAR_TYPES.has(semantics.fieldType) && !LOCAL_COLLECTION_TYPES.has(semantics.fieldType)) {
6826
+ return unsupported("WHERE_OPERATOR_UNSUPPORTED", field.field, semantics.fieldType, operator);
6827
+ }
6828
+ return {
6829
+ capability: "LOCAL_ONLY",
6830
+ reasons: [{ code: "WHERE_RESIDUAL", field: field.field, fieldType: semantics.fieldType, operator }]
6831
+ };
6832
+ }
6833
+ function hasLocalContract(fieldType, op) {
6834
+ if (LOCAL_SCALAR_TYPES.has(fieldType)) return true;
6835
+ if (!LOCAL_COLLECTION_TYPES.has(fieldType)) return false;
6836
+ return op === "=" || op === "!=" || op === "<>" || op === "IN" || op === "NOT_IN" || op === "LIKE" || op === "NOT_LIKE" || op === "KLIKE" || op === "NOT_KLIKE";
6837
+ }
6838
+ function normalizeOperator(op) {
6839
+ switch (op) {
6840
+ case "<>":
6841
+ return "!=";
6842
+ case "IN":
6843
+ return "in";
6844
+ case "NOT_IN":
6845
+ return "not in";
6846
+ case "LIKE":
6847
+ case "KLIKE":
6848
+ return "like";
6849
+ case "NOT_LIKE":
6850
+ case "NOT_KLIKE":
6851
+ return "not like";
6852
+ default:
6853
+ return op;
6854
+ }
6855
+ }
6856
+ function combineLogical(op, left, right) {
6857
+ const reasons = [...left.reasons, ...right.reasons];
6858
+ if (left.capability === "UNSUPPORTED" || right.capability === "UNSUPPORTED") {
6859
+ return { capability: "UNSUPPORTED", reasons };
6860
+ }
6861
+ if (left.capability === "EXACT_PUSHDOWN" && right.capability === "EXACT_PUSHDOWN") {
6862
+ return { capability: "EXACT_PUSHDOWN", reasons };
6863
+ }
6864
+ if (op === "AND" && (left.capability === "EXACT_PUSHDOWN" || right.capability === "EXACT_PUSHDOWN" || left.capability === "SUPERSET_PREFILTER" || right.capability === "SUPERSET_PREFILTER")) {
6865
+ return {
6866
+ capability: "SUPERSET_PREFILTER",
6867
+ reasons: [{ code: "WHERE_SUPERSET_PREFILTER" }, ...reasons]
6868
+ };
6869
+ }
6870
+ return { capability: "LOCAL_ONLY", reasons };
6871
+ }
6872
+ function localExpression() {
6873
+ return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
6874
+ }
6875
+ function unsupported(code, field, fieldType, operator) {
6876
+ return { capability: "UNSUPPORTED", reasons: [{ code, field, fieldType, operator }] };
6877
+ }
6878
+
6140
6879
  // src/execute.ts
6141
6880
  var SEARCH_ABORTED_WARNING = "\u691C\u7D22\u304C 10 \u4E07\u4EF6\u3067\u6253\u3061\u5207\u3089\u308C\u3001\u7D50\u679C\u304C\u6B20\u843D\u3057\u305F\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002";
6142
6881
  var SearchAbortedError = class extends Error {
@@ -6147,8 +6886,20 @@ var SearchAbortedError = class extends Error {
6147
6886
  };
6148
6887
  var materializedMetaBySelectResult = /* @__PURE__ */ new WeakMap();
6149
6888
  var materializedMetaByValidationResult = /* @__PURE__ */ new WeakMap();
6889
+ var defaultCacheContextByClient = /* @__PURE__ */ new WeakMap();
6890
+ var nextDefaultCacheContextId = 1;
6891
+ function resolveCacheContext(client, explicit) {
6892
+ if (explicit) return explicit;
6893
+ let context = defaultCacheContextByClient.get(client);
6894
+ if (!context) {
6895
+ context = `client:${nextDefaultCacheContextId++}`;
6896
+ defaultCacheContextByClient.set(client, context);
6897
+ }
6898
+ return context;
6899
+ }
6150
6900
  async function execute(sql, client, options = {}) {
6151
6901
  const startedAt = Date.now();
6902
+ const cacheContext = resolveCacheContext(client, options.cacheContext);
6152
6903
  const stmt = parseSql(sql);
6153
6904
  const metrics = createEmptyMetrics();
6154
6905
  const countedClient = wrapClientWithMetrics(client, metrics);
@@ -6162,7 +6913,7 @@ async function execute(sql, client, options = {}) {
6162
6913
  stmt,
6163
6914
  guardedClient,
6164
6915
  options,
6165
- options.cacheContext ?? "default"
6916
+ cacheContext
6166
6917
  );
6167
6918
  metrics.elapsedMs = Date.now() - startedAt;
6168
6919
  return { ...attachSearchAbortWarning(result, collector), metrics };
@@ -6176,6 +6927,15 @@ function createEmptyMetrics() {
6176
6927
  fieldCalls: 0,
6177
6928
  appsCalls: 0,
6178
6929
  processStatusCalls: 0,
6930
+ cursorCreateCalls: 0,
6931
+ cursorGetCalls: 0,
6932
+ cursorDeleteCalls: 0,
6933
+ cursorRecordsScanned: 0,
6934
+ cursorActiveCurrent: 0,
6935
+ cursorActivePeak: 0,
6936
+ cursorCleanupFailures: 0,
6937
+ cursorCreateOutcomeUnknown: 0,
6938
+ cursorQuarantinedCurrent: 0,
6179
6939
  fetchedRows: 0,
6180
6940
  elapsedMs: 0
6181
6941
  };
@@ -6188,6 +6948,48 @@ function wrapClientWithMetrics(client, metrics) {
6188
6948
  metrics.fetchedRows += res.records.length;
6189
6949
  return res;
6190
6950
  },
6951
+ openCursor: async (params) => {
6952
+ metrics.cursorCreateCalls += 1;
6953
+ let handle;
6954
+ try {
6955
+ handle = await client.openCursor(params);
6956
+ } catch (error) {
6957
+ if (error instanceof Error && error.name === "CursorCreateOutcomeUnknownError") {
6958
+ metrics.cursorCreateOutcomeUnknown += 1;
6959
+ metrics.cursorQuarantinedCurrent += 1;
6960
+ }
6961
+ throw error;
6962
+ }
6963
+ metrics.cursorActiveCurrent += 1;
6964
+ metrics.cursorActivePeak = Math.max(metrics.cursorActivePeak, metrics.cursorActiveCurrent);
6965
+ let released = false;
6966
+ const markReleased = () => {
6967
+ if (released) return;
6968
+ released = true;
6969
+ metrics.cursorActiveCurrent -= 1;
6970
+ };
6971
+ return {
6972
+ totalCount: handle.totalCount,
6973
+ nextPage: async () => {
6974
+ metrics.cursorGetCalls += 1;
6975
+ const page = await handle.nextPage();
6976
+ metrics.cursorRecordsScanned += page.records.length;
6977
+ if (!page.next) markReleased();
6978
+ return page;
6979
+ },
6980
+ close: async () => {
6981
+ if (!released) metrics.cursorDeleteCalls += 1;
6982
+ try {
6983
+ await handle.close();
6984
+ markReleased();
6985
+ } catch (error) {
6986
+ metrics.cursorCleanupFailures += 1;
6987
+ metrics.cursorQuarantinedCurrent += 1;
6988
+ throw error;
6989
+ }
6990
+ }
6991
+ };
6992
+ },
6191
6993
  postRecords: (params) => {
6192
6994
  metrics.postCalls += 1;
6193
6995
  return client.postRecords(params);
@@ -6227,6 +7029,37 @@ function wrapClientWithSearchAbort(client, collector, failClosed) {
6227
7029
  }
6228
7030
  };
6229
7031
  }
7032
+ function wrapClientWithCursorScope(client) {
7033
+ const active = /* @__PURE__ */ new Set();
7034
+ return {
7035
+ client: {
7036
+ ...client,
7037
+ openCursor: async (params) => {
7038
+ const handle = await client.openCursor(params);
7039
+ active.add(handle);
7040
+ const remove = () => active.delete(handle);
7041
+ return {
7042
+ totalCount: handle.totalCount,
7043
+ async nextPage() {
7044
+ const page = await handle.nextPage();
7045
+ if (!page.next) remove();
7046
+ return page;
7047
+ },
7048
+ async close() {
7049
+ try {
7050
+ await handle.close();
7051
+ } finally {
7052
+ remove();
7053
+ }
7054
+ }
7055
+ };
7056
+ }
7057
+ },
7058
+ closeActive: async () => {
7059
+ await Promise.all([...active].map((handle) => handle.close().catch(() => void 0)));
7060
+ }
7061
+ };
7062
+ }
6230
7063
  function isSelectLikeStatement(stmt) {
6231
7064
  return stmt.type === "SELECT" || stmt.type === "UNION" || stmt.type === "WITH";
6232
7065
  }
@@ -6277,7 +7110,13 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
6277
7110
  case "DESCRIBE":
6278
7111
  return executeDescribe(stmt, client, cacheContext);
6279
7112
  case "EXPLAIN":
6280
- return executeExplain(stmt);
7113
+ return executeExplain(
7114
+ stmt,
7115
+ client,
7116
+ cacheContext,
7117
+ options.maxRecords ?? 1e4,
7118
+ options.cursorMaxActive ?? 2
7119
+ );
6281
7120
  // 一時テーブルはバッチスコープのため単文実行では拒否する(executeBatch を使う)
6282
7121
  case "CREATE_TEMP_TABLE":
6283
7122
  throw new Error("ArgumentError: CREATE TEMP TABLE requires a batch (temp tables are batch-scoped).");
@@ -6308,7 +7147,7 @@ function materializedColumnMetaEqual(left, right) {
6308
7147
  if (!left || !right || left.size !== right.size) return false;
6309
7148
  for (const [column, meta] of left) {
6310
7149
  const candidate = right.get(column);
6311
- if (!candidate || candidate.sortKind !== meta.sortKind || candidate.fieldType !== meta.fieldType) return false;
7150
+ if (!candidate || candidate.sortKind !== meta.sortKind || candidate.fieldType !== meta.fieldType || !fieldSemanticsEqual(candidate.semantics, meta.semantics)) return false;
6312
7151
  }
6313
7152
  return true;
6314
7153
  }
@@ -6339,7 +7178,7 @@ async function executeBatch(sql, client, options = {}) {
6339
7178
  const countedClient = wrapClientWithMetrics(client, metrics);
6340
7179
  const startedAt = Date.now();
6341
7180
  const deadline = options.timeoutMs != null ? startedAt + options.timeoutMs : null;
6342
- const cacheContext = options.cacheContext ?? "default";
7181
+ const cacheContext = resolveCacheContext(client, options.cacheContext);
6343
7182
  const tempTables = /* @__PURE__ */ new Map();
6344
7183
  const variables = /* @__PURE__ */ new Map();
6345
7184
  const results = [];
@@ -6384,9 +7223,11 @@ async function executeBatch(sql, client, options = {}) {
6384
7223
  searchAbortCollector,
6385
7224
  info.statementType !== "SELECT" && info.statementType !== "UNION" && info.statementType !== "WITH"
6386
7225
  );
7226
+ const cursorScope = wrapClientWithCursorScope(statementClient);
6387
7227
  const outcome = await runWithDeadline(
6388
- executeBatchStatement(statements[i], info, statementClient, stmtOptions, cacheContext, tempTables, variables),
6389
- remaining
7228
+ executeBatchStatement(statements[i], info, cursorScope.client, stmtOptions, cacheContext, tempTables, variables),
7229
+ remaining,
7230
+ cursorScope.closeActive
6390
7231
  );
6391
7232
  if (outcome.result) {
6392
7233
  outcome.result = attachSearchAbortWarning(outcome.result, searchAbortCollector);
@@ -6433,7 +7274,10 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
6433
7274
  cacheContext,
6434
7275
  tempTables
6435
7276
  );
6436
- variables.set(stmt.name, { type: "string", value });
7277
+ const first = resolvedStmt2.expr.query.columns[0];
7278
+ const numeric = first?.type === "ARITH_COL" || first?.type === "ARITH_AGG_COL" || first?.type === "WINDOW_COL" || first?.type === "AGGREGATE" && (first.func === "COUNT" || first.func === "SUM" || first.func === "AVG");
7279
+ const numberValue = numeric ? Number(value) : Number.NaN;
7280
+ variables.set(stmt.name, numeric && Number.isFinite(numberValue) ? { type: "number", value: numberValue } : { type: "string", value });
6437
7281
  } catch (e) {
6438
7282
  if (e instanceof ScalarSubqueryError) {
6439
7283
  throw new Error(`ArgumentError: ${e.message}`);
@@ -6541,19 +7385,47 @@ async function runSelectLike(query, client, options, cacheContext, tempTables) {
6541
7385
  }
6542
7386
  return executeQueryWithCte(query, client, options, tempTables, cacheContext, true);
6543
7387
  }
6544
- async function runWithDeadline(work, remainingMs) {
7388
+ async function runWithDeadline(work, remainingMs, onTimeout) {
6545
7389
  if (remainingMs === null) return work;
6546
7390
  if (remainingMs <= 0) {
7391
+ if (onTimeout) await onTimeout();
6547
7392
  void work.catch(() => {
6548
7393
  });
6549
7394
  throw new BatchTimeoutError();
6550
7395
  }
6551
7396
  let timer;
7397
+ let timedOut = false;
7398
+ const guardedWork = work.then(
7399
+ (value) => timedOut ? new Promise(() => void 0) : value,
7400
+ (error) => {
7401
+ if (timedOut) return new Promise(() => void 0);
7402
+ throw error;
7403
+ }
7404
+ );
6552
7405
  try {
6553
7406
  return await Promise.race([
6554
- work,
7407
+ guardedWork,
6555
7408
  new Promise((_, reject) => {
6556
- timer = setTimeout(() => reject(new BatchTimeoutError()), remainingMs);
7409
+ timer = setTimeout(() => {
7410
+ timedOut = true;
7411
+ void (async () => {
7412
+ if (onTimeout) {
7413
+ let cleanupTimer;
7414
+ try {
7415
+ await Promise.race([
7416
+ onTimeout(),
7417
+ new Promise((resolve2) => {
7418
+ cleanupTimer = setTimeout(resolve2, 5e3);
7419
+ cleanupTimer.unref?.();
7420
+ })
7421
+ ]);
7422
+ } finally {
7423
+ if (cleanupTimer) clearTimeout(cleanupTimer);
7424
+ }
7425
+ }
7426
+ reject(new BatchTimeoutError());
7427
+ })();
7428
+ }, remainingMs);
6557
7429
  })
6558
7430
  ]);
6559
7431
  } catch (e) {
@@ -6664,13 +7536,14 @@ var ScalarSubqueryError = class extends Error {
6664
7536
  };
6665
7537
  async function executeAssert(stmt, client, options, cacheContext, tempTables) {
6666
7538
  const left = await evalAssertOperand(stmt.left, client, options, cacheContext, tempTables);
7539
+ const semantics = stmt.left.type === "NUMBER" || stmt.left.type === "ARITH" ? syntheticSemantics("number") : syntheticSemantics("string");
6667
7540
  if (stmt.op === "BETWEEN") {
6668
7541
  if (stmt.low === null || stmt.high === null) {
6669
7542
  throw new Error("ArgumentError: malformed ASSERT statement.");
6670
7543
  }
6671
7544
  const low = await evalAssertOperand(stmt.low, client, options, cacheContext, tempTables);
6672
7545
  const high = await evalAssertOperand(stmt.high, client, options, cacheContext, tempTables);
6673
- if (!compareScalarValues(">=", left, low) || !compareScalarValues("<=", left, high)) {
7546
+ if (!compareScalarValues(">=", left, low, semantics) || !compareScalarValues("<=", left, high, semantics)) {
6674
7547
  throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
6675
7548
  }
6676
7549
  return { type: "ASSERT", condition: stmt.text };
@@ -6679,7 +7552,7 @@ async function executeAssert(stmt, client, options, cacheContext, tempTables) {
6679
7552
  throw new Error("ArgumentError: malformed ASSERT statement.");
6680
7553
  }
6681
7554
  const right = await evalAssertOperand(stmt.right, client, options, cacheContext, tempTables);
6682
- if (!compareScalarValues(stmt.op, left, right)) {
7555
+ if (!compareScalarValues(stmt.op, left, right, semantics)) {
6683
7556
  throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
6684
7557
  }
6685
7558
  return { type: "ASSERT", condition: stmt.text };
@@ -6752,6 +7625,149 @@ function evalAssertArith(node) {
6752
7625
  }
6753
7626
  throw new Error(`ArgumentError: unsupported operand in ASSERT expression: ${node.type}`);
6754
7627
  }
7628
+ async function buildWhereFieldSemanticsResolver(stmt, client, cacheContext, materializedTables, forcePhysicalMetadata = false) {
7629
+ const tables = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
7630
+ const physicalAppIds = forcePhysicalMetadata || whereNeedsFieldMetadata(stmt.where) ? [...new Set(tables.filter((table) => table.cteName === null).map((table) => table.appId))] : [];
7631
+ const infosByApp = new Map(
7632
+ await Promise.all(physicalAppIds.map(async (appId) => {
7633
+ const infos = await getFieldsCached(appId, client, cacheContext);
7634
+ return [appId, new Map(infos.map((info) => [info.code, info]))];
7635
+ }))
7636
+ );
7637
+ const orderedFields = /* @__PURE__ */ new Set();
7638
+ const collectOrderedFields = (node) => {
7639
+ if (Array.isArray(node)) {
7640
+ node.forEach(collectOrderedFields);
7641
+ return;
7642
+ }
7643
+ if (node === null || typeof node !== "object") return;
7644
+ const value = node;
7645
+ if (value["type"] === "SELECT") return;
7646
+ if (value["type"] === "BINARY" && [">", "<", ">=", "<="].includes(String(value["op"]))) {
7647
+ const left = value["left"];
7648
+ if (left?.["type"] === "FIELD" && typeof left["field"] === "string") {
7649
+ orderedFields.add(left["field"]);
7650
+ }
7651
+ }
7652
+ Object.values(value).forEach(collectOrderedFields);
7653
+ };
7654
+ collectOrderedFields(stmt.where);
7655
+ collectOrderedFields(stmt.having);
7656
+ for (const column of stmt.columns) {
7657
+ if (column.type === "CASE_COL") collectOrderedFields(column.expr);
7658
+ }
7659
+ const statusOrdersByApp = /* @__PURE__ */ new Map();
7660
+ await Promise.all([...infosByApp].map(async ([appId, infos]) => {
7661
+ const needsStatus = [...orderedFields].some((field) => infos.get(field)?.fieldType === "STATUS");
7662
+ if (!needsStatus) return;
7663
+ const order = await loadProcessStatusOrder(appId, client, cacheContext);
7664
+ if (order) statusOrdersByApp.set(appId, order);
7665
+ }));
7666
+ const fromPhysical = (table, field) => {
7667
+ if (field === "$id") return withFieldSemanticSource(
7668
+ resolveFieldSemantics({ fieldType: "__ID__" }),
7669
+ table.appId,
7670
+ "$id"
7671
+ );
7672
+ const info = infosByApp.get(table.appId)?.get(fieldCodeForTypeLookup(table, field));
7673
+ if (!info) return void 0;
7674
+ const base = info.semantics ?? resolveFieldSemantics(info);
7675
+ const semantics = info.fieldType === "STATUS" && statusOrdersByApp.has(table.appId) ? { ...base, optionOrder: statusOrdersByApp.get(table.appId) } : base;
7676
+ return withFieldSemanticSource(
7677
+ semantics,
7678
+ table.appId,
7679
+ info.code
7680
+ );
7681
+ };
7682
+ return (field) => {
7683
+ if (field.tableAlias !== null) {
7684
+ if (field.tableAlias === "_p" && stmt.from.subtableCode && stmt.from.cteName === null) {
7685
+ return fromPhysical(stmt.from, field.field);
7686
+ }
7687
+ const table = tables.find((candidate) => candidate.alias === field.tableAlias);
7688
+ if (!table) return void 0;
7689
+ if (table.cteName !== null) {
7690
+ return materializedTables?.get(table.cteName)?.columnMeta?.get(field.field)?.semantics ?? syntheticSemantics("string");
7691
+ }
7692
+ return fromPhysical(table, field.field);
7693
+ }
7694
+ if (stmt.joins.length === 0) {
7695
+ if (stmt.from.cteName !== null) {
7696
+ return materializedTables?.get(stmt.from.cteName)?.columnMeta?.get(field.field)?.semantics ?? syntheticSemantics("string");
7697
+ }
7698
+ return fromPhysical(stmt.from, field.field);
7699
+ }
7700
+ const matches = tables.flatMap((table) => {
7701
+ const semantics = table.cteName !== null ? materializedTables?.get(table.cteName)?.columnMeta?.get(field.field)?.semantics : fromPhysical(table, field.field);
7702
+ return semantics ? [semantics] : [];
7703
+ });
7704
+ if (matches.length === 1) return matches[0];
7705
+ return matches.length > 1 ? syntheticSemantics("string") : void 0;
7706
+ };
7707
+ }
7708
+ function selectCaseConditionsNeedFieldMetadata(stmt) {
7709
+ return stmt.columns.some((column) => column.type === "CASE_COL" && column.expr.branches.some((branch) => whereNeedsFieldMetadata(branch.condition)));
7710
+ }
7711
+ function buildHavingFieldSemanticsResolver(stmt, rowResolver) {
7712
+ const aliases = /* @__PURE__ */ new Map();
7713
+ for (const column of stmt.columns) {
7714
+ if (!("alias" in column) || !column.alias) continue;
7715
+ let semantics;
7716
+ if (column.type === "FIELD") semantics = rowResolver(aggregateFieldRef(column.field));
7717
+ else if (column.type === "ARITH_COL" || column.type === "ARITH_AGG_COL" || column.type === "WINDOW_COL") {
7718
+ semantics = syntheticSemantics("number");
7719
+ } else if (column.type === "AGGREGATE") {
7720
+ if (column.func === "MIN" || column.func === "MAX") {
7721
+ semantics = column.arg.type === "FIELD_REF" ? rowResolver(aggregateFieldRef(column.arg.field)) : syntheticSemantics("number");
7722
+ } else {
7723
+ semantics = column.func === "GROUP_CONCAT" ? syntheticSemantics("string") : syntheticSemantics("number");
7724
+ }
7725
+ } else if (column.type === "STRFUNC_COL") {
7726
+ semantics = stringFunctionColumnMeta(column.expr).semantics;
7727
+ } else if (column.type === "LITERAL_COL" || column.type === "SCALAR_SUBQUERY_COL" || column.type === "CASE_COL") {
7728
+ semantics = syntheticSemantics("string");
7729
+ }
7730
+ if (semantics) aliases.set(column.alias, semantics);
7731
+ }
7732
+ return (field) => field.tableAlias === null && aliases.has(field.field) ? aliases.get(field.field) : rowResolver(field);
7733
+ }
7734
+ async function resolveSelectWhereCapability(stmt, client, cacheContext, materializedTables) {
7735
+ if (stmt.where === null) return classifyWhereCapability(null, () => void 0);
7736
+ const resolver = await buildWhereFieldSemanticsResolver(stmt, client, cacheContext, materializedTables);
7737
+ return classifyWhereCapability(stmt.where, resolver);
7738
+ }
7739
+ function formatWhereCapabilityFailure(result) {
7740
+ const reason = result.reasons.find(
7741
+ (candidate) => candidate.code === "WHERE_FIELD_UNRESOLVED" || candidate.code === "WHERE_OPERATOR_UNSUPPORTED"
7742
+ ) ?? result.reasons[0];
7743
+ const details = [
7744
+ reason?.field ? `field=${reason.field}` : null,
7745
+ reason?.fieldType ? `type=${reason.fieldType}` : null,
7746
+ reason?.operator ? `operator=${reason.operator}` : null,
7747
+ reason?.code ? `reason=${reason.code}` : null
7748
+ ].filter((value) => value !== null).join(", ");
7749
+ return details || "reason=WHERE_UNSUPPORTED";
7750
+ }
7751
+ function hasCanonicalOrder(stmt) {
7752
+ return stmt.orderBy.length > 0 || stmt.columns.some(
7753
+ (column) => column.type === "WINDOW_COL" && column.orderBy.length > 0
7754
+ );
7755
+ }
7756
+ async function assertDmlWhereCapability(stmt, client, cacheContext) {
7757
+ if (stmt.subtableCode || stmt.type === "UPDATE" && stmt.from != null) return;
7758
+ const fields = whereNeedsFieldMetadata(stmt.where) ? await getFieldsCached(stmt.appId, client, cacheContext) : [];
7759
+ const byCode = new Map(fields.map((field) => [field.code, field]));
7760
+ const result = classifyWhereCapability(stmt.where, (field) => {
7761
+ if (field.field === "$id") return resolveFieldSemantics({ fieldType: "__ID__" });
7762
+ const info = byCode.get(field.field);
7763
+ return info?.semantics ?? (info ? resolveFieldSemantics(info) : void 0);
7764
+ });
7765
+ if (result.capability !== "EXACT_PUSHDOWN") {
7766
+ throw new DmlConvertError(
7767
+ `WHERE predicate cannot be represented by kintone REST (${formatWhereCapabilityFailure(result)})`
7768
+ );
7769
+ }
7770
+ }
6755
7771
  async function executeSelect(stmt, client, options, cacheContext, cteCache, captureColumnMeta = false) {
6756
7772
  let result;
6757
7773
  if (isNoFromSelect(stmt)) {
@@ -6762,12 +7778,51 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
6762
7778
  return result;
6763
7779
  }
6764
7780
  await resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache);
6765
- const mode = resolveSelectMode(stmt);
6766
- await validateSelectFieldCodes(stmt, mode, client, cacheContext);
6767
- if (mode === "SIMPLE") {
6768
- result = await executeSimpleSelect(stmt, client, options, cacheContext);
6769
- } else {
6770
- result = await executeFullScanSelect(stmt, client, options, cacheContext, cteCache);
7781
+ const whereCapability = await resolveSelectWhereCapability(stmt, client, cacheContext, cteCache);
7782
+ if (whereCapability.capability === "UNSUPPORTED") {
7783
+ throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(whereCapability)}).`);
7784
+ }
7785
+ const staticMode = resolveSelectMode(stmt);
7786
+ const mode = whereCapability.capability === "EXACT_PUSHDOWN" ? staticMode : "FULL_SCAN";
7787
+ const orderMeta = await buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
7788
+ const orderPlan = hasCanonicalOrder(stmt) ? (stmt.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
7789
+ stmt,
7790
+ staticMode: mode,
7791
+ whereCapability: whereCapability.capability,
7792
+ orderSemantics: orderMeta.semantics,
7793
+ maxRecords: options.maxRecords ?? 1e4,
7794
+ hasKlike: whereHasKlike(stmt.where)
7795
+ }) : null;
7796
+ await validateSelectFieldCodes(
7797
+ stmt,
7798
+ orderPlan?.kind === "CANONICAL_LOCAL" ? "FULL_SCAN" : mode,
7799
+ client,
7800
+ cacheContext
7801
+ );
7802
+ const completeInputRequired = orderPlan?.kind === "CANONICAL_REST_TOP_N" || orderPlan?.kind === "KORDER_NATIVE" || orderPlan?.kind === "KORDER_CURSOR" ? requiresCompleteInput({ ...stmt, orderBy: [] }) : requiresCompleteInput(stmt);
7803
+ const truncateWasDisabled = completeInputRequired && options.onLimitReached === "truncate";
7804
+ const effectiveOptions = truncateWasDisabled ? { ...options, onLimitReached: "error" } : options;
7805
+ try {
7806
+ if (mode === "SIMPLE") {
7807
+ result = await executeSimpleSelect(stmt, client, effectiveOptions, cacheContext, orderPlan, orderMeta);
7808
+ } else {
7809
+ result = await executeFullScanSelect(
7810
+ stmt,
7811
+ client,
7812
+ effectiveOptions,
7813
+ cacheContext,
7814
+ cteCache,
7815
+ whereCapability.capability === "EXACT_PUSHDOWN",
7816
+ orderMeta
7817
+ );
7818
+ }
7819
+ } catch (error) {
7820
+ if (completeInputRequired && error instanceof FetchAllLimitError) {
7821
+ throw new FetchAllLimitError(
7822
+ "ORDER BY\u306E\u6B63\u3057\u3044\u7D50\u679C\u306B\u306F\u5B8C\u5168\u306A\u5019\u88DC\u96C6\u5408\u304C\u5FC5\u8981\u3067\u3059\u3002" + (truncateWasDisabled ? "onLimit=truncate\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002" : "") + error.message
7823
+ );
7824
+ }
7825
+ throw error;
6771
7826
  }
6772
7827
  if (captureColumnMeta) {
6773
7828
  materializedMetaBySelectResult.set(result, await inferSelectColumnMeta(stmt, result.columns, client, cacheContext, cteCache));
@@ -6828,19 +7883,41 @@ function executeNoFromSelect(stmt) {
6828
7883
  const rows = applyLimit(projected, stmt.limit, stmt.offset);
6829
7884
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [] };
6830
7885
  }
6831
- async function executeSimpleSelect(stmt, client, options, cacheContext) {
6832
- const params = selectToKintoneParams(stmt);
7886
+ async function executeSimpleSelect(stmt, client, options, cacheContext, orderPlan, orderMeta) {
7887
+ const restStmt = orderPlan?.kind === "CANONICAL_REST_TOP_N" ? withCanonicalRestTie(stmt) : stmt;
7888
+ const params = selectToKintoneParams(restStmt);
7889
+ const fetchFields = orderPlan?.kind === "CANONICAL_LOCAL" ? selectToFetchAllFields(stmt, stmt.from) : params.fields;
6833
7890
  const typedInFieldTypes = await loadTypedInFieldTypes(stmt, client, cacheContext);
6834
7891
  const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
7892
+ const projectionSemanticsResolver = stmt.columns.some((column) => column.type === "CASE_COL") ? await buildWhereFieldSemanticsResolver(
7893
+ stmt,
7894
+ client,
7895
+ cacheContext,
7896
+ void 0,
7897
+ selectCaseConditionsNeedFieldMetadata(stmt)
7898
+ ) : void 0;
6835
7899
  const maxRecords = options.maxRecords ?? 1e4;
6836
7900
  const warnings = /* @__PURE__ */ new Set();
6837
7901
  const onLimit = options.onLimitReached ?? "error";
6838
7902
  const parallel = options.fetchParallel ?? 1;
6839
- const useSingleGet = stmt.limit !== null && stmt.limit <= 500;
7903
+ 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;
6840
7904
  const needed = stmt.limit === null ? null : (stmt.offset ?? 0) + stmt.limit;
6841
7905
  const stopAfter = stmt.orderBy.length === 0 && needed !== null && needed <= maxRecords && !whereHasKlike(stmt.where) ? needed : void 0;
6842
7906
  let records;
6843
- if (useSingleGet) {
7907
+ if ((orderPlan?.kind === "KORDER_NATIVE" || orderPlan?.kind === "KORDER_CURSOR") && stmt.limit === 0) {
7908
+ records = [];
7909
+ } else if (orderPlan?.kind === "KORDER_CURSOR") {
7910
+ const cursorResult = await executeKorderCursor({
7911
+ client,
7912
+ app: params.app,
7913
+ fields: params.fields,
7914
+ query: buildKorderCursorQuery(stmt),
7915
+ offset: stmt.offset ?? 0,
7916
+ limit: stmt.limit
7917
+ });
7918
+ records = cursorResult.records;
7919
+ if (cursorResult.cleanupWarning) warnings.add(cursorResult.cleanupWarning);
7920
+ } else if (useRestWindow) {
6844
7921
  const res = await client.getRecords({
6845
7922
  app: params.app,
6846
7923
  query: params.query,
@@ -6853,7 +7930,7 @@ async function executeSimpleSelect(stmt, client, options, cacheContext) {
6853
7930
  client.getRecords,
6854
7931
  params.app,
6855
7932
  baseQuery,
6856
- params.fields,
7933
+ fetchFields,
6857
7934
  {
6858
7935
  parallel,
6859
7936
  maxRecords,
@@ -6866,16 +7943,23 @@ async function executeSimpleSelect(stmt, client, options, cacheContext) {
6866
7943
  );
6867
7944
  }
6868
7945
  let rows = records.map((r) => flatten(r, null));
6869
- if (!useSingleGet) {
6870
- const { optionOrders, sortKinds } = await buildOrderByMetaForSelect(stmt, client, cacheContext);
6871
- rows = applyOrderBy(rows, stmt.orderBy, optionOrders, sortKinds);
7946
+ if (!useRestWindow) {
7947
+ rows = applyOrderBy(
7948
+ rows,
7949
+ stmt.orderBy,
7950
+ orderMeta.optionOrders,
7951
+ orderMeta.sortKinds,
7952
+ orderMeta.semantics
7953
+ );
6872
7954
  rows = applyLimit(rows, stmt.limit, stmt.offset);
6873
7955
  }
6874
7956
  const { rows: projected, columns } = project(
6875
7957
  rows,
6876
7958
  stmt.columns,
6877
7959
  void 0,
6878
- fieldTypeResolvers.row
7960
+ fieldTypeResolvers.row,
7961
+ void 0,
7962
+ projectionSemanticsResolver
6879
7963
  );
6880
7964
  return { type: "SELECT", rows: projected, columns, rowCount: projected.length, warnings: [...warnings] };
6881
7965
  }
@@ -6948,8 +8032,8 @@ async function loadTypedPushdownMeta(stmt, client, cacheContext) {
6948
8032
  const statusFields = collectCandidateFieldCodes(candidates).filter((fieldCode) => fieldTypes.get(fieldCode) === "STATUS");
6949
8033
  if (statusFields.length > 0) {
6950
8034
  const process2 = await getProcessStatusesCached(appId, client, cacheContext);
6951
- if (process2.enable && process2.states.length > 0) {
6952
- const states = new Set(process2.states);
8035
+ if (process2.enable && process2.states && process2.states.length > 0) {
8036
+ const states = new Set(process2.states.map((state) => state.name));
6953
8037
  for (const fieldCode of statusFields) fieldOptions.set(fieldCode, states);
6954
8038
  }
6955
8039
  }
@@ -7127,7 +8211,18 @@ async function loadAggregateSortKindResolver(stmt, client, cacheContext, materia
7127
8211
  return [appId, new Map(infos.map((info) => [info.code, info]))];
7128
8212
  }))
7129
8213
  );
8214
+ const statusOrdersByApp = /* @__PURE__ */ new Map();
8215
+ const aggregateFieldNames = new Set(refs.map((ref) => ref.field));
8216
+ await Promise.all([...fieldInfosByApp].map(async ([appId, infos]) => {
8217
+ if (![...aggregateFieldNames].some((field) => infos.get(field)?.fieldType === "STATUS")) return;
8218
+ const order = await loadProcessStatusOrder(appId, client, cacheContext);
8219
+ if (order) statusOrdersByApp.set(appId, order);
8220
+ }));
7130
8221
  const tables = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
8222
+ const semanticsForInfo = (info, appId) => {
8223
+ const base = info.semantics ?? resolveFieldSemantics(info);
8224
+ return info.fieldType === "STATUS" && statusOrdersByApp.has(appId) ? { ...base, optionOrder: statusOrdersByApp.get(appId) } : base;
8225
+ };
7131
8226
  return (ref) => {
7132
8227
  let info;
7133
8228
  if (ref.tableAlias !== null) {
@@ -7137,40 +8232,130 @@ async function loadAggregateSortKindResolver(stmt, client, cacheContext, materia
7137
8232
  const table = tables.find((candidate) => candidate.alias === ref.tableAlias);
7138
8233
  if (!table) return void 0;
7139
8234
  if (table.cteName !== null) {
7140
- return materializedTables?.get(table.cteName)?.columnMeta?.get(ref.field)?.sortKind;
8235
+ return materializedTables?.get(table.cteName)?.columnMeta?.get(ref.field)?.semantics ?? syntheticSemantics("string");
7141
8236
  }
7142
8237
  info = fieldInfosByApp.get(table.appId)?.get(fieldCodeForTypeLookup(table, ref.field));
7143
8238
  }
7144
8239
  } else if (stmt.joins.length === 0) {
7145
8240
  if (stmt.from.cteName !== null) {
7146
- return materializedTables?.get(stmt.from.cteName)?.columnMeta?.get(ref.field)?.sortKind;
8241
+ return materializedTables?.get(stmt.from.cteName)?.columnMeta?.get(ref.field)?.semantics ?? syntheticSemantics("string");
7147
8242
  }
7148
8243
  info = fieldInfosByApp.get(stmt.from.appId)?.get(fieldCodeForTypeLookup(stmt.from, ref.field));
7149
8244
  } else {
7150
8245
  const matches = tables.flatMap((table) => {
7151
8246
  if (table.cteName !== null) {
7152
8247
  const materialized = materializedTables?.get(table.cteName);
7153
- return materialized?.columns.includes(ref.field) ? [materialized.columnMeta?.get(ref.field)?.sortKind] : [];
8248
+ return materialized?.columns.includes(ref.field) ? [materialized.columnMeta?.get(ref.field)?.semantics ?? syntheticSemantics("string")] : [];
7154
8249
  }
7155
8250
  const candidate = fieldInfosByApp.get(table.appId)?.get(fieldCodeForTypeLookup(table, ref.field));
7156
- return candidate ? [aggregateSortKind(candidate)] : [];
8251
+ return candidate ? [semanticsForInfo(candidate, table.appId)] : [];
7157
8252
  });
7158
8253
  if (matches.length !== 1) return void 0;
7159
8254
  return matches[0];
7160
8255
  }
7161
- return info ? aggregateSortKind(info) : void 0;
8256
+ if (!info) return void 0;
8257
+ const sourceTable = ref.tableAlias !== null ? tables.find((table) => table.alias === ref.tableAlias) : stmt.joins.length === 0 ? stmt.from : void 0;
8258
+ return semanticsForInfo(info, sourceTable?.appId ?? stmt.from.appId);
7162
8259
  };
7163
8260
  }
7164
8261
  function fieldCodeForTypeLookup(table, field) {
7165
8262
  if (table.subtableCode && field.startsWith("_p.")) return field.slice(3);
7166
8263
  return field;
7167
8264
  }
7168
- function materializedMetaFromFieldInfo(info) {
7169
- return { sortKind: aggregateSortKind(info), fieldType: info.fieldType };
8265
+ function materializedMetaFromFieldInfo(info, sourceAppId) {
8266
+ const semantics = info.semantics ?? resolveFieldSemantics(info);
8267
+ return {
8268
+ sortKind: aggregateSortKind(info),
8269
+ fieldType: info.fieldType,
8270
+ semantics: sourceAppId === void 0 ? semantics : withFieldSemanticSource(semantics, sourceAppId, info.code)
8271
+ };
8272
+ }
8273
+ function withCanonicalRestTie(stmt) {
8274
+ const hasId = stmt.orderBy.some(
8275
+ (item) => item.key.type === "FIELD_NAME" && item.key.name === "$id"
8276
+ );
8277
+ return hasId ? stmt : {
8278
+ ...stmt,
8279
+ orderBy: [...stmt.orderBy, { key: { type: "FIELD_NAME", name: "$id" }, direction: "ASC" }]
8280
+ };
8281
+ }
8282
+ function syntheticColumnMeta(compareMode) {
8283
+ return { sortKind: compareMode, semantics: syntheticSemantics(compareMode) };
8284
+ }
8285
+ function unknownStringColumnMeta() {
8286
+ return { semantics: syntheticSemantics("string", "KSQL_UNKNOWN") };
8287
+ }
8288
+ function unsupportedColumnMeta(fieldType = "KSQL_ARRAY") {
8289
+ return {
8290
+ semantics: { fieldType, compareMode: "unsupported", inSubtable: false, requiresCollectionOperators: false }
8291
+ };
8292
+ }
8293
+ function systemColumnMeta(field) {
8294
+ if (field === "$id" || field === "_rid" || field === "_pid") {
8295
+ return {
8296
+ sortKind: "number",
8297
+ fieldType: "__ID__",
8298
+ semantics: resolveFieldSemantics({ fieldType: "__ID__" })
8299
+ };
8300
+ }
8301
+ if (field === "$revision") return syntheticColumnMeta("number");
8302
+ return void 0;
8303
+ }
8304
+ var NUMBER_RETURNING_STRING_FUNCTIONS = /* @__PURE__ */ new Set([
8305
+ "LENGTH",
8306
+ "INSTR",
8307
+ "ROUND",
8308
+ "FLOOR",
8309
+ "CEIL",
8310
+ "TRUNCATE",
8311
+ "YEAR",
8312
+ "MONTH",
8313
+ "DAY",
8314
+ "DATEDIFF",
8315
+ "ABS",
8316
+ "MOD",
8317
+ "POWER",
8318
+ "SQRT"
8319
+ ]);
8320
+ function stringFunctionColumnMeta(expr) {
8321
+ if (expr.func === "CAST") {
8322
+ const target = expr.args[1];
8323
+ return target?.type === "STRING" && target.value === "NUMBER" ? syntheticColumnMeta("number") : syntheticColumnMeta("string");
8324
+ }
8325
+ return NUMBER_RETURNING_STRING_FUNCTIONS.has(expr.func) ? syntheticColumnMeta("number") : syntheticColumnMeta("string");
8326
+ }
8327
+ function caseResultColumnMeta(result, resolveField2) {
8328
+ if (result.type === "STRING") return syntheticColumnMeta("string");
8329
+ if (result.type === "ARRAY") return unsupportedColumnMeta();
8330
+ if (result.type === "NUMBER" || result.type === "ARITH") return syntheticColumnMeta("number");
8331
+ if (result.type === "STRING_FUNC") return stringFunctionColumnMeta(result);
8332
+ const source = resolveField2(aggregateFieldRef(result.field));
8333
+ return source ?? unknownStringColumnMeta();
8334
+ }
8335
+ function mergeExpressionColumnMeta(candidates) {
8336
+ if (candidates.length === 0) return unknownStringColumnMeta();
8337
+ const first = candidates[0];
8338
+ const withoutSource = (semantics) => {
8339
+ if (!semantics) return void 0;
8340
+ const { source: _source, ...rest } = semantics;
8341
+ return rest;
8342
+ };
8343
+ if (candidates.every(
8344
+ (candidate) => candidate.sortKind === first.sortKind && candidate.fieldType === first.fieldType && fieldSemanticsEqual(withoutSource(candidate.semantics), withoutSource(first.semantics))
8345
+ )) {
8346
+ const sameSource = candidates.every(
8347
+ (candidate) => fieldSemanticsEqual(candidate.semantics, first.semantics)
8348
+ );
8349
+ return sameSource ? first : { ...first, semantics: withoutSource(first.semantics) };
8350
+ }
8351
+ if (candidates.some((candidate) => candidate.semantics?.compareMode === "unsupported")) {
8352
+ return unsupportedColumnMeta("KSQL_MIXED_UNSUPPORTED");
8353
+ }
8354
+ return unknownStringColumnMeta();
7170
8355
  }
7171
8356
  function selectNeedsSourceColumnMeta(stmt) {
7172
8357
  return stmt.columns.some(
7173
- (column) => column.type === "FIELD" || column.type === "WILDCARD" || column.type === "PARENT_WILDCARD" || column.type === "AGGREGATE" && (column.func === "MIN" || column.func === "MAX") && column.arg.type === "FIELD_REF"
8358
+ (column) => column.type === "FIELD" || column.type === "WILDCARD" || column.type === "PARENT_WILDCARD" || column.type === "CASE_COL" || column.type === "AGGREGATE" && (column.func === "MIN" || column.func === "MAX") && column.arg.type === "FIELD_REF"
7174
8359
  );
7175
8360
  }
7176
8361
  async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext, materializedTables) {
@@ -7187,18 +8372,18 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
7187
8372
  if (ref.tableAlias !== null) {
7188
8373
  if (ref.tableAlias === "_p" && stmt.from.subtableCode && stmt.from.cteName === null) {
7189
8374
  const info2 = physicalInfos.get(stmt.from.appId)?.get(ref.field);
7190
- return info2 ? materializedMetaFromFieldInfo(info2) : void 0;
8375
+ return info2 ? materializedMetaFromFieldInfo(info2, stmt.from.appId) : void 0;
7191
8376
  }
7192
8377
  const table = tables.find((candidate) => candidate.alias === ref.tableAlias);
7193
8378
  if (!table) return void 0;
7194
8379
  if (table.cteName !== null) return materializedTables?.get(table.cteName)?.columnMeta?.get(ref.field);
7195
8380
  const info = physicalInfos.get(table.appId)?.get(fieldCodeForTypeLookup(table, ref.field));
7196
- return info ? materializedMetaFromFieldInfo(info) : void 0;
8381
+ return info ? materializedMetaFromFieldInfo(info, table.appId) : systemColumnMeta(ref.field);
7197
8382
  }
7198
8383
  if (stmt.joins.length === 0) {
7199
8384
  if (stmt.from.cteName !== null) return materializedTables?.get(stmt.from.cteName)?.columnMeta?.get(ref.field);
7200
8385
  const info = physicalInfos.get(stmt.from.appId)?.get(fieldCodeForTypeLookup(stmt.from, ref.field));
7201
- return info ? materializedMetaFromFieldInfo(info) : void 0;
8386
+ return info ? materializedMetaFromFieldInfo(info, stmt.from.appId) : systemColumnMeta(ref.field);
7202
8387
  }
7203
8388
  const matches = tables.flatMap((table) => {
7204
8389
  if (table.cteName !== null) {
@@ -7207,7 +8392,8 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
7207
8392
  return [materialized.columnMeta?.get(ref.field)];
7208
8393
  }
7209
8394
  const info = physicalInfos.get(table.appId)?.get(fieldCodeForTypeLookup(table, ref.field));
7210
- return info ? [materializedMetaFromFieldInfo(info)] : [];
8395
+ const meta = info ? materializedMetaFromFieldInfo(info, table.appId) : systemColumnMeta(ref.field);
8396
+ return meta ? [meta] : [];
7211
8397
  });
7212
8398
  return matches.length === 1 ? matches[0] : void 0;
7213
8399
  };
@@ -7237,19 +8423,27 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
7237
8423
  meta = resolveField2(aggregateFieldRef(column.field));
7238
8424
  } else if (column.type === "AGGREGATE") {
7239
8425
  if (column.func === "GROUP_CONCAT") {
7240
- meta = { sortKind: "string" };
8426
+ meta = syntheticColumnMeta("string");
7241
8427
  } else if (column.func === "COUNT" || column.func === "SUM" || column.func === "AVG") {
7242
- meta = { sortKind: "number" };
8428
+ meta = syntheticColumnMeta("number");
7243
8429
  } else if ((column.func === "MIN" || column.func === "MAX") && column.arg.type === "FIELD_REF") {
7244
8430
  const source = resolveField2(aggregateFieldRef(column.arg.field));
7245
- if (source?.sortKind) meta = { sortKind: source.sortKind };
8431
+ if (source) meta = source;
7246
8432
  }
7247
8433
  } else if (column.type === "ARITH_AGG_COL" || column.type === "ARITH_COL") {
7248
- meta = { sortKind: "number" };
8434
+ meta = syntheticColumnMeta("number");
7249
8435
  } else if (column.type === "LITERAL_COL") {
7250
- meta = { sortKind: "string" };
8436
+ meta = syntheticColumnMeta("string");
8437
+ } else if (column.type === "STRFUNC_COL") {
8438
+ meta = stringFunctionColumnMeta(column.expr);
7251
8439
  } else if (column.type === "WINDOW_COL") {
7252
- meta = { sortKind: "number" };
8440
+ meta = syntheticColumnMeta("number");
8441
+ } else if (column.type === "CASE_COL") {
8442
+ const results = column.expr.branches.map((branch) => caseResultColumnMeta(branch.result, resolveField2));
8443
+ if (column.expr.elseResult) results.push(caseResultColumnMeta(column.expr.elseResult, resolveField2));
8444
+ meta = mergeExpressionColumnMeta(results);
8445
+ } else if (column.type === "SCALAR_SUBQUERY_COL") {
8446
+ meta = unknownStringColumnMeta();
7253
8447
  }
7254
8448
  if (meta) inferred.set(output, meta);
7255
8449
  });
@@ -7263,7 +8457,8 @@ function mergeUnionColumnMeta(left, right) {
7263
8457
  const a = leftMeta?.get(column);
7264
8458
  const rightColumn = right.columns[index];
7265
8459
  const b = rightColumn === void 0 ? void 0 : rightMeta?.get(rightColumn);
7266
- if (a && b && a.sortKind === b.sortKind && a.fieldType === b.fieldType) merged.set(column, a);
8460
+ if (a && b) merged.set(column, mergeExpressionColumnMeta([a, b]));
8461
+ else if (a || b) merged.set(column, unknownStringColumnMeta());
7267
8462
  });
7268
8463
  return merged;
7269
8464
  }
@@ -7300,7 +8495,7 @@ function buildSelectFieldTypeResolvers(stmt, fieldTypesByApp) {
7300
8495
  };
7301
8496
  return { row, having };
7302
8497
  }
7303
- async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache) {
8498
+ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache, allowOriginalWherePushdown = true, preloadedOrderMeta) {
7304
8499
  const maxRecords = options.maxRecords ?? 1e4;
7305
8500
  const warnings = /* @__PURE__ */ new Set();
7306
8501
  const parallel = options.fetchParallel ?? 1;
@@ -7314,6 +8509,14 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
7314
8509
  loadAggregateSortKindResolver(stmt, client, cacheContext, cteCache)
7315
8510
  ]);
7316
8511
  const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
8512
+ const fieldSemanticsResolver = await buildWhereFieldSemanticsResolver(
8513
+ stmt,
8514
+ client,
8515
+ cacheContext,
8516
+ cteCache,
8517
+ whereNeedsFieldMetadata(stmt.having) || selectCaseConditionsNeedFieldMetadata(stmt)
8518
+ );
8519
+ const havingFieldSemanticsResolver = buildHavingFieldSemanticsResolver(stmt, fieldSemanticsResolver);
7317
8520
  const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
7318
8521
  validateKlikePushdownPlan(pushdownPlan);
7319
8522
  const mainPushDown = pushdownPlan.mainCondition;
@@ -7327,7 +8530,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
7327
8530
  true,
7328
8531
  options.onLimitReached ?? "error",
7329
8532
  warnings,
7330
- mainPushDown
8533
+ mainPushDown,
8534
+ allowOriginalWherePushdown
7331
8535
  );
7332
8536
  const parallelJoins = [];
7333
8537
  const onOptJoins = [];
@@ -7353,7 +8557,7 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
7353
8557
  }
7354
8558
  }
7355
8559
  const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
7356
- const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
8560
+ const orderByMetaPromise = preloadedOrderMeta ? Promise.resolve(preloadedOrderMeta) : buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
7357
8561
  scalarCachePromise.catch(() => {
7358
8562
  });
7359
8563
  orderByMetaPromise.catch(() => {
@@ -7390,15 +8594,18 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
7390
8594
  tables.set(join2.table.alias, joinRecords);
7391
8595
  }));
7392
8596
  const scalarCache = await scalarCachePromise;
7393
- const { optionOrders, sortKinds } = await orderByMetaPromise;
8597
+ const { optionOrders, sortKinds, semantics } = await orderByMetaPromise;
7394
8598
  const { rows, columns } = runFullScan({
7395
8599
  tables,
7396
8600
  stmt,
7397
8601
  scalarCache,
7398
8602
  optionOrders,
7399
8603
  sortKinds,
8604
+ orderSemantics: semantics,
7400
8605
  fieldTypeResolver: fieldTypeResolvers.row,
8606
+ fieldSemanticsResolver,
7401
8607
  havingFieldTypeResolver: fieldTypeResolvers.having,
8608
+ havingFieldSemanticsResolver,
7402
8609
  aggregateSortKindResolver,
7403
8610
  appliedKlikes: pushdownPlan.appliedKlikes
7404
8611
  });
@@ -7495,16 +8702,39 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
7495
8702
  resolveSubqueries(stmt.having, client, options, cacheContext, cteCache),
7496
8703
  resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache)
7497
8704
  ]);
8705
+ const whereCapability = await resolveSelectWhereCapability(stmt, client, cacheContext, cteCache);
8706
+ if (whereCapability.capability === "UNSUPPORTED") {
8707
+ throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(whereCapability)}).`);
8708
+ }
8709
+ const orderMeta = await buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
8710
+ if (hasCanonicalOrder(stmt)) {
8711
+ (stmt.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
8712
+ stmt,
8713
+ staticMode: "FULL_SCAN",
8714
+ whereCapability: whereCapability.capability,
8715
+ orderSemantics: orderMeta.semantics,
8716
+ maxRecords,
8717
+ hasKlike: whereHasKlike(stmt.where)
8718
+ });
8719
+ }
7498
8720
  const [pushdownMeta, typedInFieldTypes, aggregateSortKindResolver] = await Promise.all([
7499
8721
  loadTypedPushdownMeta(stmt, client, cacheContext),
7500
8722
  loadTypedInFieldTypes(stmt, client, cacheContext),
7501
8723
  loadAggregateSortKindResolver(stmt, client, cacheContext, cteCache)
7502
8724
  ]);
7503
8725
  const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
8726
+ const fieldSemanticsResolver = await buildWhereFieldSemanticsResolver(
8727
+ stmt,
8728
+ client,
8729
+ cacheContext,
8730
+ cteCache,
8731
+ whereNeedsFieldMetadata(stmt.having) || selectCaseConditionsNeedFieldMetadata(stmt)
8732
+ );
8733
+ const havingFieldSemanticsResolver = buildHavingFieldSemanticsResolver(stmt, fieldSemanticsResolver);
7504
8734
  const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
7505
8735
  validateKlikePushdownPlan(pushdownPlan);
7506
8736
  const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
7507
- const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
8737
+ const orderByMetaPromise = Promise.resolve(orderMeta);
7508
8738
  scalarCachePromise.catch(() => {
7509
8739
  });
7510
8740
  orderByMetaPromise.catch(() => {
@@ -7523,7 +8753,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
7523
8753
  true,
7524
8754
  options.onLimitReached ?? "error",
7525
8755
  warnings,
7526
- pushdownPlan.mainCondition
8756
+ pushdownPlan.mainCondition,
8757
+ whereCapability.capability === "EXACT_PUSHDOWN"
7527
8758
  );
7528
8759
  tables.set(stmt.from.alias, mainRecords);
7529
8760
  }
@@ -7560,7 +8791,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
7560
8791
  });
7561
8792
  await Promise.all(joinFetches);
7562
8793
  const scalarCache = await scalarCachePromise;
7563
- const { optionOrders, sortKinds } = await orderByMetaPromise;
8794
+ const { optionOrders, sortKinds, semantics } = await orderByMetaPromise;
7564
8795
  const sourceColumns = stmt.joins.length === 0 && stmt.from.cteName != null ? cteCache.get(stmt.from.cteName)?.columns : void 0;
7565
8796
  const { rows, columns } = runFullScan({
7566
8797
  tables,
@@ -7568,8 +8799,11 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
7568
8799
  scalarCache,
7569
8800
  optionOrders,
7570
8801
  sortKinds,
8802
+ orderSemantics: semantics,
7571
8803
  fieldTypeResolver: fieldTypeResolvers.row,
8804
+ fieldSemanticsResolver,
7572
8805
  havingFieldTypeResolver: fieldTypeResolvers.having,
8806
+ havingFieldSemanticsResolver,
7573
8807
  aggregateSortKindResolver,
7574
8808
  appliedKlikes: pushdownPlan.appliedKlikes,
7575
8809
  sourceColumns
@@ -7581,13 +8815,13 @@ function processRowToKintoneRecord(row) {
7581
8815
  Object.entries(row).map(([k, v]) => [k, { value: v ?? "" }])
7582
8816
  );
7583
8817
  }
7584
- async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, parallel, isMainTable, onLimit, warnings, pushDownCond = null) {
8818
+ async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, parallel, isMainTable, onLimit, warnings, pushDownCond = null, allowOriginalWherePushdown = true) {
7585
8819
  const fields = selectToFetchAllFields(stmt, table);
7586
8820
  const onTruncate = (max) => {
7587
8821
  warnings.add(`\u53D6\u5F97\u4E0A\u9650\uFF08${max} \u4EF6\uFF09\u306B\u9054\u3057\u305F\u305F\u3081\u3001${max} \u4EF6\u3067\u6253\u3061\u5207\u3063\u3066\u8868\u793A\u3057\u3066\u3044\u307E\u3059\u3002`);
7588
8822
  };
7589
8823
  if (!table.subtableCode) {
7590
- const baseQuery = isMainTable ? selectToFetchAllParams(stmt, table.appId).query : "";
8824
+ const baseQuery = isMainTable && allowOriginalWherePushdown ? selectToFetchAllParams(stmt, table.appId).query : "";
7591
8825
  const pushQuery = pushDownCond !== null ? whereToKintone(pushDownCond) : "";
7592
8826
  const query = baseQuery && pushQuery ? `(${baseQuery}) and (${pushQuery})` : baseQuery || pushQuery;
7593
8827
  const resolved = await fetchRecordsForSharedPlan(client.getRecords, table.appId, query, fields, {
@@ -7791,6 +9025,10 @@ async function getProcessStatusesCached(appId, client, cacheContext) {
7791
9025
  setScopedCacheValue(processStatusCache, cacheContext, appId, loading);
7792
9026
  return loading;
7793
9027
  }
9028
+ async function loadProcessStatusOrder(appId, client, cacheContext) {
9029
+ const process2 = await getProcessStatusesCached(appId, client, cacheContext);
9030
+ return process2.enable && process2.states !== null ? new Map(process2.states.map((state) => [state.name, state.index])) : void 0;
9031
+ }
7794
9032
  async function getFieldTypeMap(appId, client, cacheContext) {
7795
9033
  const cached = getScopedCacheValue(fieldTypeCache, cacheContext, appId);
7796
9034
  if (cached) return cached;
@@ -7834,18 +9072,118 @@ async function getSortKindMapByApp(appId, client, cacheContext) {
7834
9072
  setScopedCacheValue(sortKindCache, cacheContext, appId, map);
7835
9073
  return map;
7836
9074
  }
7837
- async function buildOrderByMetaForSelect(stmt, client, cacheContext) {
9075
+ function orderByFieldNames(stmt) {
9076
+ const items = [
9077
+ ...stmt.orderBy,
9078
+ ...stmt.columns.flatMap((column) => column.type === "WINDOW_COL" ? column.orderBy : [])
9079
+ ];
9080
+ return [...new Set(items.flatMap(
9081
+ (item) => item.key.type === "FIELD_NAME" ? [item.key.name] : []
9082
+ ))];
9083
+ }
9084
+ async function buildOrderSemanticsForSelect(stmt, client, cacheContext, materializedTables) {
9085
+ const names = orderByFieldNames(stmt);
9086
+ if (names.length === 0) return /* @__PURE__ */ new Map();
9087
+ const tables = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
9088
+ const ambiguousFields = /* @__PURE__ */ new Set();
9089
+ const infosByApp = new Map(
9090
+ await Promise.all([...new Set(
9091
+ tables.filter((table) => table.cteName === null).map((table) => table.appId)
9092
+ )].map(async (appId) => {
9093
+ const infos = await getFieldsCached(appId, client, cacheContext);
9094
+ return [appId, new Map(infos.map((info) => [info.code, info]))];
9095
+ }))
9096
+ );
9097
+ const resolveField2 = (ref) => {
9098
+ if (ref.tableAlias !== null) {
9099
+ if (ref.tableAlias === "_p" && stmt.from.subtableCode && stmt.from.cteName === null) {
9100
+ const info2 = infosByApp.get(stmt.from.appId)?.get(ref.field);
9101
+ return info2 ? materializedMetaFromFieldInfo(info2, stmt.from.appId) : void 0;
9102
+ }
9103
+ const table = tables.find((candidate) => candidate.alias === ref.tableAlias);
9104
+ if (!table) return void 0;
9105
+ if (table.cteName !== null) return materializedTables?.get(table.cteName)?.columnMeta?.get(ref.field);
9106
+ const info = infosByApp.get(table.appId)?.get(fieldCodeForTypeLookup(table, ref.field));
9107
+ return info ? materializedMetaFromFieldInfo(info, table.appId) : systemColumnMeta(ref.field);
9108
+ }
9109
+ if (stmt.joins.length === 0) {
9110
+ if (stmt.from.cteName !== null) return materializedTables?.get(stmt.from.cteName)?.columnMeta?.get(ref.field);
9111
+ const info = infosByApp.get(stmt.from.appId)?.get(fieldCodeForTypeLookup(stmt.from, ref.field));
9112
+ return info ? materializedMetaFromFieldInfo(info, stmt.from.appId) : systemColumnMeta(ref.field);
9113
+ }
9114
+ const matches = tables.flatMap((table) => {
9115
+ if (table.cteName !== null) {
9116
+ const materialized = materializedTables?.get(table.cteName);
9117
+ const meta2 = materialized?.columns.includes(ref.field) ? materialized.columnMeta?.get(ref.field) : void 0;
9118
+ return meta2 ? [meta2] : [];
9119
+ }
9120
+ const info = infosByApp.get(table.appId)?.get(fieldCodeForTypeLookup(table, ref.field));
9121
+ const meta = info ? materializedMetaFromFieldInfo(info, table.appId) : systemColumnMeta(ref.field);
9122
+ return meta ? [meta] : [];
9123
+ });
9124
+ if (matches.length > 1) ambiguousFields.add(ref.field);
9125
+ return matches.length === 1 ? matches[0] : void 0;
9126
+ };
9127
+ const aliasSemantics = /* @__PURE__ */ new Map();
9128
+ for (const column of stmt.columns) {
9129
+ if (!("alias" in column) || !column.alias) continue;
9130
+ let meta;
9131
+ if (column.type === "FIELD") meta = resolveField2(aggregateFieldRef(column.field));
9132
+ else if (column.type === "ARITH_COL" || column.type === "ARITH_AGG_COL" || column.type === "WINDOW_COL") {
9133
+ meta = syntheticColumnMeta("number");
9134
+ } else if (column.type === "LITERAL_COL") meta = syntheticColumnMeta("string");
9135
+ else if (column.type === "STRFUNC_COL") meta = stringFunctionColumnMeta(column.expr);
9136
+ else if (column.type === "SCALAR_SUBQUERY_COL") meta = unknownStringColumnMeta();
9137
+ else if (column.type === "CASE_COL") {
9138
+ const candidates = column.expr.branches.map((branch) => caseResultColumnMeta(branch.result, resolveField2));
9139
+ if (column.expr.elseResult) candidates.push(caseResultColumnMeta(column.expr.elseResult, resolveField2));
9140
+ meta = mergeExpressionColumnMeta(candidates);
9141
+ } else if (column.type === "AGGREGATE") {
9142
+ if (column.func === "MIN" || column.func === "MAX") {
9143
+ meta = column.arg.type === "FIELD_REF" ? resolveField2(aggregateFieldRef(column.arg.field)) : syntheticColumnMeta("number");
9144
+ } else {
9145
+ meta = column.func === "GROUP_CONCAT" ? syntheticColumnMeta("string") : syntheticColumnMeta("number");
9146
+ }
9147
+ }
9148
+ if (meta?.semantics) aliasSemantics.set(column.alias, meta.semantics);
9149
+ }
9150
+ const result = /* @__PURE__ */ new Map();
9151
+ for (const name of names) {
9152
+ const base = aliasSemantics.get(name) ?? resolveField2(aggregateFieldRef(name))?.semantics;
9153
+ if (!base) {
9154
+ const ref = aggregateFieldRef(name);
9155
+ if (ref.tableAlias === null && ambiguousFields.has(ref.field)) {
9156
+ result.set(name, resolveFieldSemantics({ fieldType: "KSQL_AMBIGUOUS" }));
9157
+ }
9158
+ continue;
9159
+ }
9160
+ let semantics = base;
9161
+ if (base.fieldType === "STATUS" && base.source && stmt.orderMode !== "KINTONE_NATIVE") {
9162
+ const process2 = await getProcessStatusesCached(base.source.appId, client, cacheContext);
9163
+ if (process2.enable && process2.states !== null) {
9164
+ semantics = {
9165
+ ...base,
9166
+ optionOrder: new Map(process2.states.map((state) => [state.name, state.index]))
9167
+ };
9168
+ }
9169
+ }
9170
+ result.set(name, semantics);
9171
+ }
9172
+ return result;
9173
+ }
9174
+ async function buildOrderByMetaForSelect(stmt, client, cacheContext, materializedTables) {
7838
9175
  const hasWindowOrderBy = stmt.columns.some(
7839
9176
  (column) => column.type === "WINDOW_COL" && column.orderBy.length > 0
7840
9177
  );
7841
9178
  if (stmt.orderBy.length === 0 && !hasWindowOrderBy) {
7842
- return { optionOrders: /* @__PURE__ */ new Map(), sortKinds: /* @__PURE__ */ new Map() };
9179
+ return { optionOrders: /* @__PURE__ */ new Map(), sortKinds: /* @__PURE__ */ new Map(), semantics: /* @__PURE__ */ new Map() };
7843
9180
  }
7844
- const [optionOrders, sortKinds] = await Promise.all([
9181
+ const [optionOrders, sortKinds, semantics] = await Promise.all([
7845
9182
  buildOptionOrdersForSelect(stmt, client, cacheContext),
7846
- buildSortKindsForSelect(stmt, client, cacheContext)
9183
+ buildSortKindsForSelect(stmt, client, cacheContext),
9184
+ buildOrderSemanticsForSelect(stmt, client, cacheContext, materializedTables)
7847
9185
  ]);
7848
- return { optionOrders, sortKinds };
9186
+ return { optionOrders, sortKinds, semantics };
7849
9187
  }
7850
9188
  async function buildOptionOrdersForSelect(stmt, client, cacheContext) {
7851
9189
  const optionOrders = /* @__PURE__ */ new Map();
@@ -7943,6 +9281,9 @@ var RejectLimitExceededError = class extends Error {
7943
9281
  }
7944
9282
  };
7945
9283
  async function prepareDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber) {
9284
+ if (stmt.type === "UPDATE") {
9285
+ await assertDmlWhereCapability(stmt, client, cacheContext);
9286
+ }
7946
9287
  const operation = stmt.type === "UPDATE" ? "UPDATE" : stmt.type.startsWith("UPSERT") ? "UPSERT" : "INSERT";
7947
9288
  const payloadFields = stmt.type === "UPDATE" ? ["$id", ...stmt.assignments.map((a) => a.field)] : [...stmt.fields];
7948
9289
  if (new Set(payloadFields).size !== payloadFields.length) {
@@ -7982,18 +9323,22 @@ async function prepareDmlValidation(stmt, client, options, cacheContext, tempTab
7982
9323
  const columnMeta = /* @__PURE__ */ new Map();
7983
9324
  for (const column of payloadFields) {
7984
9325
  if (column === "$id") {
7985
- columnMeta.set(column, { sortKind: "number", fieldType: "RECORD_NUMBER" });
9326
+ columnMeta.set(column, {
9327
+ sortKind: "number",
9328
+ fieldType: "RECORD_NUMBER",
9329
+ semantics: resolveFieldSemantics({ fieldType: "RECORD_NUMBER" })
9330
+ });
7986
9331
  continue;
7987
9332
  }
7988
9333
  const info = infoByCode.get(column);
7989
- if (info) columnMeta.set(column, materializedMetaFromFieldInfo(info));
7990
- }
7991
- columnMeta.set("$err_statement", { sortKind: "number" });
7992
- columnMeta.set("$err_operation", { sortKind: "string" });
7993
- columnMeta.set("$err_row", { sortKind: "number" });
7994
- columnMeta.set("$err_field", { sortKind: "string" });
7995
- columnMeta.set("$err_code", { sortKind: "string" });
7996
- columnMeta.set("$err_message", { sortKind: "string" });
9334
+ if (info) columnMeta.set(column, materializedMetaFromFieldInfo(info, stmt.appId));
9335
+ }
9336
+ columnMeta.set("$err_statement", syntheticColumnMeta("number"));
9337
+ columnMeta.set("$err_operation", syntheticColumnMeta("string"));
9338
+ columnMeta.set("$err_row", syntheticColumnMeta("number"));
9339
+ columnMeta.set("$err_field", syntheticColumnMeta("string"));
9340
+ columnMeta.set("$err_code", syntheticColumnMeta("string"));
9341
+ columnMeta.set("$err_message", syntheticColumnMeta("string"));
7997
9342
  materializedMetaByValidationResult.set(result, columnMeta);
7998
9343
  return { result, candidates, invalidRowNumbers, columnMeta };
7999
9344
  }
@@ -8379,6 +9724,7 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
8379
9724
  };
8380
9725
  }
8381
9726
  async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
9727
+ await assertDmlWhereCapability(stmt, client, cacheContext);
8382
9728
  if (stmt.subtableCode) {
8383
9729
  return executeUpdateSubtable(stmt, client, options, cacheContext);
8384
9730
  }
@@ -8456,6 +9802,7 @@ function collectUpdateFromTargetFields(stmt) {
8456
9802
  return [...fields];
8457
9803
  }
8458
9804
  async function executeDelete(stmt, client, options, cacheContext) {
9805
+ await assertDmlWhereCapability(stmt, client, cacheContext);
8459
9806
  if (stmt.subtableCode) {
8460
9807
  return executeDeleteSubtable(stmt, client, options, cacheContext);
8461
9808
  }
@@ -8828,6 +10175,18 @@ async function executeReorder(stmt, client, options, cacheContext) {
8828
10175
  client,
8829
10176
  cacheContext
8830
10177
  );
10178
+ const reorderFields = await getFieldsCached(stmt.appId, client, cacheContext);
10179
+ const reorderSemanticsByCode = new Map(reorderFields.map((field) => [
10180
+ field.code,
10181
+ field.semantics ?? resolveFieldSemantics(field)
10182
+ ]));
10183
+ const resolveReorderSemantics = (field) => {
10184
+ if (field.field === "_idx" || field.field === "_pid" || field.field === "_rid") {
10185
+ return syntheticSemantics("number");
10186
+ }
10187
+ const code = field.field.startsWith("_p.") ? field.field.slice(3) : field.field;
10188
+ return reorderSemanticsByCode.get(code) ?? syntheticSemantics("string");
10189
+ };
8831
10190
  const parents = await fetchAll(
8832
10191
  client.getRecords,
8833
10192
  stmt.appId,
@@ -8836,7 +10195,13 @@ async function executeReorder(stmt, client, options, cacheContext) {
8836
10195
  { maxRecords: options.maxRecords ?? 1e4, parallel: options.fetchParallel ?? 1 }
8837
10196
  );
8838
10197
  const expanded = expandRowsForSubtableDml(parents, stmt.subtableCode);
8839
- 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));
10198
+ const targetParentIds = stmt.all ? new Set(parents.map((p) => String(p["$id"]?.value ?? "")).filter((id) => id !== "")) : new Set(expanded.filter((r) => stmt.where && evalWhere(
10199
+ stmt.where,
10200
+ r.flat,
10201
+ resolveFieldType,
10202
+ void 0,
10203
+ resolveReorderSemantics
10204
+ )).map((r) => r.parentId));
8840
10205
  if (options.confirm) {
8841
10206
  const ok = await options.confirm(targetParentIds.size, "UPDATE");
8842
10207
  if (!ok) throw new OperationCancelledError("UPDATE", targetParentIds.size);
@@ -8847,7 +10212,7 @@ async function executeReorder(stmt, client, options, cacheContext) {
8847
10212
  if (!parent) continue;
8848
10213
  const rows = getMutableTableRows(parent, stmt.subtableCode);
8849
10214
  const sortable = rows.map((row, i) => ({ row, i, flat: buildFlatRowForSort(parent, stmt.subtableCode, row, i) }));
8850
- sortable.sort((a, b) => compareByOrder(a.flat, b.flat, stmt.by));
10215
+ sortable.sort((a, b) => compareByOrder(a.flat, b.flat, stmt.by, resolveReorderSemantics));
8851
10216
  const orderedRowIds = sortable.map((x) => x.row.id ?? "");
8852
10217
  await client.putRecords(buildSubtableReorderPutParams(stmt.appId, pid, getRevision(parent), stmt.subtableCode, orderedRowIds));
8853
10218
  }
@@ -8868,14 +10233,12 @@ function buildFlatRowForSort(parent, subtableCode, row, idx) {
8868
10233
  }
8869
10234
  return flat;
8870
10235
  }
8871
- function compareByOrder(a, b, orderBy) {
10236
+ function compareByOrder(a, b, orderBy, resolveSemantics) {
8872
10237
  for (const item of orderBy) {
8873
10238
  const av = evalOrderKeyForRow(item.key, a);
8874
10239
  const bv = evalOrderKeyForRow(item.key, b);
8875
- const an = Number(av);
8876
- const bn = Number(bv);
8877
- const numeric = !Number.isNaN(an) && !Number.isNaN(bn);
8878
- const cmp = numeric ? an - bn : av.localeCompare(bv, "ja");
10240
+ const semantics = item.key.type === "FIELD_NAME" ? resolveSemantics(aggregateFieldRef(item.key.name)) : item.key.type === "ARITH_KEY" ? syntheticSemantics("number") : stringFunctionColumnMeta(item.key.expr).semantics ?? syntheticSemantics("string");
10241
+ const cmp = compareCanonicalValues(av, bv, semantics ?? syntheticSemantics("string"));
8879
10242
  if (cmp !== 0) return item.direction === "ASC" ? cmp : -cmp;
8880
10243
  }
8881
10244
  return 0;
@@ -9072,35 +10435,140 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
9072
10435
  pending.forEach(([i], idx) => cache.set(i, values[idx]));
9073
10436
  return cache;
9074
10437
  }
9075
- function buildBatchExplainPlans(sql, injectedVariables) {
10438
+ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords = 1e4) {
10439
+ const fieldApps = /* @__PURE__ */ new Set();
10440
+ const processStatusApps = /* @__PURE__ */ new Set();
10441
+ const tracedClient = {
10442
+ ...client,
10443
+ getFields: async (appId) => {
10444
+ fieldApps.add(appId);
10445
+ return client.getFields(appId);
10446
+ },
10447
+ getProcessStatuses: async (appId) => {
10448
+ processStatusApps.add(appId);
10449
+ return client.getProcessStatuses(appId);
10450
+ }
10451
+ };
10452
+ const capabilities = /* @__PURE__ */ new Map();
10453
+ const orderPlans = /* @__PURE__ */ new Map();
10454
+ const seen = /* @__PURE__ */ new Set();
10455
+ const visit = async (node) => {
10456
+ if (node === null || typeof node !== "object") return;
10457
+ if (seen.has(node)) return;
10458
+ seen.add(node);
10459
+ if (Array.isArray(node)) {
10460
+ await Promise.all(node.map(visit));
10461
+ return;
10462
+ }
10463
+ const typed = node;
10464
+ if (typed["type"] === "SELECT") {
10465
+ const select = node;
10466
+ const physicalApps = [select.from, ...select.joins.map((join2) => join2.table)].filter((table) => table.cteName === null).map((table) => table.appId);
10467
+ const needsWhereSchema = whereNeedsFieldMetadata(select.where);
10468
+ if (needsWhereSchema || select.orderBy.length > 0 || select.columns.some((column) => column.type === "WINDOW_COL" && column.orderBy.length > 0)) {
10469
+ physicalApps.forEach((appId) => fieldApps.add(appId));
10470
+ }
10471
+ const capability = await resolveSelectWhereCapability(select, tracedClient, cacheContext);
10472
+ if (capability.capability === "UNSUPPORTED") {
10473
+ throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
10474
+ }
10475
+ capabilities.set(select, capability);
10476
+ if (select.orderBy.length > 0 || select.columns.some((column) => column.type === "WINDOW_COL" && column.orderBy.length > 0)) {
10477
+ const meta = await buildOrderByMetaForSelect(select, tracedClient, cacheContext);
10478
+ if (select.orderMode !== "KINTONE_NATIVE") {
10479
+ for (const semantics of meta.semantics.values()) {
10480
+ if (semantics.fieldType === "STATUS" && semantics.source) {
10481
+ processStatusApps.add(semantics.source.appId);
10482
+ }
10483
+ }
10484
+ }
10485
+ const hasUnmaterializedSource = [select.from, ...select.joins.map((join2) => join2.table)].some((table) => table.cteName !== null);
10486
+ if (hasCanonicalOrder(select) && !hasUnmaterializedSource) {
10487
+ const mode = capability.capability === "EXACT_PUSHDOWN" ? resolveSelectMode(select) : "FULL_SCAN";
10488
+ orderPlans.set(select, (select.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
10489
+ stmt: select,
10490
+ staticMode: mode,
10491
+ whereCapability: capability.capability,
10492
+ orderSemantics: meta.semantics,
10493
+ maxRecords,
10494
+ hasKlike: whereHasKlike(select.where)
10495
+ }));
10496
+ }
10497
+ }
10498
+ } else if (typed["type"] === "UPDATE" || typed["type"] === "DELETE") {
10499
+ fieldApps.add(node.appId);
10500
+ await assertDmlWhereCapability(
10501
+ node,
10502
+ tracedClient,
10503
+ cacheContext
10504
+ );
10505
+ }
10506
+ await Promise.all(Object.values(typed).map(visit));
10507
+ };
10508
+ await visit(query);
10509
+ if (typeof query === "object" && query !== null && query.type === "WITH" && canInlineSingleCte(query)) {
10510
+ const inlined = buildInlinedQuery(query);
10511
+ const capability = await resolveSelectWhereCapability(inlined, tracedClient, cacheContext);
10512
+ if (capability.capability === "UNSUPPORTED") {
10513
+ throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
10514
+ }
10515
+ capabilities.set(inlined, capability);
10516
+ if (hasCanonicalOrder(inlined)) {
10517
+ const meta = await buildOrderByMetaForSelect(inlined, tracedClient, cacheContext);
10518
+ orderPlans.set(inlined, (inlined.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
10519
+ stmt: inlined,
10520
+ staticMode: capability.capability === "EXACT_PUSHDOWN" ? resolveSelectMode(inlined) : "FULL_SCAN",
10521
+ whereCapability: capability.capability,
10522
+ orderSemantics: meta.semantics,
10523
+ maxRecords,
10524
+ hasKlike: whereHasKlike(inlined.where)
10525
+ }));
10526
+ }
10527
+ }
10528
+ return { capabilities, orderPlans, fieldApps, processStatusApps };
10529
+ }
10530
+ function explainMetadataLines(analysis) {
10531
+ return [
10532
+ ...[...analysis.fieldApps].sort((a, b) => a - b).map((appId) => ` metadata API: form definition APP${appId}`),
10533
+ ...[...analysis.processStatusApps].sort((a, b) => a - b).map((appId) => ` metadata API: process status APP${appId}`)
10534
+ ];
10535
+ }
10536
+ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords = 1e4, cursorMaxActive = 2) {
9076
10537
  const statements = parseSqlBatch(sql);
9077
10538
  const analysis = analyzeBatch(statements);
9078
10539
  validateDeclaredBatchVariables(statements, injectedVariables);
9079
10540
  const variables = /* @__PURE__ */ new Map();
9080
- return {
9081
- statementCount: statements.length,
9082
- statements: statements.map((stmt, i) => {
9083
- const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveVariableRefs(stmt.expr, variables) } : stmt : resolveVariableRefs(stmt, variables);
9084
- validateKlikeStatement(planStmt);
9085
- const result = {
9086
- index: i,
9087
- type: analysis.statements[i].statementType,
9088
- plan: buildBatchStatementPlan(planStmt, analysis.statements[i])
9089
- };
9090
- if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
9091
- variables.set(stmt.name, { type: "string", value: `@${stmt.name}` });
9092
- }
9093
- return result;
9094
- })
9095
- };
10541
+ const plans = [];
10542
+ for (let i = 0; i < statements.length; i++) {
10543
+ const stmt = statements[i];
10544
+ const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveVariableRefs(stmt.expr, variables) } : stmt : resolveVariableRefs(stmt, variables);
10545
+ validateKlikeStatement(planStmt);
10546
+ const whereAnalysis = await buildExplainWhereAnalysis(planStmt, client, cacheContext, maxRecords);
10547
+ const statementPlan = addCursorConcurrency(buildBatchStatementPlan(
10548
+ planStmt,
10549
+ analysis.statements[i],
10550
+ whereAnalysis.capabilities,
10551
+ whereAnalysis.orderPlans
10552
+ ), cursorMaxActive);
10553
+ const metadataPlan = explainMetadataLines(whereAnalysis);
10554
+ plans.push({
10555
+ index: i,
10556
+ type: analysis.statements[i].statementType,
10557
+ plan: statementPlan.length === 0 ? metadataPlan : [statementPlan[0], ...metadataPlan, ...statementPlan.slice(1)]
10558
+ });
10559
+ if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
10560
+ variables.set(stmt.name, { type: "string", value: `@${stmt.name}` });
10561
+ }
10562
+ }
10563
+ return { statementCount: statements.length, statements: plans };
9096
10564
  }
9097
- function buildBatchStatementPlan(stmt, info) {
10565
+ function buildBatchStatementPlan(stmt, info, capabilities, orderPlans) {
9098
10566
  if (stmt.type === "CREATE_TEMP_TABLE") {
9099
10567
  return [
9100
10568
  `CREATE TEMP TABLE ${stmt.name}`,
9101
10569
  ` scope: batch\uFF08\u30D0\u30C3\u30C1\u7D42\u4E86\u6642\u306B\u81EA\u52D5\u7834\u68C4\uFF09`,
9102
10570
  ` rows: \u5B9F\u4F53\u5316\u524D\u306E\u305F\u3081\u4E0D\u660E\uFF08\u65E2\u5B9A\u4E0A\u9650 ${TEMP_TABLE_MAX_ROWS} \u884C\u3001tempTableMaxRows \u3067\u5909\u66F4\u53EF\u3001\u8D85\u904E\u306F\u30A8\u30E9\u30FC\uFF09`,
9103
- ...buildPlanForBatchQuery(stmt.query, info).map((l) => ` ${l}`)
10571
+ ...buildPlanForBatchQuery(stmt.query, info, capabilities, orderPlans).map((l) => ` ${l}`)
9104
10572
  ];
9105
10573
  }
9106
10574
  if (stmt.type === "DROP_TEMP_TABLE") {
@@ -9116,7 +10584,7 @@ function buildBatchStatementPlan(stmt, info) {
9116
10584
  `SET @${stmt.name} = (SELECT ...)`,
9117
10585
  " value: \u30B5\u30D6\u30AF\u30A8\u30EA\u3092\u5B9F\u884C\u6642\u306B1\u56DE\u8A55\u4FA1\uFF081\u884C1\u5217\u30FB\u30D0\u30C3\u30C1\u5185\u5B9A\u6570\u30FB\u7D50\u679C\u30E1\u30BF\u30C7\u30FC\u30BF\u306B\u306F\u975E\u516C\u958B\uFF09",
9118
10586
  " subquery:",
9119
- ...buildPlanForBatchQuery(stmt.expr.query, subInfo).map((l) => ` ${l}`)
10587
+ ...buildPlanForBatchQuery(stmt.expr.query, subInfo, capabilities, orderPlans).map((l) => ` ${l}`)
9120
10588
  ];
9121
10589
  }
9122
10590
  return [
@@ -9132,7 +10600,7 @@ function buildBatchStatementPlan(stmt, info) {
9132
10600
  }
9133
10601
  if (stmt.type === "SHOW_APPS") return ["SHOW APPS\uFF08\u30A2\u30D7\u30EA\u4E00\u89A7\u306E\u53D6\u5F97\uFF09"];
9134
10602
  if (stmt.type === "DESCRIBE") return [`DESCRIBE APP${stmt.appId}\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u5B9A\u7FA9\u306E\u53D6\u5F97\uFF09`];
9135
- if (stmt.type === "EXPLAIN") return buildPlanForBatchQuery(stmt.query, info);
10603
+ if (stmt.type === "EXPLAIN") return buildPlanForBatchQuery(stmt.query, info, capabilities, orderPlans);
9136
10604
  if (stmt.type === "ASSERT") {
9137
10605
  const lines = [
9138
10606
  `ASSERT ${stmt.text}`,
@@ -9144,11 +10612,11 @@ function buildBatchStatementPlan(stmt, info) {
9144
10612
  subqueries.forEach((sq, i) => {
9145
10613
  lines.push(subqueries.length > 1 ? ` subquery[${i + 1}]:` : " subquery:");
9146
10614
  const subInfo = hasTempTableRef(sq.query) ? info : { ...info, tempTablesReferenced: [] };
9147
- lines.push(...buildPlanForBatchQuery(sq.query, subInfo).map((l) => ` ${l}`));
10615
+ lines.push(...buildPlanForBatchQuery(sq.query, subInfo, capabilities, orderPlans).map((l) => ` ${l}`));
9148
10616
  });
9149
10617
  return lines;
9150
10618
  }
9151
- return buildPlanForBatchQuery(stmt, info);
10619
+ return buildPlanForBatchQuery(stmt, info, capabilities, orderPlans);
9152
10620
  }
9153
10621
  function hasTempTableRef(node) {
9154
10622
  if (Array.isArray(node)) return node.some(hasTempTableRef);
@@ -9160,9 +10628,9 @@ function hasTempTableRef(node) {
9160
10628
  }
9161
10629
  return false;
9162
10630
  }
9163
- function buildPlanForBatchQuery(query, info) {
10631
+ function buildPlanForBatchQuery(query, info, capabilities, orderPlans) {
9164
10632
  if (info.tempTablesReferenced.length === 0) {
9165
- return buildExplainPlan(query);
10633
+ return buildExplainPlan(query, void 0, capabilities, orderPlans);
9166
10634
  }
9167
10635
  const lines = [];
9168
10636
  if (query.type === "INSERT_SELECT") {
@@ -9187,8 +10655,15 @@ function buildPlanForBatchQuery(query, info) {
9187
10655
  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");
9188
10656
  return lines;
9189
10657
  }
9190
- function executeExplain(stmt) {
9191
- const lines = buildExplainPlan(stmt.query);
10658
+ async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxActive) {
10659
+ const analysis = await buildExplainWhereAnalysis(stmt.query, client, cacheContext, maxRecords);
10660
+ const lines = [
10661
+ ...explainMetadataLines(analysis),
10662
+ ...addCursorConcurrency(
10663
+ buildExplainPlan(stmt.query, void 0, analysis.capabilities, analysis.orderPlans),
10664
+ cursorMaxActive
10665
+ )
10666
+ ];
9192
10667
  return {
9193
10668
  type: "SELECT",
9194
10669
  columns: ["plan"],
@@ -9196,31 +10671,64 @@ function executeExplain(stmt) {
9196
10671
  rowCount: lines.length
9197
10672
  };
9198
10673
  }
9199
- function buildExplainPlan(query, label) {
9200
- if (query.type === "UNION") return buildUnionPlan(query);
9201
- if (query.type === "WITH") return buildWithPlan(query);
10674
+ function addCursorConcurrency(lines, cursorMaxActive) {
10675
+ const result = [];
10676
+ for (const line of lines) {
10677
+ result.push(line);
10678
+ if (line.trim() === "cursor page size: 500") {
10679
+ const indent = line.match(/^\s*/)?.[0] ?? "";
10680
+ result.push(`${indent}cursor concurrency: ${cursorMaxActive} per domain (process-local)`);
10681
+ }
10682
+ }
10683
+ return result;
10684
+ }
10685
+ function buildExplainPlan(query, label, capabilities, orderPlans) {
10686
+ if (query.type === "UNION") return buildUnionPlan(query, capabilities, orderPlans);
10687
+ if (query.type === "WITH") return buildWithPlan(query, capabilities, orderPlans);
9202
10688
  if (query.type === "INSERT") return buildInsertPlan(query, label);
9203
- if (query.type === "INSERT_SELECT") return buildInsertSelectPlan(query, label);
10689
+ if (query.type === "INSERT_SELECT") return buildInsertSelectPlan(query, label, capabilities, orderPlans);
9204
10690
  if (query.type === "UPSERT") return buildUpsertPlan(query, label);
9205
- if (query.type === "UPSERT_SELECT") return buildUpsertSelectPlan(query, label);
9206
- if (query.type === "UPDATE") return buildUpdatePlan(query, label);
10691
+ if (query.type === "UPSERT_SELECT") return buildUpsertSelectPlan(query, label, capabilities, orderPlans);
10692
+ if (query.type === "UPDATE") return buildUpdatePlan(query, label, capabilities, orderPlans);
9207
10693
  if (query.type === "DELETE") return buildDeletePlan(query, label);
9208
10694
  if (query.type === "REORDER") return buildReorderPlan(query, label);
9209
- return buildSelectPlan(query, label);
10695
+ return buildSelectPlan(query, label, capabilities, orderPlans);
9210
10696
  }
9211
- function buildSelectPlan(stmt, label) {
9212
- const mode = resolveSelectMode(stmt);
10697
+ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
10698
+ const whereCapability = capabilities?.get(stmt) ?? (capabilities ? [...capabilities].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
10699
+ const orderPlan = orderPlans?.get(stmt) ?? (orderPlans ? [...orderPlans].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
10700
+ const mode = orderPlan?.kind === "CANONICAL_LOCAL" ? "FULL_SCAN" : whereCapability && whereCapability.capability !== "EXACT_PUSHDOWN" ? "FULL_SCAN" : resolveSelectMode(stmt);
9213
10701
  const reasons = collectFullScanReasons(stmt);
10702
+ if (whereCapability && whereCapability.capability !== "EXACT_PUSHDOWN") {
10703
+ reasons.push(...whereCapability.reasons.map((reason) => reason.code));
10704
+ }
9214
10705
  const lines = [];
9215
10706
  if (label) lines.push(label);
9216
10707
  lines.push(` mode: ${mode}`);
10708
+ if (orderPlan) {
10709
+ lines.push(` order plan: ${orderPlan.kind}`);
10710
+ if (orderPlan.reasonCodes.length > 0) lines.push(` order reason: ${orderPlan.reasonCodes.join(", ")}`);
10711
+ if (orderPlan.kind === "KORDER_NATIVE") {
10712
+ lines.push(" order semantics: kintone native (not kSQL canonical)");
10713
+ lines.push(" REST execution: single GET");
10714
+ } else if (orderPlan.kind === "KORDER_CURSOR") {
10715
+ lines.push(" order semantics: kintone native (not kSQL canonical)");
10716
+ lines.push(" fetch API: POST/GET/DELETE records/cursor.json");
10717
+ lines.push(" cursor page size: 500");
10718
+ lines.push(` scan rows: ${orderPlan.scanRows}`);
10719
+ }
10720
+ }
10721
+ if (orderPlan?.requiresCompleteInput ?? requiresCompleteInput(stmt)) {
10722
+ lines.push(" complete input: required (ORDER BY / window ORDER BY; onLimit=truncate disabled)");
10723
+ }
9217
10724
  if (mode === "FULL_SCAN" && reasons.length > 0) {
9218
10725
  lines.push(` reason: ${reasons.join(", ")}`);
9219
10726
  }
9220
10727
  if (mode === "SIMPLE") {
9221
- const params = selectToKintoneParams(stmt);
10728
+ const params = selectToKintoneParams(orderPlan?.kind === "CANONICAL_REST_TOP_N" ? withCanonicalRestTie(stmt) : stmt);
9222
10729
  lines.push(` app: APP${stmt.from.appId} (${stmt.from.appId})`);
9223
- lines.push(` kintone query: ${params.query || "(\u306A\u3057)"}`);
10730
+ const displayedQuery = orderPlan?.kind === "KORDER_CURSOR" ? buildKorderCursorQuery(stmt) : params.query;
10731
+ lines.push(` kintone query: ${displayedQuery || "(\u306A\u3057)"}`);
9224
10732
  lines.push(` fields: ${params.fields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : params.fields.join(", ")}`);
9225
10733
  } else {
9226
10734
  const pushdownPlan = buildKlikePushdownPlan(stmt);
@@ -9228,7 +10736,8 @@ function buildSelectPlan(stmt, label) {
9228
10736
  const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
9229
10737
  const mainPushDown = pushdownPlan.mainCondition;
9230
10738
  const mainCandidate = extractMainTypedPushdownCandidate(stmt);
9231
- const mainQ = mainPushDown !== null ? whereToKintone(mainPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
10739
+ const exactOriginalWhere = stmt.joins.length === 0 && whereCapability?.capability === "EXACT_PUSHDOWN" && stmt.where !== null && !whereRequiresJsEval(stmt.where) ? whereToKintone(stmt.where) : "";
10740
+ const mainQ = mainPushDown !== null ? whereToKintone(mainPushDown) : exactOriginalWhere || "(\u5168\u4EF6\u53D6\u5F97)";
9232
10741
  lines.push(` app: APP${stmt.from.appId}${mainAliasStr} (${stmt.from.appId})`);
9233
10742
  lines.push(` kintone query: ${mainQ}`);
9234
10743
  if (mainCandidate !== null) {
@@ -9250,10 +10759,10 @@ function buildSelectPlan(stmt, label) {
9250
10759
  lines.push(` fields: ${joinFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : joinFields.join(", ")}`);
9251
10760
  }
9252
10761
  }
9253
- lines.push(...collectSubqueryPlans(stmt));
10762
+ lines.push(...collectSubqueryPlans(stmt, capabilities, orderPlans));
9254
10763
  return lines;
9255
10764
  }
9256
- function buildUnionPlan(stmt) {
10765
+ function buildUnionPlan(stmt, capabilities, orderPlans) {
9257
10766
  const selects = [];
9258
10767
  const collect = (u) => {
9259
10768
  if (u.type === "SELECT") {
@@ -9267,24 +10776,25 @@ function buildUnionPlan(stmt) {
9267
10776
  const lines = [];
9268
10777
  selects.forEach((sel, i) => {
9269
10778
  if (i > 0) lines.push("");
9270
- lines.push(...buildSelectPlan(sel, `[union:${i + 1}]`));
10779
+ lines.push(...buildSelectPlan(sel, `[union:${i + 1}]`, capabilities, orderPlans));
9271
10780
  });
9272
10781
  return lines;
9273
10782
  }
9274
- function buildWithPlan(stmt) {
10783
+ function buildWithPlan(stmt, capabilities, orderPlans) {
9275
10784
  const lines = [];
9276
10785
  for (const cte of stmt.ctes) {
9277
10786
  if (cte.query.type === "SELECT") {
9278
- lines.push(...buildSelectPlan(cte.query, `[cte: ${cte.name}]`));
10787
+ lines.push(...buildSelectPlan(cte.query, `[cte: ${cte.name}]`, capabilities, orderPlans));
9279
10788
  lines.push("");
9280
10789
  }
9281
10790
  }
9282
10791
  if (stmt.query.type === "SELECT" || stmt.query.type === "UNION") {
9283
- lines.push(...buildExplainPlan(stmt.query, "[main]"));
10792
+ lines.push(...buildExplainPlan(stmt.query, "[main]", capabilities, orderPlans));
9284
10793
  }
9285
10794
  if (canInlineSingleCte(stmt)) {
9286
10795
  lines.push("");
9287
- lines.push(...buildSelectPlan(buildInlinedQuery(stmt), "[effective: inlined CTE]"));
10796
+ const inlined = buildInlinedQuery(stmt);
10797
+ lines.push(...buildSelectPlan(inlined, "[effective: inlined CTE]", capabilities, orderPlans));
9288
10798
  }
9289
10799
  return lines;
9290
10800
  }
@@ -9312,7 +10822,7 @@ function collectFullScanReasons(stmt) {
9312
10822
  r.push("ORDER BY \u306B\u5F0F");
9313
10823
  return r;
9314
10824
  }
9315
- function collectSubqueryPlans(stmt) {
10825
+ function collectSubqueryPlans(stmt, capabilities, orderPlans) {
9316
10826
  const lines = [];
9317
10827
  let idx = 1;
9318
10828
  const visitWhere = (w) => {
@@ -9321,16 +10831,16 @@ function collectSubqueryPlans(stmt) {
9321
10831
  case "BINARY":
9322
10832
  if (w.right.type === "SCALAR_SUBQUERY") {
9323
10833
  lines.push("");
9324
- lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`));
10834
+ lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans));
9325
10835
  }
9326
10836
  if (w.right.type === "SUBQUERY_IN_LIST") {
9327
10837
  lines.push("");
9328
- lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`));
10838
+ lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans));
9329
10839
  }
9330
10840
  break;
9331
10841
  case "EXISTS":
9332
10842
  lines.push("");
9333
- lines.push(...buildSelectPlan(w.query, `[subquery:${idx++}]`));
10843
+ lines.push(...buildSelectPlan(w.query, `[subquery:${idx++}]`, capabilities, orderPlans));
9334
10844
  break;
9335
10845
  case "LOGICAL":
9336
10846
  visitWhere(w.left);
@@ -9348,7 +10858,7 @@ function collectSubqueryPlans(stmt) {
9348
10858
  for (const col of stmt.columns) {
9349
10859
  if (col.type === "SCALAR_SUBQUERY_COL") {
9350
10860
  lines.push("");
9351
- lines.push(...buildSelectPlan(col.query, `[subquery:${idx++}]`));
10861
+ lines.push(...buildSelectPlan(col.query, `[subquery:${idx++}]`, capabilities, orderPlans));
9352
10862
  }
9353
10863
  }
9354
10864
  if (stmt.having) visitWhere(stmt.having);
@@ -9366,7 +10876,7 @@ function buildInsertPlan(stmt, label) {
9366
10876
  lines.push(` fields: ${stmt.fields.join(", ")}`);
9367
10877
  return lines;
9368
10878
  }
9369
- function buildInsertSelectPlan(stmt, label) {
10879
+ function buildInsertSelectPlan(stmt, label, capabilities, orderPlans) {
9370
10880
  const lines = [];
9371
10881
  if (label) lines.push(label);
9372
10882
  lines.push(` [INSERT SELECT]`);
@@ -9374,10 +10884,10 @@ function buildInsertSelectPlan(stmt, label) {
9374
10884
  lines.push(` fields: ${stmt.fields.join(", ")}`);
9375
10885
  lines.push(` api: POST /k/v1/records.json\uFF08\u4EF6\u6570\u306F SELECT \u7D50\u679C\u306B\u4F9D\u5B58\u3001100 \u4EF6\u3054\u3068\u306B\u30D0\u30C3\u30C1\uFF09`);
9376
10886
  lines.push("");
9377
- lines.push(...buildSelectPlan(stmt.select, "[source SELECT]"));
10887
+ lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans));
9378
10888
  return lines;
9379
10889
  }
9380
- function buildUpdatePlan(stmt, label) {
10890
+ function buildUpdatePlan(stmt, label, capabilities, orderPlans) {
9381
10891
  const isArith = hasArithAssignment(stmt);
9382
10892
  const isSubq = stmt.assignments.some((a) => a.value.type === "SCALAR_SUBQUERY");
9383
10893
  const lines = [];
@@ -9411,7 +10921,7 @@ function buildUpdatePlan(stmt, label) {
9411
10921
  for (const a of stmt.assignments) {
9412
10922
  if (a.value.type === "SCALAR_SUBQUERY") {
9413
10923
  lines.push("");
9414
- lines.push(...buildSelectPlan(a.value.query, `[subquery: ${a.field}]`));
10924
+ lines.push(...buildSelectPlan(a.value.query, `[subquery: ${a.field}]`, capabilities, orderPlans));
9415
10925
  }
9416
10926
  }
9417
10927
  return lines;
@@ -9438,7 +10948,7 @@ function buildUpsertPlan(stmt, label) {
9438
10948
  ` api: GET /k/v1/records.json\uFF08\u91CD\u8907\u5224\u5B9A\uFF09\u2192 POST \u307E\u305F\u306F PUT /k/v1/records.json \xD7 ${batchCount}`
9439
10949
  ];
9440
10950
  }
9441
- function buildUpsertSelectPlan(stmt, label) {
10951
+ function buildUpsertSelectPlan(stmt, label, capabilities, orderPlans) {
9442
10952
  const lines = [
9443
10953
  ...label ? [label] : [],
9444
10954
  ` [UPSERT SELECT]`,
@@ -9448,7 +10958,7 @@ function buildUpsertSelectPlan(stmt, label) {
9448
10958
  ` api: GET /k/v1/records.json\uFF08\u91CD\u8907\u5224\u5B9A\uFF09\u2192 POST \u307E\u305F\u306F PUT /k/v1/records.json\uFF08100 \u4EF6\u3054\u3068\u306B\u30D0\u30C3\u30C1\uFF09`,
9449
10959
  ``
9450
10960
  ];
9451
- lines.push(...buildSelectPlan(stmt.select, "[source SELECT]"));
10961
+ lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans));
9452
10962
  return lines;
9453
10963
  }
9454
10964
  function buildReorderPlan(stmt, label) {
@@ -9786,6 +11296,12 @@ function validateKsqlConfig(config) {
9786
11296
  }
9787
11297
  const logicalApps = normalizeLogicalApps(profileName, profile.logicalApps);
9788
11298
  if (logicalApps !== void 0) profile.logicalApps = logicalApps;
11299
+ if (profile.query?.cursorMaxActive !== void 0) {
11300
+ const value = profile.query.cursorMaxActive;
11301
+ if (!Number.isSafeInteger(value) || value < 1 || value > 5) {
11302
+ throw argumentError(`query.cursorMaxActive for profile "${profileName}" must be an integer from 1 to 5.`);
11303
+ }
11304
+ }
9789
11305
  }
9790
11306
  return config;
9791
11307
  }
@@ -9930,6 +11446,10 @@ var RequestGate = class {
9930
11446
  async runMutation(fn) {
9931
11447
  return this.withSlot(fn);
9932
11448
  }
11449
+ /** Cursor Create/Get/Delete: セマフォのみ。GETでも位置を進めるため再試行しない。 */
11450
+ async runCursorStep(fn) {
11451
+ return this.withSlot(fn);
11452
+ }
9933
11453
  async withSlot(fn) {
9934
11454
  await this.acquire();
9935
11455
  try {
@@ -9961,6 +11481,14 @@ var RequestGate = class {
9961
11481
  function withRequestGate(client, gate) {
9962
11482
  return {
9963
11483
  getRecords: (params) => gate.runReadOnly(() => client.getRecords(params)),
11484
+ openCursor: async (params) => {
11485
+ const handle = await gate.runCursorStep(() => client.openCursor(params));
11486
+ return {
11487
+ totalCount: handle.totalCount,
11488
+ nextPage: () => gate.runCursorStep(() => handle.nextPage()),
11489
+ close: () => gate.runCursorStep(() => handle.close())
11490
+ };
11491
+ },
9964
11492
  getApps: () => gate.runReadOnly(() => client.getApps()),
9965
11493
  getFields: (appId) => gate.runReadOnly(() => client.getFields(appId)),
9966
11494
  getProcessStatuses: (appId) => gate.runReadOnly(() => client.getProcessStatuses(appId)),
@@ -9990,12 +11518,14 @@ function flattenFormFieldProperties(properties) {
9990
11518
  function flattenFields(properties, lookupCopyFields, inSubtable = false) {
9991
11519
  const out = [];
9992
11520
  for (const field of Object.values(properties)) {
9993
- out.push({
11521
+ const optionOrder = toOptionOrderMap(field.options);
11522
+ const sortKind = detectSortKind(field.type, field.format);
11523
+ const info = {
9994
11524
  code: field.code,
9995
11525
  label: field.label,
9996
11526
  fieldType: field.type,
9997
- optionOrder: toOptionOrderMap(field.options),
9998
- sortKind: detectSortKind(field.type, field.format),
11527
+ optionOrder,
11528
+ sortKind,
9999
11529
  required: field.required,
10000
11530
  minValue: normalizeConstraintValue(field.minValue),
10001
11531
  maxValue: normalizeConstraintValue(field.maxValue),
@@ -10004,7 +11534,9 @@ function flattenFields(properties, lookupCopyFields, inSubtable = false) {
10004
11534
  defaultValue: field.defaultValue,
10005
11535
  inSubtable,
10006
11536
  writable: !lookupCopyFields.has(field.code) && !NON_WRITABLE_FIELD_TYPES2.has(field.type)
10007
- });
11537
+ };
11538
+ info.semantics = resolveFieldSemantics(info);
11539
+ out.push(info);
10008
11540
  if (field.fields) out.push(...flattenFields(field.fields, lookupCopyFields, true));
10009
11541
  }
10010
11542
  return out;
@@ -10059,7 +11591,230 @@ function detectSortKind(fieldType, calcFormat) {
10059
11591
  return void 0;
10060
11592
  }
10061
11593
 
11594
+ // src/core/processStatus.ts
11595
+ function normalizeProcessStatusStates(states) {
11596
+ if (states === null) return null;
11597
+ return Object.values(states).map((state) => {
11598
+ const index = Number(state.index);
11599
+ if (!Number.isSafeInteger(index) || index < 0) {
11600
+ throw new Error(`ArgumentError: invalid process status index: ${String(state.index)}`);
11601
+ }
11602
+ return { name: state.name, index };
11603
+ });
11604
+ }
11605
+
11606
+ // src/api/kintoneCursor.ts
11607
+ function isAlreadyReleasedCursorError(error) {
11608
+ const shaped = error;
11609
+ return shaped?.status === 404 && shaped.code === "GAIA_CN01";
11610
+ }
11611
+ async function deleteCursorWithConfirmation(deleteCursor, sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms)), isAlreadyReleased = isAlreadyReleasedCursorError) {
11612
+ try {
11613
+ await deleteCursor();
11614
+ return;
11615
+ } catch (firstError) {
11616
+ if (isAlreadyReleased(firstError)) return;
11617
+ }
11618
+ await sleep(250);
11619
+ try {
11620
+ await deleteCursor();
11621
+ } catch (confirmationError) {
11622
+ if (isAlreadyReleased(confirmationError)) return;
11623
+ throw confirmationError;
11624
+ }
11625
+ }
11626
+ async function withTimeout(promise, timeoutMs) {
11627
+ let timer;
11628
+ const timeout = new Promise((_resolve, reject) => {
11629
+ timer = setTimeout(() => reject(new Error(`CursorCleanupTimeoutError: cleanup exceeded ${timeoutMs}ms.`)), timeoutMs);
11630
+ timer.unref?.();
11631
+ });
11632
+ try {
11633
+ return await Promise.race([promise, timeout]);
11634
+ } finally {
11635
+ if (timer) clearTimeout(timer);
11636
+ }
11637
+ }
11638
+ function createKintoneCursorHandle(totalCount, operations) {
11639
+ let released = false;
11640
+ let closing = false;
11641
+ let pageTail = Promise.resolve();
11642
+ let closePromise = null;
11643
+ const nextPage = () => {
11644
+ if (closing || released) return Promise.resolve({ records: [], next: false });
11645
+ const result = pageTail.then(async () => {
11646
+ if (closing || released) return { records: [], next: false };
11647
+ const page = await operations.get();
11648
+ if (!page.next) {
11649
+ released = true;
11650
+ operations.onReleased?.();
11651
+ }
11652
+ return page;
11653
+ });
11654
+ pageTail = result.then(() => void 0, () => void 0);
11655
+ return result;
11656
+ };
11657
+ const close = () => {
11658
+ if (released) return Promise.resolve();
11659
+ if (closePromise) return closePromise;
11660
+ closing = true;
11661
+ closePromise = pageTail.then(async () => {
11662
+ if (released) return;
11663
+ try {
11664
+ await withTimeout(
11665
+ deleteCursorWithConfirmation(
11666
+ operations.delete,
11667
+ operations.sleep,
11668
+ operations.isAlreadyReleasedError
11669
+ ),
11670
+ operations.cleanupTimeoutMs ?? 5e3
11671
+ );
11672
+ released = true;
11673
+ operations.onReleased?.();
11674
+ } catch (error) {
11675
+ operations.onReleaseUnknown?.();
11676
+ throw error;
11677
+ }
11678
+ });
11679
+ return closePromise;
11680
+ };
11681
+ return { totalCount, nextPage, close };
11682
+ }
11683
+
11684
+ // src/api/cursorLeaseManager.ts
11685
+ var DEFAULT_MAX_ACTIVE = 2;
11686
+ var MAX_ACTIVE = 5;
11687
+ var DEFAULT_WAIT_MS = 3e4;
11688
+ var DEFAULT_QUARANTINE_MS = 10 * 6e4 + 3e4;
11689
+ var CursorLeaseManager = class {
11690
+ constructor(host, options = {}) {
11691
+ this.host = host;
11692
+ this.active = 0;
11693
+ this.peak = 0;
11694
+ this.quarantined = 0;
11695
+ this.waiters = [];
11696
+ this.createTail = Promise.resolve();
11697
+ const maxActive = options.maxActive ?? DEFAULT_MAX_ACTIVE;
11698
+ if (!Number.isSafeInteger(maxActive) || maxActive < 1 || maxActive > MAX_ACTIVE) {
11699
+ throw new Error(`ArgumentError: cursorMaxActive must be an integer from 1 to ${MAX_ACTIVE}.`);
11700
+ }
11701
+ this.maxActive = maxActive;
11702
+ this.waitTimeoutMs = options.waitTimeoutMs ?? DEFAULT_WAIT_MS;
11703
+ this.quarantineMs = options.quarantineMs ?? DEFAULT_QUARANTINE_MS;
11704
+ }
11705
+ acquire() {
11706
+ if (this.active < this.maxActive) {
11707
+ this.active += 1;
11708
+ this.peak = Math.max(this.peak, this.active);
11709
+ return Promise.resolve(this.makeLease());
11710
+ }
11711
+ return new Promise((resolve2, reject) => {
11712
+ const waiter = {};
11713
+ waiter.resolve = resolve2;
11714
+ waiter.reject = reject;
11715
+ waiter.timer = setTimeout(() => {
11716
+ const index = this.waiters.indexOf(waiter);
11717
+ if (index >= 0) this.waiters.splice(index, 1);
11718
+ reject(new CursorCapacityError(this.host, this.maxActive, this.waitTimeoutMs));
11719
+ }, this.waitTimeoutMs);
11720
+ waiter.timer.unref?.();
11721
+ this.waiters.push(waiter);
11722
+ });
11723
+ }
11724
+ /**
11725
+ * 同一hostを共有する後続surfaceの設定を反映する。
11726
+ * 縮小時は既存leaseを強制終了せず、activeが新上限を下回るまで新規取得だけを止める。
11727
+ */
11728
+ setMaxActive(maxActive) {
11729
+ this.validateMaxActive(maxActive);
11730
+ if (this.maxActive === maxActive) return;
11731
+ this.maxActive = maxActive;
11732
+ this.dispatchWaiters();
11733
+ }
11734
+ async runCreate(fn) {
11735
+ const previous = this.createTail;
11736
+ let unlock;
11737
+ this.createTail = new Promise((resolve2) => {
11738
+ unlock = resolve2;
11739
+ });
11740
+ await previous;
11741
+ try {
11742
+ return await fn();
11743
+ } finally {
11744
+ unlock();
11745
+ }
11746
+ }
11747
+ snapshot() {
11748
+ return {
11749
+ active: this.active,
11750
+ peak: this.peak,
11751
+ quarantined: this.quarantined,
11752
+ waiting: this.waiters.length,
11753
+ limit: this.maxActive
11754
+ };
11755
+ }
11756
+ makeLease() {
11757
+ let done = false;
11758
+ return {
11759
+ release: () => {
11760
+ if (done) return;
11761
+ done = true;
11762
+ this.returnPermit();
11763
+ },
11764
+ quarantine: (durationMs = this.quarantineMs) => {
11765
+ if (done) return;
11766
+ done = true;
11767
+ this.quarantined += 1;
11768
+ const timer = setTimeout(() => {
11769
+ this.quarantined -= 1;
11770
+ this.returnPermit();
11771
+ }, durationMs);
11772
+ timer.unref?.();
11773
+ }
11774
+ };
11775
+ }
11776
+ returnPermit() {
11777
+ this.active -= 1;
11778
+ this.dispatchWaiters();
11779
+ }
11780
+ dispatchWaiters() {
11781
+ while (this.active < this.maxActive) {
11782
+ const waiter = this.waiters.shift();
11783
+ if (!waiter) return;
11784
+ clearTimeout(waiter.timer);
11785
+ this.active += 1;
11786
+ this.peak = Math.max(this.peak, this.active);
11787
+ waiter.resolve(this.makeLease());
11788
+ }
11789
+ }
11790
+ validateMaxActive(maxActive) {
11791
+ if (!Number.isSafeInteger(maxActive) || maxActive < 1 || maxActive > MAX_ACTIVE) {
11792
+ throw new Error(`ArgumentError: cursorMaxActive must be an integer from 1 to ${MAX_ACTIVE}.`);
11793
+ }
11794
+ }
11795
+ };
11796
+ var managers = /* @__PURE__ */ new Map();
11797
+ function getCursorLeaseManager(host, maxActive = DEFAULT_MAX_ACTIVE) {
11798
+ const key = host.toLowerCase();
11799
+ let manager = managers.get(key);
11800
+ if (!manager) {
11801
+ manager = new CursorLeaseManager(key, { maxActive });
11802
+ managers.set(key, manager);
11803
+ } else {
11804
+ manager.setMaxActive(maxActive);
11805
+ }
11806
+ return manager;
11807
+ }
11808
+
10062
11809
  // src/cli/nodeKintoneClient.ts
11810
+ var KintoneApiError = class extends Error {
11811
+ constructor(status, code, bodyText) {
11812
+ super(`kintone API error ${status}: ${bodyText}`);
11813
+ this.status = status;
11814
+ this.code = code;
11815
+ this.name = "KintoneApiError";
11816
+ }
11817
+ };
10063
11818
  var SEARCH_ABORTED_HEADER_VALUE = "Filter aborted because of too many search results";
10064
11819
  function createNodeKintoneClient(baseUrl, tokenResolver) {
10065
11820
  const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
@@ -10107,7 +11862,13 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
10107
11862
  if (tokenResolver.debug) {
10108
11863
  tokenResolver.log?.(`[debug] response status=${res.status} body=${bodyText}`);
10109
11864
  }
10110
- throw new Error(`kintone API error ${res.status}: ${bodyText}`);
11865
+ let code;
11866
+ try {
11867
+ const body = JSON.parse(bodyText);
11868
+ if (typeof body.code === "string") code = body.code;
11869
+ } catch {
11870
+ }
11871
+ throw new KintoneApiError(res.status, code, bodyText);
10111
11872
  }
10112
11873
  if (tokenResolver.debug) {
10113
11874
  tokenResolver.log?.(`[debug] response status=${res.status}`);
@@ -10174,6 +11935,48 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
10174
11935
  return response.searchAborted ? { ...response.body, searchAborted: true } : response.body;
10175
11936
  }
10176
11937
  },
11938
+ async openCursor(params) {
11939
+ const manager = getCursorLeaseManager(new URL(normalizedBaseUrl).host, tokenResolver.cursorMaxActive);
11940
+ const lease = await manager.acquire();
11941
+ let created;
11942
+ try {
11943
+ created = await manager.runCreate(() => requestJson(
11944
+ `${apiBasePath}/records/cursor.json`,
11945
+ {
11946
+ method: "POST",
11947
+ body: JSON.stringify({
11948
+ app: params.app,
11949
+ query: params.query,
11950
+ size: params.size,
11951
+ fields: params.fields && params.fields.length > 0 ? params.fields : void 0
11952
+ })
11953
+ },
11954
+ params.app
11955
+ ));
11956
+ } catch (error) {
11957
+ if (error instanceof KintoneApiError) {
11958
+ lease.release();
11959
+ throw error;
11960
+ }
11961
+ lease.quarantine();
11962
+ throw new CursorCreateOutcomeUnknownError(error);
11963
+ }
11964
+ const cursorId = created.id;
11965
+ return createKintoneCursorHandle(Number(created.totalCount), {
11966
+ get: () => requestJson(
11967
+ `${apiBasePath}/records/cursor.json?id=${encodeURIComponent(cursorId)}`,
11968
+ { method: "GET" },
11969
+ params.app
11970
+ ),
11971
+ delete: () => requestJson(
11972
+ `${apiBasePath}/records/cursor.json`,
11973
+ { method: "DELETE", body: JSON.stringify({ id: cursorId }) },
11974
+ params.app
11975
+ ),
11976
+ onReleased: () => lease.release(),
11977
+ onReleaseUnknown: () => lease.quarantine()
11978
+ });
11979
+ },
10177
11980
  async postRecords(_params) {
10178
11981
  const res = await requestJson(
10179
11982
  `${apiBasePath}/records.json`,
@@ -10256,7 +12059,7 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
10256
12059
  const res = await requestJson(`${apiBasePath}/app/status.json?${qs.toString()}`, { method: "GET" }, appId);
10257
12060
  return {
10258
12061
  enable: res.enable,
10259
- states: Object.values(res.states ?? {}).map((state) => state.name)
12062
+ states: normalizeProcessStatusStates(res.states)
10260
12063
  };
10261
12064
  }
10262
12065
  };
@@ -10726,11 +12529,12 @@ Options:
10726
12529
  (batch + json: prints one JSON envelope for the whole batch)
10727
12530
  --max-records <n> Max records to fetch (default: 500)
10728
12531
  --fetch-parallel <n> Parallel page fetches per query: 1-10 (default: 3)
10729
- --on-limit <mode> On record limit: error | truncate
12532
+ --on-limit <mode> On record limit: error | truncate (local ORDER BY needs complete input)
10730
12533
  --temp-table-max-rows <n> Max rows per temp table (default: 10000, always errors on overflow)
10731
12534
  --timeout <ms> Request timeout in milliseconds (default: 30000)
10732
12535
  --max-concurrent <n> Max concurrent kintone requests: 1-50 (default: 10)
10733
12536
  (process-wide; fixed at first resolution; KSQL_MAX_CONCURRENT wins)
12537
+ --cursor-max-active <n> Max active cursors per host: 1-5 (default: 2; KSQL_CURSOR_MAX_ACTIVE wins)
10734
12538
  --retry <n> GET retry count: 0-10, 0 disables (default: 3; KSQL_RETRY wins)
10735
12539
  --retry-base-delay <ms> GET retry backoff base delay (default: 500)
10736
12540
  --retry-max-delay <ms> GET retry backoff max delay (default: 8000)
@@ -10811,6 +12615,7 @@ function parseArgs(argv) {
10811
12615
  continueOnError: false,
10812
12616
  dmlMaxRows: null,
10813
12617
  maxConcurrent: null,
12618
+ cursorMaxActive: null,
10814
12619
  retry: null,
10815
12620
  retryBaseDelay: null,
10816
12621
  retryMaxDelay: null,
@@ -11067,6 +12872,13 @@ function parseArgs(argv) {
11067
12872
  i++;
11068
12873
  continue;
11069
12874
  }
12875
+ if (a === "--cursor-max-active") {
12876
+ const n = Number(v);
12877
+ if (!Number.isInteger(n) || n < 1 || n > 5) throw new Error("ArgumentError: --cursor-max-active must be an integer between 1 and 5.");
12878
+ out.cursorMaxActive = n;
12879
+ i++;
12880
+ continue;
12881
+ }
11070
12882
  if (a === "--retry") {
11071
12883
  const n = Number(v);
11072
12884
  if (!Number.isInteger(n) || n < 0 || n > 10) throw new Error("ArgumentError: --retry must be an integer between 0 and 10 (0 disables retry).");
@@ -11362,6 +13174,7 @@ function createDryRunClient() {
11362
13174
  };
11363
13175
  return {
11364
13176
  getRecords: notUsed,
13177
+ openCursor: notUsed,
11365
13178
  postRecords: notUsed,
11366
13179
  putRecords: notUsed,
11367
13180
  deleteRecords: notUsed,
@@ -11504,6 +13317,7 @@ function buildReplExecArgv(base, sql, dryRun, format) {
11504
13317
  pushOpt(argv, "--attachment-format", base.attachmentFormat);
11505
13318
  pushOpt(argv, "--dml-max-rows", base.dmlMaxRows);
11506
13319
  pushOpt(argv, "--max-concurrent", base.maxConcurrent);
13320
+ pushOpt(argv, "--cursor-max-active", base.cursorMaxActive);
11507
13321
  pushOpt(argv, "--retry", base.retry);
11508
13322
  pushOpt(argv, "--retry-base-delay", base.retryBaseDelay);
11509
13323
  pushOpt(argv, "--retry-max-delay", base.retryMaxDelay);
@@ -12010,7 +13824,7 @@ async function run() {
12010
13824
  let isBatchSql = false;
12011
13825
  let batchContainsDml = false;
12012
13826
  let batchAnalysis = null;
12013
- let needsCompleteInput = false;
13827
+ let dryRunNeedsMetadata = false;
12014
13828
  if (args.diagRecordId === null) {
12015
13829
  sql = args.executeSql;
12016
13830
  if (!sql && args.filePath) sql = (0, import_fs2.readFileSync)(args.filePath, "utf-8");
@@ -12037,17 +13851,16 @@ async function run() {
12037
13851
  }
12038
13852
  try {
12039
13853
  const statements = parseSqlStatements(sql);
13854
+ dryRunNeedsMetadata = statements.some(explainNeedsAppMetadata);
12040
13855
  if (statements.length > 1) {
12041
13856
  batchAnalysis = analyzeBatch(statements);
12042
13857
  isBatchSql = true;
12043
13858
  batchContainsDml = batchAnalysis.containsDml;
12044
- needsCompleteInput = batchAnalysis.requiresCompleteInput;
12045
13859
  } else {
12046
13860
  const stmt = parseSqlStatement(sql);
12047
13861
  parsedStmt = stmt;
12048
13862
  stmtType = getStatementType(stmt);
12049
13863
  isDmlStatement = writesKintone(stmt);
12050
- needsCompleteInput = requiresCompleteInput(stmt);
12051
13864
  hasWhere = hasWhereClause(stmt);
12052
13865
  insertValuesCount = getInsertValuesCount(stmt);
12053
13866
  const supported = stmtType === "SELECT" || stmtType === "UNION" || stmtType === "WITH" || stmtType === "EXPLAIN" || stmtType === "SHOW_APPS" || stmtType === "DESCRIBE" || stmtType === "ASSERT" || isDmlType(stmtType);
@@ -12072,6 +13885,11 @@ async function run() {
12072
13885
  const onLimit = args.onLimit ?? envOnLimit2("KSQL_ON_LIMIT") ?? profile.query?.onLimit ?? "error";
12073
13886
  const timeout = args.timeout ?? envInt2("KSQL_TIMEOUT") ?? profile.query?.timeout ?? 3e4;
12074
13887
  const tempTableMaxRows = args.tempTableMaxRows ?? envInt2("KSQL_TEMP_TABLE_MAX_ROWS") ?? profile.query?.tempTableMaxRows ?? void 0;
13888
+ const cursorMaxActive = args.cursorMaxActive ?? envInt2("KSQL_CURSOR_MAX_ACTIVE") ?? profile.query?.cursorMaxActive ?? 2;
13889
+ if (!Number.isSafeInteger(cursorMaxActive) || cursorMaxActive < 1 || cursorMaxActive > 5) {
13890
+ process.stderr.write("ArgumentError: cursorMaxActive must be an integer from 1 to 5.\n");
13891
+ return 2;
13892
+ }
12075
13893
  if (!Number.isInteger(fetchParallel) || fetchParallel < 1 || fetchParallel > 10) {
12076
13894
  process.stderr.write("ArgumentError: fetch-parallel must be an integer between 1 and 10.\n");
12077
13895
  return 2;
@@ -12094,10 +13912,13 @@ async function run() {
12094
13912
  const yes = args.yes || envBool("KSQL_YES") === true || Boolean(profile.dml?.yes);
12095
13913
  const allowWithoutWhere = args.allowWithoutWhere || envBool("KSQL_ALLOW_WITHOUT_WHERE") === true || Boolean(profile.dml?.allowWithoutWhere);
12096
13914
  const dmlMaxRows = args.dmlMaxRows ?? envInt2("KSQL_DML_MAX_ROWS") ?? profile.dml?.maxRows ?? 100;
12097
- const dmlForcesOnLimitError = needsCompleteInput;
12098
- const effectiveOnLimit = dmlForcesOnLimitError ? "error" : onLimit;
12099
- if (dmlForcesOnLimitError && onLimit === "truncate" && !quiet && !args.dryRun) {
12100
- process.stderr.write(isDmlStatement || batchContainsDml ? "note: onLimit=truncate is ignored for DML (forced to error)\n" : "note: onLimit=truncate is ignored for VALIDATE ONLY (forced to error)\n");
13915
+ const isValidationOnly = batchAnalysis?.containsValidationOnly === true || parsedStmt !== null && typeof parsedStmt === "object" && "validateOnly" in parsedStmt && parsedStmt.validateOnly === true;
13916
+ const surfaceForcesOnLimitError = isDmlStatement || batchContainsDml || isValidationOnly;
13917
+ const effectiveOnLimit = surfaceForcesOnLimitError ? "error" : onLimit;
13918
+ if (surfaceForcesOnLimitError && onLimit === "truncate" && !quiet && !args.dryRun) {
13919
+ const reason = isDmlStatement || batchContainsDml ? "DML" : "VALIDATE ONLY";
13920
+ process.stderr.write(`note: onLimit=truncate is ignored for ${reason} (forced to error)
13921
+ `);
12101
13922
  }
12102
13923
  if (format === "markdown" && noHeader) {
12103
13924
  process.stderr.write("ArgumentError: --no-header cannot be used with --format markdown|md.\n");
@@ -12128,30 +13949,6 @@ async function run() {
12128
13949
  process.stderr.write("ArgumentError: DML is disabled. Use --allow-dml to enable UPDATE/DELETE/INSERT/UPSERT/REORDER.\n");
12129
13950
  return 2;
12130
13951
  }
12131
- if (args.dryRun) {
12132
- let plans;
12133
- try {
12134
- plans = buildBatchExplainPlans(sql, args.variables);
12135
- } catch (err) {
12136
- const restored = sourceSql && sqlDiagnosticContext ? restoreSqlContextError(err, sourceSql, {
12137
- bindings: sqlDiagnosticContext.appBindingByMappedApp,
12138
- rewriteSegments: sqlDiagnosticContext.rewriteSegments
12139
- }) : err;
12140
- process.stderr.write(`${restored instanceof Error ? restored.message : String(restored)}
12141
- `);
12142
- return toExitCodeFromError(restored);
12143
- }
12144
- const out = [];
12145
- const restoredStatements = sqlDiagnosticContext ? restoreSqlDiagnosticValue(plans.statements, sqlDiagnosticContext.appBindingByMappedApp) : plans.statements;
12146
- restoredStatements.forEach((p) => {
12147
- if (p.index > 0) out.push("");
12148
- out.push(`[${p.index + 1}] ${p.type}`);
12149
- out.push(...p.plan);
12150
- });
12151
- process.stdout.write(`${out.join("\n")}
12152
- `);
12153
- return 0;
12154
- }
12155
13952
  }
12156
13953
  if (isDmlStatement) {
12157
13954
  if (hasProfileSyntax && stmtType === "DELETE") {
@@ -12178,7 +13975,7 @@ async function run() {
12178
13975
  appProfileByApp.set(appId, appBindingByMappedApp.get(appId)?.profile ?? profileName.toLowerCase());
12179
13976
  }
12180
13977
  const cacheContext = buildCacheContext(profileName, appBindingByMappedApp);
12181
- if (args.dryRun) {
13978
+ if (args.dryRun && !dryRunNeedsMetadata) {
12182
13979
  client = createDryRunClient();
12183
13980
  } else {
12184
13981
  for (const explicitProfile of appProfileByApp.values()) {
@@ -12223,6 +14020,7 @@ async function run() {
12223
14020
  }
12224
14021
  profileClientMap.set(pName, createNodeKintoneClient(baseUrl, {
12225
14022
  guestSpaceId,
14023
+ cursorMaxActive,
12226
14024
  timeoutMs: timeout,
12227
14025
  debug,
12228
14026
  debugHeaders,
@@ -12255,6 +14053,7 @@ async function run() {
12255
14053
  missingAppProfiles.push(...resolvedTokens.missing);
12256
14054
  profileClientMap.set(pName, createNodeKintoneClient(baseUrl, {
12257
14055
  guestSpaceId,
14056
+ cursorMaxActive,
12258
14057
  timeoutMs: timeout,
12259
14058
  debug,
12260
14059
  debugHeaders,
@@ -12366,6 +14165,12 @@ async function run() {
12366
14165
  if (!routed) throw new Error(`AuthError: profile "${pName}" is not resolved for APP${params.app}.`);
12367
14166
  return routed.getRecords({ ...params, app: binding.appId });
12368
14167
  },
14168
+ openCursor: (params) => {
14169
+ const binding = appBindingByMappedApp.get(params.app) ?? { appId: params.app, profile: profileName.toLowerCase() };
14170
+ const routed = profileClientMap.get(binding.profile);
14171
+ if (!routed) throw new Error(`AuthError: profile "${binding.profile}" is not resolved for APP${params.app}.`);
14172
+ return routed.openCursor({ ...params, app: binding.appId });
14173
+ },
12369
14174
  postRecords: (params) => {
12370
14175
  const binding = appBindingByMappedApp.get(params.app) ?? { appId: params.app, profile: profileName.toLowerCase() };
12371
14176
  const pName = binding.profile;
@@ -12412,6 +14217,36 @@ async function run() {
12412
14217
  maxDelayMs: args.retryMaxDelay ?? profile.query?.retryMaxDelayMs
12413
14218
  })));
12414
14219
  }
14220
+ if (isBatchSql && args.dryRun) {
14221
+ try {
14222
+ const plans = await buildBatchExplainPlans(
14223
+ sql,
14224
+ client,
14225
+ args.variables,
14226
+ cacheContext,
14227
+ maxRecords,
14228
+ cursorMaxActive
14229
+ );
14230
+ const out = [];
14231
+ const restoredStatements = sqlDiagnosticContext ? restoreSqlDiagnosticValue(plans.statements, sqlDiagnosticContext.appBindingByMappedApp) : plans.statements;
14232
+ restoredStatements.forEach((p) => {
14233
+ if (p.index > 0) out.push("");
14234
+ out.push(`[${p.index + 1}] ${p.type}`);
14235
+ out.push(...p.plan);
14236
+ });
14237
+ process.stdout.write(`${out.join("\n")}
14238
+ `);
14239
+ return 0;
14240
+ } catch (err) {
14241
+ const restored = sourceSql && sqlDiagnosticContext ? restoreSqlContextError(err, sourceSql, {
14242
+ bindings: sqlDiagnosticContext.appBindingByMappedApp,
14243
+ rewriteSegments: sqlDiagnosticContext.rewriteSegments
14244
+ }) : err;
14245
+ process.stderr.write(`${restored instanceof Error ? restored.message : String(restored)}
14246
+ `);
14247
+ return toExitCodeFromError(restored);
14248
+ }
14249
+ }
12415
14250
  try {
12416
14251
  if (isDmlStatement && !args.dryRun) {
12417
14252
  const stmtAppId = parsedStmt && typeof parsedStmt === "object" && typeof parsedStmt.appId === "number" ? parsedStmt.appId : appIds[0];
@@ -12455,6 +14290,7 @@ query=${label}`);
12455
14290
  continueOnError: args.continueOnError,
12456
14291
  tempTableMaxRows,
12457
14292
  timeoutMs: timeout,
14293
+ cursorMaxActive,
12458
14294
  variables: args.variables,
12459
14295
  confirm: batchContainsDml ? async (count, operation) => {
12460
14296
  if (count > dmlMaxRows) {
@@ -12465,12 +14301,18 @@ query=${label}`);
12465
14301
  });
12466
14302
  return writeBatchOutput(batchResult, { format, noHeader, pretty, displayOptions, outputPath, quiet });
12467
14303
  }
12468
- let result = args.dryRun ? await execute(`EXPLAIN ${sql}`, client, { maxRecords, onLimitReached: onLimit, cacheContext }) : await execute(sql, client, {
14304
+ let result = args.dryRun ? await execute(`EXPLAIN ${sql}`, client, {
14305
+ maxRecords,
14306
+ onLimitReached: onLimit,
14307
+ cacheContext,
14308
+ cursorMaxActive
14309
+ }) : await execute(sql, client, {
12469
14310
  maxRecords,
12470
14311
  fetchParallel,
12471
14312
  onLimitReached: effectiveOnLimit,
12472
14313
  confirm: isDmlStatement ? confirm : void 0,
12473
- cacheContext
14314
+ cacheContext,
14315
+ cursorMaxActive
12474
14316
  });
12475
14317
  if (args.dryRun && sqlDiagnosticContext) {
12476
14318
  result = restoreSqlDiagnosticValue(result, sqlDiagnosticContext.appBindingByMappedApp);