@rex0220/kintone-sql-tools 2.17.0 → 3.0.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,141 @@ 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 planKorderNative(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 || stmt.limit < 0 || stmt.limit > 500) {
5677
+ reasons.push(`KORDER_LIMIT_INVALID(limit=${String(stmt.limit)})`);
5678
+ }
5679
+ if (stmt.limit !== null && stmt.limit > input.maxRecords) {
5680
+ reasons.push(`KORDER_LIMIT_EXCEEDS_MAX_RECORDS(limit=${stmt.limit}, maxRecords=${input.maxRecords})`);
5681
+ }
5682
+ const offset = stmt.offset ?? 0;
5683
+ if (offset < 0 || offset > 1e4) reasons.push(`KORDER_OFFSET_INVALID(offset=${offset})`);
5684
+ const unique = [...new Set(reasons)];
5685
+ if (unique.length > 0) {
5686
+ throw new Error(
5687
+ `ArgumentError: KORDER BY cannot be executed (mode=KINTONE_NATIVE; ${unique.join(", ")}). Use ORDER BY for canonical local ordering or simplify the query.`
5688
+ );
5689
+ }
5690
+ return {
5691
+ kind: "KORDER_NATIVE",
5692
+ requiresCompleteInput: false,
5693
+ localOrderBy: false,
5694
+ applyLocalOffsetLimit: false,
5695
+ reasonCodes: []
5696
+ };
5697
+ }
5698
+
5239
5699
  // src/engine/process.ts
5240
5700
  function flatten(record, alias) {
5241
5701
  const row = {};
@@ -5300,9 +5760,9 @@ function applyJoin(leftRows, rightRows, join2) {
5300
5760
  }
5301
5761
  return result;
5302
5762
  }
5303
- function applyFilter(rows, where, resolveFieldType, appliedKlikes) {
5763
+ function applyFilter(rows, where, resolveFieldType, appliedKlikes, resolveFieldSemantics2) {
5304
5764
  if (where === null) return rows;
5305
- return rows.filter((row) => evalWhere(where, row, resolveFieldType, appliedKlikes));
5765
+ return rows.filter((row) => evalWhere(where, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2));
5306
5766
  }
5307
5767
  function hasAggregateColumns(columns) {
5308
5768
  return columns.some(
@@ -5363,7 +5823,7 @@ function evalAggregate(func, distinct, arg, separator, rows, resolveAggSortKind)
5363
5823
  let strVal;
5364
5824
  if (arg.type === "FIELD_REF") {
5365
5825
  const raw = row[arg.field];
5366
- if (raw === void 0 || raw === "") continue;
5826
+ if (raw === void 0 || raw === "" && func !== "MIN" && func !== "MAX") continue;
5367
5827
  strVal = raw;
5368
5828
  } else {
5369
5829
  const n = evalArithExpr(arg, row);
@@ -5375,10 +5835,16 @@ function evalAggregate(func, distinct, arg, separator, rows, resolveAggSortKind)
5375
5835
  const eff = distinct ? [...new Set(strValues)] : strValues;
5376
5836
  if (func === "COUNT") return eff.length;
5377
5837
  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);
5838
+ const comparison = (func === "MIN" || func === "MAX") && arg.type === "FIELD_REF" ? resolveAggSortKind?.(toAggregateFieldRef(arg.field)) : void 0;
5839
+ if (func === "MIN" || func === "MAX") {
5840
+ if (eff.length === 0) return 0;
5841
+ const semantics = typeof comparison === "string" ? syntheticSemantics(comparison) : comparison ?? (arg.type === "FIELD_REF" ? syntheticSemantics("string") : syntheticSemantics("number"));
5842
+ let result = eff[0];
5843
+ for (const candidate of eff.slice(1)) {
5844
+ const cmp = compareCanonicalValues(candidate, result, semantics);
5845
+ if (func === "MAX" && cmp > 0 || func === "MIN" && cmp < 0) result = candidate;
5846
+ }
5847
+ return result;
5382
5848
  }
5383
5849
  const nums = eff.map(Number);
5384
5850
  switch (func) {
@@ -5386,37 +5852,12 @@ function evalAggregate(func, distinct, arg, separator, rows, resolveAggSortKind)
5386
5852
  return nums.reduce((a, b) => a + b, 0);
5387
5853
  case "AVG":
5388
5854
  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
5855
  }
5395
5856
  }
5396
5857
  function toAggregateFieldRef(field) {
5397
5858
  const dot = field.indexOf(".");
5398
5859
  return dot > 0 ? { type: "FIELD", tableAlias: field.slice(0, dot), field: field.slice(dot + 1) } : { type: "FIELD", tableAlias: null, field };
5399
5860
  }
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
5861
  function evalAggArithExpr(node, rows, resolveAggSortKind) {
5421
5862
  if (node.type === "NUMBER") return node.value;
5422
5863
  if (node.type === "AGG_REF") return Number(evalAggregate(node.func, node.distinct, node.arg, node.separator, rows, resolveAggSortKind));
@@ -5448,9 +5889,9 @@ function aggregateSyntheticName2(func, distinct, arg) {
5448
5889
  const argStr = aggregateArgLabel(arg);
5449
5890
  return distinct ? `${func}(DISTINCT ${argStr})` : `${func}(${argStr})`;
5450
5891
  }
5451
- function applyHaving(rows, having, resolveFieldType) {
5892
+ function applyHaving(rows, having, resolveFieldType, resolveFieldSemantics2) {
5452
5893
  if (having === null) return rows;
5453
- return rows.filter((row) => evalWhere(having, row, resolveFieldType));
5894
+ return rows.filter((row) => evalWhere(having, row, resolveFieldType, void 0, resolveFieldSemantics2));
5454
5895
  }
5455
5896
  function applyDistinct(rows, columns) {
5456
5897
  if (rows.length === 0) return rows;
@@ -5502,27 +5943,37 @@ function buildDistinctKeyBuilder(rows, columns) {
5502
5943
  return JSON.stringify(values);
5503
5944
  };
5504
5945
  }
5505
- function applyOrderBy(rows, orderBy, optionOrders, sortKinds) {
5946
+ function applyOrderBy(rows, orderBy, optionOrders, sortKinds, fieldSemantics2) {
5506
5947
  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
- }));
5948
+ return sortDecoratedRows(rows, orderBy, optionOrders, sortKinds, fieldSemantics2).rows.map((item) => item.row);
5949
+ }
5950
+ function sortDecoratedRows(rows, orderBy, optionOrders, sortKinds, fieldSemantics2) {
5951
+ const keyMeta = orderBy.map(({ key }) => {
5952
+ if (key.type === "ARITH_KEY") return { semantics: syntheticSemantics("number") };
5953
+ if (key.type === "FUNC_KEY") {
5954
+ return { semantics: syntheticSemantics(NUMERIC_ORDER_FUNCTIONS.has(key.expr.func) ? "number" : "string") };
5955
+ }
5956
+ const semantics = fieldSemantics2?.get(key.name);
5957
+ if (semantics) return { semantics };
5958
+ const orderMap = optionOrders?.get(key.name);
5959
+ if (orderMap) {
5960
+ return {
5961
+ semantics: {
5962
+ fieldType: "MULTI_SELECT",
5963
+ compareMode: "option",
5964
+ inSubtable: false,
5965
+ requiresCollectionOperators: false,
5966
+ optionOrder: orderMap
5967
+ }
5968
+ };
5969
+ }
5970
+ return { semantics: syntheticSemantics(sortKinds?.get(key.name) ?? "string") };
5971
+ });
5514
5972
  const decorated = rows.map((row) => ({
5515
5973
  row,
5516
5974
  keys: orderBy.map(({ key }, i) => {
5517
5975
  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
- };
5976
+ return { s };
5526
5977
  })
5527
5978
  }));
5528
5979
  const compare = (a, b) => compareDecoratedRows(a, b, orderBy, keyMeta);
@@ -5537,15 +5988,24 @@ function compareDecoratedRows(a, b, orderBy, keyMeta) {
5537
5988
  return 0;
5538
5989
  }
5539
5990
  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
- }
5991
+ return compareCanonicalValues(a.s, b.s, meta.semantics);
5992
+ }
5993
+ var NUMERIC_ORDER_FUNCTIONS = /* @__PURE__ */ new Set([
5994
+ "LENGTH",
5995
+ "INSTR",
5996
+ "ROUND",
5997
+ "FLOOR",
5998
+ "CEIL",
5999
+ "TRUNCATE",
6000
+ "YEAR",
6001
+ "MONTH",
6002
+ "DAY",
6003
+ "DATEDIFF",
6004
+ "ABS",
6005
+ "MOD",
6006
+ "POWER",
6007
+ "SQRT"
6008
+ ]);
5549
6009
  function evalOrderKey(key, row) {
5550
6010
  switch (key.type) {
5551
6011
  case "FIELD_NAME":
@@ -5556,30 +6016,7 @@ function evalOrderKey(key, row) {
5556
6016
  return evalStringFunc(key.expr, row);
5557
6017
  }
5558
6018
  }
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) {
6019
+ function applyWindow(rows, columns, optionOrders, sortKinds, fieldSemantics2) {
5583
6020
  const windows = columns.filter((column) => column.type === "WINDOW_COL");
5584
6021
  if (rows.length === 0 || windows.length === 0) return rows;
5585
6022
  for (const window of windows) {
@@ -5591,7 +6028,7 @@ function applyWindow(rows, columns, optionOrders, sortKinds) {
5591
6028
  else partitions.set(key, [row]);
5592
6029
  }
5593
6030
  for (const partition of partitions.values()) {
5594
- const sortedResult = sortDecoratedRows(partition, window.orderBy, optionOrders, sortKinds);
6031
+ const sortedResult = sortDecoratedRows(partition, window.orderBy, optionOrders, sortKinds, fieldSemantics2);
5595
6032
  const sorted = sortedResult.rows;
5596
6033
  let rank = 1;
5597
6034
  let denseRank = 1;
@@ -5616,7 +6053,7 @@ function applyLimit(rows, limit, offset) {
5616
6053
  if (limit === null) return rows.slice(start);
5617
6054
  return rows.slice(start, start + limit);
5618
6055
  }
5619
- function project(rows, columns, scalarCache, resolveFieldType, sourceColumns) {
6056
+ function project(rows, columns, scalarCache, resolveFieldType, sourceColumns, resolveFieldSemantics2) {
5620
6057
  if (columns.length === 1 && columns[0].type === "WILDCARD") {
5621
6058
  const projected2 = rows.map((row) => stripParentShortcutColumns(row));
5622
6059
  const cols = projected2.length > 0 ? Object.keys(projected2[0]) : [...sourceColumns ?? []];
@@ -5680,7 +6117,7 @@ function project(rows, columns, scalarCache, resolveFieldType, sourceColumns) {
5680
6117
  }
5681
6118
  case "CASE_COL": {
5682
6119
  const key = outputKeys?.[colIdx] ?? col.alias ?? "case";
5683
- out[key] = evalCaseWhen(col.expr, row, resolveFieldType);
6120
+ out[key] = evalCaseWhen(col.expr, row, resolveFieldType, resolveFieldSemantics2);
5684
6121
  if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
5685
6122
  break;
5686
6123
  }
@@ -5835,6 +6272,26 @@ function resolveAggInStringFuncExpr(expr, rows, resolveAggSortKind) {
5835
6272
  args: expr.args.map((arg) => resolveAggInStringFuncArg(arg, rows, resolveAggSortKind))
5836
6273
  };
5837
6274
  }
6275
+ function deriveOutputOrderSemantics(columns) {
6276
+ const result = /* @__PURE__ */ new Map();
6277
+ for (const column of columns) {
6278
+ if (!("alias" in column) || !column.alias) continue;
6279
+ if (column.type === "ARITH_COL" || column.type === "ARITH_AGG_COL" || column.type === "WINDOW_COL") {
6280
+ result.set(column.alias, syntheticSemantics("number"));
6281
+ } else if (column.type === "AGGREGATE") {
6282
+ if (column.func === "COUNT" || column.func === "SUM" || column.func === "AVG") {
6283
+ result.set(column.alias, syntheticSemantics("number"));
6284
+ } else if (column.func === "GROUP_CONCAT") {
6285
+ result.set(column.alias, syntheticSemantics("string"));
6286
+ }
6287
+ } else if (column.type === "LITERAL_COL" || column.type === "CASE_COL" || column.type === "SCALAR_SUBQUERY_COL") {
6288
+ result.set(column.alias, syntheticSemantics("string"));
6289
+ } else if (column.type === "STRFUNC_COL") {
6290
+ result.set(column.alias, syntheticSemantics(NUMERIC_ORDER_FUNCTIONS.has(column.expr.func) ? "number" : "string"));
6291
+ }
6292
+ }
6293
+ return result;
6294
+ }
5838
6295
  function runFullScan(input) {
5839
6296
  const {
5840
6297
  stmt,
@@ -5842,12 +6299,17 @@ function runFullScan(input) {
5842
6299
  scalarCache,
5843
6300
  optionOrders,
5844
6301
  sortKinds,
6302
+ orderSemantics,
5845
6303
  fieldTypeResolver,
6304
+ fieldSemanticsResolver,
5846
6305
  havingFieldTypeResolver,
6306
+ havingFieldSemanticsResolver,
5847
6307
  aggregateSortKindResolver,
5848
6308
  appliedKlikes,
5849
6309
  sourceColumns
5850
6310
  } = input;
6311
+ const effectiveOrderSemantics = deriveOutputOrderSemantics(stmt.columns);
6312
+ for (const [key, value] of orderSemantics ?? []) effectiveOrderSemantics.set(key, value);
5851
6313
  let rows = [];
5852
6314
  const mainAlias = stmt.from.alias;
5853
6315
  const mainRecords = tables.get(mainAlias) ?? tables.get(null) ?? [];
@@ -5858,18 +6320,18 @@ function runFullScan(input) {
5858
6320
  const rightRows = rightRecords.map((r) => flatten(r, rightAlias));
5859
6321
  rows = applyJoin(rows, rightRows, join2);
5860
6322
  }
5861
- rows = applyFilter(rows, stmt.where, fieldTypeResolver, appliedKlikes);
6323
+ rows = applyFilter(rows, stmt.where, fieldTypeResolver, appliedKlikes, fieldSemanticsResolver);
5862
6324
  if (stmt.groupBy.length > 0 || hasAggregateColumns(stmt.columns)) {
5863
6325
  rows = applyGroupBy(rows, stmt.groupBy, stmt.columns, aggregateSortKindResolver);
5864
6326
  }
5865
- rows = applyHaving(rows, stmt.having, havingFieldTypeResolver);
5866
- rows = applyWindow(rows, stmt.columns, optionOrders, sortKinds);
6327
+ rows = applyHaving(rows, stmt.having, havingFieldTypeResolver, havingFieldSemanticsResolver);
6328
+ rows = applyWindow(rows, stmt.columns, optionOrders, sortKinds, effectiveOrderSemantics);
5867
6329
  if (stmt.distinct) {
5868
6330
  rows = applyDistinct(rows, stmt.columns);
5869
6331
  }
5870
- rows = applyOrderBy(rows, stmt.orderBy, optionOrders, sortKinds);
6332
+ rows = applyOrderBy(rows, stmt.orderBy, optionOrders, sortKinds, effectiveOrderSemantics);
5871
6333
  rows = applyLimit(rows, stmt.limit, stmt.offset);
5872
- return project(rows, stmt.columns, scalarCache, fieldTypeResolver, sourceColumns);
6334
+ return project(rows, stmt.columns, scalarCache, fieldTypeResolver, sourceColumns, fieldSemanticsResolver);
5873
6335
  }
5874
6336
 
5875
6337
  // src/converter/subtableAdapter.ts
@@ -6137,6 +6599,192 @@ function renderValidationValue(value) {
6137
6599
  return String(value);
6138
6600
  }
6139
6601
 
6602
+ // src/core/optimization/whereCapability.ts
6603
+ var RANGE_AND_EQUALITY = ["=", "!=", ">", "<", ">=", "<="];
6604
+ var EQUALITY_IN = ["=", "!=", "in", "not in"];
6605
+ var NATIVE_OPERATORS = /* @__PURE__ */ new Map([
6606
+ ["RECORD_NUMBER", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
6607
+ ["__ID__", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
6608
+ ["CREATOR", /* @__PURE__ */ new Set(["in", "not in"])],
6609
+ ["MODIFIER", /* @__PURE__ */ new Set(["in", "not in"])],
6610
+ ["CREATED_TIME", new Set(RANGE_AND_EQUALITY)],
6611
+ ["UPDATED_TIME", new Set(RANGE_AND_EQUALITY)],
6612
+ ["DATE", new Set(RANGE_AND_EQUALITY)],
6613
+ ["TIME", new Set(RANGE_AND_EQUALITY)],
6614
+ ["DATETIME", new Set(RANGE_AND_EQUALITY)],
6615
+ ["SINGLE_LINE_TEXT", /* @__PURE__ */ new Set(["=", "!=", "in", "not in", "like", "not like"])],
6616
+ ["LINK", /* @__PURE__ */ new Set(["=", "!=", "in", "not in", "like", "not like"])],
6617
+ ["NUMBER", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
6618
+ ["CALC", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
6619
+ ["MULTI_LINE_TEXT", /* @__PURE__ */ new Set(["like", "not like"])],
6620
+ ["RICH_TEXT", /* @__PURE__ */ new Set(["like", "not like"])],
6621
+ ["CHECK_BOX", /* @__PURE__ */ new Set(["in", "not in"])],
6622
+ ["RADIO_BUTTON", /* @__PURE__ */ new Set(["in", "not in"])],
6623
+ ["DROP_DOWN", /* @__PURE__ */ new Set(["in", "not in"])],
6624
+ ["MULTI_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
6625
+ ["FILE", /* @__PURE__ */ new Set(["like", "not like"])],
6626
+ ["USER_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
6627
+ ["ORGANIZATION_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
6628
+ ["GROUP_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
6629
+ ["STATUS", new Set(EQUALITY_IN)]
6630
+ ]);
6631
+ var LOCAL_SCALAR_TYPES = /* @__PURE__ */ new Set([
6632
+ "RECORD_NUMBER",
6633
+ "__ID__",
6634
+ "CREATOR",
6635
+ "MODIFIER",
6636
+ "CREATED_TIME",
6637
+ "UPDATED_TIME",
6638
+ "DATE",
6639
+ "TIME",
6640
+ "DATETIME",
6641
+ "SINGLE_LINE_TEXT",
6642
+ "LINK",
6643
+ "NUMBER",
6644
+ "CALC",
6645
+ "MULTI_LINE_TEXT",
6646
+ "RICH_TEXT",
6647
+ "RADIO_BUTTON",
6648
+ "DROP_DOWN",
6649
+ "STATUS",
6650
+ // 一時表・CTE・式列は kintone REST へは送らず、共有ローカル評価器で扱う。
6651
+ "KSQL_STRING",
6652
+ "KSQL_NUMBER",
6653
+ "KSQL_BOOLEAN"
6654
+ ]);
6655
+ var LOCAL_COLLECTION_TYPES = /* @__PURE__ */ new Set([
6656
+ "CHECK_BOX",
6657
+ "MULTI_SELECT",
6658
+ "FILE",
6659
+ "USER_SELECT",
6660
+ "ORGANIZATION_SELECT",
6661
+ "GROUP_SELECT",
6662
+ "STATUS_ASSIGNEE",
6663
+ "CATEGORY"
6664
+ ]);
6665
+ function nativeWhereOperatorsForType(fieldType) {
6666
+ return NATIVE_OPERATORS.get(fieldType) ?? /* @__PURE__ */ new Set();
6667
+ }
6668
+ function classifyWhereCapability(where, resolveField2) {
6669
+ if (where === null) {
6670
+ return { capability: "EXACT_PUSHDOWN", reasons: [{ code: "WHERE_EXACT" }] };
6671
+ }
6672
+ return classifyNode(where, resolveField2);
6673
+ }
6674
+ function classifyNode(where, resolveField2) {
6675
+ switch (where.type) {
6676
+ case "BINARY":
6677
+ return classifyBinary(where.op, where.left, where.right.type, resolveField2);
6678
+ case "NULL_CHECK":
6679
+ if (where.field.type !== "FIELD") return localExpression();
6680
+ return classifyLocalOnlyField(where.field, where.not ? "IS NOT NULL" : "IS NULL", resolveField2);
6681
+ case "EXISTS":
6682
+ return localExpression();
6683
+ case "GROUP":
6684
+ return classifyNode(where.expr, resolveField2);
6685
+ case "NOT": {
6686
+ const inner = classifyNode(where.expr, resolveField2);
6687
+ return inner.capability === "SUPERSET_PREFILTER" ? { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] } : inner;
6688
+ }
6689
+ case "LOGICAL": {
6690
+ const left = classifyNode(where.left, resolveField2);
6691
+ const right = classifyNode(where.right, resolveField2);
6692
+ return combineLogical(where.op, left, right);
6693
+ }
6694
+ }
6695
+ }
6696
+ function classifyBinary(op, left, rightType, resolveField2) {
6697
+ if (left.type !== "FIELD") return localExpression();
6698
+ const semantics = resolveField2(left);
6699
+ if (!semantics) {
6700
+ return unsupported("WHERE_FIELD_UNRESOLVED", left.field, void 0, normalizeOperator(op));
6701
+ }
6702
+ if (!hasLocalContract(semantics.fieldType, op)) {
6703
+ return unsupported("WHERE_OPERATOR_UNSUPPORTED", left.field, semantics.fieldType, normalizeOperator(op));
6704
+ }
6705
+ const nativeOp = normalizeOperator(op);
6706
+ const native = nativeWhereOperatorsForType(semantics.fieldType);
6707
+ const rightCanPush = rightType === "STRING" || rightType === "NUMBER" || rightType === "IN_LIST" || rightType === "KINTONE_FUNC";
6708
+ const structureAllows = !semantics.requiresCollectionOperators || nativeOp !== "=" && nativeOp !== "!=";
6709
+ const sqlLikeIsResidual = op === "LIKE" || op === "NOT_LIKE";
6710
+ if (rightCanPush && structureAllows && native.has(nativeOp) && !sqlLikeIsResidual) {
6711
+ return {
6712
+ capability: "EXACT_PUSHDOWN",
6713
+ reasons: [{
6714
+ code: "WHERE_EXACT",
6715
+ field: left.field,
6716
+ fieldType: semantics.fieldType,
6717
+ operator: nativeOp
6718
+ }]
6719
+ };
6720
+ }
6721
+ return {
6722
+ capability: "LOCAL_ONLY",
6723
+ reasons: [{
6724
+ code: "WHERE_RESIDUAL",
6725
+ field: left.field,
6726
+ fieldType: semantics.fieldType,
6727
+ operator: nativeOp
6728
+ }]
6729
+ };
6730
+ }
6731
+ function classifyLocalOnlyField(field, operator, resolveField2) {
6732
+ const semantics = resolveField2(field);
6733
+ if (!semantics) return unsupported("WHERE_FIELD_UNRESOLVED", field.field, void 0, operator);
6734
+ if (!LOCAL_SCALAR_TYPES.has(semantics.fieldType) && !LOCAL_COLLECTION_TYPES.has(semantics.fieldType)) {
6735
+ return unsupported("WHERE_OPERATOR_UNSUPPORTED", field.field, semantics.fieldType, operator);
6736
+ }
6737
+ return {
6738
+ capability: "LOCAL_ONLY",
6739
+ reasons: [{ code: "WHERE_RESIDUAL", field: field.field, fieldType: semantics.fieldType, operator }]
6740
+ };
6741
+ }
6742
+ function hasLocalContract(fieldType, op) {
6743
+ if (LOCAL_SCALAR_TYPES.has(fieldType)) return true;
6744
+ if (!LOCAL_COLLECTION_TYPES.has(fieldType)) return false;
6745
+ return op === "=" || op === "!=" || op === "<>" || op === "IN" || op === "NOT_IN" || op === "LIKE" || op === "NOT_LIKE" || op === "KLIKE" || op === "NOT_KLIKE";
6746
+ }
6747
+ function normalizeOperator(op) {
6748
+ switch (op) {
6749
+ case "<>":
6750
+ return "!=";
6751
+ case "IN":
6752
+ return "in";
6753
+ case "NOT_IN":
6754
+ return "not in";
6755
+ case "LIKE":
6756
+ case "KLIKE":
6757
+ return "like";
6758
+ case "NOT_LIKE":
6759
+ case "NOT_KLIKE":
6760
+ return "not like";
6761
+ default:
6762
+ return op;
6763
+ }
6764
+ }
6765
+ function combineLogical(op, left, right) {
6766
+ const reasons = [...left.reasons, ...right.reasons];
6767
+ if (left.capability === "UNSUPPORTED" || right.capability === "UNSUPPORTED") {
6768
+ return { capability: "UNSUPPORTED", reasons };
6769
+ }
6770
+ if (left.capability === "EXACT_PUSHDOWN" && right.capability === "EXACT_PUSHDOWN") {
6771
+ return { capability: "EXACT_PUSHDOWN", reasons };
6772
+ }
6773
+ if (op === "AND" && (left.capability === "EXACT_PUSHDOWN" || right.capability === "EXACT_PUSHDOWN" || left.capability === "SUPERSET_PREFILTER" || right.capability === "SUPERSET_PREFILTER")) {
6774
+ return {
6775
+ capability: "SUPERSET_PREFILTER",
6776
+ reasons: [{ code: "WHERE_SUPERSET_PREFILTER" }, ...reasons]
6777
+ };
6778
+ }
6779
+ return { capability: "LOCAL_ONLY", reasons };
6780
+ }
6781
+ function localExpression() {
6782
+ return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
6783
+ }
6784
+ function unsupported(code, field, fieldType, operator) {
6785
+ return { capability: "UNSUPPORTED", reasons: [{ code, field, fieldType, operator }] };
6786
+ }
6787
+
6140
6788
  // src/execute.ts
6141
6789
  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
6790
  var SearchAbortedError = class extends Error {
@@ -6147,8 +6795,20 @@ var SearchAbortedError = class extends Error {
6147
6795
  };
6148
6796
  var materializedMetaBySelectResult = /* @__PURE__ */ new WeakMap();
6149
6797
  var materializedMetaByValidationResult = /* @__PURE__ */ new WeakMap();
6798
+ var defaultCacheContextByClient = /* @__PURE__ */ new WeakMap();
6799
+ var nextDefaultCacheContextId = 1;
6800
+ function resolveCacheContext(client, explicit) {
6801
+ if (explicit) return explicit;
6802
+ let context = defaultCacheContextByClient.get(client);
6803
+ if (!context) {
6804
+ context = `client:${nextDefaultCacheContextId++}`;
6805
+ defaultCacheContextByClient.set(client, context);
6806
+ }
6807
+ return context;
6808
+ }
6150
6809
  async function execute(sql, client, options = {}) {
6151
6810
  const startedAt = Date.now();
6811
+ const cacheContext = resolveCacheContext(client, options.cacheContext);
6152
6812
  const stmt = parseSql(sql);
6153
6813
  const metrics = createEmptyMetrics();
6154
6814
  const countedClient = wrapClientWithMetrics(client, metrics);
@@ -6162,7 +6822,7 @@ async function execute(sql, client, options = {}) {
6162
6822
  stmt,
6163
6823
  guardedClient,
6164
6824
  options,
6165
- options.cacheContext ?? "default"
6825
+ cacheContext
6166
6826
  );
6167
6827
  metrics.elapsedMs = Date.now() - startedAt;
6168
6828
  return { ...attachSearchAbortWarning(result, collector), metrics };
@@ -6277,7 +6937,7 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
6277
6937
  case "DESCRIBE":
6278
6938
  return executeDescribe(stmt, client, cacheContext);
6279
6939
  case "EXPLAIN":
6280
- return executeExplain(stmt);
6940
+ return executeExplain(stmt, client, cacheContext, options.maxRecords ?? 1e4);
6281
6941
  // 一時テーブルはバッチスコープのため単文実行では拒否する(executeBatch を使う)
6282
6942
  case "CREATE_TEMP_TABLE":
6283
6943
  throw new Error("ArgumentError: CREATE TEMP TABLE requires a batch (temp tables are batch-scoped).");
@@ -6308,7 +6968,7 @@ function materializedColumnMetaEqual(left, right) {
6308
6968
  if (!left || !right || left.size !== right.size) return false;
6309
6969
  for (const [column, meta] of left) {
6310
6970
  const candidate = right.get(column);
6311
- if (!candidate || candidate.sortKind !== meta.sortKind || candidate.fieldType !== meta.fieldType) return false;
6971
+ if (!candidate || candidate.sortKind !== meta.sortKind || candidate.fieldType !== meta.fieldType || !fieldSemanticsEqual(candidate.semantics, meta.semantics)) return false;
6312
6972
  }
6313
6973
  return true;
6314
6974
  }
@@ -6339,7 +6999,7 @@ async function executeBatch(sql, client, options = {}) {
6339
6999
  const countedClient = wrapClientWithMetrics(client, metrics);
6340
7000
  const startedAt = Date.now();
6341
7001
  const deadline = options.timeoutMs != null ? startedAt + options.timeoutMs : null;
6342
- const cacheContext = options.cacheContext ?? "default";
7002
+ const cacheContext = resolveCacheContext(client, options.cacheContext);
6343
7003
  const tempTables = /* @__PURE__ */ new Map();
6344
7004
  const variables = /* @__PURE__ */ new Map();
6345
7005
  const results = [];
@@ -6433,7 +7093,10 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
6433
7093
  cacheContext,
6434
7094
  tempTables
6435
7095
  );
6436
- variables.set(stmt.name, { type: "string", value });
7096
+ const first = resolvedStmt2.expr.query.columns[0];
7097
+ 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");
7098
+ const numberValue = numeric ? Number(value) : Number.NaN;
7099
+ variables.set(stmt.name, numeric && Number.isFinite(numberValue) ? { type: "number", value: numberValue } : { type: "string", value });
6437
7100
  } catch (e) {
6438
7101
  if (e instanceof ScalarSubqueryError) {
6439
7102
  throw new Error(`ArgumentError: ${e.message}`);
@@ -6664,13 +7327,14 @@ var ScalarSubqueryError = class extends Error {
6664
7327
  };
6665
7328
  async function executeAssert(stmt, client, options, cacheContext, tempTables) {
6666
7329
  const left = await evalAssertOperand(stmt.left, client, options, cacheContext, tempTables);
7330
+ const semantics = stmt.left.type === "NUMBER" || stmt.left.type === "ARITH" ? syntheticSemantics("number") : syntheticSemantics("string");
6667
7331
  if (stmt.op === "BETWEEN") {
6668
7332
  if (stmt.low === null || stmt.high === null) {
6669
7333
  throw new Error("ArgumentError: malformed ASSERT statement.");
6670
7334
  }
6671
7335
  const low = await evalAssertOperand(stmt.low, client, options, cacheContext, tempTables);
6672
7336
  const high = await evalAssertOperand(stmt.high, client, options, cacheContext, tempTables);
6673
- if (!compareScalarValues(">=", left, low) || !compareScalarValues("<=", left, high)) {
7337
+ if (!compareScalarValues(">=", left, low, semantics) || !compareScalarValues("<=", left, high, semantics)) {
6674
7338
  throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
6675
7339
  }
6676
7340
  return { type: "ASSERT", condition: stmt.text };
@@ -6679,7 +7343,7 @@ async function executeAssert(stmt, client, options, cacheContext, tempTables) {
6679
7343
  throw new Error("ArgumentError: malformed ASSERT statement.");
6680
7344
  }
6681
7345
  const right = await evalAssertOperand(stmt.right, client, options, cacheContext, tempTables);
6682
- if (!compareScalarValues(stmt.op, left, right)) {
7346
+ if (!compareScalarValues(stmt.op, left, right, semantics)) {
6683
7347
  throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
6684
7348
  }
6685
7349
  return { type: "ASSERT", condition: stmt.text };
@@ -6752,6 +7416,149 @@ function evalAssertArith(node) {
6752
7416
  }
6753
7417
  throw new Error(`ArgumentError: unsupported operand in ASSERT expression: ${node.type}`);
6754
7418
  }
7419
+ async function buildWhereFieldSemanticsResolver(stmt, client, cacheContext, materializedTables, forcePhysicalMetadata = false) {
7420
+ const tables = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
7421
+ const physicalAppIds = forcePhysicalMetadata || whereNeedsFieldMetadata(stmt.where) ? [...new Set(tables.filter((table) => table.cteName === null).map((table) => table.appId))] : [];
7422
+ const infosByApp = new Map(
7423
+ await Promise.all(physicalAppIds.map(async (appId) => {
7424
+ const infos = await getFieldsCached(appId, client, cacheContext);
7425
+ return [appId, new Map(infos.map((info) => [info.code, info]))];
7426
+ }))
7427
+ );
7428
+ const orderedFields = /* @__PURE__ */ new Set();
7429
+ const collectOrderedFields = (node) => {
7430
+ if (Array.isArray(node)) {
7431
+ node.forEach(collectOrderedFields);
7432
+ return;
7433
+ }
7434
+ if (node === null || typeof node !== "object") return;
7435
+ const value = node;
7436
+ if (value["type"] === "SELECT") return;
7437
+ if (value["type"] === "BINARY" && [">", "<", ">=", "<="].includes(String(value["op"]))) {
7438
+ const left = value["left"];
7439
+ if (left?.["type"] === "FIELD" && typeof left["field"] === "string") {
7440
+ orderedFields.add(left["field"]);
7441
+ }
7442
+ }
7443
+ Object.values(value).forEach(collectOrderedFields);
7444
+ };
7445
+ collectOrderedFields(stmt.where);
7446
+ collectOrderedFields(stmt.having);
7447
+ for (const column of stmt.columns) {
7448
+ if (column.type === "CASE_COL") collectOrderedFields(column.expr);
7449
+ }
7450
+ const statusOrdersByApp = /* @__PURE__ */ new Map();
7451
+ await Promise.all([...infosByApp].map(async ([appId, infos]) => {
7452
+ const needsStatus = [...orderedFields].some((field) => infos.get(field)?.fieldType === "STATUS");
7453
+ if (!needsStatus) return;
7454
+ const order = await loadProcessStatusOrder(appId, client, cacheContext);
7455
+ if (order) statusOrdersByApp.set(appId, order);
7456
+ }));
7457
+ const fromPhysical = (table, field) => {
7458
+ if (field === "$id") return withFieldSemanticSource(
7459
+ resolveFieldSemantics({ fieldType: "__ID__" }),
7460
+ table.appId,
7461
+ "$id"
7462
+ );
7463
+ const info = infosByApp.get(table.appId)?.get(fieldCodeForTypeLookup(table, field));
7464
+ if (!info) return void 0;
7465
+ const base = info.semantics ?? resolveFieldSemantics(info);
7466
+ const semantics = info.fieldType === "STATUS" && statusOrdersByApp.has(table.appId) ? { ...base, optionOrder: statusOrdersByApp.get(table.appId) } : base;
7467
+ return withFieldSemanticSource(
7468
+ semantics,
7469
+ table.appId,
7470
+ info.code
7471
+ );
7472
+ };
7473
+ return (field) => {
7474
+ if (field.tableAlias !== null) {
7475
+ if (field.tableAlias === "_p" && stmt.from.subtableCode && stmt.from.cteName === null) {
7476
+ return fromPhysical(stmt.from, field.field);
7477
+ }
7478
+ const table = tables.find((candidate) => candidate.alias === field.tableAlias);
7479
+ if (!table) return void 0;
7480
+ if (table.cteName !== null) {
7481
+ return materializedTables?.get(table.cteName)?.columnMeta?.get(field.field)?.semantics ?? syntheticSemantics("string");
7482
+ }
7483
+ return fromPhysical(table, field.field);
7484
+ }
7485
+ if (stmt.joins.length === 0) {
7486
+ if (stmt.from.cteName !== null) {
7487
+ return materializedTables?.get(stmt.from.cteName)?.columnMeta?.get(field.field)?.semantics ?? syntheticSemantics("string");
7488
+ }
7489
+ return fromPhysical(stmt.from, field.field);
7490
+ }
7491
+ const matches = tables.flatMap((table) => {
7492
+ const semantics = table.cteName !== null ? materializedTables?.get(table.cteName)?.columnMeta?.get(field.field)?.semantics : fromPhysical(table, field.field);
7493
+ return semantics ? [semantics] : [];
7494
+ });
7495
+ if (matches.length === 1) return matches[0];
7496
+ return matches.length > 1 ? syntheticSemantics("string") : void 0;
7497
+ };
7498
+ }
7499
+ function selectCaseConditionsNeedFieldMetadata(stmt) {
7500
+ return stmt.columns.some((column) => column.type === "CASE_COL" && column.expr.branches.some((branch) => whereNeedsFieldMetadata(branch.condition)));
7501
+ }
7502
+ function buildHavingFieldSemanticsResolver(stmt, rowResolver) {
7503
+ const aliases = /* @__PURE__ */ new Map();
7504
+ for (const column of stmt.columns) {
7505
+ if (!("alias" in column) || !column.alias) continue;
7506
+ let semantics;
7507
+ if (column.type === "FIELD") semantics = rowResolver(aggregateFieldRef(column.field));
7508
+ else if (column.type === "ARITH_COL" || column.type === "ARITH_AGG_COL" || column.type === "WINDOW_COL") {
7509
+ semantics = syntheticSemantics("number");
7510
+ } else if (column.type === "AGGREGATE") {
7511
+ if (column.func === "MIN" || column.func === "MAX") {
7512
+ semantics = column.arg.type === "FIELD_REF" ? rowResolver(aggregateFieldRef(column.arg.field)) : syntheticSemantics("number");
7513
+ } else {
7514
+ semantics = column.func === "GROUP_CONCAT" ? syntheticSemantics("string") : syntheticSemantics("number");
7515
+ }
7516
+ } else if (column.type === "STRFUNC_COL") {
7517
+ semantics = stringFunctionColumnMeta(column.expr).semantics;
7518
+ } else if (column.type === "LITERAL_COL" || column.type === "SCALAR_SUBQUERY_COL" || column.type === "CASE_COL") {
7519
+ semantics = syntheticSemantics("string");
7520
+ }
7521
+ if (semantics) aliases.set(column.alias, semantics);
7522
+ }
7523
+ return (field) => field.tableAlias === null && aliases.has(field.field) ? aliases.get(field.field) : rowResolver(field);
7524
+ }
7525
+ async function resolveSelectWhereCapability(stmt, client, cacheContext, materializedTables) {
7526
+ if (stmt.where === null) return classifyWhereCapability(null, () => void 0);
7527
+ const resolver = await buildWhereFieldSemanticsResolver(stmt, client, cacheContext, materializedTables);
7528
+ return classifyWhereCapability(stmt.where, resolver);
7529
+ }
7530
+ function formatWhereCapabilityFailure(result) {
7531
+ const reason = result.reasons.find(
7532
+ (candidate) => candidate.code === "WHERE_FIELD_UNRESOLVED" || candidate.code === "WHERE_OPERATOR_UNSUPPORTED"
7533
+ ) ?? result.reasons[0];
7534
+ const details = [
7535
+ reason?.field ? `field=${reason.field}` : null,
7536
+ reason?.fieldType ? `type=${reason.fieldType}` : null,
7537
+ reason?.operator ? `operator=${reason.operator}` : null,
7538
+ reason?.code ? `reason=${reason.code}` : null
7539
+ ].filter((value) => value !== null).join(", ");
7540
+ return details || "reason=WHERE_UNSUPPORTED";
7541
+ }
7542
+ function hasCanonicalOrder(stmt) {
7543
+ return stmt.orderBy.length > 0 || stmt.columns.some(
7544
+ (column) => column.type === "WINDOW_COL" && column.orderBy.length > 0
7545
+ );
7546
+ }
7547
+ async function assertDmlWhereCapability(stmt, client, cacheContext) {
7548
+ if (stmt.subtableCode || stmt.type === "UPDATE" && stmt.from != null) return;
7549
+ const fields = whereNeedsFieldMetadata(stmt.where) ? await getFieldsCached(stmt.appId, client, cacheContext) : [];
7550
+ const byCode = new Map(fields.map((field) => [field.code, field]));
7551
+ const result = classifyWhereCapability(stmt.where, (field) => {
7552
+ if (field.field === "$id") return resolveFieldSemantics({ fieldType: "__ID__" });
7553
+ const info = byCode.get(field.field);
7554
+ return info?.semantics ?? (info ? resolveFieldSemantics(info) : void 0);
7555
+ });
7556
+ if (result.capability !== "EXACT_PUSHDOWN") {
7557
+ throw new DmlConvertError(
7558
+ `WHERE predicate cannot be represented by kintone REST (${formatWhereCapabilityFailure(result)})`
7559
+ );
7560
+ }
7561
+ }
6755
7562
  async function executeSelect(stmt, client, options, cacheContext, cteCache, captureColumnMeta = false) {
6756
7563
  let result;
6757
7564
  if (isNoFromSelect(stmt)) {
@@ -6762,12 +7569,51 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
6762
7569
  return result;
6763
7570
  }
6764
7571
  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);
7572
+ const whereCapability = await resolveSelectWhereCapability(stmt, client, cacheContext, cteCache);
7573
+ if (whereCapability.capability === "UNSUPPORTED") {
7574
+ throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(whereCapability)}).`);
7575
+ }
7576
+ const staticMode = resolveSelectMode(stmt);
7577
+ const mode = whereCapability.capability === "EXACT_PUSHDOWN" ? staticMode : "FULL_SCAN";
7578
+ const orderMeta = await buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
7579
+ const orderPlan = hasCanonicalOrder(stmt) ? (stmt.orderMode === "KINTONE_NATIVE" ? planKorderNative : planCanonicalOrder)({
7580
+ stmt,
7581
+ staticMode: mode,
7582
+ whereCapability: whereCapability.capability,
7583
+ orderSemantics: orderMeta.semantics,
7584
+ maxRecords: options.maxRecords ?? 1e4,
7585
+ hasKlike: whereHasKlike(stmt.where)
7586
+ }) : null;
7587
+ await validateSelectFieldCodes(
7588
+ stmt,
7589
+ orderPlan?.kind === "CANONICAL_LOCAL" ? "FULL_SCAN" : mode,
7590
+ client,
7591
+ cacheContext
7592
+ );
7593
+ const completeInputRequired = orderPlan?.kind === "CANONICAL_REST_TOP_N" || orderPlan?.kind === "KORDER_NATIVE" ? requiresCompleteInput({ ...stmt, orderBy: [] }) : requiresCompleteInput(stmt);
7594
+ const truncateWasDisabled = completeInputRequired && options.onLimitReached === "truncate";
7595
+ const effectiveOptions = truncateWasDisabled ? { ...options, onLimitReached: "error" } : options;
7596
+ try {
7597
+ if (mode === "SIMPLE") {
7598
+ result = await executeSimpleSelect(stmt, client, effectiveOptions, cacheContext, orderPlan, orderMeta);
7599
+ } else {
7600
+ result = await executeFullScanSelect(
7601
+ stmt,
7602
+ client,
7603
+ effectiveOptions,
7604
+ cacheContext,
7605
+ cteCache,
7606
+ whereCapability.capability === "EXACT_PUSHDOWN",
7607
+ orderMeta
7608
+ );
7609
+ }
7610
+ } catch (error) {
7611
+ if (completeInputRequired && error instanceof FetchAllLimitError) {
7612
+ throw new FetchAllLimitError(
7613
+ "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
7614
+ );
7615
+ }
7616
+ throw error;
6771
7617
  }
6772
7618
  if (captureColumnMeta) {
6773
7619
  materializedMetaBySelectResult.set(result, await inferSelectColumnMeta(stmt, result.columns, client, cacheContext, cteCache));
@@ -6828,19 +7674,30 @@ function executeNoFromSelect(stmt) {
6828
7674
  const rows = applyLimit(projected, stmt.limit, stmt.offset);
6829
7675
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [] };
6830
7676
  }
6831
- async function executeSimpleSelect(stmt, client, options, cacheContext) {
6832
- const params = selectToKintoneParams(stmt);
7677
+ async function executeSimpleSelect(stmt, client, options, cacheContext, orderPlan, orderMeta) {
7678
+ const restStmt = orderPlan?.kind === "CANONICAL_REST_TOP_N" ? withCanonicalRestTie(stmt) : stmt;
7679
+ const params = selectToKintoneParams(restStmt);
7680
+ const fetchFields = orderPlan?.kind === "CANONICAL_LOCAL" ? selectToFetchAllFields(stmt, stmt.from) : params.fields;
6833
7681
  const typedInFieldTypes = await loadTypedInFieldTypes(stmt, client, cacheContext);
6834
7682
  const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
7683
+ const projectionSemanticsResolver = stmt.columns.some((column) => column.type === "CASE_COL") ? await buildWhereFieldSemanticsResolver(
7684
+ stmt,
7685
+ client,
7686
+ cacheContext,
7687
+ void 0,
7688
+ selectCaseConditionsNeedFieldMetadata(stmt)
7689
+ ) : void 0;
6835
7690
  const maxRecords = options.maxRecords ?? 1e4;
6836
7691
  const warnings = /* @__PURE__ */ new Set();
6837
7692
  const onLimit = options.onLimitReached ?? "error";
6838
7693
  const parallel = options.fetchParallel ?? 1;
6839
- const useSingleGet = stmt.limit !== null && stmt.limit <= 500;
7694
+ const useRestWindow = stmt.orderBy.length > 0 ? orderPlan?.kind === "CANONICAL_REST_TOP_N" || orderPlan?.kind === "KORDER_NATIVE" : stmt.limit !== null && stmt.limit <= 500;
6840
7695
  const needed = stmt.limit === null ? null : (stmt.offset ?? 0) + stmt.limit;
6841
7696
  const stopAfter = stmt.orderBy.length === 0 && needed !== null && needed <= maxRecords && !whereHasKlike(stmt.where) ? needed : void 0;
6842
7697
  let records;
6843
- if (useSingleGet) {
7698
+ if (orderPlan?.kind === "KORDER_NATIVE" && stmt.limit === 0) {
7699
+ records = [];
7700
+ } else if (useRestWindow) {
6844
7701
  const res = await client.getRecords({
6845
7702
  app: params.app,
6846
7703
  query: params.query,
@@ -6853,7 +7710,7 @@ async function executeSimpleSelect(stmt, client, options, cacheContext) {
6853
7710
  client.getRecords,
6854
7711
  params.app,
6855
7712
  baseQuery,
6856
- params.fields,
7713
+ fetchFields,
6857
7714
  {
6858
7715
  parallel,
6859
7716
  maxRecords,
@@ -6866,16 +7723,23 @@ async function executeSimpleSelect(stmt, client, options, cacheContext) {
6866
7723
  );
6867
7724
  }
6868
7725
  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);
7726
+ if (!useRestWindow) {
7727
+ rows = applyOrderBy(
7728
+ rows,
7729
+ stmt.orderBy,
7730
+ orderMeta.optionOrders,
7731
+ orderMeta.sortKinds,
7732
+ orderMeta.semantics
7733
+ );
6872
7734
  rows = applyLimit(rows, stmt.limit, stmt.offset);
6873
7735
  }
6874
7736
  const { rows: projected, columns } = project(
6875
7737
  rows,
6876
7738
  stmt.columns,
6877
7739
  void 0,
6878
- fieldTypeResolvers.row
7740
+ fieldTypeResolvers.row,
7741
+ void 0,
7742
+ projectionSemanticsResolver
6879
7743
  );
6880
7744
  return { type: "SELECT", rows: projected, columns, rowCount: projected.length, warnings: [...warnings] };
6881
7745
  }
@@ -6948,8 +7812,8 @@ async function loadTypedPushdownMeta(stmt, client, cacheContext) {
6948
7812
  const statusFields = collectCandidateFieldCodes(candidates).filter((fieldCode) => fieldTypes.get(fieldCode) === "STATUS");
6949
7813
  if (statusFields.length > 0) {
6950
7814
  const process2 = await getProcessStatusesCached(appId, client, cacheContext);
6951
- if (process2.enable && process2.states.length > 0) {
6952
- const states = new Set(process2.states);
7815
+ if (process2.enable && process2.states && process2.states.length > 0) {
7816
+ const states = new Set(process2.states.map((state) => state.name));
6953
7817
  for (const fieldCode of statusFields) fieldOptions.set(fieldCode, states);
6954
7818
  }
6955
7819
  }
@@ -7127,7 +7991,18 @@ async function loadAggregateSortKindResolver(stmt, client, cacheContext, materia
7127
7991
  return [appId, new Map(infos.map((info) => [info.code, info]))];
7128
7992
  }))
7129
7993
  );
7994
+ const statusOrdersByApp = /* @__PURE__ */ new Map();
7995
+ const aggregateFieldNames = new Set(refs.map((ref) => ref.field));
7996
+ await Promise.all([...fieldInfosByApp].map(async ([appId, infos]) => {
7997
+ if (![...aggregateFieldNames].some((field) => infos.get(field)?.fieldType === "STATUS")) return;
7998
+ const order = await loadProcessStatusOrder(appId, client, cacheContext);
7999
+ if (order) statusOrdersByApp.set(appId, order);
8000
+ }));
7130
8001
  const tables = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
8002
+ const semanticsForInfo = (info, appId) => {
8003
+ const base = info.semantics ?? resolveFieldSemantics(info);
8004
+ return info.fieldType === "STATUS" && statusOrdersByApp.has(appId) ? { ...base, optionOrder: statusOrdersByApp.get(appId) } : base;
8005
+ };
7131
8006
  return (ref) => {
7132
8007
  let info;
7133
8008
  if (ref.tableAlias !== null) {
@@ -7137,40 +8012,130 @@ async function loadAggregateSortKindResolver(stmt, client, cacheContext, materia
7137
8012
  const table = tables.find((candidate) => candidate.alias === ref.tableAlias);
7138
8013
  if (!table) return void 0;
7139
8014
  if (table.cteName !== null) {
7140
- return materializedTables?.get(table.cteName)?.columnMeta?.get(ref.field)?.sortKind;
8015
+ return materializedTables?.get(table.cteName)?.columnMeta?.get(ref.field)?.semantics ?? syntheticSemantics("string");
7141
8016
  }
7142
8017
  info = fieldInfosByApp.get(table.appId)?.get(fieldCodeForTypeLookup(table, ref.field));
7143
8018
  }
7144
8019
  } else if (stmt.joins.length === 0) {
7145
8020
  if (stmt.from.cteName !== null) {
7146
- return materializedTables?.get(stmt.from.cteName)?.columnMeta?.get(ref.field)?.sortKind;
8021
+ return materializedTables?.get(stmt.from.cteName)?.columnMeta?.get(ref.field)?.semantics ?? syntheticSemantics("string");
7147
8022
  }
7148
8023
  info = fieldInfosByApp.get(stmt.from.appId)?.get(fieldCodeForTypeLookup(stmt.from, ref.field));
7149
8024
  } else {
7150
8025
  const matches = tables.flatMap((table) => {
7151
8026
  if (table.cteName !== null) {
7152
8027
  const materialized = materializedTables?.get(table.cteName);
7153
- return materialized?.columns.includes(ref.field) ? [materialized.columnMeta?.get(ref.field)?.sortKind] : [];
8028
+ return materialized?.columns.includes(ref.field) ? [materialized.columnMeta?.get(ref.field)?.semantics ?? syntheticSemantics("string")] : [];
7154
8029
  }
7155
8030
  const candidate = fieldInfosByApp.get(table.appId)?.get(fieldCodeForTypeLookup(table, ref.field));
7156
- return candidate ? [aggregateSortKind(candidate)] : [];
8031
+ return candidate ? [semanticsForInfo(candidate, table.appId)] : [];
7157
8032
  });
7158
8033
  if (matches.length !== 1) return void 0;
7159
8034
  return matches[0];
7160
8035
  }
7161
- return info ? aggregateSortKind(info) : void 0;
8036
+ if (!info) return void 0;
8037
+ const sourceTable = ref.tableAlias !== null ? tables.find((table) => table.alias === ref.tableAlias) : stmt.joins.length === 0 ? stmt.from : void 0;
8038
+ return semanticsForInfo(info, sourceTable?.appId ?? stmt.from.appId);
7162
8039
  };
7163
8040
  }
7164
8041
  function fieldCodeForTypeLookup(table, field) {
7165
8042
  if (table.subtableCode && field.startsWith("_p.")) return field.slice(3);
7166
8043
  return field;
7167
8044
  }
7168
- function materializedMetaFromFieldInfo(info) {
7169
- return { sortKind: aggregateSortKind(info), fieldType: info.fieldType };
8045
+ function materializedMetaFromFieldInfo(info, sourceAppId) {
8046
+ const semantics = info.semantics ?? resolveFieldSemantics(info);
8047
+ return {
8048
+ sortKind: aggregateSortKind(info),
8049
+ fieldType: info.fieldType,
8050
+ semantics: sourceAppId === void 0 ? semantics : withFieldSemanticSource(semantics, sourceAppId, info.code)
8051
+ };
8052
+ }
8053
+ function withCanonicalRestTie(stmt) {
8054
+ const hasId = stmt.orderBy.some(
8055
+ (item) => item.key.type === "FIELD_NAME" && item.key.name === "$id"
8056
+ );
8057
+ return hasId ? stmt : {
8058
+ ...stmt,
8059
+ orderBy: [...stmt.orderBy, { key: { type: "FIELD_NAME", name: "$id" }, direction: "ASC" }]
8060
+ };
8061
+ }
8062
+ function syntheticColumnMeta(compareMode) {
8063
+ return { sortKind: compareMode, semantics: syntheticSemantics(compareMode) };
8064
+ }
8065
+ function unknownStringColumnMeta() {
8066
+ return { semantics: syntheticSemantics("string", "KSQL_UNKNOWN") };
8067
+ }
8068
+ function unsupportedColumnMeta(fieldType = "KSQL_ARRAY") {
8069
+ return {
8070
+ semantics: { fieldType, compareMode: "unsupported", inSubtable: false, requiresCollectionOperators: false }
8071
+ };
8072
+ }
8073
+ function systemColumnMeta(field) {
8074
+ if (field === "$id" || field === "_rid" || field === "_pid") {
8075
+ return {
8076
+ sortKind: "number",
8077
+ fieldType: "__ID__",
8078
+ semantics: resolveFieldSemantics({ fieldType: "__ID__" })
8079
+ };
8080
+ }
8081
+ if (field === "$revision") return syntheticColumnMeta("number");
8082
+ return void 0;
8083
+ }
8084
+ var NUMBER_RETURNING_STRING_FUNCTIONS = /* @__PURE__ */ new Set([
8085
+ "LENGTH",
8086
+ "INSTR",
8087
+ "ROUND",
8088
+ "FLOOR",
8089
+ "CEIL",
8090
+ "TRUNCATE",
8091
+ "YEAR",
8092
+ "MONTH",
8093
+ "DAY",
8094
+ "DATEDIFF",
8095
+ "ABS",
8096
+ "MOD",
8097
+ "POWER",
8098
+ "SQRT"
8099
+ ]);
8100
+ function stringFunctionColumnMeta(expr) {
8101
+ if (expr.func === "CAST") {
8102
+ const target = expr.args[1];
8103
+ return target?.type === "STRING" && target.value === "NUMBER" ? syntheticColumnMeta("number") : syntheticColumnMeta("string");
8104
+ }
8105
+ return NUMBER_RETURNING_STRING_FUNCTIONS.has(expr.func) ? syntheticColumnMeta("number") : syntheticColumnMeta("string");
8106
+ }
8107
+ function caseResultColumnMeta(result, resolveField2) {
8108
+ if (result.type === "STRING") return syntheticColumnMeta("string");
8109
+ if (result.type === "ARRAY") return unsupportedColumnMeta();
8110
+ if (result.type === "NUMBER" || result.type === "ARITH") return syntheticColumnMeta("number");
8111
+ if (result.type === "STRING_FUNC") return stringFunctionColumnMeta(result);
8112
+ const source = resolveField2(aggregateFieldRef(result.field));
8113
+ return source ?? unknownStringColumnMeta();
8114
+ }
8115
+ function mergeExpressionColumnMeta(candidates) {
8116
+ if (candidates.length === 0) return unknownStringColumnMeta();
8117
+ const first = candidates[0];
8118
+ const withoutSource = (semantics) => {
8119
+ if (!semantics) return void 0;
8120
+ const { source: _source, ...rest } = semantics;
8121
+ return rest;
8122
+ };
8123
+ if (candidates.every(
8124
+ (candidate) => candidate.sortKind === first.sortKind && candidate.fieldType === first.fieldType && fieldSemanticsEqual(withoutSource(candidate.semantics), withoutSource(first.semantics))
8125
+ )) {
8126
+ const sameSource = candidates.every(
8127
+ (candidate) => fieldSemanticsEqual(candidate.semantics, first.semantics)
8128
+ );
8129
+ return sameSource ? first : { ...first, semantics: withoutSource(first.semantics) };
8130
+ }
8131
+ if (candidates.some((candidate) => candidate.semantics?.compareMode === "unsupported")) {
8132
+ return unsupportedColumnMeta("KSQL_MIXED_UNSUPPORTED");
8133
+ }
8134
+ return unknownStringColumnMeta();
7170
8135
  }
7171
8136
  function selectNeedsSourceColumnMeta(stmt) {
7172
8137
  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"
8138
+ (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
8139
  );
7175
8140
  }
7176
8141
  async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext, materializedTables) {
@@ -7187,18 +8152,18 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
7187
8152
  if (ref.tableAlias !== null) {
7188
8153
  if (ref.tableAlias === "_p" && stmt.from.subtableCode && stmt.from.cteName === null) {
7189
8154
  const info2 = physicalInfos.get(stmt.from.appId)?.get(ref.field);
7190
- return info2 ? materializedMetaFromFieldInfo(info2) : void 0;
8155
+ return info2 ? materializedMetaFromFieldInfo(info2, stmt.from.appId) : void 0;
7191
8156
  }
7192
8157
  const table = tables.find((candidate) => candidate.alias === ref.tableAlias);
7193
8158
  if (!table) return void 0;
7194
8159
  if (table.cteName !== null) return materializedTables?.get(table.cteName)?.columnMeta?.get(ref.field);
7195
8160
  const info = physicalInfos.get(table.appId)?.get(fieldCodeForTypeLookup(table, ref.field));
7196
- return info ? materializedMetaFromFieldInfo(info) : void 0;
8161
+ return info ? materializedMetaFromFieldInfo(info, table.appId) : systemColumnMeta(ref.field);
7197
8162
  }
7198
8163
  if (stmt.joins.length === 0) {
7199
8164
  if (stmt.from.cteName !== null) return materializedTables?.get(stmt.from.cteName)?.columnMeta?.get(ref.field);
7200
8165
  const info = physicalInfos.get(stmt.from.appId)?.get(fieldCodeForTypeLookup(stmt.from, ref.field));
7201
- return info ? materializedMetaFromFieldInfo(info) : void 0;
8166
+ return info ? materializedMetaFromFieldInfo(info, stmt.from.appId) : systemColumnMeta(ref.field);
7202
8167
  }
7203
8168
  const matches = tables.flatMap((table) => {
7204
8169
  if (table.cteName !== null) {
@@ -7207,7 +8172,8 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
7207
8172
  return [materialized.columnMeta?.get(ref.field)];
7208
8173
  }
7209
8174
  const info = physicalInfos.get(table.appId)?.get(fieldCodeForTypeLookup(table, ref.field));
7210
- return info ? [materializedMetaFromFieldInfo(info)] : [];
8175
+ const meta = info ? materializedMetaFromFieldInfo(info, table.appId) : systemColumnMeta(ref.field);
8176
+ return meta ? [meta] : [];
7211
8177
  });
7212
8178
  return matches.length === 1 ? matches[0] : void 0;
7213
8179
  };
@@ -7237,19 +8203,27 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
7237
8203
  meta = resolveField2(aggregateFieldRef(column.field));
7238
8204
  } else if (column.type === "AGGREGATE") {
7239
8205
  if (column.func === "GROUP_CONCAT") {
7240
- meta = { sortKind: "string" };
8206
+ meta = syntheticColumnMeta("string");
7241
8207
  } else if (column.func === "COUNT" || column.func === "SUM" || column.func === "AVG") {
7242
- meta = { sortKind: "number" };
8208
+ meta = syntheticColumnMeta("number");
7243
8209
  } else if ((column.func === "MIN" || column.func === "MAX") && column.arg.type === "FIELD_REF") {
7244
8210
  const source = resolveField2(aggregateFieldRef(column.arg.field));
7245
- if (source?.sortKind) meta = { sortKind: source.sortKind };
8211
+ if (source) meta = source;
7246
8212
  }
7247
8213
  } else if (column.type === "ARITH_AGG_COL" || column.type === "ARITH_COL") {
7248
- meta = { sortKind: "number" };
8214
+ meta = syntheticColumnMeta("number");
7249
8215
  } else if (column.type === "LITERAL_COL") {
7250
- meta = { sortKind: "string" };
8216
+ meta = syntheticColumnMeta("string");
8217
+ } else if (column.type === "STRFUNC_COL") {
8218
+ meta = stringFunctionColumnMeta(column.expr);
7251
8219
  } else if (column.type === "WINDOW_COL") {
7252
- meta = { sortKind: "number" };
8220
+ meta = syntheticColumnMeta("number");
8221
+ } else if (column.type === "CASE_COL") {
8222
+ const results = column.expr.branches.map((branch) => caseResultColumnMeta(branch.result, resolveField2));
8223
+ if (column.expr.elseResult) results.push(caseResultColumnMeta(column.expr.elseResult, resolveField2));
8224
+ meta = mergeExpressionColumnMeta(results);
8225
+ } else if (column.type === "SCALAR_SUBQUERY_COL") {
8226
+ meta = unknownStringColumnMeta();
7253
8227
  }
7254
8228
  if (meta) inferred.set(output, meta);
7255
8229
  });
@@ -7263,7 +8237,8 @@ function mergeUnionColumnMeta(left, right) {
7263
8237
  const a = leftMeta?.get(column);
7264
8238
  const rightColumn = right.columns[index];
7265
8239
  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);
8240
+ if (a && b) merged.set(column, mergeExpressionColumnMeta([a, b]));
8241
+ else if (a || b) merged.set(column, unknownStringColumnMeta());
7267
8242
  });
7268
8243
  return merged;
7269
8244
  }
@@ -7300,7 +8275,7 @@ function buildSelectFieldTypeResolvers(stmt, fieldTypesByApp) {
7300
8275
  };
7301
8276
  return { row, having };
7302
8277
  }
7303
- async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache) {
8278
+ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache, allowOriginalWherePushdown = true, preloadedOrderMeta) {
7304
8279
  const maxRecords = options.maxRecords ?? 1e4;
7305
8280
  const warnings = /* @__PURE__ */ new Set();
7306
8281
  const parallel = options.fetchParallel ?? 1;
@@ -7314,6 +8289,14 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
7314
8289
  loadAggregateSortKindResolver(stmt, client, cacheContext, cteCache)
7315
8290
  ]);
7316
8291
  const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
8292
+ const fieldSemanticsResolver = await buildWhereFieldSemanticsResolver(
8293
+ stmt,
8294
+ client,
8295
+ cacheContext,
8296
+ cteCache,
8297
+ whereNeedsFieldMetadata(stmt.having) || selectCaseConditionsNeedFieldMetadata(stmt)
8298
+ );
8299
+ const havingFieldSemanticsResolver = buildHavingFieldSemanticsResolver(stmt, fieldSemanticsResolver);
7317
8300
  const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
7318
8301
  validateKlikePushdownPlan(pushdownPlan);
7319
8302
  const mainPushDown = pushdownPlan.mainCondition;
@@ -7327,7 +8310,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
7327
8310
  true,
7328
8311
  options.onLimitReached ?? "error",
7329
8312
  warnings,
7330
- mainPushDown
8313
+ mainPushDown,
8314
+ allowOriginalWherePushdown
7331
8315
  );
7332
8316
  const parallelJoins = [];
7333
8317
  const onOptJoins = [];
@@ -7353,7 +8337,7 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
7353
8337
  }
7354
8338
  }
7355
8339
  const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
7356
- const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
8340
+ const orderByMetaPromise = preloadedOrderMeta ? Promise.resolve(preloadedOrderMeta) : buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
7357
8341
  scalarCachePromise.catch(() => {
7358
8342
  });
7359
8343
  orderByMetaPromise.catch(() => {
@@ -7390,15 +8374,18 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
7390
8374
  tables.set(join2.table.alias, joinRecords);
7391
8375
  }));
7392
8376
  const scalarCache = await scalarCachePromise;
7393
- const { optionOrders, sortKinds } = await orderByMetaPromise;
8377
+ const { optionOrders, sortKinds, semantics } = await orderByMetaPromise;
7394
8378
  const { rows, columns } = runFullScan({
7395
8379
  tables,
7396
8380
  stmt,
7397
8381
  scalarCache,
7398
8382
  optionOrders,
7399
8383
  sortKinds,
8384
+ orderSemantics: semantics,
7400
8385
  fieldTypeResolver: fieldTypeResolvers.row,
8386
+ fieldSemanticsResolver,
7401
8387
  havingFieldTypeResolver: fieldTypeResolvers.having,
8388
+ havingFieldSemanticsResolver,
7402
8389
  aggregateSortKindResolver,
7403
8390
  appliedKlikes: pushdownPlan.appliedKlikes
7404
8391
  });
@@ -7495,16 +8482,39 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
7495
8482
  resolveSubqueries(stmt.having, client, options, cacheContext, cteCache),
7496
8483
  resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache)
7497
8484
  ]);
8485
+ const whereCapability = await resolveSelectWhereCapability(stmt, client, cacheContext, cteCache);
8486
+ if (whereCapability.capability === "UNSUPPORTED") {
8487
+ throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(whereCapability)}).`);
8488
+ }
8489
+ const orderMeta = await buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
8490
+ if (hasCanonicalOrder(stmt)) {
8491
+ (stmt.orderMode === "KINTONE_NATIVE" ? planKorderNative : planCanonicalOrder)({
8492
+ stmt,
8493
+ staticMode: "FULL_SCAN",
8494
+ whereCapability: whereCapability.capability,
8495
+ orderSemantics: orderMeta.semantics,
8496
+ maxRecords,
8497
+ hasKlike: whereHasKlike(stmt.where)
8498
+ });
8499
+ }
7498
8500
  const [pushdownMeta, typedInFieldTypes, aggregateSortKindResolver] = await Promise.all([
7499
8501
  loadTypedPushdownMeta(stmt, client, cacheContext),
7500
8502
  loadTypedInFieldTypes(stmt, client, cacheContext),
7501
8503
  loadAggregateSortKindResolver(stmt, client, cacheContext, cteCache)
7502
8504
  ]);
7503
8505
  const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
8506
+ const fieldSemanticsResolver = await buildWhereFieldSemanticsResolver(
8507
+ stmt,
8508
+ client,
8509
+ cacheContext,
8510
+ cteCache,
8511
+ whereNeedsFieldMetadata(stmt.having) || selectCaseConditionsNeedFieldMetadata(stmt)
8512
+ );
8513
+ const havingFieldSemanticsResolver = buildHavingFieldSemanticsResolver(stmt, fieldSemanticsResolver);
7504
8514
  const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
7505
8515
  validateKlikePushdownPlan(pushdownPlan);
7506
8516
  const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
7507
- const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
8517
+ const orderByMetaPromise = Promise.resolve(orderMeta);
7508
8518
  scalarCachePromise.catch(() => {
7509
8519
  });
7510
8520
  orderByMetaPromise.catch(() => {
@@ -7523,7 +8533,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
7523
8533
  true,
7524
8534
  options.onLimitReached ?? "error",
7525
8535
  warnings,
7526
- pushdownPlan.mainCondition
8536
+ pushdownPlan.mainCondition,
8537
+ whereCapability.capability === "EXACT_PUSHDOWN"
7527
8538
  );
7528
8539
  tables.set(stmt.from.alias, mainRecords);
7529
8540
  }
@@ -7560,7 +8571,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
7560
8571
  });
7561
8572
  await Promise.all(joinFetches);
7562
8573
  const scalarCache = await scalarCachePromise;
7563
- const { optionOrders, sortKinds } = await orderByMetaPromise;
8574
+ const { optionOrders, sortKinds, semantics } = await orderByMetaPromise;
7564
8575
  const sourceColumns = stmt.joins.length === 0 && stmt.from.cteName != null ? cteCache.get(stmt.from.cteName)?.columns : void 0;
7565
8576
  const { rows, columns } = runFullScan({
7566
8577
  tables,
@@ -7568,8 +8579,11 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
7568
8579
  scalarCache,
7569
8580
  optionOrders,
7570
8581
  sortKinds,
8582
+ orderSemantics: semantics,
7571
8583
  fieldTypeResolver: fieldTypeResolvers.row,
8584
+ fieldSemanticsResolver,
7572
8585
  havingFieldTypeResolver: fieldTypeResolvers.having,
8586
+ havingFieldSemanticsResolver,
7573
8587
  aggregateSortKindResolver,
7574
8588
  appliedKlikes: pushdownPlan.appliedKlikes,
7575
8589
  sourceColumns
@@ -7581,13 +8595,13 @@ function processRowToKintoneRecord(row) {
7581
8595
  Object.entries(row).map(([k, v]) => [k, { value: v ?? "" }])
7582
8596
  );
7583
8597
  }
7584
- async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, parallel, isMainTable, onLimit, warnings, pushDownCond = null) {
8598
+ async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, parallel, isMainTable, onLimit, warnings, pushDownCond = null, allowOriginalWherePushdown = true) {
7585
8599
  const fields = selectToFetchAllFields(stmt, table);
7586
8600
  const onTruncate = (max) => {
7587
8601
  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
8602
  };
7589
8603
  if (!table.subtableCode) {
7590
- const baseQuery = isMainTable ? selectToFetchAllParams(stmt, table.appId).query : "";
8604
+ const baseQuery = isMainTable && allowOriginalWherePushdown ? selectToFetchAllParams(stmt, table.appId).query : "";
7591
8605
  const pushQuery = pushDownCond !== null ? whereToKintone(pushDownCond) : "";
7592
8606
  const query = baseQuery && pushQuery ? `(${baseQuery}) and (${pushQuery})` : baseQuery || pushQuery;
7593
8607
  const resolved = await fetchRecordsForSharedPlan(client.getRecords, table.appId, query, fields, {
@@ -7791,6 +8805,10 @@ async function getProcessStatusesCached(appId, client, cacheContext) {
7791
8805
  setScopedCacheValue(processStatusCache, cacheContext, appId, loading);
7792
8806
  return loading;
7793
8807
  }
8808
+ async function loadProcessStatusOrder(appId, client, cacheContext) {
8809
+ const process2 = await getProcessStatusesCached(appId, client, cacheContext);
8810
+ return process2.enable && process2.states !== null ? new Map(process2.states.map((state) => [state.name, state.index])) : void 0;
8811
+ }
7794
8812
  async function getFieldTypeMap(appId, client, cacheContext) {
7795
8813
  const cached = getScopedCacheValue(fieldTypeCache, cacheContext, appId);
7796
8814
  if (cached) return cached;
@@ -7834,18 +8852,118 @@ async function getSortKindMapByApp(appId, client, cacheContext) {
7834
8852
  setScopedCacheValue(sortKindCache, cacheContext, appId, map);
7835
8853
  return map;
7836
8854
  }
7837
- async function buildOrderByMetaForSelect(stmt, client, cacheContext) {
8855
+ function orderByFieldNames(stmt) {
8856
+ const items = [
8857
+ ...stmt.orderBy,
8858
+ ...stmt.columns.flatMap((column) => column.type === "WINDOW_COL" ? column.orderBy : [])
8859
+ ];
8860
+ return [...new Set(items.flatMap(
8861
+ (item) => item.key.type === "FIELD_NAME" ? [item.key.name] : []
8862
+ ))];
8863
+ }
8864
+ async function buildOrderSemanticsForSelect(stmt, client, cacheContext, materializedTables) {
8865
+ const names = orderByFieldNames(stmt);
8866
+ if (names.length === 0) return /* @__PURE__ */ new Map();
8867
+ const tables = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
8868
+ const ambiguousFields = /* @__PURE__ */ new Set();
8869
+ const infosByApp = new Map(
8870
+ await Promise.all([...new Set(
8871
+ tables.filter((table) => table.cteName === null).map((table) => table.appId)
8872
+ )].map(async (appId) => {
8873
+ const infos = await getFieldsCached(appId, client, cacheContext);
8874
+ return [appId, new Map(infos.map((info) => [info.code, info]))];
8875
+ }))
8876
+ );
8877
+ const resolveField2 = (ref) => {
8878
+ if (ref.tableAlias !== null) {
8879
+ if (ref.tableAlias === "_p" && stmt.from.subtableCode && stmt.from.cteName === null) {
8880
+ const info2 = infosByApp.get(stmt.from.appId)?.get(ref.field);
8881
+ return info2 ? materializedMetaFromFieldInfo(info2, stmt.from.appId) : void 0;
8882
+ }
8883
+ const table = tables.find((candidate) => candidate.alias === ref.tableAlias);
8884
+ if (!table) return void 0;
8885
+ if (table.cteName !== null) return materializedTables?.get(table.cteName)?.columnMeta?.get(ref.field);
8886
+ const info = infosByApp.get(table.appId)?.get(fieldCodeForTypeLookup(table, ref.field));
8887
+ return info ? materializedMetaFromFieldInfo(info, table.appId) : systemColumnMeta(ref.field);
8888
+ }
8889
+ if (stmt.joins.length === 0) {
8890
+ if (stmt.from.cteName !== null) return materializedTables?.get(stmt.from.cteName)?.columnMeta?.get(ref.field);
8891
+ const info = infosByApp.get(stmt.from.appId)?.get(fieldCodeForTypeLookup(stmt.from, ref.field));
8892
+ return info ? materializedMetaFromFieldInfo(info, stmt.from.appId) : systemColumnMeta(ref.field);
8893
+ }
8894
+ const matches = tables.flatMap((table) => {
8895
+ if (table.cteName !== null) {
8896
+ const materialized = materializedTables?.get(table.cteName);
8897
+ const meta2 = materialized?.columns.includes(ref.field) ? materialized.columnMeta?.get(ref.field) : void 0;
8898
+ return meta2 ? [meta2] : [];
8899
+ }
8900
+ const info = infosByApp.get(table.appId)?.get(fieldCodeForTypeLookup(table, ref.field));
8901
+ const meta = info ? materializedMetaFromFieldInfo(info, table.appId) : systemColumnMeta(ref.field);
8902
+ return meta ? [meta] : [];
8903
+ });
8904
+ if (matches.length > 1) ambiguousFields.add(ref.field);
8905
+ return matches.length === 1 ? matches[0] : void 0;
8906
+ };
8907
+ const aliasSemantics = /* @__PURE__ */ new Map();
8908
+ for (const column of stmt.columns) {
8909
+ if (!("alias" in column) || !column.alias) continue;
8910
+ let meta;
8911
+ if (column.type === "FIELD") meta = resolveField2(aggregateFieldRef(column.field));
8912
+ else if (column.type === "ARITH_COL" || column.type === "ARITH_AGG_COL" || column.type === "WINDOW_COL") {
8913
+ meta = syntheticColumnMeta("number");
8914
+ } else if (column.type === "LITERAL_COL") meta = syntheticColumnMeta("string");
8915
+ else if (column.type === "STRFUNC_COL") meta = stringFunctionColumnMeta(column.expr);
8916
+ else if (column.type === "SCALAR_SUBQUERY_COL") meta = unknownStringColumnMeta();
8917
+ else if (column.type === "CASE_COL") {
8918
+ const candidates = column.expr.branches.map((branch) => caseResultColumnMeta(branch.result, resolveField2));
8919
+ if (column.expr.elseResult) candidates.push(caseResultColumnMeta(column.expr.elseResult, resolveField2));
8920
+ meta = mergeExpressionColumnMeta(candidates);
8921
+ } else if (column.type === "AGGREGATE") {
8922
+ if (column.func === "MIN" || column.func === "MAX") {
8923
+ meta = column.arg.type === "FIELD_REF" ? resolveField2(aggregateFieldRef(column.arg.field)) : syntheticColumnMeta("number");
8924
+ } else {
8925
+ meta = column.func === "GROUP_CONCAT" ? syntheticColumnMeta("string") : syntheticColumnMeta("number");
8926
+ }
8927
+ }
8928
+ if (meta?.semantics) aliasSemantics.set(column.alias, meta.semantics);
8929
+ }
8930
+ const result = /* @__PURE__ */ new Map();
8931
+ for (const name of names) {
8932
+ const base = aliasSemantics.get(name) ?? resolveField2(aggregateFieldRef(name))?.semantics;
8933
+ if (!base) {
8934
+ const ref = aggregateFieldRef(name);
8935
+ if (ref.tableAlias === null && ambiguousFields.has(ref.field)) {
8936
+ result.set(name, resolveFieldSemantics({ fieldType: "KSQL_AMBIGUOUS" }));
8937
+ }
8938
+ continue;
8939
+ }
8940
+ let semantics = base;
8941
+ if (base.fieldType === "STATUS" && base.source && stmt.orderMode !== "KINTONE_NATIVE") {
8942
+ const process2 = await getProcessStatusesCached(base.source.appId, client, cacheContext);
8943
+ if (process2.enable && process2.states !== null) {
8944
+ semantics = {
8945
+ ...base,
8946
+ optionOrder: new Map(process2.states.map((state) => [state.name, state.index]))
8947
+ };
8948
+ }
8949
+ }
8950
+ result.set(name, semantics);
8951
+ }
8952
+ return result;
8953
+ }
8954
+ async function buildOrderByMetaForSelect(stmt, client, cacheContext, materializedTables) {
7838
8955
  const hasWindowOrderBy = stmt.columns.some(
7839
8956
  (column) => column.type === "WINDOW_COL" && column.orderBy.length > 0
7840
8957
  );
7841
8958
  if (stmt.orderBy.length === 0 && !hasWindowOrderBy) {
7842
- return { optionOrders: /* @__PURE__ */ new Map(), sortKinds: /* @__PURE__ */ new Map() };
8959
+ return { optionOrders: /* @__PURE__ */ new Map(), sortKinds: /* @__PURE__ */ new Map(), semantics: /* @__PURE__ */ new Map() };
7843
8960
  }
7844
- const [optionOrders, sortKinds] = await Promise.all([
8961
+ const [optionOrders, sortKinds, semantics] = await Promise.all([
7845
8962
  buildOptionOrdersForSelect(stmt, client, cacheContext),
7846
- buildSortKindsForSelect(stmt, client, cacheContext)
8963
+ buildSortKindsForSelect(stmt, client, cacheContext),
8964
+ buildOrderSemanticsForSelect(stmt, client, cacheContext, materializedTables)
7847
8965
  ]);
7848
- return { optionOrders, sortKinds };
8966
+ return { optionOrders, sortKinds, semantics };
7849
8967
  }
7850
8968
  async function buildOptionOrdersForSelect(stmt, client, cacheContext) {
7851
8969
  const optionOrders = /* @__PURE__ */ new Map();
@@ -7943,6 +9061,9 @@ var RejectLimitExceededError = class extends Error {
7943
9061
  }
7944
9062
  };
7945
9063
  async function prepareDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber) {
9064
+ if (stmt.type === "UPDATE") {
9065
+ await assertDmlWhereCapability(stmt, client, cacheContext);
9066
+ }
7946
9067
  const operation = stmt.type === "UPDATE" ? "UPDATE" : stmt.type.startsWith("UPSERT") ? "UPSERT" : "INSERT";
7947
9068
  const payloadFields = stmt.type === "UPDATE" ? ["$id", ...stmt.assignments.map((a) => a.field)] : [...stmt.fields];
7948
9069
  if (new Set(payloadFields).size !== payloadFields.length) {
@@ -7982,18 +9103,22 @@ async function prepareDmlValidation(stmt, client, options, cacheContext, tempTab
7982
9103
  const columnMeta = /* @__PURE__ */ new Map();
7983
9104
  for (const column of payloadFields) {
7984
9105
  if (column === "$id") {
7985
- columnMeta.set(column, { sortKind: "number", fieldType: "RECORD_NUMBER" });
9106
+ columnMeta.set(column, {
9107
+ sortKind: "number",
9108
+ fieldType: "RECORD_NUMBER",
9109
+ semantics: resolveFieldSemantics({ fieldType: "RECORD_NUMBER" })
9110
+ });
7986
9111
  continue;
7987
9112
  }
7988
9113
  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" });
9114
+ if (info) columnMeta.set(column, materializedMetaFromFieldInfo(info, stmt.appId));
9115
+ }
9116
+ columnMeta.set("$err_statement", syntheticColumnMeta("number"));
9117
+ columnMeta.set("$err_operation", syntheticColumnMeta("string"));
9118
+ columnMeta.set("$err_row", syntheticColumnMeta("number"));
9119
+ columnMeta.set("$err_field", syntheticColumnMeta("string"));
9120
+ columnMeta.set("$err_code", syntheticColumnMeta("string"));
9121
+ columnMeta.set("$err_message", syntheticColumnMeta("string"));
7997
9122
  materializedMetaByValidationResult.set(result, columnMeta);
7998
9123
  return { result, candidates, invalidRowNumbers, columnMeta };
7999
9124
  }
@@ -8379,6 +9504,7 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
8379
9504
  };
8380
9505
  }
8381
9506
  async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
9507
+ await assertDmlWhereCapability(stmt, client, cacheContext);
8382
9508
  if (stmt.subtableCode) {
8383
9509
  return executeUpdateSubtable(stmt, client, options, cacheContext);
8384
9510
  }
@@ -8456,6 +9582,7 @@ function collectUpdateFromTargetFields(stmt) {
8456
9582
  return [...fields];
8457
9583
  }
8458
9584
  async function executeDelete(stmt, client, options, cacheContext) {
9585
+ await assertDmlWhereCapability(stmt, client, cacheContext);
8459
9586
  if (stmt.subtableCode) {
8460
9587
  return executeDeleteSubtable(stmt, client, options, cacheContext);
8461
9588
  }
@@ -8828,6 +9955,18 @@ async function executeReorder(stmt, client, options, cacheContext) {
8828
9955
  client,
8829
9956
  cacheContext
8830
9957
  );
9958
+ const reorderFields = await getFieldsCached(stmt.appId, client, cacheContext);
9959
+ const reorderSemanticsByCode = new Map(reorderFields.map((field) => [
9960
+ field.code,
9961
+ field.semantics ?? resolveFieldSemantics(field)
9962
+ ]));
9963
+ const resolveReorderSemantics = (field) => {
9964
+ if (field.field === "_idx" || field.field === "_pid" || field.field === "_rid") {
9965
+ return syntheticSemantics("number");
9966
+ }
9967
+ const code = field.field.startsWith("_p.") ? field.field.slice(3) : field.field;
9968
+ return reorderSemanticsByCode.get(code) ?? syntheticSemantics("string");
9969
+ };
8831
9970
  const parents = await fetchAll(
8832
9971
  client.getRecords,
8833
9972
  stmt.appId,
@@ -8836,7 +9975,13 @@ async function executeReorder(stmt, client, options, cacheContext) {
8836
9975
  { maxRecords: options.maxRecords ?? 1e4, parallel: options.fetchParallel ?? 1 }
8837
9976
  );
8838
9977
  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));
9978
+ const targetParentIds = stmt.all ? new Set(parents.map((p) => String(p["$id"]?.value ?? "")).filter((id) => id !== "")) : new Set(expanded.filter((r) => stmt.where && evalWhere(
9979
+ stmt.where,
9980
+ r.flat,
9981
+ resolveFieldType,
9982
+ void 0,
9983
+ resolveReorderSemantics
9984
+ )).map((r) => r.parentId));
8840
9985
  if (options.confirm) {
8841
9986
  const ok = await options.confirm(targetParentIds.size, "UPDATE");
8842
9987
  if (!ok) throw new OperationCancelledError("UPDATE", targetParentIds.size);
@@ -8847,7 +9992,7 @@ async function executeReorder(stmt, client, options, cacheContext) {
8847
9992
  if (!parent) continue;
8848
9993
  const rows = getMutableTableRows(parent, stmt.subtableCode);
8849
9994
  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));
9995
+ sortable.sort((a, b) => compareByOrder(a.flat, b.flat, stmt.by, resolveReorderSemantics));
8851
9996
  const orderedRowIds = sortable.map((x) => x.row.id ?? "");
8852
9997
  await client.putRecords(buildSubtableReorderPutParams(stmt.appId, pid, getRevision(parent), stmt.subtableCode, orderedRowIds));
8853
9998
  }
@@ -8868,14 +10013,12 @@ function buildFlatRowForSort(parent, subtableCode, row, idx) {
8868
10013
  }
8869
10014
  return flat;
8870
10015
  }
8871
- function compareByOrder(a, b, orderBy) {
10016
+ function compareByOrder(a, b, orderBy, resolveSemantics) {
8872
10017
  for (const item of orderBy) {
8873
10018
  const av = evalOrderKeyForRow(item.key, a);
8874
10019
  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");
10020
+ 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");
10021
+ const cmp = compareCanonicalValues(av, bv, semantics ?? syntheticSemantics("string"));
8879
10022
  if (cmp !== 0) return item.direction === "ASC" ? cmp : -cmp;
8880
10023
  }
8881
10024
  return 0;
@@ -9072,35 +10215,140 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
9072
10215
  pending.forEach(([i], idx) => cache.set(i, values[idx]));
9073
10216
  return cache;
9074
10217
  }
9075
- function buildBatchExplainPlans(sql, injectedVariables) {
10218
+ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords = 1e4) {
10219
+ const fieldApps = /* @__PURE__ */ new Set();
10220
+ const processStatusApps = /* @__PURE__ */ new Set();
10221
+ const tracedClient = {
10222
+ ...client,
10223
+ getFields: async (appId) => {
10224
+ fieldApps.add(appId);
10225
+ return client.getFields(appId);
10226
+ },
10227
+ getProcessStatuses: async (appId) => {
10228
+ processStatusApps.add(appId);
10229
+ return client.getProcessStatuses(appId);
10230
+ }
10231
+ };
10232
+ const capabilities = /* @__PURE__ */ new Map();
10233
+ const orderPlans = /* @__PURE__ */ new Map();
10234
+ const seen = /* @__PURE__ */ new Set();
10235
+ const visit = async (node) => {
10236
+ if (node === null || typeof node !== "object") return;
10237
+ if (seen.has(node)) return;
10238
+ seen.add(node);
10239
+ if (Array.isArray(node)) {
10240
+ await Promise.all(node.map(visit));
10241
+ return;
10242
+ }
10243
+ const typed = node;
10244
+ if (typed["type"] === "SELECT") {
10245
+ const select = node;
10246
+ const physicalApps = [select.from, ...select.joins.map((join2) => join2.table)].filter((table) => table.cteName === null).map((table) => table.appId);
10247
+ const needsWhereSchema = whereNeedsFieldMetadata(select.where);
10248
+ if (needsWhereSchema || select.orderBy.length > 0 || select.columns.some((column) => column.type === "WINDOW_COL" && column.orderBy.length > 0)) {
10249
+ physicalApps.forEach((appId) => fieldApps.add(appId));
10250
+ }
10251
+ const capability = await resolveSelectWhereCapability(select, tracedClient, cacheContext);
10252
+ if (capability.capability === "UNSUPPORTED") {
10253
+ throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
10254
+ }
10255
+ capabilities.set(select, capability);
10256
+ if (select.orderBy.length > 0 || select.columns.some((column) => column.type === "WINDOW_COL" && column.orderBy.length > 0)) {
10257
+ const meta = await buildOrderByMetaForSelect(select, tracedClient, cacheContext);
10258
+ if (select.orderMode !== "KINTONE_NATIVE") {
10259
+ for (const semantics of meta.semantics.values()) {
10260
+ if (semantics.fieldType === "STATUS" && semantics.source) {
10261
+ processStatusApps.add(semantics.source.appId);
10262
+ }
10263
+ }
10264
+ }
10265
+ const hasUnmaterializedSource = [select.from, ...select.joins.map((join2) => join2.table)].some((table) => table.cteName !== null);
10266
+ if (hasCanonicalOrder(select) && !hasUnmaterializedSource) {
10267
+ const mode = capability.capability === "EXACT_PUSHDOWN" ? resolveSelectMode(select) : "FULL_SCAN";
10268
+ orderPlans.set(select, (select.orderMode === "KINTONE_NATIVE" ? planKorderNative : planCanonicalOrder)({
10269
+ stmt: select,
10270
+ staticMode: mode,
10271
+ whereCapability: capability.capability,
10272
+ orderSemantics: meta.semantics,
10273
+ maxRecords,
10274
+ hasKlike: whereHasKlike(select.where)
10275
+ }));
10276
+ }
10277
+ }
10278
+ } else if (typed["type"] === "UPDATE" || typed["type"] === "DELETE") {
10279
+ fieldApps.add(node.appId);
10280
+ await assertDmlWhereCapability(
10281
+ node,
10282
+ tracedClient,
10283
+ cacheContext
10284
+ );
10285
+ }
10286
+ await Promise.all(Object.values(typed).map(visit));
10287
+ };
10288
+ await visit(query);
10289
+ if (typeof query === "object" && query !== null && query.type === "WITH" && canInlineSingleCte(query)) {
10290
+ const inlined = buildInlinedQuery(query);
10291
+ const capability = await resolveSelectWhereCapability(inlined, tracedClient, cacheContext);
10292
+ if (capability.capability === "UNSUPPORTED") {
10293
+ throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
10294
+ }
10295
+ capabilities.set(inlined, capability);
10296
+ if (hasCanonicalOrder(inlined)) {
10297
+ const meta = await buildOrderByMetaForSelect(inlined, tracedClient, cacheContext);
10298
+ orderPlans.set(inlined, (inlined.orderMode === "KINTONE_NATIVE" ? planKorderNative : planCanonicalOrder)({
10299
+ stmt: inlined,
10300
+ staticMode: capability.capability === "EXACT_PUSHDOWN" ? resolveSelectMode(inlined) : "FULL_SCAN",
10301
+ whereCapability: capability.capability,
10302
+ orderSemantics: meta.semantics,
10303
+ maxRecords,
10304
+ hasKlike: whereHasKlike(inlined.where)
10305
+ }));
10306
+ }
10307
+ }
10308
+ return { capabilities, orderPlans, fieldApps, processStatusApps };
10309
+ }
10310
+ function explainMetadataLines(analysis) {
10311
+ return [
10312
+ ...[...analysis.fieldApps].sort((a, b) => a - b).map((appId) => ` metadata API: form definition APP${appId}`),
10313
+ ...[...analysis.processStatusApps].sort((a, b) => a - b).map((appId) => ` metadata API: process status APP${appId}`)
10314
+ ];
10315
+ }
10316
+ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords = 1e4) {
9076
10317
  const statements = parseSqlBatch(sql);
9077
10318
  const analysis = analyzeBatch(statements);
9078
10319
  validateDeclaredBatchVariables(statements, injectedVariables);
9079
10320
  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
- };
10321
+ const plans = [];
10322
+ for (let i = 0; i < statements.length; i++) {
10323
+ const stmt = statements[i];
10324
+ const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveVariableRefs(stmt.expr, variables) } : stmt : resolveVariableRefs(stmt, variables);
10325
+ validateKlikeStatement(planStmt);
10326
+ const whereAnalysis = await buildExplainWhereAnalysis(planStmt, client, cacheContext, maxRecords);
10327
+ const statementPlan = buildBatchStatementPlan(
10328
+ planStmt,
10329
+ analysis.statements[i],
10330
+ whereAnalysis.capabilities,
10331
+ whereAnalysis.orderPlans
10332
+ );
10333
+ const metadataPlan = explainMetadataLines(whereAnalysis);
10334
+ plans.push({
10335
+ index: i,
10336
+ type: analysis.statements[i].statementType,
10337
+ plan: statementPlan.length === 0 ? metadataPlan : [statementPlan[0], ...metadataPlan, ...statementPlan.slice(1)]
10338
+ });
10339
+ if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
10340
+ variables.set(stmt.name, { type: "string", value: `@${stmt.name}` });
10341
+ }
10342
+ }
10343
+ return { statementCount: statements.length, statements: plans };
9096
10344
  }
9097
- function buildBatchStatementPlan(stmt, info) {
10345
+ function buildBatchStatementPlan(stmt, info, capabilities, orderPlans) {
9098
10346
  if (stmt.type === "CREATE_TEMP_TABLE") {
9099
10347
  return [
9100
10348
  `CREATE TEMP TABLE ${stmt.name}`,
9101
10349
  ` scope: batch\uFF08\u30D0\u30C3\u30C1\u7D42\u4E86\u6642\u306B\u81EA\u52D5\u7834\u68C4\uFF09`,
9102
10350
  ` 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}`)
10351
+ ...buildPlanForBatchQuery(stmt.query, info, capabilities, orderPlans).map((l) => ` ${l}`)
9104
10352
  ];
9105
10353
  }
9106
10354
  if (stmt.type === "DROP_TEMP_TABLE") {
@@ -9116,7 +10364,7 @@ function buildBatchStatementPlan(stmt, info) {
9116
10364
  `SET @${stmt.name} = (SELECT ...)`,
9117
10365
  " 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
10366
  " subquery:",
9119
- ...buildPlanForBatchQuery(stmt.expr.query, subInfo).map((l) => ` ${l}`)
10367
+ ...buildPlanForBatchQuery(stmt.expr.query, subInfo, capabilities, orderPlans).map((l) => ` ${l}`)
9120
10368
  ];
9121
10369
  }
9122
10370
  return [
@@ -9132,7 +10380,7 @@ function buildBatchStatementPlan(stmt, info) {
9132
10380
  }
9133
10381
  if (stmt.type === "SHOW_APPS") return ["SHOW APPS\uFF08\u30A2\u30D7\u30EA\u4E00\u89A7\u306E\u53D6\u5F97\uFF09"];
9134
10382
  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);
10383
+ if (stmt.type === "EXPLAIN") return buildPlanForBatchQuery(stmt.query, info, capabilities, orderPlans);
9136
10384
  if (stmt.type === "ASSERT") {
9137
10385
  const lines = [
9138
10386
  `ASSERT ${stmt.text}`,
@@ -9144,11 +10392,11 @@ function buildBatchStatementPlan(stmt, info) {
9144
10392
  subqueries.forEach((sq, i) => {
9145
10393
  lines.push(subqueries.length > 1 ? ` subquery[${i + 1}]:` : " subquery:");
9146
10394
  const subInfo = hasTempTableRef(sq.query) ? info : { ...info, tempTablesReferenced: [] };
9147
- lines.push(...buildPlanForBatchQuery(sq.query, subInfo).map((l) => ` ${l}`));
10395
+ lines.push(...buildPlanForBatchQuery(sq.query, subInfo, capabilities, orderPlans).map((l) => ` ${l}`));
9148
10396
  });
9149
10397
  return lines;
9150
10398
  }
9151
- return buildPlanForBatchQuery(stmt, info);
10399
+ return buildPlanForBatchQuery(stmt, info, capabilities, orderPlans);
9152
10400
  }
9153
10401
  function hasTempTableRef(node) {
9154
10402
  if (Array.isArray(node)) return node.some(hasTempTableRef);
@@ -9160,9 +10408,9 @@ function hasTempTableRef(node) {
9160
10408
  }
9161
10409
  return false;
9162
10410
  }
9163
- function buildPlanForBatchQuery(query, info) {
10411
+ function buildPlanForBatchQuery(query, info, capabilities, orderPlans) {
9164
10412
  if (info.tempTablesReferenced.length === 0) {
9165
- return buildExplainPlan(query);
10413
+ return buildExplainPlan(query, void 0, capabilities, orderPlans);
9166
10414
  }
9167
10415
  const lines = [];
9168
10416
  if (query.type === "INSERT_SELECT") {
@@ -9187,8 +10435,12 @@ function buildPlanForBatchQuery(query, info) {
9187
10435
  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
10436
  return lines;
9189
10437
  }
9190
- function executeExplain(stmt) {
9191
- const lines = buildExplainPlan(stmt.query);
10438
+ async function executeExplain(stmt, client, cacheContext, maxRecords) {
10439
+ const analysis = await buildExplainWhereAnalysis(stmt.query, client, cacheContext, maxRecords);
10440
+ const lines = [
10441
+ ...explainMetadataLines(analysis),
10442
+ ...buildExplainPlan(stmt.query, void 0, analysis.capabilities, analysis.orderPlans)
10443
+ ];
9192
10444
  return {
9193
10445
  type: "SELECT",
9194
10446
  columns: ["plan"],
@@ -9196,29 +10448,45 @@ function executeExplain(stmt) {
9196
10448
  rowCount: lines.length
9197
10449
  };
9198
10450
  }
9199
- function buildExplainPlan(query, label) {
9200
- if (query.type === "UNION") return buildUnionPlan(query);
9201
- if (query.type === "WITH") return buildWithPlan(query);
10451
+ function buildExplainPlan(query, label, capabilities, orderPlans) {
10452
+ if (query.type === "UNION") return buildUnionPlan(query, capabilities, orderPlans);
10453
+ if (query.type === "WITH") return buildWithPlan(query, capabilities, orderPlans);
9202
10454
  if (query.type === "INSERT") return buildInsertPlan(query, label);
9203
- if (query.type === "INSERT_SELECT") return buildInsertSelectPlan(query, label);
10455
+ if (query.type === "INSERT_SELECT") return buildInsertSelectPlan(query, label, capabilities, orderPlans);
9204
10456
  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);
10457
+ if (query.type === "UPSERT_SELECT") return buildUpsertSelectPlan(query, label, capabilities, orderPlans);
10458
+ if (query.type === "UPDATE") return buildUpdatePlan(query, label, capabilities, orderPlans);
9207
10459
  if (query.type === "DELETE") return buildDeletePlan(query, label);
9208
10460
  if (query.type === "REORDER") return buildReorderPlan(query, label);
9209
- return buildSelectPlan(query, label);
10461
+ return buildSelectPlan(query, label, capabilities, orderPlans);
9210
10462
  }
9211
- function buildSelectPlan(stmt, label) {
9212
- const mode = resolveSelectMode(stmt);
10463
+ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
10464
+ const whereCapability = capabilities?.get(stmt) ?? (capabilities ? [...capabilities].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
10465
+ const orderPlan = orderPlans?.get(stmt) ?? (orderPlans ? [...orderPlans].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
10466
+ const mode = orderPlan?.kind === "CANONICAL_LOCAL" ? "FULL_SCAN" : whereCapability && whereCapability.capability !== "EXACT_PUSHDOWN" ? "FULL_SCAN" : resolveSelectMode(stmt);
9213
10467
  const reasons = collectFullScanReasons(stmt);
10468
+ if (whereCapability && whereCapability.capability !== "EXACT_PUSHDOWN") {
10469
+ reasons.push(...whereCapability.reasons.map((reason) => reason.code));
10470
+ }
9214
10471
  const lines = [];
9215
10472
  if (label) lines.push(label);
9216
10473
  lines.push(` mode: ${mode}`);
10474
+ if (orderPlan) {
10475
+ lines.push(` order plan: ${orderPlan.kind}`);
10476
+ if (orderPlan.reasonCodes.length > 0) lines.push(` order reason: ${orderPlan.reasonCodes.join(", ")}`);
10477
+ if (orderPlan.kind === "KORDER_NATIVE") {
10478
+ lines.push(" order semantics: kintone native (not kSQL canonical)");
10479
+ lines.push(" REST execution: single GET");
10480
+ }
10481
+ }
10482
+ if (orderPlan?.requiresCompleteInput ?? requiresCompleteInput(stmt)) {
10483
+ lines.push(" complete input: required (ORDER BY / window ORDER BY; onLimit=truncate disabled)");
10484
+ }
9217
10485
  if (mode === "FULL_SCAN" && reasons.length > 0) {
9218
10486
  lines.push(` reason: ${reasons.join(", ")}`);
9219
10487
  }
9220
10488
  if (mode === "SIMPLE") {
9221
- const params = selectToKintoneParams(stmt);
10489
+ const params = selectToKintoneParams(orderPlan?.kind === "CANONICAL_REST_TOP_N" ? withCanonicalRestTie(stmt) : stmt);
9222
10490
  lines.push(` app: APP${stmt.from.appId} (${stmt.from.appId})`);
9223
10491
  lines.push(` kintone query: ${params.query || "(\u306A\u3057)"}`);
9224
10492
  lines.push(` fields: ${params.fields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : params.fields.join(", ")}`);
@@ -9228,7 +10496,8 @@ function buildSelectPlan(stmt, label) {
9228
10496
  const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
9229
10497
  const mainPushDown = pushdownPlan.mainCondition;
9230
10498
  const mainCandidate = extractMainTypedPushdownCandidate(stmt);
9231
- const mainQ = mainPushDown !== null ? whereToKintone(mainPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
10499
+ const exactOriginalWhere = stmt.joins.length === 0 && whereCapability?.capability === "EXACT_PUSHDOWN" && stmt.where !== null && !whereRequiresJsEval(stmt.where) ? whereToKintone(stmt.where) : "";
10500
+ const mainQ = mainPushDown !== null ? whereToKintone(mainPushDown) : exactOriginalWhere || "(\u5168\u4EF6\u53D6\u5F97)";
9232
10501
  lines.push(` app: APP${stmt.from.appId}${mainAliasStr} (${stmt.from.appId})`);
9233
10502
  lines.push(` kintone query: ${mainQ}`);
9234
10503
  if (mainCandidate !== null) {
@@ -9250,10 +10519,10 @@ function buildSelectPlan(stmt, label) {
9250
10519
  lines.push(` fields: ${joinFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : joinFields.join(", ")}`);
9251
10520
  }
9252
10521
  }
9253
- lines.push(...collectSubqueryPlans(stmt));
10522
+ lines.push(...collectSubqueryPlans(stmt, capabilities, orderPlans));
9254
10523
  return lines;
9255
10524
  }
9256
- function buildUnionPlan(stmt) {
10525
+ function buildUnionPlan(stmt, capabilities, orderPlans) {
9257
10526
  const selects = [];
9258
10527
  const collect = (u) => {
9259
10528
  if (u.type === "SELECT") {
@@ -9267,24 +10536,25 @@ function buildUnionPlan(stmt) {
9267
10536
  const lines = [];
9268
10537
  selects.forEach((sel, i) => {
9269
10538
  if (i > 0) lines.push("");
9270
- lines.push(...buildSelectPlan(sel, `[union:${i + 1}]`));
10539
+ lines.push(...buildSelectPlan(sel, `[union:${i + 1}]`, capabilities, orderPlans));
9271
10540
  });
9272
10541
  return lines;
9273
10542
  }
9274
- function buildWithPlan(stmt) {
10543
+ function buildWithPlan(stmt, capabilities, orderPlans) {
9275
10544
  const lines = [];
9276
10545
  for (const cte of stmt.ctes) {
9277
10546
  if (cte.query.type === "SELECT") {
9278
- lines.push(...buildSelectPlan(cte.query, `[cte: ${cte.name}]`));
10547
+ lines.push(...buildSelectPlan(cte.query, `[cte: ${cte.name}]`, capabilities, orderPlans));
9279
10548
  lines.push("");
9280
10549
  }
9281
10550
  }
9282
10551
  if (stmt.query.type === "SELECT" || stmt.query.type === "UNION") {
9283
- lines.push(...buildExplainPlan(stmt.query, "[main]"));
10552
+ lines.push(...buildExplainPlan(stmt.query, "[main]", capabilities, orderPlans));
9284
10553
  }
9285
10554
  if (canInlineSingleCte(stmt)) {
9286
10555
  lines.push("");
9287
- lines.push(...buildSelectPlan(buildInlinedQuery(stmt), "[effective: inlined CTE]"));
10556
+ const inlined = buildInlinedQuery(stmt);
10557
+ lines.push(...buildSelectPlan(inlined, "[effective: inlined CTE]", capabilities, orderPlans));
9288
10558
  }
9289
10559
  return lines;
9290
10560
  }
@@ -9312,7 +10582,7 @@ function collectFullScanReasons(stmt) {
9312
10582
  r.push("ORDER BY \u306B\u5F0F");
9313
10583
  return r;
9314
10584
  }
9315
- function collectSubqueryPlans(stmt) {
10585
+ function collectSubqueryPlans(stmt, capabilities, orderPlans) {
9316
10586
  const lines = [];
9317
10587
  let idx = 1;
9318
10588
  const visitWhere = (w) => {
@@ -9321,16 +10591,16 @@ function collectSubqueryPlans(stmt) {
9321
10591
  case "BINARY":
9322
10592
  if (w.right.type === "SCALAR_SUBQUERY") {
9323
10593
  lines.push("");
9324
- lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`));
10594
+ lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans));
9325
10595
  }
9326
10596
  if (w.right.type === "SUBQUERY_IN_LIST") {
9327
10597
  lines.push("");
9328
- lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`));
10598
+ lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans));
9329
10599
  }
9330
10600
  break;
9331
10601
  case "EXISTS":
9332
10602
  lines.push("");
9333
- lines.push(...buildSelectPlan(w.query, `[subquery:${idx++}]`));
10603
+ lines.push(...buildSelectPlan(w.query, `[subquery:${idx++}]`, capabilities, orderPlans));
9334
10604
  break;
9335
10605
  case "LOGICAL":
9336
10606
  visitWhere(w.left);
@@ -9348,7 +10618,7 @@ function collectSubqueryPlans(stmt) {
9348
10618
  for (const col of stmt.columns) {
9349
10619
  if (col.type === "SCALAR_SUBQUERY_COL") {
9350
10620
  lines.push("");
9351
- lines.push(...buildSelectPlan(col.query, `[subquery:${idx++}]`));
10621
+ lines.push(...buildSelectPlan(col.query, `[subquery:${idx++}]`, capabilities, orderPlans));
9352
10622
  }
9353
10623
  }
9354
10624
  if (stmt.having) visitWhere(stmt.having);
@@ -9366,7 +10636,7 @@ function buildInsertPlan(stmt, label) {
9366
10636
  lines.push(` fields: ${stmt.fields.join(", ")}`);
9367
10637
  return lines;
9368
10638
  }
9369
- function buildInsertSelectPlan(stmt, label) {
10639
+ function buildInsertSelectPlan(stmt, label, capabilities, orderPlans) {
9370
10640
  const lines = [];
9371
10641
  if (label) lines.push(label);
9372
10642
  lines.push(` [INSERT SELECT]`);
@@ -9374,10 +10644,10 @@ function buildInsertSelectPlan(stmt, label) {
9374
10644
  lines.push(` fields: ${stmt.fields.join(", ")}`);
9375
10645
  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
10646
  lines.push("");
9377
- lines.push(...buildSelectPlan(stmt.select, "[source SELECT]"));
10647
+ lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans));
9378
10648
  return lines;
9379
10649
  }
9380
- function buildUpdatePlan(stmt, label) {
10650
+ function buildUpdatePlan(stmt, label, capabilities, orderPlans) {
9381
10651
  const isArith = hasArithAssignment(stmt);
9382
10652
  const isSubq = stmt.assignments.some((a) => a.value.type === "SCALAR_SUBQUERY");
9383
10653
  const lines = [];
@@ -9411,7 +10681,7 @@ function buildUpdatePlan(stmt, label) {
9411
10681
  for (const a of stmt.assignments) {
9412
10682
  if (a.value.type === "SCALAR_SUBQUERY") {
9413
10683
  lines.push("");
9414
- lines.push(...buildSelectPlan(a.value.query, `[subquery: ${a.field}]`));
10684
+ lines.push(...buildSelectPlan(a.value.query, `[subquery: ${a.field}]`, capabilities, orderPlans));
9415
10685
  }
9416
10686
  }
9417
10687
  return lines;
@@ -9438,7 +10708,7 @@ function buildUpsertPlan(stmt, label) {
9438
10708
  ` 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
10709
  ];
9440
10710
  }
9441
- function buildUpsertSelectPlan(stmt, label) {
10711
+ function buildUpsertSelectPlan(stmt, label, capabilities, orderPlans) {
9442
10712
  const lines = [
9443
10713
  ...label ? [label] : [],
9444
10714
  ` [UPSERT SELECT]`,
@@ -9448,7 +10718,7 @@ function buildUpsertSelectPlan(stmt, label) {
9448
10718
  ` 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
10719
  ``
9450
10720
  ];
9451
- lines.push(...buildSelectPlan(stmt.select, "[source SELECT]"));
10721
+ lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans));
9452
10722
  return lines;
9453
10723
  }
9454
10724
  function buildReorderPlan(stmt, label) {
@@ -9990,12 +11260,14 @@ function flattenFormFieldProperties(properties) {
9990
11260
  function flattenFields(properties, lookupCopyFields, inSubtable = false) {
9991
11261
  const out = [];
9992
11262
  for (const field of Object.values(properties)) {
9993
- out.push({
11263
+ const optionOrder = toOptionOrderMap(field.options);
11264
+ const sortKind = detectSortKind(field.type, field.format);
11265
+ const info = {
9994
11266
  code: field.code,
9995
11267
  label: field.label,
9996
11268
  fieldType: field.type,
9997
- optionOrder: toOptionOrderMap(field.options),
9998
- sortKind: detectSortKind(field.type, field.format),
11269
+ optionOrder,
11270
+ sortKind,
9999
11271
  required: field.required,
10000
11272
  minValue: normalizeConstraintValue(field.minValue),
10001
11273
  maxValue: normalizeConstraintValue(field.maxValue),
@@ -10004,7 +11276,9 @@ function flattenFields(properties, lookupCopyFields, inSubtable = false) {
10004
11276
  defaultValue: field.defaultValue,
10005
11277
  inSubtable,
10006
11278
  writable: !lookupCopyFields.has(field.code) && !NON_WRITABLE_FIELD_TYPES2.has(field.type)
10007
- });
11279
+ };
11280
+ info.semantics = resolveFieldSemantics(info);
11281
+ out.push(info);
10008
11282
  if (field.fields) out.push(...flattenFields(field.fields, lookupCopyFields, true));
10009
11283
  }
10010
11284
  return out;
@@ -10059,6 +11333,18 @@ function detectSortKind(fieldType, calcFormat) {
10059
11333
  return void 0;
10060
11334
  }
10061
11335
 
11336
+ // src/core/processStatus.ts
11337
+ function normalizeProcessStatusStates(states) {
11338
+ if (states === null) return null;
11339
+ return Object.values(states).map((state) => {
11340
+ const index = Number(state.index);
11341
+ if (!Number.isSafeInteger(index) || index < 0) {
11342
+ throw new Error(`ArgumentError: invalid process status index: ${String(state.index)}`);
11343
+ }
11344
+ return { name: state.name, index };
11345
+ });
11346
+ }
11347
+
10062
11348
  // src/cli/nodeKintoneClient.ts
10063
11349
  var SEARCH_ABORTED_HEADER_VALUE = "Filter aborted because of too many search results";
10064
11350
  function createNodeKintoneClient(baseUrl, tokenResolver) {
@@ -10256,7 +11542,7 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
10256
11542
  const res = await requestJson(`${apiBasePath}/app/status.json?${qs.toString()}`, { method: "GET" }, appId);
10257
11543
  return {
10258
11544
  enable: res.enable,
10259
- states: Object.values(res.states ?? {}).map((state) => state.name)
11545
+ states: normalizeProcessStatusStates(res.states)
10260
11546
  };
10261
11547
  }
10262
11548
  };
@@ -10726,7 +12012,7 @@ Options:
10726
12012
  (batch + json: prints one JSON envelope for the whole batch)
10727
12013
  --max-records <n> Max records to fetch (default: 500)
10728
12014
  --fetch-parallel <n> Parallel page fetches per query: 1-10 (default: 3)
10729
- --on-limit <mode> On record limit: error | truncate
12015
+ --on-limit <mode> On record limit: error | truncate (local ORDER BY needs complete input)
10730
12016
  --temp-table-max-rows <n> Max rows per temp table (default: 10000, always errors on overflow)
10731
12017
  --timeout <ms> Request timeout in milliseconds (default: 30000)
10732
12018
  --max-concurrent <n> Max concurrent kintone requests: 1-50 (default: 10)
@@ -12010,7 +13296,7 @@ async function run() {
12010
13296
  let isBatchSql = false;
12011
13297
  let batchContainsDml = false;
12012
13298
  let batchAnalysis = null;
12013
- let needsCompleteInput = false;
13299
+ let dryRunNeedsMetadata = false;
12014
13300
  if (args.diagRecordId === null) {
12015
13301
  sql = args.executeSql;
12016
13302
  if (!sql && args.filePath) sql = (0, import_fs2.readFileSync)(args.filePath, "utf-8");
@@ -12037,17 +13323,16 @@ async function run() {
12037
13323
  }
12038
13324
  try {
12039
13325
  const statements = parseSqlStatements(sql);
13326
+ dryRunNeedsMetadata = statements.some(explainNeedsAppMetadata);
12040
13327
  if (statements.length > 1) {
12041
13328
  batchAnalysis = analyzeBatch(statements);
12042
13329
  isBatchSql = true;
12043
13330
  batchContainsDml = batchAnalysis.containsDml;
12044
- needsCompleteInput = batchAnalysis.requiresCompleteInput;
12045
13331
  } else {
12046
13332
  const stmt = parseSqlStatement(sql);
12047
13333
  parsedStmt = stmt;
12048
13334
  stmtType = getStatementType(stmt);
12049
13335
  isDmlStatement = writesKintone(stmt);
12050
- needsCompleteInput = requiresCompleteInput(stmt);
12051
13336
  hasWhere = hasWhereClause(stmt);
12052
13337
  insertValuesCount = getInsertValuesCount(stmt);
12053
13338
  const supported = stmtType === "SELECT" || stmtType === "UNION" || stmtType === "WITH" || stmtType === "EXPLAIN" || stmtType === "SHOW_APPS" || stmtType === "DESCRIBE" || stmtType === "ASSERT" || isDmlType(stmtType);
@@ -12094,10 +13379,13 @@ async function run() {
12094
13379
  const yes = args.yes || envBool("KSQL_YES") === true || Boolean(profile.dml?.yes);
12095
13380
  const allowWithoutWhere = args.allowWithoutWhere || envBool("KSQL_ALLOW_WITHOUT_WHERE") === true || Boolean(profile.dml?.allowWithoutWhere);
12096
13381
  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");
13382
+ const isValidationOnly = batchAnalysis?.containsValidationOnly === true || parsedStmt !== null && typeof parsedStmt === "object" && "validateOnly" in parsedStmt && parsedStmt.validateOnly === true;
13383
+ const surfaceForcesOnLimitError = isDmlStatement || batchContainsDml || isValidationOnly;
13384
+ const effectiveOnLimit = surfaceForcesOnLimitError ? "error" : onLimit;
13385
+ if (surfaceForcesOnLimitError && onLimit === "truncate" && !quiet && !args.dryRun) {
13386
+ const reason = isDmlStatement || batchContainsDml ? "DML" : "VALIDATE ONLY";
13387
+ process.stderr.write(`note: onLimit=truncate is ignored for ${reason} (forced to error)
13388
+ `);
12101
13389
  }
12102
13390
  if (format === "markdown" && noHeader) {
12103
13391
  process.stderr.write("ArgumentError: --no-header cannot be used with --format markdown|md.\n");
@@ -12128,30 +13416,6 @@ async function run() {
12128
13416
  process.stderr.write("ArgumentError: DML is disabled. Use --allow-dml to enable UPDATE/DELETE/INSERT/UPSERT/REORDER.\n");
12129
13417
  return 2;
12130
13418
  }
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
13419
  }
12156
13420
  if (isDmlStatement) {
12157
13421
  if (hasProfileSyntax && stmtType === "DELETE") {
@@ -12178,7 +13442,7 @@ async function run() {
12178
13442
  appProfileByApp.set(appId, appBindingByMappedApp.get(appId)?.profile ?? profileName.toLowerCase());
12179
13443
  }
12180
13444
  const cacheContext = buildCacheContext(profileName, appBindingByMappedApp);
12181
- if (args.dryRun) {
13445
+ if (args.dryRun && !dryRunNeedsMetadata) {
12182
13446
  client = createDryRunClient();
12183
13447
  } else {
12184
13448
  for (const explicitProfile of appProfileByApp.values()) {
@@ -12412,6 +13676,29 @@ async function run() {
12412
13676
  maxDelayMs: args.retryMaxDelay ?? profile.query?.retryMaxDelayMs
12413
13677
  })));
12414
13678
  }
13679
+ if (isBatchSql && args.dryRun) {
13680
+ try {
13681
+ const plans = await buildBatchExplainPlans(sql, client, args.variables, cacheContext, maxRecords);
13682
+ const out = [];
13683
+ const restoredStatements = sqlDiagnosticContext ? restoreSqlDiagnosticValue(plans.statements, sqlDiagnosticContext.appBindingByMappedApp) : plans.statements;
13684
+ restoredStatements.forEach((p) => {
13685
+ if (p.index > 0) out.push("");
13686
+ out.push(`[${p.index + 1}] ${p.type}`);
13687
+ out.push(...p.plan);
13688
+ });
13689
+ process.stdout.write(`${out.join("\n")}
13690
+ `);
13691
+ return 0;
13692
+ } catch (err) {
13693
+ const restored = sourceSql && sqlDiagnosticContext ? restoreSqlContextError(err, sourceSql, {
13694
+ bindings: sqlDiagnosticContext.appBindingByMappedApp,
13695
+ rewriteSegments: sqlDiagnosticContext.rewriteSegments
13696
+ }) : err;
13697
+ process.stderr.write(`${restored instanceof Error ? restored.message : String(restored)}
13698
+ `);
13699
+ return toExitCodeFromError(restored);
13700
+ }
13701
+ }
12415
13702
  try {
12416
13703
  if (isDmlStatement && !args.dryRun) {
12417
13704
  const stmtAppId = parsedStmt && typeof parsedStmt === "object" && typeof parsedStmt.appId === "number" ? parsedStmt.appId : appIds[0];