@rex0220/kintone-sql-tools 3.72.0 → 3.74.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/README.md CHANGED
@@ -289,6 +289,15 @@ try {
289
289
  }
290
290
  ```
291
291
 
292
+ 読取上限は既定 10,000 件(`maxRecords`)です。一時テーブルの実体化には**独立の** `tempTableMaxRows`(既定 10,000 行・超過は常にエラー)が適用されるため、大きなバッチでは両方を併せて指定してください:
293
+
294
+ ```ts
295
+ const ctx = createExecutionContext({ client, script: source, maxRecords: 25000, tempTableMaxRows: 25000 });
296
+ await explainScript(source, { client, maxRecords: 25000 }); // EXPLAIN 面にも同じ上限を渡せる
297
+ ```
298
+
299
+ `executeStatement` / `previewStatement` は実行コンテキストの `maxRecords` を共有します(3 面同値)。詳細は[言語リファレンス §27.9](docs/ksql_language_reference.md)。
300
+
292
301
  ### エンジンバージョン × dialect 対応表
293
302
 
294
303
  | エンジン | dialect 0(既定・宣言なし) | dialect 1(`-- @ksql dialect: 1`) | ksql-flow |
package/dist-cli/ksql.js CHANGED
@@ -18466,6 +18466,24 @@ var materializedMetaBySelectResult = /* @__PURE__ */ new WeakMap();
18466
18466
  var materializedMetaByValidationResult = /* @__PURE__ */ new WeakMap();
18467
18467
  var importSourceByDmlStatement = /* @__PURE__ */ new WeakMap();
18468
18468
  var statementEvaluationContextKey = /* @__PURE__ */ Symbol("statementEvaluationContext");
18469
+ var nativeUpsertExecutionKey = /* @__PURE__ */ Symbol("nativeUpsertExecution");
18470
+ var nativeUpsertExplainCapabilityKey = /* @__PURE__ */ Symbol("nativeUpsertExplainCapability");
18471
+ function withNativeUpsertExecutionOption(options, enabled, explainClientCapability) {
18472
+ return {
18473
+ ...options,
18474
+ [nativeUpsertExecutionKey]: enabled,
18475
+ ...explainClientCapability === void 0 ? {} : { [nativeUpsertExplainCapabilityKey]: explainClientCapability }
18476
+ };
18477
+ }
18478
+ function nativeUpsertExecutionEnabled(options) {
18479
+ return options[nativeUpsertExecutionKey] === true;
18480
+ }
18481
+ function hasNativeUpsertExecutionOption(options) {
18482
+ return nativeUpsertExecutionKey in options;
18483
+ }
18484
+ function nativeUpsertExplainClientCapability(options) {
18485
+ return options[nativeUpsertExplainCapabilityKey];
18486
+ }
18469
18487
  function bindStatementEvaluationContext(options) {
18470
18488
  const internal = options;
18471
18489
  if (internal[statementEvaluationContextKey]) return options;
@@ -18530,6 +18548,7 @@ function createEmptyMetrics() {
18530
18548
  getCalls: 0,
18531
18549
  postCalls: 0,
18532
18550
  putCalls: 0,
18551
+ nativeUpsertCalls: 0,
18533
18552
  deleteCalls: 0,
18534
18553
  fieldCalls: 0,
18535
18554
  numberPrecisionCalls: 0,
@@ -18558,7 +18577,7 @@ function markLimitReached(client, appId) {
18558
18577
  if (!metrics.limitReachedApps.includes(appId)) metrics.limitReachedApps.push(appId);
18559
18578
  }
18560
18579
  function wrapClientWithMetrics(client, metrics) {
18561
- return {
18580
+ const wrapped = {
18562
18581
  [LIMIT_METRICS_SINK]: metrics,
18563
18582
  getRecords: async (params) => {
18564
18583
  metrics.getCalls += 1;
@@ -18637,6 +18656,14 @@ function wrapClientWithMetrics(client, metrics) {
18637
18656
  return client.getProcessStatuses(appId);
18638
18657
  }
18639
18658
  };
18659
+ if (clientHasNativeUpsert(client)) {
18660
+ wrapped.upsertRecords = (params) => {
18661
+ metrics.putCalls += 1;
18662
+ metrics.nativeUpsertCalls += 1;
18663
+ return client.upsertRecords(params);
18664
+ };
18665
+ }
18666
+ return wrapped;
18640
18667
  }
18641
18668
  var SEARCH_ABORT_FAIL_CLOSED = /* @__PURE__ */ Symbol("searchAbortFailClosed");
18642
18669
  function wrapClientWithSearchAbort(client, collector, failClosed) {
@@ -18809,7 +18836,13 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
18809
18836
  relativeDatePlan,
18810
18837
  options.recursiveCteMaxDepth,
18811
18838
  options.recursiveCteMaxRows,
18812
- options.recursiveCteMaxExpansions
18839
+ options.recursiveCteMaxExpansions,
18840
+ options.resolveMetadata !== false,
18841
+ hasNativeUpsertExecutionOption(options) ? {
18842
+ surface: "CLI",
18843
+ enableNativeUpsert: nativeUpsertExecutionEnabled(options),
18844
+ clientHasNativeUpsert: nativeUpsertExplainClientCapability(options) ?? clientHasNativeUpsert(client)
18845
+ } : { surface: "DOCUMENT_ONLY" }
18813
18846
  );
18814
18847
  // 一時テーブルはバッチスコープのため単文実行では拒否する(executeBatch を使う)
18815
18848
  case "CREATE_TEMP_TABLE":
@@ -22813,6 +22846,206 @@ function upsertCompositeKey(parts) {
22813
22846
  function upsertNormalizedKey(parts, numericKey) {
22814
22847
  return JSON.stringify(parts.map((p, i) => numericKey[i] ? normalizeKeyPart(p) : p));
22815
22848
  }
22849
+ var NATIVE_UPSERT_CONDITION_NAMES = {
22850
+ 1: "CLIENT_CAPABILITY",
22851
+ 2: "OPT_IN",
22852
+ 3: "KEY_SCHEMA",
22853
+ 4: "PLAIN_UPSERT",
22854
+ 5: "EMPTY_KEY",
22855
+ 6: "SOURCE_DUPLICATE"
22856
+ };
22857
+ function nativeUpsertExplainReason(condition, unknown, surface, unknownConditions = []) {
22858
+ if (unknown) {
22859
+ if (condition === 1) return "\u901A\u5E38\u5B9F\u884C client \u306E native \u80FD\u529B\u304C\u4E0D\u660E";
22860
+ if (condition === 2) return "native \u8A2D\u5B9A\u304C\u4E0D\u660E";
22861
+ if (condition === 3) return "\u30D5\u30A9\u30FC\u30E0\u30E1\u30BF\u30C7\u30FC\u30BF\u672A\u53D6\u5F97";
22862
+ if (condition === 5 || unknownConditions.includes(5)) return "\u30BD\u30FC\u30B9\u884C\u672A materialize";
22863
+ if (condition === 6) return "\u30AD\u30FC\u578B\u60C5\u5831\u304C\u672A\u78BA\u5B9A\u306E\u305F\u3081\u91CD\u8907\u5224\u5B9A\u4E0D\u80FD";
22864
+ }
22865
+ if (condition === 1) return "client \u304C upsertRecords \u80FD\u529B\u3092\u6301\u305F\u306A\u3044";
22866
+ if (condition === 2) return surface === "CLI" ? "--native-upsert \u304C\u6307\u5B9A\u3055\u308C\u3066\u3044\u306A\u3044" : "enableNativeUpsert \u304C false";
22867
+ if (condition === 3) return "\u30AD\u30FC\u9805\u76EE\u306F\u91CD\u8907\u7981\u6B62\u306E SINGLE_LINE_TEXT \u307E\u305F\u306F NUMBER \u3067\u306F\u306A\u3044";
22868
+ if (condition === 4) return "CHECK / APPLY / IMPORT / VALIDATE ONLY / ON ERROR SKIP \u3092\u4F34\u3046\u7D20\u3067\u306A\u3044 UPSERT";
22869
+ if (condition === 5) return "\u30BD\u30FC\u30B9\u306B\u7A7A\u6587\u5B57\u30AD\u30FC\u304C\u3042\u308B";
22870
+ return "\u30BD\u30FC\u30B9\u5185\u306B\u540C\u4E00\u30AD\u30FC\u304C\u3042\u308B";
22871
+ }
22872
+ function renderNativeUpsertEligibilityResult(label, result, surface) {
22873
+ if (result.status === "ELIGIBLE") {
22874
+ return ` ${label}: ELIGIBLE\uFF08${label.includes("statement/data") ? "\u6761\u4EF6 3\u301C6 \u3092" : "6 \u6761\u4EF6\u3092\u3059\u3079\u3066"}\u6E80\u305F\u3059\uFF09`;
22875
+ }
22876
+ if (result.status === "INELIGIBLE") {
22877
+ return ` ${label}: INELIGIBLE\uFF08\u6761\u4EF6 ${result.condition}: ${NATIVE_UPSERT_CONDITION_NAMES[result.condition]} \u2014 ${nativeUpsertExplainReason(result.condition, false, surface)}\uFF09`;
22878
+ }
22879
+ const unknownConditionNumbers = result.unknownConditions.map(({ condition }) => condition);
22880
+ const details = result.unknownConditions.map(
22881
+ ({ condition }) => `\u6761\u4EF6 ${condition}: ${NATIVE_UPSERT_CONDITION_NAMES[condition]} \u2014 ${nativeUpsertExplainReason(condition, true, surface, unknownConditionNumbers)}`
22882
+ );
22883
+ return ` ${label}: UNKNOWN\uFF08${details.join("; ")}\uFF09`;
22884
+ }
22885
+ function renderNativeUpsertEligibility(evaluation, surface) {
22886
+ if (surface === "DOCUMENT_ONLY") {
22887
+ return [
22888
+ renderNativeUpsertEligibilityResult("native UPSERT statement/data eligibility", evaluation.statement, surface),
22889
+ ` native UPSERT execution surface: NOT_APPLICABLE\uFF08\u3053\u306E\u9762\u3067\u306F\u5B9F\u884C\u3057\u306A\u3044${evaluation.statement.status === "ELIGIBLE" ? "\u3002/flow \u307E\u305F\u306F CLI --native-upsert \u3067\u306F native \u5019\u88DC" : ""}\uFF09`
22890
+ ];
22891
+ }
22892
+ const lines = [renderNativeUpsertEligibilityResult("native UPSERT eligibility", evaluation.execution, surface)];
22893
+ if (evaluation.conditions[1].state === "FAIL" || evaluation.conditions[2].state === "FAIL") {
22894
+ lines.push(renderNativeUpsertEligibilityResult("native UPSERT statement/data eligibility", evaluation.statement, surface));
22895
+ }
22896
+ return lines;
22897
+ }
22898
+ function explainNativeUpsertStatement(statement) {
22899
+ const candidate = statement.type === "EXPLAIN" ? statement.query : statement;
22900
+ return candidate.type === "UPSERT" || candidate.type === "UPSERT_SELECT" ? candidate : null;
22901
+ }
22902
+ function nativeUpsertExplainEvaluation(statement, fieldInfos, options) {
22903
+ let rowKeyValues = null;
22904
+ if (statement.type === "UPSERT") {
22905
+ try {
22906
+ rowKeyValues = buildUpsertRowKeyValues(statement);
22907
+ } catch {
22908
+ }
22909
+ }
22910
+ return evaluateNativeUpsertEligibility({
22911
+ surface: options.surface === "DOCUMENT_ONLY" ? "DOCUMENT_ONLY" : "EXECUTION",
22912
+ clientCapability: options.surface === "DOCUMENT_ONLY" ? null : options.clientHasNativeUpsert ?? null,
22913
+ enabled: options.surface === "DOCUMENT_ONLY" ? null : options.surface === "FLOW" ? options.enableNativeUpsert !== false : options.enableNativeUpsert === true,
22914
+ statement: {
22915
+ kind: statement.type === "UPSERT" ? "VALUES" : "SELECT",
22916
+ keyFields: statement.keyFields,
22917
+ hasCheck: Boolean(statement.checkGroups?.length),
22918
+ hasApply: statement.type === "UPSERT" && Boolean(statement.onInsertApplyBlocks?.length || statement.onUpdateApplyBlocks?.length),
22919
+ validateOnly: statement.validateOnly === true,
22920
+ onErrorSkip: statement.onErrorSkip === true,
22921
+ importDerived: importSourceByDmlStatement.has(statement)
22922
+ },
22923
+ fieldInfos,
22924
+ rowKeyValues
22925
+ });
22926
+ }
22927
+ async function nativeUpsertExplainLines(statement, cacheContext, options) {
22928
+ const upsert = explainNativeUpsertStatement(statement);
22929
+ if (!upsert) return [];
22930
+ const fieldInfos = await getFieldsIfCached(upsert.appId, cacheContext);
22931
+ return renderNativeUpsertEligibility(
22932
+ nativeUpsertExplainEvaluation(upsert, fieldInfos, options),
22933
+ options.surface
22934
+ );
22935
+ }
22936
+ function evaluateNativeUpsertEligibility(input) {
22937
+ const keyField = input.statement.keyFields.length === 1 ? input.statement.keyFields[0] : void 0;
22938
+ const keyInfo = keyField === void 0 || input.fieldInfos === null ? void 0 : input.fieldInfos.find((field) => field.code === keyField);
22939
+ const supportedKeyType = keyInfo?.fieldType === "SINGLE_LINE_TEXT" || keyInfo?.fieldType === "NUMBER";
22940
+ const schemaPass = input.fieldInfos === null ? null : Boolean(
22941
+ keyField !== void 0 && keyInfo && supportedKeyType && keyInfo.isUnique === true
22942
+ );
22943
+ const plain = !input.statement.hasCheck && !input.statement.hasApply && !input.statement.validateOnly && !input.statement.onErrorSkip && !(input.statement.kind === "SELECT" && input.statement.importDerived);
22944
+ const rows = input.rowKeyValues;
22945
+ const noEmpty = rows === null ? null : rows.every((parts) => parts.every((part) => part !== ""));
22946
+ let noDuplicates = null;
22947
+ if (rows !== null && keyField !== void 0 && supportedKeyType) {
22948
+ const seen = /* @__PURE__ */ new Set();
22949
+ noDuplicates = true;
22950
+ for (const parts of rows) {
22951
+ const normalized = upsertNormalizedKey([...parts], [keyInfo.fieldType === "NUMBER"]);
22952
+ if (seen.has(normalized)) {
22953
+ noDuplicates = false;
22954
+ break;
22955
+ }
22956
+ seen.add(normalized);
22957
+ }
22958
+ }
22959
+ const state = (value, reason) => ({
22960
+ state: value === null ? "UNKNOWN" : value ? "PASS" : "FAIL",
22961
+ reason
22962
+ });
22963
+ const conditions = {
22964
+ 1: input.surface === "DOCUMENT_ONLY" ? { state: "NOT_APPLICABLE", reason: "client capability is not applicable on this surface" } : state(input.clientCapability, "client does not provide upsertRecords"),
22965
+ 2: input.surface === "DOCUMENT_ONLY" ? { state: "NOT_APPLICABLE", reason: "native setting is not applicable on this surface" } : state(input.enabled, "native UPSERT is disabled"),
22966
+ 3: state(schemaPass, "update key schema is not a single unique text or number field"),
22967
+ 4: state(plain, "statement is not a plain UPSERT"),
22968
+ 5: state(noEmpty, "source contains an empty update key"),
22969
+ 6: state(noDuplicates, "source contains duplicate update keys")
22970
+ };
22971
+ const summarize = (ordered) => {
22972
+ const failed = ordered.find((condition) => conditions[condition].state === "FAIL");
22973
+ if (failed !== void 0) return { status: "INELIGIBLE", condition: failed, reason: conditions[failed].reason };
22974
+ const unknownConditions = ordered.filter((condition) => conditions[condition].state === "UNKNOWN").map((condition) => ({ condition, reason: conditions[condition].reason }));
22975
+ return unknownConditions.length > 0 ? { status: "UNKNOWN", unknownConditions } : { status: "ELIGIBLE" };
22976
+ };
22977
+ return {
22978
+ conditions,
22979
+ execution: summarize([1, 2, 3, 4, 5, 6]),
22980
+ statement: summarize([3, 4, 5, 6])
22981
+ };
22982
+ }
22983
+ function clientHasNativeUpsert(client) {
22984
+ return "upsertRecords" in client && typeof client.upsertRecords === "function";
22985
+ }
22986
+ function nativeEligibilityInput(stmt, client, options, fieldInfos, rowKeyValues) {
22987
+ return {
22988
+ surface: "EXECUTION",
22989
+ clientCapability: clientHasNativeUpsert(client),
22990
+ enabled: nativeUpsertExecutionEnabled(options),
22991
+ statement: {
22992
+ kind: stmt.type === "UPSERT" ? "VALUES" : "SELECT",
22993
+ keyFields: stmt.keyFields,
22994
+ hasCheck: Boolean(stmt.checkGroups?.length),
22995
+ hasApply: stmt.type === "UPSERT" && Boolean(stmt.onInsertApplyBlocks?.length || stmt.onUpdateApplyBlocks?.length),
22996
+ validateOnly: stmt.validateOnly === true,
22997
+ onErrorSkip: stmt.onErrorSkip === true,
22998
+ importDerived: importSourceByDmlStatement.has(stmt)
22999
+ },
23000
+ fieldInfos,
23001
+ rowKeyValues
23002
+ };
23003
+ }
23004
+ var NativeUpsertResponseError = class extends Error {
23005
+ constructor() {
23006
+ super("NativeUpsertResponseError: upsertRecords returned an invalid response.");
23007
+ this.name = "NativeUpsertResponseError";
23008
+ }
23009
+ };
23010
+ function validateNativeUpsertResponse(response, expectedRecords) {
23011
+ if (!response || !Array.isArray(response.records) || response.records.length !== expectedRecords) {
23012
+ throw new NativeUpsertResponseError();
23013
+ }
23014
+ let insertedCount = 0;
23015
+ let updatedCount = 0;
23016
+ for (const record of response.records) {
23017
+ if (!record || typeof record.id !== "string" || typeof record.revision !== "string" || record.operation !== "INSERT" && record.operation !== "UPDATE") {
23018
+ throw new NativeUpsertResponseError();
23019
+ }
23020
+ if (record.operation === "INSERT") insertedCount += 1;
23021
+ else updatedCount += 1;
23022
+ }
23023
+ return { insertedCount, updatedCount };
23024
+ }
23025
+ async function executeNativeUpsertRecords(appId, keyField, records, rowKeyValues, client, options) {
23026
+ if (records.length === 0) return { type: "UPSERT", insertedCount: 0, updatedCount: 0 };
23027
+ if (options.confirm) {
23028
+ const ok = await options.confirm(records.length, "UPDATE");
23029
+ if (!ok) throw new OperationCancelledError("UPDATE", records.length);
23030
+ }
23031
+ let insertedCount = 0;
23032
+ let updatedCount = 0;
23033
+ for (let offset = 0; offset < records.length; offset += 100) {
23034
+ const nativeRecords = records.slice(offset, offset + 100).map((record, index) => {
23035
+ const payload = {};
23036
+ for (const [field, value] of Object.entries(record)) if (field !== keyField) payload[field] = value;
23037
+ return {
23038
+ updateKey: { field: keyField, value: rowKeyValues[offset + index][0] },
23039
+ record: payload
23040
+ };
23041
+ });
23042
+ const response = await client.upsertRecords({ app: appId, upsert: true, records: nativeRecords });
23043
+ const counts = validateNativeUpsertResponse(response, nativeRecords.length);
23044
+ insertedCount += counts.insertedCount;
23045
+ updatedCount += counts.updatedCount;
23046
+ }
23047
+ return { type: "UPSERT", insertedCount, updatedCount };
23048
+ }
22816
23049
  function lookupUpsertTarget(index, keyParts) {
22817
23050
  const exact = index.raw.get(upsertCompositeKey(keyParts));
22818
23051
  if (exact !== void 0) return exact;
@@ -23029,6 +23262,10 @@ async function getFieldsCached(appId, client, cacheContext) {
23029
23262
  setScopedCacheValue(fieldInfoCache, cacheContext, appId, loading);
23030
23263
  return loading;
23031
23264
  }
23265
+ async function getFieldsIfCached(appId, cacheContext) {
23266
+ const cached = getScopedCacheValue(fieldInfoCache, cacheContext, appId);
23267
+ return cached ? await cached : null;
23268
+ }
23032
23269
  async function getNumberPrecisionCached(appId, client, cacheContext) {
23033
23270
  const cached = getScopedCacheValue(numberPrecisionCache, cacheContext, appId);
23034
23271
  if (cached) return cached;
@@ -25596,6 +25833,16 @@ async function executeDelete(stmt, client, options, cacheContext) {
25596
25833
  }
25597
25834
  return { type: "DELETE", deletedCount: ids.length };
25598
25835
  }
25836
+ function materializeUpsertValueRecords(stmt, fieldTypes, options) {
25837
+ return stmt.values.map((row) => {
25838
+ const record = {};
25839
+ stmt.fields.forEach((field, index) => {
25840
+ const value = row[index];
25841
+ record[field] = { value: value.type === "CASE_VALUE" ? evalCaseWhenValue(value.expr, {}, fieldTypes.get(field), statementEvaluationContext(options)) : toKintoneValue(value, fieldTypes.get(field)) };
25842
+ });
25843
+ return record;
25844
+ });
25845
+ }
25599
25846
  async function executeUpsert(stmt, client, options, cacheContext) {
25600
25847
  if (stmt.onInsertApplyBlocks?.length || stmt.onUpdateApplyBlocks?.length) {
25601
25848
  return executeApplyUpsert(stmt, client, options, cacheContext);
@@ -25607,6 +25854,14 @@ async function executeUpsert(stmt, client, options, cacheContext) {
25607
25854
  const toUpdate = [];
25608
25855
  const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
25609
25856
  const rowKeyValues = buildUpsertRowKeyValues(stmt);
25857
+ const nativeEligibility = evaluateNativeUpsertEligibility(
25858
+ nativeEligibilityInput(stmt, client, options, fieldInfos, rowKeyValues)
25859
+ );
25860
+ if (nativeEligibility.execution.status === "ELIGIBLE") {
25861
+ const records = materializeUpsertValueRecords(stmt, fieldTypes, options);
25862
+ assertValidDmlRecords(records, stmt.fields, fieldInfos, numberPrecision);
25863
+ return executeNativeUpsertRecords(stmt.appId, stmt.keyFields[0], records, rowKeyValues, client, options);
25864
+ }
25610
25865
  const targetIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeyValues, client, options, fieldTypes);
25611
25866
  stmt.values.forEach((row, rowIdx) => {
25612
25867
  const record = {};
@@ -26095,6 +26350,12 @@ async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache
26095
26350
  const rowKeyValues = records.map(
26096
26351
  (record) => stmt.keyFields.map((key) => String(record[key]?.value ?? ""))
26097
26352
  );
26353
+ const nativeEligibility = evaluateNativeUpsertEligibility(
26354
+ nativeEligibilityInput(stmt, client, options, fieldInfos, rowKeyValues)
26355
+ );
26356
+ if (nativeEligibility.execution.status === "ELIGIBLE") {
26357
+ return executeNativeUpsertRecords(stmt.appId, stmt.keyFields[0], records, rowKeyValues, client, options);
26358
+ }
26098
26359
  if (importSourceByDmlStatement.has(stmt)) {
26099
26360
  const numericKey = stmt.keyFields.map((key) => fieldTypes.get(key) === "NUMBER");
26100
26361
  const sourceKeys = /* @__PURE__ */ new Set();
@@ -26524,6 +26785,10 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
26524
26785
  return;
26525
26786
  }
26526
26787
  const typed = node;
26788
+ if (typed["type"] === "UPSERT" || typed["type"] === "UPSERT_SELECT") {
26789
+ const upsert = node;
26790
+ await getFieldsCached(upsert.appId, tracedClient, cacheContext);
26791
+ }
26527
26792
  if (typed["type"] === "SELECT") {
26528
26793
  const select = node;
26529
26794
  await validateSelectGroupingPlanning(select, tracedClient, cacheContext);
@@ -27146,7 +27411,7 @@ var EXPLAIN_FETCH_PLAN = /* @__PURE__ */ Symbol("ksql.explainFetchPlan");
27146
27411
  function setExplainFetchPlan(result, plan) {
27147
27412
  result[EXPLAIN_FETCH_PLAN] = plan;
27148
27413
  }
27149
- async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords = 1e4, cursorMaxActive = 2, enableImport = false, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, resolveMetadata = true, recursiveCteMaxDepth, recursiveCteMaxRows, recursiveCteMaxExpansions, asOf, timezone) {
27414
+ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords = 1e4, cursorMaxActive = 2, enableImport = false, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, resolveMetadata = true, recursiveCteMaxDepth, recursiveCteMaxRows, recursiveCteMaxExpansions, asOf, timezone, nativeUpsertOptions = { surface: "DOCUMENT_ONLY" }) {
27150
27415
  const asOfClock = createAsOfClock(asOf ?? /* @__PURE__ */ new Date(), timezone);
27151
27416
  const recursiveLimits = resolveRecursiveCteLimits({
27152
27417
  recursiveCteMaxDepth,
@@ -27245,10 +27510,15 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
27245
27510
  maxRecords,
27246
27511
  dmlMaxRows
27247
27512
  ) : [];
27513
+ const nativeUpsertPlan = await nativeUpsertExplainLines(
27514
+ planStmt,
27515
+ invocationCacheContext,
27516
+ nativeUpsertOptions
27517
+ );
27248
27518
  plans.push({
27249
27519
  index: i,
27250
27520
  type: analysis.statements[i].statementType,
27251
- plan: statementPlan.length === 0 ? [...metadataPlan, ...dialect1Estimate] : [statementPlan[0], ...metadataPlan, ...statementPlan.slice(1), ...dialect1Estimate]
27521
+ plan: statementPlan.length === 0 ? [...metadataPlan, ...nativeUpsertPlan, ...dialect1Estimate] : [statementPlan[0], ...metadataPlan, ...statementPlan.slice(1), ...nativeUpsertPlan, ...dialect1Estimate]
27252
27522
  });
27253
27523
  fetchStatements.push({
27254
27524
  index: i,
@@ -27562,21 +27832,29 @@ var explainMaterializedTables = /* @__PURE__ */ new WeakMap();
27562
27832
  function defaultRecursiveExplainContext() {
27563
27833
  return { maxRecords: 1e4, recursiveLimits: resolveRecursiveCteLimits({}) };
27564
27834
  }
27565
- async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxActive, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, relativeDatePlan, recursiveCteMaxDepth, recursiveCteMaxRows, recursiveCteMaxExpansions) {
27835
+ async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxActive, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, relativeDatePlan, recursiveCteMaxDepth, recursiveCteMaxRows, recursiveCteMaxExpansions, resolveMetadata = true, nativeUpsertOptions = { surface: "DOCUMENT_ONLY" }) {
27566
27836
  const recursiveLimits = resolveRecursiveCteLimits({
27567
27837
  recursiveCteMaxDepth,
27568
27838
  recursiveCteMaxRows,
27569
27839
  recursiveCteMaxExpansions
27570
27840
  });
27571
27841
  const sharedPlan = relativeDatePlan ?? await resolveRelativeDateExecutionPlan(stmt.query, client, cacheContext);
27572
- const analysis = await buildExplainWhereAnalysis(
27842
+ const analysis = resolveMetadata ? await buildExplainWhereAnalysis(
27573
27843
  stmt.query,
27574
27844
  client,
27575
27845
  cacheContext,
27576
27846
  maxRecords,
27577
27847
  sharedPlan,
27578
27848
  explainMaterializedTables.get(stmt)
27579
- );
27849
+ ) : {
27850
+ capabilities: /* @__PURE__ */ new Map(),
27851
+ orderPlans: /* @__PURE__ */ new Map(),
27852
+ plainGroupByPlans: /* @__PURE__ */ new Map(),
27853
+ fieldApps: /* @__PURE__ */ new Set(),
27854
+ processStatusApps: /* @__PURE__ */ new Set(),
27855
+ numberPrecisionApps: /* @__PURE__ */ new Set(),
27856
+ relativeDatePlan: sharedPlan
27857
+ };
27580
27858
  const fetchCollector = { sources: [] };
27581
27859
  const relativeLines = relativeDateExplainLines(sharedPlan);
27582
27860
  const planLines = sharedPlan.hasServerOnlyWhereFunction && !sharedPlan.allowed ? [...explainMetadataLines(analysis), ...relativeLines] : [
@@ -27600,7 +27878,8 @@ async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxA
27600
27878
  cursorMaxActive
27601
27879
  )
27602
27880
  ];
27603
- const lines = addFetchSummary(planLines, fetchCollector.sources);
27881
+ const nativeUpsertPlan = await nativeUpsertExplainLines(stmt, cacheContext, nativeUpsertOptions);
27882
+ const lines = addFetchSummary([...planLines, ...nativeUpsertPlan], fetchCollector.sources);
27604
27883
  const result = {
27605
27884
  type: "SELECT",
27606
27885
  columns: ["plan"],
@@ -29523,7 +29802,7 @@ var RequestGate = class {
29523
29802
  }
29524
29803
  };
29525
29804
  function withRequestGate(client, gate) {
29526
- return {
29805
+ const wrapped = {
29527
29806
  getRecords: (params) => gate.runReadOnly(() => client.getRecords(params)),
29528
29807
  openCursor: async (params) => {
29529
29808
  const handle = await gate.runCursorStep(() => client.openCursor(params));
@@ -29541,6 +29820,10 @@ function withRequestGate(client, gate) {
29541
29820
  putRecords: (params) => gate.runMutation(() => client.putRecords(params)),
29542
29821
  deleteRecords: (params) => gate.runMutation(() => client.deleteRecords(params))
29543
29822
  };
29823
+ if ("upsertRecords" in client && typeof client.upsertRecords === "function") {
29824
+ wrapped.upsertRecords = (params) => gate.runMutation(() => client.upsertRecords(params));
29825
+ }
29826
+ return wrapped;
29544
29827
  }
29545
29828
  var globalGate = null;
29546
29829
  function getGlobalRequestGate(options) {
@@ -30297,6 +30580,16 @@ function createNodeKintoneConnection(baseUrl, tokenResolver) {
30297
30580
  _params.app
30298
30581
  );
30299
30582
  },
30583
+ async upsertRecords(_params) {
30584
+ return requestJson(
30585
+ `${apiBasePath}/records.json`,
30586
+ {
30587
+ method: "PUT",
30588
+ body: JSON.stringify({ app: _params.app, upsert: true, records: _params.records })
30589
+ },
30590
+ _params.app
30591
+ );
30592
+ },
30300
30593
  async deleteRecords(_params) {
30301
30594
  await requestJson(
30302
30595
  `${apiBasePath}/records.json`,
@@ -30696,6 +30989,9 @@ var CLI_HELP_TEXT = HELP_TEXT.replace(
30696
30989
  " --max-records <n> Max records to fetch (default: 500)",
30697
30990
  ` --max-records <n> Max records to fetch (default: 500)
30698
30991
  ${RECURSIVE_CTE_HELP_LINES}`
30992
+ ).replace(
30993
+ " --allow-dml Enable UPDATE/DELETE/INSERT/UPSERT/REORDER execution",
30994
+ " --allow-dml Enable UPDATE/DELETE/INSERT/UPSERT/REORDER execution\n --native-upsert Allow eligible plain UPSERT to use kintone native UPSERT"
30699
30995
  );
30700
30996
  var CLI_IMPORT_SOURCE_REQUIRED_MESSAGE = "IMPORT \u306B\u306F\u30BD\u30FC\u30B9\u304C\u5FC5\u8981\u3067\u3059\u3002--import-csv <name=path> \u307E\u305F\u306F --import-json <name=path> \u3067\u30D5\u30A1\u30A4\u30EB\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002";
30701
30997
  function toCliImportError(error, importEnabled) {
@@ -30747,6 +31043,7 @@ function parseArgs(argv) {
30747
31043
  debugHeaders: false,
30748
31044
  exitOnEmpty: false,
30749
31045
  allowDml: false,
31046
+ nativeUpsert: false,
30750
31047
  yes: false,
30751
31048
  allowWithoutWhere: false,
30752
31049
  continueOnError: false,
@@ -30819,6 +31116,10 @@ function parseArgs(argv) {
30819
31116
  out.allowDml = true;
30820
31117
  continue;
30821
31118
  }
31119
+ if (a === "--native-upsert") {
31120
+ out.nativeUpsert = true;
31121
+ continue;
31122
+ }
30822
31123
  if (a === "--yes") {
30823
31124
  out.yes = true;
30824
31125
  continue;
@@ -31656,6 +31957,7 @@ function buildReplExecArgv(base, sql, dryRun, format) {
31656
31957
  if (base.exitOnEmpty) argv.push("--exit-on-empty");
31657
31958
  if (base.allowDml) argv.push("--yes");
31658
31959
  if (base.allowDml) argv.push("--allow-dml");
31960
+ if (base.nativeUpsert) argv.push("--native-upsert");
31659
31961
  if (base.allowWithoutWhere) argv.push("--allow-without-where");
31660
31962
  if (base.continueOnError) argv.push("--continue-on-error");
31661
31963
  return argv;
@@ -31832,7 +32134,8 @@ async function runConsole(base) {
31832
32134
  ` auth=${base.auth ?? "(auto)"}`,
31833
32135
  ` format=${format ?? "(default)"}`,
31834
32136
  ` dryrun=${dryRun ? "on" : "off"}`,
31835
- ` allow-dml=${base.allowDml ? "on" : "off"}`
32137
+ ` allow-dml=${base.allowDml ? "on" : "off"}`,
32138
+ ` native-upsert=${base.nativeUpsert ? "on" : "off"}`
31836
32139
  ].join("\n") + "\n"
31837
32140
  );
31838
32141
  try {
@@ -31963,6 +32266,7 @@ async function runConsole(base) {
31963
32266
  `app=${base.app ?? "(from SQL or config)"}`,
31964
32267
  `resolved-app-profiles=${lastResolvedProfiles}`,
31965
32268
  `allow-dml=${base.allowDml ? "on" : "off"}`,
32269
+ `native-upsert=${base.nativeUpsert ? "on" : "off"}`,
31966
32270
  `dml-max-rows=${base.dmlMaxRows ?? "(default)"}`,
31967
32271
  `dml-max-subtable-rows=${base.dmlMaxSubtableRows ?? "(default)"}`
31968
32272
  ];
@@ -32335,7 +32639,8 @@ async function run() {
32335
32639
  appProfileByApp.set(appId, appBindingByMappedApp.get(appId)?.profile ?? profileName.toLowerCase());
32336
32640
  }
32337
32641
  const cacheContext = buildCacheContext(profileName, appBindingByMappedApp);
32338
- if (args.dryRun && (!dryRunNeedsMetadata || dryRunUsesStaticTypedPlan)) {
32642
+ const fullyOfflineDryRun = args.dryRun && (!dryRunNeedsMetadata || dryRunUsesStaticTypedPlan);
32643
+ if (fullyOfflineDryRun) {
32339
32644
  client = createDryRunClient();
32340
32645
  } else {
32341
32646
  for (const explicitProfile of appProfileByApp.values()) {
@@ -32545,6 +32850,14 @@ async function run() {
32545
32850
  if (!routed) throw new Error(`AuthError: profile "${pName}" is not resolved for APP${params.app}.`);
32546
32851
  return routed.putRecords({ ...params, app: binding.appId });
32547
32852
  },
32853
+ upsertRecords: (params) => {
32854
+ const binding = appBindingByMappedApp.get(params.app) ?? { appId: params.app, profile: profileName.toLowerCase() };
32855
+ const routed = profileClientMap.get(binding.profile);
32856
+ if (!routed || typeof routed.upsertRecords !== "function") {
32857
+ throw new Error(`AuthError: native UPSERT client is not resolved for APP${params.app}.`);
32858
+ }
32859
+ return routed.upsertRecords({ ...params, app: binding.appId });
32860
+ },
32548
32861
  deleteRecords: (params) => {
32549
32862
  const binding = appBindingByMappedApp.get(params.app) ?? { appId: params.app, profile: profileName.toLowerCase() };
32550
32863
  const pName = binding.profile;
@@ -32595,10 +32908,17 @@ async function run() {
32595
32908
  Object.keys(args.importCsv).length > 0 || Object.keys(args.importJson).length > 0,
32596
32909
  dmlMaxRows,
32597
32910
  dmlMaxSubtableRows,
32598
- !dryRunUsesStaticTypedPlan,
32911
+ !fullyOfflineDryRun,
32599
32912
  recursiveCteMaxDepth,
32600
32913
  recursiveCteMaxRows,
32601
- recursiveCteMaxExpansions
32914
+ recursiveCteMaxExpansions,
32915
+ void 0,
32916
+ void 0,
32917
+ {
32918
+ surface: "CLI",
32919
+ enableNativeUpsert: args.nativeUpsert,
32920
+ clientHasNativeUpsert: true
32921
+ }
32602
32922
  );
32603
32923
  const out = [];
32604
32924
  const restoredStatements = sqlDiagnosticContext ? restoreSqlDiagnosticValue(plans.statements, sqlDiagnosticContext.appBindingByMappedApp) : plans.statements;
@@ -32679,7 +32999,7 @@ query=${label}`);
32679
32999
  return 2;
32680
33000
  }
32681
33001
  }
32682
- let batchResult = await executeBatch(sql, client, {
33002
+ let batchResult = await executeBatch(sql, client, withNativeUpsertExecutionOption({
32683
33003
  maxRecords,
32684
33004
  fetchParallel,
32685
33005
  onLimitReached: effectiveOnLimit,
@@ -32717,7 +33037,7 @@ query=${label}`);
32717
33037
  }
32718
33038
  return true;
32719
33039
  } : void 0
32720
- });
33040
+ }, args.nativeUpsert, true));
32721
33041
  if (sqlDiagnosticContext) {
32722
33042
  batchResult = {
32723
33043
  ...batchResult,
@@ -32734,7 +33054,7 @@ query=${label}`);
32734
33054
  }
32735
33055
  return writeBatchOutput(batchResult, { format, noHeader, pretty, displayOptions, outputPath, quiet });
32736
33056
  }
32737
- let result = args.dryRun ? await execute(`EXPLAIN ${sql}`, client, {
33057
+ let result = args.dryRun ? await execute(`EXPLAIN ${sql}`, client, withNativeUpsertExecutionOption({
32738
33058
  maxRecords,
32739
33059
  onLimitReached: onLimit,
32740
33060
  cacheContext,
@@ -32745,8 +33065,9 @@ query=${label}`);
32745
33065
  dmlMaxSubtableRows,
32746
33066
  recursiveCteMaxDepth,
32747
33067
  recursiveCteMaxRows,
32748
- recursiveCteMaxExpansions
32749
- }) : await execute(sql, client, {
33068
+ recursiveCteMaxExpansions,
33069
+ resolveMetadata: !fullyOfflineDryRun
33070
+ }, args.nativeUpsert, true)) : await execute(sql, client, withNativeUpsertExecutionOption({
32750
33071
  maxRecords,
32751
33072
  fetchParallel,
32752
33073
  onLimitReached: effectiveOnLimit,
@@ -32764,7 +33085,7 @@ query=${label}`);
32764
33085
  dmlMaxSubtableRows
32765
33086
  } : {},
32766
33087
  ...containsApplyMutation ? { allowApplyMutation: true } : {}
32767
- });
33088
+ }, args.nativeUpsert, true));
32768
33089
  if ((args.dryRun || parsedStatements[0]?.type === "EXPLAIN") && sqlDiagnosticContext) {
32769
33090
  result = restoreSqlDiagnosticValue(result, sqlDiagnosticContext.appBindingByMappedApp);
32770
33091
  }