@rex0220/kintone-sql-tools 3.72.0 → 3.73.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,12 @@ 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
+ hasNativeUpsertExecutionOption(options) ? {
18841
+ surface: "CLI",
18842
+ enableNativeUpsert: nativeUpsertExecutionEnabled(options),
18843
+ clientHasNativeUpsert: nativeUpsertExplainClientCapability(options) ?? clientHasNativeUpsert(client)
18844
+ } : { surface: "DOCUMENT_ONLY" }
18813
18845
  );
18814
18846
  // 一時テーブルはバッチスコープのため単文実行では拒否する(executeBatch を使う)
18815
18847
  case "CREATE_TEMP_TABLE":
@@ -22813,6 +22845,206 @@ function upsertCompositeKey(parts) {
22813
22845
  function upsertNormalizedKey(parts, numericKey) {
22814
22846
  return JSON.stringify(parts.map((p, i) => numericKey[i] ? normalizeKeyPart(p) : p));
22815
22847
  }
22848
+ var NATIVE_UPSERT_CONDITION_NAMES = {
22849
+ 1: "CLIENT_CAPABILITY",
22850
+ 2: "OPT_IN",
22851
+ 3: "KEY_SCHEMA",
22852
+ 4: "PLAIN_UPSERT",
22853
+ 5: "EMPTY_KEY",
22854
+ 6: "SOURCE_DUPLICATE"
22855
+ };
22856
+ function nativeUpsertExplainReason(condition, unknown, surface, unknownConditions = []) {
22857
+ if (unknown) {
22858
+ if (condition === 1) return "\u901A\u5E38\u5B9F\u884C client \u306E native \u80FD\u529B\u304C\u4E0D\u660E";
22859
+ if (condition === 2) return "native \u8A2D\u5B9A\u304C\u4E0D\u660E";
22860
+ if (condition === 3) return "\u30D5\u30A9\u30FC\u30E0\u30E1\u30BF\u30C7\u30FC\u30BF\u672A\u53D6\u5F97";
22861
+ if (condition === 5 || unknownConditions.includes(5)) return "\u30BD\u30FC\u30B9\u884C\u672A materialize";
22862
+ if (condition === 6) return "\u30AD\u30FC\u578B\u60C5\u5831\u304C\u672A\u78BA\u5B9A\u306E\u305F\u3081\u91CD\u8907\u5224\u5B9A\u4E0D\u80FD";
22863
+ }
22864
+ if (condition === 1) return "client \u304C upsertRecords \u80FD\u529B\u3092\u6301\u305F\u306A\u3044";
22865
+ if (condition === 2) return surface === "CLI" ? "--native-upsert \u304C\u6307\u5B9A\u3055\u308C\u3066\u3044\u306A\u3044" : "enableNativeUpsert \u304C false";
22866
+ 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";
22867
+ if (condition === 4) return "CHECK / APPLY / IMPORT / VALIDATE ONLY / ON ERROR SKIP \u3092\u4F34\u3046\u7D20\u3067\u306A\u3044 UPSERT";
22868
+ if (condition === 5) return "\u30BD\u30FC\u30B9\u306B\u7A7A\u6587\u5B57\u30AD\u30FC\u304C\u3042\u308B";
22869
+ return "\u30BD\u30FC\u30B9\u5185\u306B\u540C\u4E00\u30AD\u30FC\u304C\u3042\u308B";
22870
+ }
22871
+ function renderNativeUpsertEligibilityResult(label, result, surface) {
22872
+ if (result.status === "ELIGIBLE") {
22873
+ return ` ${label}: ELIGIBLE\uFF08${label.includes("statement/data") ? "\u6761\u4EF6 3\u301C6 \u3092" : "6 \u6761\u4EF6\u3092\u3059\u3079\u3066"}\u6E80\u305F\u3059\uFF09`;
22874
+ }
22875
+ if (result.status === "INELIGIBLE") {
22876
+ return ` ${label}: INELIGIBLE\uFF08\u6761\u4EF6 ${result.condition}: ${NATIVE_UPSERT_CONDITION_NAMES[result.condition]} \u2014 ${nativeUpsertExplainReason(result.condition, false, surface)}\uFF09`;
22877
+ }
22878
+ const unknownConditionNumbers = result.unknownConditions.map(({ condition }) => condition);
22879
+ const details = result.unknownConditions.map(
22880
+ ({ condition }) => `\u6761\u4EF6 ${condition}: ${NATIVE_UPSERT_CONDITION_NAMES[condition]} \u2014 ${nativeUpsertExplainReason(condition, true, surface, unknownConditionNumbers)}`
22881
+ );
22882
+ return ` ${label}: UNKNOWN\uFF08${details.join("; ")}\uFF09`;
22883
+ }
22884
+ function renderNativeUpsertEligibility(evaluation, surface) {
22885
+ if (surface === "DOCUMENT_ONLY") {
22886
+ return [
22887
+ renderNativeUpsertEligibilityResult("native UPSERT statement/data eligibility", evaluation.statement, surface),
22888
+ ` 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`
22889
+ ];
22890
+ }
22891
+ const lines = [renderNativeUpsertEligibilityResult("native UPSERT eligibility", evaluation.execution, surface)];
22892
+ if (evaluation.conditions[1].state === "FAIL" || evaluation.conditions[2].state === "FAIL") {
22893
+ lines.push(renderNativeUpsertEligibilityResult("native UPSERT statement/data eligibility", evaluation.statement, surface));
22894
+ }
22895
+ return lines;
22896
+ }
22897
+ function explainNativeUpsertStatement(statement) {
22898
+ const candidate = statement.type === "EXPLAIN" ? statement.query : statement;
22899
+ return candidate.type === "UPSERT" || candidate.type === "UPSERT_SELECT" ? candidate : null;
22900
+ }
22901
+ function nativeUpsertExplainEvaluation(statement, fieldInfos, options) {
22902
+ let rowKeyValues = null;
22903
+ if (statement.type === "UPSERT") {
22904
+ try {
22905
+ rowKeyValues = buildUpsertRowKeyValues(statement);
22906
+ } catch {
22907
+ }
22908
+ }
22909
+ return evaluateNativeUpsertEligibility({
22910
+ surface: options.surface === "DOCUMENT_ONLY" ? "DOCUMENT_ONLY" : "EXECUTION",
22911
+ clientCapability: options.surface === "DOCUMENT_ONLY" ? null : options.clientHasNativeUpsert ?? null,
22912
+ enabled: options.surface === "DOCUMENT_ONLY" ? null : options.surface === "FLOW" ? options.enableNativeUpsert !== false : options.enableNativeUpsert === true,
22913
+ statement: {
22914
+ kind: statement.type === "UPSERT" ? "VALUES" : "SELECT",
22915
+ keyFields: statement.keyFields,
22916
+ hasCheck: Boolean(statement.checkGroups?.length),
22917
+ hasApply: statement.type === "UPSERT" && Boolean(statement.onInsertApplyBlocks?.length || statement.onUpdateApplyBlocks?.length),
22918
+ validateOnly: statement.validateOnly === true,
22919
+ onErrorSkip: statement.onErrorSkip === true,
22920
+ importDerived: importSourceByDmlStatement.has(statement)
22921
+ },
22922
+ fieldInfos,
22923
+ rowKeyValues
22924
+ });
22925
+ }
22926
+ async function nativeUpsertExplainLines(statement, cacheContext, options) {
22927
+ const upsert = explainNativeUpsertStatement(statement);
22928
+ if (!upsert) return [];
22929
+ const fieldInfos = await getFieldsIfCached(upsert.appId, cacheContext);
22930
+ return renderNativeUpsertEligibility(
22931
+ nativeUpsertExplainEvaluation(upsert, fieldInfos, options),
22932
+ options.surface
22933
+ );
22934
+ }
22935
+ function evaluateNativeUpsertEligibility(input) {
22936
+ const keyField = input.statement.keyFields.length === 1 ? input.statement.keyFields[0] : void 0;
22937
+ const keyInfo = keyField === void 0 || input.fieldInfos === null ? void 0 : input.fieldInfos.find((field) => field.code === keyField);
22938
+ const supportedKeyType = keyInfo?.fieldType === "SINGLE_LINE_TEXT" || keyInfo?.fieldType === "NUMBER";
22939
+ const schemaPass = input.fieldInfos === null ? null : Boolean(
22940
+ keyField !== void 0 && keyInfo && supportedKeyType && keyInfo.isUnique === true
22941
+ );
22942
+ const plain = !input.statement.hasCheck && !input.statement.hasApply && !input.statement.validateOnly && !input.statement.onErrorSkip && !(input.statement.kind === "SELECT" && input.statement.importDerived);
22943
+ const rows = input.rowKeyValues;
22944
+ const noEmpty = rows === null ? null : rows.every((parts) => parts.every((part) => part !== ""));
22945
+ let noDuplicates = null;
22946
+ if (rows !== null && keyField !== void 0 && supportedKeyType) {
22947
+ const seen = /* @__PURE__ */ new Set();
22948
+ noDuplicates = true;
22949
+ for (const parts of rows) {
22950
+ const normalized = upsertNormalizedKey([...parts], [keyInfo.fieldType === "NUMBER"]);
22951
+ if (seen.has(normalized)) {
22952
+ noDuplicates = false;
22953
+ break;
22954
+ }
22955
+ seen.add(normalized);
22956
+ }
22957
+ }
22958
+ const state = (value, reason) => ({
22959
+ state: value === null ? "UNKNOWN" : value ? "PASS" : "FAIL",
22960
+ reason
22961
+ });
22962
+ const conditions = {
22963
+ 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"),
22964
+ 2: input.surface === "DOCUMENT_ONLY" ? { state: "NOT_APPLICABLE", reason: "native setting is not applicable on this surface" } : state(input.enabled, "native UPSERT is disabled"),
22965
+ 3: state(schemaPass, "update key schema is not a single unique text or number field"),
22966
+ 4: state(plain, "statement is not a plain UPSERT"),
22967
+ 5: state(noEmpty, "source contains an empty update key"),
22968
+ 6: state(noDuplicates, "source contains duplicate update keys")
22969
+ };
22970
+ const summarize = (ordered) => {
22971
+ const failed = ordered.find((condition) => conditions[condition].state === "FAIL");
22972
+ if (failed !== void 0) return { status: "INELIGIBLE", condition: failed, reason: conditions[failed].reason };
22973
+ const unknownConditions = ordered.filter((condition) => conditions[condition].state === "UNKNOWN").map((condition) => ({ condition, reason: conditions[condition].reason }));
22974
+ return unknownConditions.length > 0 ? { status: "UNKNOWN", unknownConditions } : { status: "ELIGIBLE" };
22975
+ };
22976
+ return {
22977
+ conditions,
22978
+ execution: summarize([1, 2, 3, 4, 5, 6]),
22979
+ statement: summarize([3, 4, 5, 6])
22980
+ };
22981
+ }
22982
+ function clientHasNativeUpsert(client) {
22983
+ return "upsertRecords" in client && typeof client.upsertRecords === "function";
22984
+ }
22985
+ function nativeEligibilityInput(stmt, client, options, fieldInfos, rowKeyValues) {
22986
+ return {
22987
+ surface: "EXECUTION",
22988
+ clientCapability: clientHasNativeUpsert(client),
22989
+ enabled: nativeUpsertExecutionEnabled(options),
22990
+ statement: {
22991
+ kind: stmt.type === "UPSERT" ? "VALUES" : "SELECT",
22992
+ keyFields: stmt.keyFields,
22993
+ hasCheck: Boolean(stmt.checkGroups?.length),
22994
+ hasApply: stmt.type === "UPSERT" && Boolean(stmt.onInsertApplyBlocks?.length || stmt.onUpdateApplyBlocks?.length),
22995
+ validateOnly: stmt.validateOnly === true,
22996
+ onErrorSkip: stmt.onErrorSkip === true,
22997
+ importDerived: importSourceByDmlStatement.has(stmt)
22998
+ },
22999
+ fieldInfos,
23000
+ rowKeyValues
23001
+ };
23002
+ }
23003
+ var NativeUpsertResponseError = class extends Error {
23004
+ constructor() {
23005
+ super("NativeUpsertResponseError: upsertRecords returned an invalid response.");
23006
+ this.name = "NativeUpsertResponseError";
23007
+ }
23008
+ };
23009
+ function validateNativeUpsertResponse(response, expectedRecords) {
23010
+ if (!response || !Array.isArray(response.records) || response.records.length !== expectedRecords) {
23011
+ throw new NativeUpsertResponseError();
23012
+ }
23013
+ let insertedCount = 0;
23014
+ let updatedCount = 0;
23015
+ for (const record of response.records) {
23016
+ if (!record || typeof record.id !== "string" || typeof record.revision !== "string" || record.operation !== "INSERT" && record.operation !== "UPDATE") {
23017
+ throw new NativeUpsertResponseError();
23018
+ }
23019
+ if (record.operation === "INSERT") insertedCount += 1;
23020
+ else updatedCount += 1;
23021
+ }
23022
+ return { insertedCount, updatedCount };
23023
+ }
23024
+ async function executeNativeUpsertRecords(appId, keyField, records, rowKeyValues, client, options) {
23025
+ if (records.length === 0) return { type: "UPSERT", insertedCount: 0, updatedCount: 0 };
23026
+ if (options.confirm) {
23027
+ const ok = await options.confirm(records.length, "UPDATE");
23028
+ if (!ok) throw new OperationCancelledError("UPDATE", records.length);
23029
+ }
23030
+ let insertedCount = 0;
23031
+ let updatedCount = 0;
23032
+ for (let offset = 0; offset < records.length; offset += 100) {
23033
+ const nativeRecords = records.slice(offset, offset + 100).map((record, index) => {
23034
+ const payload = {};
23035
+ for (const [field, value] of Object.entries(record)) if (field !== keyField) payload[field] = value;
23036
+ return {
23037
+ updateKey: { field: keyField, value: rowKeyValues[offset + index][0] },
23038
+ record: payload
23039
+ };
23040
+ });
23041
+ const response = await client.upsertRecords({ app: appId, upsert: true, records: nativeRecords });
23042
+ const counts = validateNativeUpsertResponse(response, nativeRecords.length);
23043
+ insertedCount += counts.insertedCount;
23044
+ updatedCount += counts.updatedCount;
23045
+ }
23046
+ return { type: "UPSERT", insertedCount, updatedCount };
23047
+ }
22816
23048
  function lookupUpsertTarget(index, keyParts) {
22817
23049
  const exact = index.raw.get(upsertCompositeKey(keyParts));
22818
23050
  if (exact !== void 0) return exact;
@@ -23029,6 +23261,10 @@ async function getFieldsCached(appId, client, cacheContext) {
23029
23261
  setScopedCacheValue(fieldInfoCache, cacheContext, appId, loading);
23030
23262
  return loading;
23031
23263
  }
23264
+ async function getFieldsIfCached(appId, cacheContext) {
23265
+ const cached = getScopedCacheValue(fieldInfoCache, cacheContext, appId);
23266
+ return cached ? await cached : null;
23267
+ }
23032
23268
  async function getNumberPrecisionCached(appId, client, cacheContext) {
23033
23269
  const cached = getScopedCacheValue(numberPrecisionCache, cacheContext, appId);
23034
23270
  if (cached) return cached;
@@ -25596,6 +25832,16 @@ async function executeDelete(stmt, client, options, cacheContext) {
25596
25832
  }
25597
25833
  return { type: "DELETE", deletedCount: ids.length };
25598
25834
  }
25835
+ function materializeUpsertValueRecords(stmt, fieldTypes, options) {
25836
+ return stmt.values.map((row) => {
25837
+ const record = {};
25838
+ stmt.fields.forEach((field, index) => {
25839
+ const value = row[index];
25840
+ record[field] = { value: value.type === "CASE_VALUE" ? evalCaseWhenValue(value.expr, {}, fieldTypes.get(field), statementEvaluationContext(options)) : toKintoneValue(value, fieldTypes.get(field)) };
25841
+ });
25842
+ return record;
25843
+ });
25844
+ }
25599
25845
  async function executeUpsert(stmt, client, options, cacheContext) {
25600
25846
  if (stmt.onInsertApplyBlocks?.length || stmt.onUpdateApplyBlocks?.length) {
25601
25847
  return executeApplyUpsert(stmt, client, options, cacheContext);
@@ -25607,6 +25853,14 @@ async function executeUpsert(stmt, client, options, cacheContext) {
25607
25853
  const toUpdate = [];
25608
25854
  const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
25609
25855
  const rowKeyValues = buildUpsertRowKeyValues(stmt);
25856
+ const nativeEligibility = evaluateNativeUpsertEligibility(
25857
+ nativeEligibilityInput(stmt, client, options, fieldInfos, rowKeyValues)
25858
+ );
25859
+ if (nativeEligibility.execution.status === "ELIGIBLE") {
25860
+ const records = materializeUpsertValueRecords(stmt, fieldTypes, options);
25861
+ assertValidDmlRecords(records, stmt.fields, fieldInfos, numberPrecision);
25862
+ return executeNativeUpsertRecords(stmt.appId, stmt.keyFields[0], records, rowKeyValues, client, options);
25863
+ }
25610
25864
  const targetIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeyValues, client, options, fieldTypes);
25611
25865
  stmt.values.forEach((row, rowIdx) => {
25612
25866
  const record = {};
@@ -26095,6 +26349,12 @@ async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache
26095
26349
  const rowKeyValues = records.map(
26096
26350
  (record) => stmt.keyFields.map((key) => String(record[key]?.value ?? ""))
26097
26351
  );
26352
+ const nativeEligibility = evaluateNativeUpsertEligibility(
26353
+ nativeEligibilityInput(stmt, client, options, fieldInfos, rowKeyValues)
26354
+ );
26355
+ if (nativeEligibility.execution.status === "ELIGIBLE") {
26356
+ return executeNativeUpsertRecords(stmt.appId, stmt.keyFields[0], records, rowKeyValues, client, options);
26357
+ }
26098
26358
  if (importSourceByDmlStatement.has(stmt)) {
26099
26359
  const numericKey = stmt.keyFields.map((key) => fieldTypes.get(key) === "NUMBER");
26100
26360
  const sourceKeys = /* @__PURE__ */ new Set();
@@ -27146,7 +27406,7 @@ var EXPLAIN_FETCH_PLAN = /* @__PURE__ */ Symbol("ksql.explainFetchPlan");
27146
27406
  function setExplainFetchPlan(result, plan) {
27147
27407
  result[EXPLAIN_FETCH_PLAN] = plan;
27148
27408
  }
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) {
27409
+ 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
27410
  const asOfClock = createAsOfClock(asOf ?? /* @__PURE__ */ new Date(), timezone);
27151
27411
  const recursiveLimits = resolveRecursiveCteLimits({
27152
27412
  recursiveCteMaxDepth,
@@ -27245,10 +27505,15 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
27245
27505
  maxRecords,
27246
27506
  dmlMaxRows
27247
27507
  ) : [];
27508
+ const nativeUpsertPlan = await nativeUpsertExplainLines(
27509
+ planStmt,
27510
+ invocationCacheContext,
27511
+ nativeUpsertOptions
27512
+ );
27248
27513
  plans.push({
27249
27514
  index: i,
27250
27515
  type: analysis.statements[i].statementType,
27251
- plan: statementPlan.length === 0 ? [...metadataPlan, ...dialect1Estimate] : [statementPlan[0], ...metadataPlan, ...statementPlan.slice(1), ...dialect1Estimate]
27516
+ plan: statementPlan.length === 0 ? [...metadataPlan, ...nativeUpsertPlan, ...dialect1Estimate] : [statementPlan[0], ...metadataPlan, ...statementPlan.slice(1), ...nativeUpsertPlan, ...dialect1Estimate]
27252
27517
  });
27253
27518
  fetchStatements.push({
27254
27519
  index: i,
@@ -27562,7 +27827,7 @@ var explainMaterializedTables = /* @__PURE__ */ new WeakMap();
27562
27827
  function defaultRecursiveExplainContext() {
27563
27828
  return { maxRecords: 1e4, recursiveLimits: resolveRecursiveCteLimits({}) };
27564
27829
  }
27565
- async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxActive, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, relativeDatePlan, recursiveCteMaxDepth, recursiveCteMaxRows, recursiveCteMaxExpansions) {
27830
+ async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxActive, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, relativeDatePlan, recursiveCteMaxDepth, recursiveCteMaxRows, recursiveCteMaxExpansions, nativeUpsertOptions = { surface: "DOCUMENT_ONLY" }) {
27566
27831
  const recursiveLimits = resolveRecursiveCteLimits({
27567
27832
  recursiveCteMaxDepth,
27568
27833
  recursiveCteMaxRows,
@@ -27600,7 +27865,8 @@ async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxA
27600
27865
  cursorMaxActive
27601
27866
  )
27602
27867
  ];
27603
- const lines = addFetchSummary(planLines, fetchCollector.sources);
27868
+ const nativeUpsertPlan = await nativeUpsertExplainLines(stmt, cacheContext, nativeUpsertOptions);
27869
+ const lines = addFetchSummary([...planLines, ...nativeUpsertPlan], fetchCollector.sources);
27604
27870
  const result = {
27605
27871
  type: "SELECT",
27606
27872
  columns: ["plan"],
@@ -29523,7 +29789,7 @@ var RequestGate = class {
29523
29789
  }
29524
29790
  };
29525
29791
  function withRequestGate(client, gate) {
29526
- return {
29792
+ const wrapped = {
29527
29793
  getRecords: (params) => gate.runReadOnly(() => client.getRecords(params)),
29528
29794
  openCursor: async (params) => {
29529
29795
  const handle = await gate.runCursorStep(() => client.openCursor(params));
@@ -29541,6 +29807,10 @@ function withRequestGate(client, gate) {
29541
29807
  putRecords: (params) => gate.runMutation(() => client.putRecords(params)),
29542
29808
  deleteRecords: (params) => gate.runMutation(() => client.deleteRecords(params))
29543
29809
  };
29810
+ if ("upsertRecords" in client && typeof client.upsertRecords === "function") {
29811
+ wrapped.upsertRecords = (params) => gate.runMutation(() => client.upsertRecords(params));
29812
+ }
29813
+ return wrapped;
29544
29814
  }
29545
29815
  var globalGate = null;
29546
29816
  function getGlobalRequestGate(options) {
@@ -30297,6 +30567,16 @@ function createNodeKintoneConnection(baseUrl, tokenResolver) {
30297
30567
  _params.app
30298
30568
  );
30299
30569
  },
30570
+ async upsertRecords(_params) {
30571
+ return requestJson(
30572
+ `${apiBasePath}/records.json`,
30573
+ {
30574
+ method: "PUT",
30575
+ body: JSON.stringify({ app: _params.app, upsert: true, records: _params.records })
30576
+ },
30577
+ _params.app
30578
+ );
30579
+ },
30300
30580
  async deleteRecords(_params) {
30301
30581
  await requestJson(
30302
30582
  `${apiBasePath}/records.json`,
@@ -30696,6 +30976,9 @@ var CLI_HELP_TEXT = HELP_TEXT.replace(
30696
30976
  " --max-records <n> Max records to fetch (default: 500)",
30697
30977
  ` --max-records <n> Max records to fetch (default: 500)
30698
30978
  ${RECURSIVE_CTE_HELP_LINES}`
30979
+ ).replace(
30980
+ " --allow-dml Enable UPDATE/DELETE/INSERT/UPSERT/REORDER execution",
30981
+ " --allow-dml Enable UPDATE/DELETE/INSERT/UPSERT/REORDER execution\n --native-upsert Allow eligible plain UPSERT to use kintone native UPSERT"
30699
30982
  );
30700
30983
  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
30984
  function toCliImportError(error, importEnabled) {
@@ -30747,6 +31030,7 @@ function parseArgs(argv) {
30747
31030
  debugHeaders: false,
30748
31031
  exitOnEmpty: false,
30749
31032
  allowDml: false,
31033
+ nativeUpsert: false,
30750
31034
  yes: false,
30751
31035
  allowWithoutWhere: false,
30752
31036
  continueOnError: false,
@@ -30819,6 +31103,10 @@ function parseArgs(argv) {
30819
31103
  out.allowDml = true;
30820
31104
  continue;
30821
31105
  }
31106
+ if (a === "--native-upsert") {
31107
+ out.nativeUpsert = true;
31108
+ continue;
31109
+ }
30822
31110
  if (a === "--yes") {
30823
31111
  out.yes = true;
30824
31112
  continue;
@@ -31656,6 +31944,7 @@ function buildReplExecArgv(base, sql, dryRun, format) {
31656
31944
  if (base.exitOnEmpty) argv.push("--exit-on-empty");
31657
31945
  if (base.allowDml) argv.push("--yes");
31658
31946
  if (base.allowDml) argv.push("--allow-dml");
31947
+ if (base.nativeUpsert) argv.push("--native-upsert");
31659
31948
  if (base.allowWithoutWhere) argv.push("--allow-without-where");
31660
31949
  if (base.continueOnError) argv.push("--continue-on-error");
31661
31950
  return argv;
@@ -31832,7 +32121,8 @@ async function runConsole(base) {
31832
32121
  ` auth=${base.auth ?? "(auto)"}`,
31833
32122
  ` format=${format ?? "(default)"}`,
31834
32123
  ` dryrun=${dryRun ? "on" : "off"}`,
31835
- ` allow-dml=${base.allowDml ? "on" : "off"}`
32124
+ ` allow-dml=${base.allowDml ? "on" : "off"}`,
32125
+ ` native-upsert=${base.nativeUpsert ? "on" : "off"}`
31836
32126
  ].join("\n") + "\n"
31837
32127
  );
31838
32128
  try {
@@ -31963,6 +32253,7 @@ async function runConsole(base) {
31963
32253
  `app=${base.app ?? "(from SQL or config)"}`,
31964
32254
  `resolved-app-profiles=${lastResolvedProfiles}`,
31965
32255
  `allow-dml=${base.allowDml ? "on" : "off"}`,
32256
+ `native-upsert=${base.nativeUpsert ? "on" : "off"}`,
31966
32257
  `dml-max-rows=${base.dmlMaxRows ?? "(default)"}`,
31967
32258
  `dml-max-subtable-rows=${base.dmlMaxSubtableRows ?? "(default)"}`
31968
32259
  ];
@@ -32545,6 +32836,14 @@ async function run() {
32545
32836
  if (!routed) throw new Error(`AuthError: profile "${pName}" is not resolved for APP${params.app}.`);
32546
32837
  return routed.putRecords({ ...params, app: binding.appId });
32547
32838
  },
32839
+ upsertRecords: (params) => {
32840
+ const binding = appBindingByMappedApp.get(params.app) ?? { appId: params.app, profile: profileName.toLowerCase() };
32841
+ const routed = profileClientMap.get(binding.profile);
32842
+ if (!routed || typeof routed.upsertRecords !== "function") {
32843
+ throw new Error(`AuthError: native UPSERT client is not resolved for APP${params.app}.`);
32844
+ }
32845
+ return routed.upsertRecords({ ...params, app: binding.appId });
32846
+ },
32548
32847
  deleteRecords: (params) => {
32549
32848
  const binding = appBindingByMappedApp.get(params.app) ?? { appId: params.app, profile: profileName.toLowerCase() };
32550
32849
  const pName = binding.profile;
@@ -32598,7 +32897,14 @@ async function run() {
32598
32897
  !dryRunUsesStaticTypedPlan,
32599
32898
  recursiveCteMaxDepth,
32600
32899
  recursiveCteMaxRows,
32601
- recursiveCteMaxExpansions
32900
+ recursiveCteMaxExpansions,
32901
+ void 0,
32902
+ void 0,
32903
+ {
32904
+ surface: "CLI",
32905
+ enableNativeUpsert: args.nativeUpsert,
32906
+ clientHasNativeUpsert: true
32907
+ }
32602
32908
  );
32603
32909
  const out = [];
32604
32910
  const restoredStatements = sqlDiagnosticContext ? restoreSqlDiagnosticValue(plans.statements, sqlDiagnosticContext.appBindingByMappedApp) : plans.statements;
@@ -32679,7 +32985,7 @@ query=${label}`);
32679
32985
  return 2;
32680
32986
  }
32681
32987
  }
32682
- let batchResult = await executeBatch(sql, client, {
32988
+ let batchResult = await executeBatch(sql, client, withNativeUpsertExecutionOption({
32683
32989
  maxRecords,
32684
32990
  fetchParallel,
32685
32991
  onLimitReached: effectiveOnLimit,
@@ -32717,7 +33023,7 @@ query=${label}`);
32717
33023
  }
32718
33024
  return true;
32719
33025
  } : void 0
32720
- });
33026
+ }, args.nativeUpsert, true));
32721
33027
  if (sqlDiagnosticContext) {
32722
33028
  batchResult = {
32723
33029
  ...batchResult,
@@ -32734,7 +33040,7 @@ query=${label}`);
32734
33040
  }
32735
33041
  return writeBatchOutput(batchResult, { format, noHeader, pretty, displayOptions, outputPath, quiet });
32736
33042
  }
32737
- let result = args.dryRun ? await execute(`EXPLAIN ${sql}`, client, {
33043
+ let result = args.dryRun ? await execute(`EXPLAIN ${sql}`, client, withNativeUpsertExecutionOption({
32738
33044
  maxRecords,
32739
33045
  onLimitReached: onLimit,
32740
33046
  cacheContext,
@@ -32746,7 +33052,7 @@ query=${label}`);
32746
33052
  recursiveCteMaxDepth,
32747
33053
  recursiveCteMaxRows,
32748
33054
  recursiveCteMaxExpansions
32749
- }) : await execute(sql, client, {
33055
+ }, args.nativeUpsert, true)) : await execute(sql, client, withNativeUpsertExecutionOption({
32750
33056
  maxRecords,
32751
33057
  fetchParallel,
32752
33058
  onLimitReached: effectiveOnLimit,
@@ -32764,7 +33070,7 @@ query=${label}`);
32764
33070
  dmlMaxSubtableRows
32765
33071
  } : {},
32766
33072
  ...containsApplyMutation ? { allowApplyMutation: true } : {}
32767
- });
33073
+ }, args.nativeUpsert, true));
32768
33074
  if ((args.dryRun || parsedStatements[0]?.type === "EXPLAIN") && sqlDiagnosticContext) {
32769
33075
  result = restoreSqlDiagnosticValue(result, sqlDiagnosticContext.appBindingByMappedApp);
32770
33076
  }