@rex0220/kintone-sql-tools 3.31.1 → 3.33.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
@@ -4260,6 +4260,15 @@ function containsStatisticalAggregate(value, seen = /* @__PURE__ */ new Set()) {
4260
4260
  if (record.type === "FIELD" && typeof record.field === "string" && /^(STDDEV_POP|STDDEV_SAMP|VAR_POP|VAR_SAMP|MEDIAN|MODE)\(/i.test(record.field)) return true;
4261
4261
  return Object.values(record).some((child) => Array.isArray(child) ? child.some((entry) => containsStatisticalAggregate(entry, seen)) : containsStatisticalAggregate(child, seen));
4262
4262
  }
4263
+ function containsAggregate(value, seen = /* @__PURE__ */ new Set()) {
4264
+ if (value === null || typeof value !== "object") return false;
4265
+ if (seen.has(value)) return false;
4266
+ seen.add(value);
4267
+ const record = value;
4268
+ if ((record.type === "AGGREGATE" || record.type === "AGG_REF") && typeof record.func === "string" && !STATISTICAL_AGGREGATES.has(record.func)) return true;
4269
+ if (record.type === "FIELD" && typeof record.field === "string" && /^(COUNT|SUM|AVG|MIN|MAX|GROUP_CONCAT)\(/i.test(record.field)) return true;
4270
+ return Object.values(record).some((child) => Array.isArray(child) ? child.some((entry) => containsAggregate(entry, seen)) : containsAggregate(child, seen));
4271
+ }
4263
4272
  function completeInputReasons(stmt) {
4264
4273
  const reasons = /* @__PURE__ */ new Set();
4265
4274
  if (isDmlType(stmt.type)) {
@@ -4289,6 +4298,7 @@ function completeInputReasons(stmt) {
4289
4298
  break;
4290
4299
  }
4291
4300
  if (containsStatisticalAggregate(stmt)) reasons.add("STATISTICAL_AGGREGATE");
4301
+ if (containsAggregate(stmt)) reasons.add("AGGREGATE");
4292
4302
  return reasons;
4293
4303
  }
4294
4304
  function requiresCompleteInput(stmt) {
@@ -4297,11 +4307,15 @@ function requiresCompleteInput(stmt) {
4297
4307
  function unionCompleteInputReasons(stmt) {
4298
4308
  const reasons = stmt.left.type === "SELECT" ? selectCompleteInputReasons(stmt.left) : unionCompleteInputReasons(stmt.left);
4299
4309
  addReasons(reasons, selectCompleteInputReasons(stmt.right));
4310
+ if (!stmt.all) reasons.add("DISTINCT");
4300
4311
  return reasons;
4301
4312
  }
4302
4313
  function selectCompleteInputReasons(stmt) {
4303
4314
  const reasons = /* @__PURE__ */ new Set();
4304
- if (normalizeGroupingSpec(stmt).type === "GROUPING_SETS") reasons.add("GROUPING_SETS");
4315
+ const grouping = normalizeGroupingSpec(stmt);
4316
+ if (grouping.type === "GROUPING_SETS") reasons.add("GROUPING_SETS");
4317
+ if (grouping.type === "PLAIN") reasons.add("GROUP_BY");
4318
+ if (stmt.distinct) reasons.add("DISTINCT");
4305
4319
  if (stmt.orderBy.length > 0) reasons.add("LOCAL_ORDER");
4306
4320
  for (const column of stmt.columns) {
4307
4321
  if (column.type === "WINDOW_COL" && column.orderBy.length > 0) reasons.add("WINDOW_ORDER");
@@ -4313,6 +4327,7 @@ function selectCompleteInputReasons(stmt) {
4313
4327
  addReasons(reasons, whereCompleteInputReasons(stmt.where));
4314
4328
  addReasons(reasons, whereCompleteInputReasons(stmt.having));
4315
4329
  if (containsStatisticalAggregate(stmt)) reasons.add("STATISTICAL_AGGREGATE");
4330
+ if (containsAggregate(stmt)) reasons.add("AGGREGATE");
4316
4331
  return reasons;
4317
4332
  }
4318
4333
  function whereCompleteInputReasons(where) {
@@ -6505,19 +6520,19 @@ function collectNonAggregateFieldRefs(node, out) {
6505
6520
  }
6506
6521
  Object.values(value).forEach((item) => collectNonAggregateFieldRefs(item, out));
6507
6522
  }
6508
- function containsAggregate(node) {
6523
+ function containsAggregate2(node) {
6509
6524
  if (node === null || typeof node !== "object") return false;
6510
- if (Array.isArray(node)) return node.some(containsAggregate);
6525
+ if (Array.isArray(node)) return node.some(containsAggregate2);
6511
6526
  const value = node;
6512
6527
  if (value["type"] === "AGG_REF" || value["type"] === "AGG_ARITH") return true;
6513
6528
  if (value["type"] === "SELECT" || value["type"] === "SCALAR_SUBQUERY") return false;
6514
- return Object.values(value).some(containsAggregate);
6529
+ return Object.values(value).some(containsAggregate2);
6515
6530
  }
6516
6531
  function isAggregateMaterializedAlias(column) {
6517
6532
  if (!("alias" in column) || column.alias === null) return false;
6518
6533
  if (column.type === "AGGREGATE" || column.type === "ARITH_AGG_COL") return true;
6519
6534
  if (column.type === "STRFUNC_COL" || column.type === "SCALAR_VALUE_COL") {
6520
- return containsAggregate(column);
6535
+ return containsAggregate2(column);
6521
6536
  }
6522
6537
  return false;
6523
6538
  }
@@ -10893,8 +10908,9 @@ function getLastId(records) {
10893
10908
  return Number.isFinite(id) ? id : 0;
10894
10909
  }
10895
10910
  var FetchAllLimitError = class extends Error {
10896
- constructor(message) {
10911
+ constructor(message, completeInputWrapped = false) {
10897
10912
  super(message);
10913
+ this.completeInputWrapped = completeInputWrapped;
10898
10914
  this.name = "FetchAllLimitError";
10899
10915
  }
10900
10916
  };
@@ -12405,7 +12421,7 @@ function classifyPreGroupAlias(column) {
12405
12421
  case "STRFUNC_COL":
12406
12422
  case "SCALAR_VALUE_COL":
12407
12423
  case "SCALAR_SUBQUERY_COL":
12408
- return containsAggregate(column) || containsAggregateColumnNode(column) ? "AGGREGATE_DEPENDENT" : "SAFE";
12424
+ return containsAggregate2(column) || containsAggregateColumnNode(column) ? "AGGREGATE_DEPENDENT" : "SAFE";
12409
12425
  }
12410
12426
  }
12411
12427
  var SUBTABLE_SYSTEM_COLUMNS = ["_pid", "_rid", "_idx"];
@@ -12530,11 +12546,11 @@ var EMPTY_WILDCARD_FIELD_TYPE_POLICY = {
12530
12546
  UPDATED_TIME: "RECORD",
12531
12547
  USER_SELECT: "RECORD"
12532
12548
  };
12533
- function fieldPolicy(fieldType) {
12549
+ function fieldPolicy(fieldType, fieldCode) {
12534
12550
  const policy = EMPTY_WILDCARD_FIELD_TYPE_POLICY[fieldType];
12535
12551
  if (policy === void 0) {
12536
12552
  throw new Error(
12537
- `InternalError: empty SELECT * schema policy is not defined for field type ${fieldType}.`
12553
+ `ArgumentError: getFields returned unknown fieldType "${fieldType}" for field "${fieldCode}". getFields must return only the fields from /k/v1/app/form/fields.json; $id and $revision are synthesized by the engine and must not be added.`
12538
12554
  );
12539
12555
  }
12540
12556
  return policy;
@@ -12549,10 +12565,10 @@ async function deriveEmptyWildcardColumns(fields, subtableCode, loadProcessStatu
12549
12565
  ];
12550
12566
  }
12551
12567
  const topLevel = fields.filter((field) => !field.inSubtable);
12552
- const needsProcessSettings = topLevel.some((field) => fieldPolicy(field.fieldType) === "PROCESS");
12568
+ const needsProcessSettings = topLevel.some((field) => fieldPolicy(field.fieldType, field.code) === "PROCESS");
12553
12569
  const processEnabled = needsProcessSettings ? (await loadProcessStatuses()).enable : false;
12554
12570
  const columns = topLevel.filter((field) => {
12555
- const policy = fieldPolicy(field.fieldType);
12571
+ const policy = fieldPolicy(field.fieldType, field.code);
12556
12572
  return policy === "RECORD" || policy === "PROCESS" && processEnabled;
12557
12573
  }).map((field) => field.code);
12558
12574
  return [...columns, "$revision", "$id"];
@@ -15448,11 +15464,21 @@ function createEmptyMetrics() {
15448
15464
  cursorCreateOutcomeUnknown: 0,
15449
15465
  cursorQuarantinedCurrent: 0,
15450
15466
  fetchedRows: 0,
15467
+ limitReached: false,
15468
+ limitReachedApps: [],
15451
15469
  elapsedMs: 0
15452
15470
  };
15453
15471
  }
15472
+ var LIMIT_METRICS_SINK = /* @__PURE__ */ Symbol("limitMetricsSink");
15473
+ function markLimitReached(client, appId) {
15474
+ const metrics = client[LIMIT_METRICS_SINK];
15475
+ if (!metrics) return;
15476
+ metrics.limitReached = true;
15477
+ if (!metrics.limitReachedApps.includes(appId)) metrics.limitReachedApps.push(appId);
15478
+ }
15454
15479
  function wrapClientWithMetrics(client, metrics) {
15455
15480
  return {
15481
+ [LIMIT_METRICS_SINK]: metrics,
15456
15482
  getRecords: async (params) => {
15457
15483
  metrics.getCalls += 1;
15458
15484
  const res = await client.getRecords(params);
@@ -15652,7 +15678,7 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
15652
15678
  return executeExistingRecordValidation(stmt, client, options, cacheContext);
15653
15679
  case "SELECT":
15654
15680
  return executeSelect(
15655
- stmt,
15681
+ markCountTotalCountRoot(stmt),
15656
15682
  client,
15657
15683
  options,
15658
15684
  cacheContext,
@@ -16788,7 +16814,7 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
16788
16814
  cteCache
16789
16815
  );
16790
16816
  await resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache);
16791
- const whereCapability = await resolveSelectWhereCapability(stmt, client, cacheContext, cteCache);
16817
+ const whereCapability = rememberSelectWhereCapability(stmt, await resolveSelectWhereCapability(stmt, client, cacheContext, cteCache));
16792
16818
  if (whereCapability.capability === "UNSUPPORTED") {
16793
16819
  throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(whereCapability)}).`);
16794
16820
  }
@@ -17054,11 +17080,21 @@ async function validateSelectGroupingPlanning(stmt, client, cacheContext, materi
17054
17080
  }
17055
17081
  function completeInputErrorPrefix(reasons) {
17056
17082
  const reasonList = [...reasons].join(", ");
17057
- const subject = reasons.size === 1 && reasons.has("STATISTICAL_AGGREGATE") ? "\u7D71\u8A08\u96C6\u7D04\u306E\u6B63\u3057\u3044\u7D50\u679C" : reasons.size === 1 && reasons.has("GROUPING_SETS") ? "\u5C0F\u8A08\u30FB\u7DCF\u8A08\u306E\u6B63\u3057\u3044\u7D50\u679C" : reasons.has("GROUPING_SETS") ? "\u30AF\u30A8\u30EA\u306E\u6B63\u3057\u3044\u7D50\u679C" : "ORDER BY\u306E\u6B63\u3057\u3044\u7D50\u679C";
17083
+ const aggregateSubjects = [
17084
+ ["GROUPING_SETS", "\u5C0F\u8A08\u30FB\u7DCF\u8A08\u306E\u6B63\u3057\u3044\u7D50\u679C"],
17085
+ ["STATISTICAL_AGGREGATE", "\u7D71\u8A08\u96C6\u7D04\u306E\u6B63\u3057\u3044\u7D50\u679C"],
17086
+ ["AGGREGATE", "\u96C6\u8A08\u306E\u6B63\u3057\u3044\u7D50\u679C"],
17087
+ ["GROUP_BY", "\u30B0\u30EB\u30FC\u30D7\u96C6\u8A08\u306E\u6B63\u3057\u3044\u7D50\u679C"],
17088
+ ["DISTINCT", "DISTINCT \u306E\u6B63\u3057\u3044\u7D50\u679C"]
17089
+ ];
17090
+ const hasAggregateReason = aggregateSubjects.some(([reason]) => reasons.has(reason));
17091
+ const hasOrderReason = reasons.has("LOCAL_ORDER") || reasons.has("WINDOW_ORDER");
17092
+ const legacySubject = reasons.size === 1 && reasons.has("STATISTICAL_AGGREGATE") ? "\u7D71\u8A08\u96C6\u7D04\u306E\u6B63\u3057\u3044\u7D50\u679C" : reasons.size === 1 && reasons.has("GROUPING_SETS") ? "\u5C0F\u8A08\u30FB\u7DCF\u8A08\u306E\u6B63\u3057\u3044\u7D50\u679C" : reasons.size === 1 && reasons.has("AGGREGATE") ? "\u96C6\u8A08\u306E\u6B63\u3057\u3044\u7D50\u679C" : reasons.size === 1 && reasons.has("GROUP_BY") ? "\u30B0\u30EB\u30FC\u30D7\u96C6\u8A08\u306E\u6B63\u3057\u3044\u7D50\u679C" : reasons.size === 1 && reasons.has("DISTINCT") ? "DISTINCT \u306E\u6B63\u3057\u3044\u7D50\u679C" : reasons.has("GROUPING_SETS") ? "\u30AF\u30A8\u30EA\u306E\u6B63\u3057\u3044\u7D50\u679C" : "ORDER BY\u306E\u6B63\u3057\u3044\u7D50\u679C";
17093
+ const subject = reasons.has("DML") || reasons.has("VALIDATE") ? legacySubject : hasAggregateReason && hasOrderReason ? "\u30AF\u30A8\u30EA\u306E\u6B63\u3057\u3044\u7D50\u679C" : hasAggregateReason ? aggregateSubjects.find(([reason]) => reasons.has(reason))[1] : "ORDER BY\u306E\u6B63\u3057\u3044\u7D50\u679C";
17058
17094
  return `${subject}\u306B\u306F\u5B8C\u5168\u306A\u5019\u88DC\u96C6\u5408\u304C\u5FC5\u8981\u3067\u3059\u3002complete input reason: ${reasonList}\u3002`;
17059
17095
  }
17060
17096
  function buildCompleteInputPolicy(stmt, options, orderPlan) {
17061
- const reasons = orderPlan?.kind === "CANONICAL_REST_TOP_N" || orderPlan?.kind === "KORDER_NATIVE" || orderPlan?.kind === "KORDER_CURSOR" ? completeInputReasons({ ...stmt, orderBy: [] }) : completeInputReasons(stmt);
17097
+ const reasons = stmt.type === "SELECT" && (orderPlan?.kind === "CANONICAL_REST_TOP_N" || orderPlan?.kind === "KORDER_NATIVE" || orderPlan?.kind === "KORDER_CURSOR") ? completeInputReasons({ ...stmt, orderBy: [] }) : completeInputReasons(stmt);
17062
17098
  const truncateWasDisabled = reasons.size > 0 && options.onLimitReached === "truncate";
17063
17099
  return {
17064
17100
  reasons,
@@ -17067,9 +17103,10 @@ function buildCompleteInputPolicy(stmt, options, orderPlan) {
17067
17103
  };
17068
17104
  }
17069
17105
  function throwCompleteInputError(policy, error) {
17070
- if (policy.reasons.size > 0 && error instanceof FetchAllLimitError) {
17106
+ if (policy.reasons.size > 0 && error instanceof FetchAllLimitError && !error.completeInputWrapped) {
17071
17107
  throw new FetchAllLimitError(
17072
- completeInputErrorPrefix(policy.reasons) + (policy.truncateWasDisabled ? "onLimit=truncate\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002" : "") + error.message
17108
+ completeInputErrorPrefix(policy.reasons) + (policy.truncateWasDisabled ? "onLimit=truncate\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002" : "") + error.message,
17109
+ true
17073
17110
  );
17074
17111
  }
17075
17112
  throw error;
@@ -17214,6 +17251,7 @@ async function executeSimpleSelect(stmt, client, options, cacheContext, orderPla
17214
17251
  onLimit,
17215
17252
  onTruncate: (max) => {
17216
17253
  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`);
17254
+ markLimitReached(client, stmt.from.appId);
17217
17255
  }
17218
17256
  }
17219
17257
  );
@@ -18099,7 +18137,55 @@ function buildSelectFieldTypeResolvers(stmt, fieldTypesByApp) {
18099
18137
  };
18100
18138
  return { row, having };
18101
18139
  }
18140
+ var countTotalCountRootSelects = /* @__PURE__ */ new WeakSet();
18141
+ var resolvedWhereCapabilities = /* @__PURE__ */ new WeakMap();
18142
+ function markCountTotalCountRoot(stmt) {
18143
+ countTotalCountRootSelects.add(stmt);
18144
+ return stmt;
18145
+ }
18146
+ function rememberSelectWhereCapability(stmt, capability) {
18147
+ resolvedWhereCapabilities.set(stmt, capability);
18148
+ return capability;
18149
+ }
18150
+ function isCountStarTotalCountEligible(stmt, whereCapability) {
18151
+ if (whereCapability.capability !== "EXACT_PUSHDOWN") return false;
18152
+ if (stmt.columns.length !== 1) return false;
18153
+ const column = stmt.columns[0];
18154
+ if (column?.type !== "AGGREGATE" || column.func !== "COUNT" || column.distinct || column.arg.type !== "WILDCARD") {
18155
+ return false;
18156
+ }
18157
+ return !stmt.distinct && normalizeGroupingSpec(stmt).type === "NONE" && stmt.having === null && stmt.joins.length === 0 && stmt.from.cteName === null && !stmt.from.subtableCode && stmt.limit === null && stmt.offset === null;
18158
+ }
18159
+ function isValidTotalCount(value) {
18160
+ return typeof value === "string" && /^(?:0|[1-9]\d*)$/.test(value);
18161
+ }
18162
+ async function tryCountStarWithTotalCount(stmt, client) {
18163
+ const countColumn = stmt.columns[0];
18164
+ if (countColumn?.type !== "AGGREGATE") return null;
18165
+ const baseQuery = stmt.where === null ? "" : whereToKintone(stmt.where);
18166
+ const response = await client.getRecords({
18167
+ app: stmt.from.appId,
18168
+ query: `${baseQuery}${baseQuery ? " " : ""}limit 1`,
18169
+ fields: ["$id"],
18170
+ totalCount: true
18171
+ });
18172
+ if (response.searchAborted) throw new SearchAbortedError();
18173
+ if (!isValidTotalCount(response.totalCount)) return null;
18174
+ const column = countColumn.alias ?? "COUNT(*)";
18175
+ return {
18176
+ type: "SELECT",
18177
+ rows: [{ [column]: response.totalCount }],
18178
+ columns: [column],
18179
+ rowCount: 1,
18180
+ warnings: []
18181
+ };
18182
+ }
18102
18183
  async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache, allowOriginalWherePushdown = true, preloadedOrderMeta, prefilterPlan, plainGroupByPlan) {
18184
+ const whereCapability = resolvedWhereCapabilities.get(stmt);
18185
+ if (countTotalCountRootSelects.has(stmt) && whereCapability !== void 0 && isCountStarTotalCountEligible(stmt, whereCapability)) {
18186
+ const totalCountResult = await tryCountStarWithTotalCount(stmt, client);
18187
+ if (totalCountResult !== null) return totalCountResult;
18188
+ }
18103
18189
  const maxRecords = options.maxRecords ?? 1e4;
18104
18190
  const warnings = /* @__PURE__ */ new Set();
18105
18191
  const parallel = options.fetchParallel ?? 1;
@@ -18253,26 +18339,53 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
18253
18339
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
18254
18340
  }
18255
18341
  async function executeUnion(stmt, client, options, cacheContext, captureColumnMeta = false, forLibraryCapture = false) {
18256
- const [leftResult, rightResult] = await Promise.all([
18257
- stmt.left.type === "UNION" ? executeUnion(stmt.left, client, options, cacheContext, captureColumnMeta, forLibraryCapture) : executeSelect(stmt.left, client, options, cacheContext, void 0, captureColumnMeta, forLibraryCapture),
18258
- executeSelect(stmt.right, client, options, cacheContext, void 0, captureColumnMeta, forLibraryCapture)
18259
- ]);
18260
- const leftCols = leftResult.columns;
18261
- const rightCols = rightResult.columns;
18262
- const remappedRight = rightResult.rows.map((row) => {
18263
- const mapped = {};
18264
- leftCols.forEach((col, i) => {
18265
- mapped[col] = row[rightCols[i] ?? col] ?? "";
18342
+ const completePolicy = buildCompleteInputPolicy(stmt, options, null);
18343
+ return withCompleteInputPolicy(completePolicy, async () => {
18344
+ const effectiveOptions = completePolicy.effectiveOptions;
18345
+ const [leftResult, rightResult] = await Promise.all([
18346
+ stmt.left.type === "UNION" ? executeUnion(
18347
+ stmt.left,
18348
+ client,
18349
+ effectiveOptions,
18350
+ cacheContext,
18351
+ captureColumnMeta,
18352
+ forLibraryCapture
18353
+ ) : executeSelect(
18354
+ stmt.left,
18355
+ client,
18356
+ effectiveOptions,
18357
+ cacheContext,
18358
+ void 0,
18359
+ captureColumnMeta,
18360
+ forLibraryCapture
18361
+ ),
18362
+ executeSelect(
18363
+ stmt.right,
18364
+ client,
18365
+ effectiveOptions,
18366
+ cacheContext,
18367
+ void 0,
18368
+ captureColumnMeta,
18369
+ forLibraryCapture
18370
+ )
18371
+ ]);
18372
+ const leftCols = leftResult.columns;
18373
+ const rightCols = rightResult.columns;
18374
+ const remappedRight = rightResult.rows.map((row) => {
18375
+ const mapped = {};
18376
+ leftCols.forEach((col, i) => {
18377
+ mapped[col] = row[rightCols[i] ?? col] ?? "";
18378
+ });
18379
+ return mapped;
18266
18380
  });
18267
- return mapped;
18381
+ const combined = [...leftResult.rows, ...remappedRight];
18382
+ const rows = stmt.all ? combined : deduplicateRows(combined, leftCols);
18383
+ const result = { type: "SELECT", rows, columns: leftCols, rowCount: rows.length };
18384
+ if (captureColumnMeta) {
18385
+ materializedMetaBySelectResult.set(result, mergeUnionColumnMeta(leftResult, rightResult));
18386
+ }
18387
+ return result;
18268
18388
  });
18269
- const combined = [...leftResult.rows, ...remappedRight];
18270
- const rows = stmt.all ? combined : deduplicateRows(combined, leftCols);
18271
- const result = { type: "SELECT", rows, columns: leftCols, rowCount: rows.length };
18272
- if (captureColumnMeta) {
18273
- materializedMetaBySelectResult.set(result, mergeUnionColumnMeta(leftResult, rightResult));
18274
- }
18275
- return result;
18276
18389
  }
18277
18390
  function deduplicateRows(rows, columns) {
18278
18391
  const seen = /* @__PURE__ */ new Set();
@@ -18540,6 +18653,7 @@ async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords, par
18540
18653
  const fields = selectToFetchAllFields(stmt, table, plainGroupByPlan);
18541
18654
  const onTruncate = (max) => {
18542
18655
  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`);
18656
+ markLimitReached(client, table.appId);
18543
18657
  };
18544
18658
  if (!table.subtableCode) {
18545
18659
  const baseQuery = isMainTable && allowOriginalWherePushdown ? selectToFetchAllParams(stmt, table.appId).query : "";
@@ -18695,6 +18809,7 @@ async function tryFetchJoinRecordsBySourceKeys(stmt, join2, tables, client, maxR
18695
18809
  const fields = selectToFetchAllFields(stmt, join2.table, plainGroupByPlan);
18696
18810
  const onTruncate = (max) => {
18697
18811
  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`);
18812
+ markLimitReached(client, join2.table.appId);
18698
18813
  };
18699
18814
  const chunks = splitChunks(values, JOIN_IN_CHUNK_SIZE);
18700
18815
  const merged = [];
@@ -22714,11 +22829,12 @@ function buildValidatePlan(stmt, label) {
22714
22829
  lines.push(" records/mutation API during EXPLAIN: none; violation count unavailable");
22715
22830
  return lines;
22716
22831
  }
22717
- function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlans) {
22832
+ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlans, allowTotalCountPlan = true) {
22718
22833
  const whereCapability = capabilities?.get(stmt) ?? (capabilities ? [...capabilities].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
22719
22834
  const orderPlan = orderPlans?.get(stmt) ?? (orderPlans ? [...orderPlans].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
22720
22835
  const plainGroupByPlan = plainGroupByPlans?.get(stmt) ?? (plainGroupByPlans ? [...plainGroupByPlans].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
22721
- const mode = orderPlan?.kind === "CANONICAL_LOCAL" ? "FULL_SCAN" : whereCapability && whereCapability.capability !== "EXACT_PUSHDOWN" ? "FULL_SCAN" : resolveSelectMode(stmt);
22836
+ const totalCountPlan = allowTotalCountPlan && whereCapability !== void 0 && isCountStarTotalCountEligible(stmt, whereCapability);
22837
+ const mode = totalCountPlan ? "COUNT_TOTAL_COUNT" : orderPlan?.kind === "CANONICAL_LOCAL" ? "FULL_SCAN" : whereCapability && whereCapability.capability !== "EXACT_PUSHDOWN" ? "FULL_SCAN" : resolveSelectMode(stmt);
22722
22838
  const reasons = collectFullScanReasons(stmt);
22723
22839
  if (whereCapability && whereCapability.capability !== "EXACT_PUSHDOWN") {
22724
22840
  reasons.push(...whereCapability.reasons.map((reason) => reason.code));
@@ -22765,6 +22881,20 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlan
22765
22881
  }
22766
22882
  }
22767
22883
  }
22884
+ if (totalCountPlan) {
22885
+ const baseQuery = stmt.where === null ? "" : whereToKintone(stmt.where);
22886
+ lines.push(` app: APP${stmt.from.appId} (${stmt.from.appId})`);
22887
+ lines.push(
22888
+ ` kintone query: ${baseQuery}${baseQuery ? " " : ""}limit 1`
22889
+ );
22890
+ lines.push(" fields: $id");
22891
+ lines.push(" fetch API: GET records.json (totalCount=true)");
22892
+ lines.push(" REST execution: single GET");
22893
+ lines.push(" record limit: maxRecords/onLimitReached not applied");
22894
+ lines.push(" fallback: full record scan when totalCount is missing or invalid");
22895
+ lines.push(" search abort: fail-closed (SearchAbortedError)");
22896
+ return lines;
22897
+ }
22768
22898
  if (orderPlan) {
22769
22899
  lines.push(` order plan: ${orderPlan.kind}`);
22770
22900
  if (orderPlan.reasonCodes.length > 0) lines.push(` order reason: ${orderPlan.reasonCodes.join(", ")}`);
@@ -22901,7 +23031,7 @@ function buildUnionPlan(stmt, capabilities, orderPlans, plainGroupByPlans) {
22901
23031
  const lines = [];
22902
23032
  selects.forEach((sel, i) => {
22903
23033
  if (i > 0) lines.push("");
22904
- lines.push(...buildSelectPlan(sel, `[union:${i + 1}]`, capabilities, orderPlans, plainGroupByPlans));
23034
+ lines.push(...buildSelectPlan(sel, `[union:${i + 1}]`, capabilities, orderPlans, plainGroupByPlans, false));
22905
23035
  });
22906
23036
  return lines;
22907
23037
  }
@@ -22909,7 +23039,7 @@ function buildWithPlan(stmt, capabilities, orderPlans, plainGroupByPlans) {
22909
23039
  const lines = [];
22910
23040
  for (const cte of stmt.ctes) {
22911
23041
  if (cte.query.type === "SELECT") {
22912
- lines.push(...buildSelectPlan(cte.query, `[cte: ${cte.name}]`, capabilities, orderPlans, plainGroupByPlans));
23042
+ lines.push(...buildSelectPlan(cte.query, `[cte: ${cte.name}]`, capabilities, orderPlans, plainGroupByPlans, false));
22913
23043
  lines.push("");
22914
23044
  }
22915
23045
  }
@@ -22928,7 +23058,7 @@ function buildWithPlan(stmt, capabilities, orderPlans, plainGroupByPlans) {
22928
23058
  if (canInlineSingleCte(stmt)) {
22929
23059
  lines.push("");
22930
23060
  const inlined = buildInlinedQuery(stmt);
22931
- lines.push(...buildSelectPlan(inlined, "[effective: inlined CTE]", capabilities, orderPlans, plainGroupByPlans));
23061
+ lines.push(...buildSelectPlan(inlined, "[effective: inlined CTE]", capabilities, orderPlans, plainGroupByPlans, false));
22932
23062
  }
22933
23063
  return lines;
22934
23064
  }
@@ -22970,16 +23100,16 @@ function collectSubqueryPlans(stmt, capabilities, orderPlans, plainGroupByPlans)
22970
23100
  case "BINARY":
22971
23101
  if (w.right.type === "SCALAR_SUBQUERY") {
22972
23102
  lines.push("");
22973
- lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans));
23103
+ lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans, false));
22974
23104
  }
22975
23105
  if (w.right.type === "SUBQUERY_IN_LIST") {
22976
23106
  lines.push("");
22977
- lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans));
23107
+ lines.push(...buildSelectPlan(w.right.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans, false));
22978
23108
  }
22979
23109
  break;
22980
23110
  case "EXISTS":
22981
23111
  lines.push("");
22982
- lines.push(...buildSelectPlan(w.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans));
23112
+ lines.push(...buildSelectPlan(w.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans, false));
22983
23113
  break;
22984
23114
  case "LOGICAL":
22985
23115
  visitWhere(w.left);
@@ -22999,7 +23129,7 @@ function collectSubqueryPlans(stmt, capabilities, orderPlans, plainGroupByPlans)
22999
23129
  for (const col of stmt.columns) {
23000
23130
  if (col.type === "SCALAR_SUBQUERY_COL") {
23001
23131
  lines.push("");
23002
- lines.push(...buildSelectPlan(col.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans));
23132
+ lines.push(...buildSelectPlan(col.query, `[subquery:${idx++}]`, capabilities, orderPlans, plainGroupByPlans, false));
23003
23133
  }
23004
23134
  }
23005
23135
  if (stmt.having) visitWhere(stmt.having);
@@ -23025,7 +23155,7 @@ function buildInsertSelectPlan(stmt, label, capabilities, orderPlans, plainGroup
23025
23155
  lines.push(` fields: ${stmt.fields.join(", ")}`);
23026
23156
  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`);
23027
23157
  lines.push("");
23028
- lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans, plainGroupByPlans));
23158
+ lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans, plainGroupByPlans, false));
23029
23159
  return lines;
23030
23160
  }
23031
23161
  function buildUpdatePlan(stmt, label, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, maxRecords = 1e4) {
@@ -23072,7 +23202,7 @@ function buildUpdatePlan(stmt, label, capabilities, orderPlans, dmlMaxRows = 100
23072
23202
  for (const a of stmt.assignments) {
23073
23203
  if (a.value.type === "SCALAR_SUBQUERY") {
23074
23204
  lines.push("");
23075
- lines.push(...buildSelectPlan(a.value.query, `[subquery: ${a.field}]`, capabilities, orderPlans));
23205
+ lines.push(...buildSelectPlan(a.value.query, `[subquery: ${a.field}]`, capabilities, orderPlans, void 0, false));
23076
23206
  }
23077
23207
  }
23078
23208
  return lines;
@@ -23189,7 +23319,7 @@ function buildUpsertSelectPlan(stmt, label, capabilities, orderPlans, plainGroup
23189
23319
  ` 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`,
23190
23320
  ``
23191
23321
  ];
23192
- lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans, plainGroupByPlans));
23322
+ lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans, plainGroupByPlans, false));
23193
23323
  return lines;
23194
23324
  }
23195
23325
  function buildReorderPlan(stmt, label) {
@@ -24493,7 +24623,8 @@ function createNodeKintoneConnection(baseUrl, tokenResolver) {
24493
24623
  const queryPart = `query=${encodeURIComponent(params.query)}`;
24494
24624
  const appPart = `app=${encodeURIComponent(String(params.app))}`;
24495
24625
  const fieldParts = params.fields.map((f) => `fields[]=${encodeURIComponent(f)}`);
24496
- const qs = [appPart, queryPart, ...fieldParts].join("&");
24626
+ const totalCountPart = params.totalCount === true ? ["totalCount=true"] : [];
24627
+ const qs = [appPart, queryPart, ...fieldParts, ...totalCountPart].join("&");
24497
24628
  if (tokenResolver.debug) {
24498
24629
  tokenResolver.log?.(
24499
24630
  `[debug] getRecords app=${params.app} query="${params.query}" fields=${params.fields.length > 0 ? params.fields.join(",") : "(all)"} auth=${tokenResolver.auth.type}`