@rex0220/kintone-sql-tools 3.71.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 +17 -5
- package/dist-cli/ksql.js +376 -39
- package/dist-engine/index.cjs +14 -14
- package/dist-engine/index.mjs +14 -14
- package/dist-engine/ksql-engine.umd.js +14 -14
- package/dist-engine/meta/bundle-baseline.json +7 -7
- package/dist-engine/meta/cjs.json +12 -12
- package/dist-engine/meta/esm.json +12 -12
- package/dist-engine/meta/umd.json +12 -12
- package/dist-flow/flow-library/index.d.ts +1 -1
- package/dist-flow/flow-library/publicTypes.d.ts +31 -1
- package/dist-flow/index.cjs +12 -12
- package/dist-flow/index.mjs +12 -12
- package/dist-flow/meta/cjs.json +12 -12
- package/dist-flow/meta/esm.json +12 -12
- package/dist-flow/types/ast.d.ts +6 -4
- package/dist-mcp/ksql-mcp.js +342 -35
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
package/dist-cli/ksql.js
CHANGED
|
@@ -4161,7 +4161,7 @@ var Parser = class {
|
|
|
4161
4161
|
const values = [];
|
|
4162
4162
|
do {
|
|
4163
4163
|
this.expect("(" /* LPAREN */);
|
|
4164
|
-
const row = this.parseInsertRow(fields.length);
|
|
4164
|
+
const row = this.parseInsertRow(fields.length, true);
|
|
4165
4165
|
this.expect(")" /* RPAREN */);
|
|
4166
4166
|
values.push(row);
|
|
4167
4167
|
} while (this.consume("," /* COMMA */));
|
|
@@ -4222,7 +4222,7 @@ var Parser = class {
|
|
|
4222
4222
|
const values = [];
|
|
4223
4223
|
do {
|
|
4224
4224
|
this.expect("(" /* LPAREN */);
|
|
4225
|
-
values.push(this.parseInsertRow(fields.length));
|
|
4225
|
+
values.push(this.parseInsertRow(fields.length, false));
|
|
4226
4226
|
this.expect(")" /* RPAREN */);
|
|
4227
4227
|
} while (this.consume("," /* COMMA */));
|
|
4228
4228
|
const keyFields = this.parseOnDuplicate();
|
|
@@ -4546,7 +4546,7 @@ var Parser = class {
|
|
|
4546
4546
|
this.expect("]" /* RBRACKET */);
|
|
4547
4547
|
return { type: "ARRAY", elements };
|
|
4548
4548
|
}
|
|
4549
|
-
parseInsertRow(expectedLen) {
|
|
4549
|
+
parseInsertRow(expectedLen, allowAsOf) {
|
|
4550
4550
|
const row = [];
|
|
4551
4551
|
do {
|
|
4552
4552
|
if (this.peek().kind === "[" /* LBRACKET */) {
|
|
@@ -4567,8 +4567,11 @@ var Parser = class {
|
|
|
4567
4567
|
row.push({ type: "STRING", value: tok.value });
|
|
4568
4568
|
} else if (tok.kind === "NUMBER" /* NUMBER */) {
|
|
4569
4569
|
row.push(makeNumberLiteral(tok.value));
|
|
4570
|
+
} else if (allowAsOf && this.isDialect1AsOfCall(tok)) {
|
|
4571
|
+
row.push(this.finishVariableReference(tok));
|
|
4570
4572
|
} else {
|
|
4571
|
-
|
|
4573
|
+
const hint = this.isDialect1AsOfCall(tok) ? " INSERT ... VALUES \u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u3059\u3002\u3053\u306E\u6587\u578B\u3067\u306F INSERT ... SELECT \u3067\u6CE8\u5165\u3057\u3066\u304F\u3060\u3055\u3044\u3002" : "";
|
|
4574
|
+
throw new ParseError(`INSERT \u306E\u5024\u306B\u306F\u6587\u5B57\u5217\u30FB\u6570\u5024\u30FB\u914D\u5217\u30EA\u30C6\u30E9\u30EB\u30FBCASE WHEN \u304C\u5FC5\u8981\u3067\u3059${hint}`, tok);
|
|
4572
4575
|
}
|
|
4573
4576
|
}
|
|
4574
4577
|
} while (this.consume("," /* COMMA */));
|
|
@@ -4580,6 +4583,9 @@ var Parser = class {
|
|
|
4580
4583
|
}
|
|
4581
4584
|
return row;
|
|
4582
4585
|
}
|
|
4586
|
+
isDialect1AsOfCall(tok) {
|
|
4587
|
+
return this.capabilities.dialect1 === true && tok.kind === "VARIABLE" /* VARIABLE */ && isAsOfFunctionName(tok.value.slice(1).toUpperCase()) && this.peek().kind === "(" /* LPAREN */ && this.peekAt(1).kind === ")" /* RPAREN */;
|
|
4588
|
+
}
|
|
4583
4589
|
// ----------------------------------------------------------
|
|
4584
4590
|
// UPDATE
|
|
4585
4591
|
// ----------------------------------------------------------
|
|
@@ -4742,7 +4748,7 @@ var Parser = class {
|
|
|
4742
4748
|
const values = [];
|
|
4743
4749
|
do {
|
|
4744
4750
|
this.expect("(" /* LPAREN */, "APPEND VALUES \u306E\u5404\u884C\u306F ( \u3067\u59CB\u3081\u3066\u304F\u3060\u3055\u3044");
|
|
4745
|
-
values.push(this.parseInsertRow(fields.length));
|
|
4751
|
+
values.push(this.parseInsertRow(fields.length, false));
|
|
4746
4752
|
this.expect(")" /* RPAREN */);
|
|
4747
4753
|
} while (this.consume("," /* COMMA */));
|
|
4748
4754
|
return { kind: "APPEND", fields, values };
|
|
@@ -18460,6 +18466,24 @@ var materializedMetaBySelectResult = /* @__PURE__ */ new WeakMap();
|
|
|
18460
18466
|
var materializedMetaByValidationResult = /* @__PURE__ */ new WeakMap();
|
|
18461
18467
|
var importSourceByDmlStatement = /* @__PURE__ */ new WeakMap();
|
|
18462
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
|
+
}
|
|
18463
18487
|
function bindStatementEvaluationContext(options) {
|
|
18464
18488
|
const internal = options;
|
|
18465
18489
|
if (internal[statementEvaluationContextKey]) return options;
|
|
@@ -18524,6 +18548,7 @@ function createEmptyMetrics() {
|
|
|
18524
18548
|
getCalls: 0,
|
|
18525
18549
|
postCalls: 0,
|
|
18526
18550
|
putCalls: 0,
|
|
18551
|
+
nativeUpsertCalls: 0,
|
|
18527
18552
|
deleteCalls: 0,
|
|
18528
18553
|
fieldCalls: 0,
|
|
18529
18554
|
numberPrecisionCalls: 0,
|
|
@@ -18552,7 +18577,7 @@ function markLimitReached(client, appId) {
|
|
|
18552
18577
|
if (!metrics.limitReachedApps.includes(appId)) metrics.limitReachedApps.push(appId);
|
|
18553
18578
|
}
|
|
18554
18579
|
function wrapClientWithMetrics(client, metrics) {
|
|
18555
|
-
|
|
18580
|
+
const wrapped = {
|
|
18556
18581
|
[LIMIT_METRICS_SINK]: metrics,
|
|
18557
18582
|
getRecords: async (params) => {
|
|
18558
18583
|
metrics.getCalls += 1;
|
|
@@ -18631,6 +18656,14 @@ function wrapClientWithMetrics(client, metrics) {
|
|
|
18631
18656
|
return client.getProcessStatuses(appId);
|
|
18632
18657
|
}
|
|
18633
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;
|
|
18634
18667
|
}
|
|
18635
18668
|
var SEARCH_ABORT_FAIL_CLOSED = /* @__PURE__ */ Symbol("searchAbortFailClosed");
|
|
18636
18669
|
function wrapClientWithSearchAbort(client, collector, failClosed) {
|
|
@@ -18803,7 +18836,12 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
18803
18836
|
relativeDatePlan,
|
|
18804
18837
|
options.recursiveCteMaxDepth,
|
|
18805
18838
|
options.recursiveCteMaxRows,
|
|
18806
|
-
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" }
|
|
18807
18845
|
);
|
|
18808
18846
|
// 一時テーブルはバッチスコープのため単文実行では拒否する(executeBatch を使う)
|
|
18809
18847
|
case "CREATE_TEMP_TABLE":
|
|
@@ -19266,18 +19304,12 @@ async function executeBatchStatement(context) {
|
|
|
19266
19304
|
cacheContext,
|
|
19267
19305
|
tempTables
|
|
19268
19306
|
);
|
|
19269
|
-
const
|
|
19270
|
-
|
|
19271
|
-
|
|
19272
|
-
|
|
19273
|
-
|
|
19274
|
-
|
|
19275
|
-
client,
|
|
19276
|
-
cacheContext,
|
|
19277
|
-
tempTables
|
|
19278
|
-
)).get("__scalar__");
|
|
19279
|
-
numeric = meta?.semantics?.compareMode === "number" || meta?.sortKind === "number";
|
|
19280
|
-
}
|
|
19307
|
+
const numeric = await scalarSubqueryHasNumericResult(
|
|
19308
|
+
resolvedStmt2.expr.query,
|
|
19309
|
+
client,
|
|
19310
|
+
cacheContext,
|
|
19311
|
+
tempTables
|
|
19312
|
+
);
|
|
19281
19313
|
const numberValue = numeric ? Number(value) : Number.NaN;
|
|
19282
19314
|
variables.set(stmt.name, numeric && Number.isFinite(numberValue) ? { type: "number", value: numberValue, raw: value } : { type: "string", value });
|
|
19283
19315
|
} catch (e) {
|
|
@@ -19762,7 +19794,6 @@ async function executeExit(stmt, client, options, cacheContext, tempTables) {
|
|
|
19762
19794
|
}
|
|
19763
19795
|
async function evaluateAssertCondition(stmt, client, options, cacheContext, tempTables) {
|
|
19764
19796
|
const left = await evalAssertOperand(stmt.left, client, options, cacheContext, tempTables);
|
|
19765
|
-
const semantics = stmt.left.type === "NUMBER" || stmt.left.type === "ARITH" ? syntheticSemantics("number") : syntheticSemantics("string");
|
|
19766
19797
|
if (stmt.op === "BETWEEN") {
|
|
19767
19798
|
if (stmt.low === null || stmt.high === null) {
|
|
19768
19799
|
throw new Error("ArgumentError: malformed ASSERT statement.");
|
|
@@ -19770,35 +19801,49 @@ async function evaluateAssertCondition(stmt, client, options, cacheContext, temp
|
|
|
19770
19801
|
const low = await evalAssertOperand(stmt.low, client, options, cacheContext, tempTables);
|
|
19771
19802
|
const high = await evalAssertOperand(stmt.high, client, options, cacheContext, tempTables);
|
|
19772
19803
|
return {
|
|
19773
|
-
passed: compareScalarValues(">=", left, low,
|
|
19774
|
-
actual: left
|
|
19804
|
+
passed: compareScalarValues(">=", left.value, low.value, assertComparisonSemantics(left, low)) && compareScalarValues("<=", left.value, high.value, assertComparisonSemantics(left, high)),
|
|
19805
|
+
actual: left.value
|
|
19775
19806
|
};
|
|
19776
19807
|
}
|
|
19777
19808
|
if (stmt.right === null) {
|
|
19778
19809
|
throw new Error("ArgumentError: malformed ASSERT statement.");
|
|
19779
19810
|
}
|
|
19780
19811
|
const right = await evalAssertOperand(stmt.right, client, options, cacheContext, tempTables);
|
|
19781
|
-
|
|
19812
|
+
const semantics = stmt.op === "=" || stmt.op === "!=" || stmt.op === "<>" ? stmt.left.type === "NUMBER" || stmt.left.type === "ARITH" ? syntheticSemantics("number") : syntheticSemantics("string") : assertComparisonSemantics(left, right);
|
|
19813
|
+
return {
|
|
19814
|
+
passed: compareScalarValues(stmt.op, left.value, right.value, semantics),
|
|
19815
|
+
actual: left.value
|
|
19816
|
+
};
|
|
19817
|
+
}
|
|
19818
|
+
function assertComparisonSemantics(left, right) {
|
|
19819
|
+
return syntheticSemantics(left.semantics === "number" && right.semantics === "number" ? "number" : "string");
|
|
19782
19820
|
}
|
|
19783
19821
|
async function evalAssertOperand(operand, client, options, cacheContext, tempTables) {
|
|
19784
19822
|
switch (operand.type) {
|
|
19785
19823
|
case "VARIABLE":
|
|
19786
19824
|
throw new Error(`ParseError: unresolved batch variable @${operand.name}.`);
|
|
19787
19825
|
case "NUMBER":
|
|
19788
|
-
return numberLiteralText(operand);
|
|
19826
|
+
return { value: numberLiteralText(operand), semantics: "number" };
|
|
19789
19827
|
case "STRING":
|
|
19790
|
-
return operand.value;
|
|
19828
|
+
return { value: operand.value, semantics: "string" };
|
|
19791
19829
|
case "ARITH":
|
|
19792
|
-
return String(evalAssertArith(operand));
|
|
19830
|
+
return { value: String(evalAssertArith(operand)), semantics: "number" };
|
|
19793
19831
|
case "SCALAR_SUBQUERY": {
|
|
19794
19832
|
try {
|
|
19795
|
-
|
|
19833
|
+
const value = await evaluateScalarSubquery(
|
|
19796
19834
|
operand.query,
|
|
19797
19835
|
client,
|
|
19798
19836
|
options,
|
|
19799
19837
|
cacheContext,
|
|
19800
19838
|
tempTables
|
|
19801
19839
|
);
|
|
19840
|
+
const numeric = await scalarSubqueryHasNumericResult(
|
|
19841
|
+
operand.query,
|
|
19842
|
+
client,
|
|
19843
|
+
cacheContext,
|
|
19844
|
+
tempTables
|
|
19845
|
+
);
|
|
19846
|
+
return { value, semantics: numeric ? "number" : "string" };
|
|
19802
19847
|
} catch (e) {
|
|
19803
19848
|
if (e instanceof ScalarSubqueryError) throw new AssertError(e.message);
|
|
19804
19849
|
throw e;
|
|
@@ -19806,6 +19851,21 @@ async function evalAssertOperand(operand, client, options, cacheContext, tempTab
|
|
|
19806
19851
|
}
|
|
19807
19852
|
}
|
|
19808
19853
|
}
|
|
19854
|
+
async function scalarSubqueryHasNumericResult(query, client, cacheContext, tempTables) {
|
|
19855
|
+
const first = query.columns[0];
|
|
19856
|
+
let numeric = first?.type === "ARITH_COL" || first?.type === "ARITH_AGG_COL" || first?.type === "WINDOW_COL" && (first.windowKind === void 0 || first.windowKind === "RANKING" || first.windowKind === "AGGREGATE" && (first.aggFunc === "COUNT" || first.aggFunc === "SUM" || first.aggFunc === "AVG")) || first?.type === "AGGREGATE" && (first.func === "COUNT" || first.func === "SUM" || first.func === "AVG" || first.func === "STDDEV_POP" || first.func === "STDDEV_SAMP" || first.func === "VAR_POP" || first.func === "VAR_SAMP" || first.func === "MEDIAN");
|
|
19857
|
+
if (first?.type === "AGGREGATE" && first.func === "MODE" || first?.type === "WINDOW_COL" && first.windowKind === "VALUE" || first?.type === "WINDOW_COL" && first.windowKind === "AGGREGATE" && (first.aggFunc === "MIN" || first.aggFunc === "MAX")) {
|
|
19858
|
+
const meta = (await inferSelectColumnMeta(
|
|
19859
|
+
query,
|
|
19860
|
+
["__scalar__"],
|
|
19861
|
+
client,
|
|
19862
|
+
cacheContext,
|
|
19863
|
+
tempTables
|
|
19864
|
+
)).get("__scalar__");
|
|
19865
|
+
numeric = meta?.semantics?.compareMode === "number" || meta?.sortKind === "number";
|
|
19866
|
+
}
|
|
19867
|
+
return numeric;
|
|
19868
|
+
}
|
|
19809
19869
|
async function evaluateScalarSubquery(sourceQuery, client, options, cacheContext, tempTables) {
|
|
19810
19870
|
const { query, probed } = withScalarProbeLimit(sourceQuery);
|
|
19811
19871
|
const result = await runSubquery(query, client, options, cacheContext, tempTables);
|
|
@@ -22785,6 +22845,206 @@ function upsertCompositeKey(parts) {
|
|
|
22785
22845
|
function upsertNormalizedKey(parts, numericKey) {
|
|
22786
22846
|
return JSON.stringify(parts.map((p, i) => numericKey[i] ? normalizeKeyPart(p) : p));
|
|
22787
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
|
+
}
|
|
22788
23048
|
function lookupUpsertTarget(index, keyParts) {
|
|
22789
23049
|
const exact = index.raw.get(upsertCompositeKey(keyParts));
|
|
22790
23050
|
if (exact !== void 0) return exact;
|
|
@@ -23001,6 +23261,10 @@ async function getFieldsCached(appId, client, cacheContext) {
|
|
|
23001
23261
|
setScopedCacheValue(fieldInfoCache, cacheContext, appId, loading);
|
|
23002
23262
|
return loading;
|
|
23003
23263
|
}
|
|
23264
|
+
async function getFieldsIfCached(appId, cacheContext) {
|
|
23265
|
+
const cached = getScopedCacheValue(fieldInfoCache, cacheContext, appId);
|
|
23266
|
+
return cached ? await cached : null;
|
|
23267
|
+
}
|
|
23004
23268
|
async function getNumberPrecisionCached(appId, client, cacheContext) {
|
|
23005
23269
|
const cached = getScopedCacheValue(numberPrecisionCache, cacheContext, appId);
|
|
23006
23270
|
if (cached) return cached;
|
|
@@ -25568,6 +25832,16 @@ async function executeDelete(stmt, client, options, cacheContext) {
|
|
|
25568
25832
|
}
|
|
25569
25833
|
return { type: "DELETE", deletedCount: ids.length };
|
|
25570
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
|
+
}
|
|
25571
25845
|
async function executeUpsert(stmt, client, options, cacheContext) {
|
|
25572
25846
|
if (stmt.onInsertApplyBlocks?.length || stmt.onUpdateApplyBlocks?.length) {
|
|
25573
25847
|
return executeApplyUpsert(stmt, client, options, cacheContext);
|
|
@@ -25579,6 +25853,14 @@ async function executeUpsert(stmt, client, options, cacheContext) {
|
|
|
25579
25853
|
const toUpdate = [];
|
|
25580
25854
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
25581
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
|
+
}
|
|
25582
25864
|
const targetIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeyValues, client, options, fieldTypes);
|
|
25583
25865
|
stmt.values.forEach((row, rowIdx) => {
|
|
25584
25866
|
const record = {};
|
|
@@ -25905,6 +26187,9 @@ function buildSubtableReorderPutParams(appId, parentId, revision, subtableCode,
|
|
|
25905
26187
|
};
|
|
25906
26188
|
}
|
|
25907
26189
|
function valueToString(value) {
|
|
26190
|
+
if (value.type === "VARIABLE") {
|
|
26191
|
+
throw new Error(`InternalError: unresolved variable @${value.name} reached INSERT evaluation.`);
|
|
26192
|
+
}
|
|
25908
26193
|
if (value.type === "STRING") return value.value;
|
|
25909
26194
|
if (value.type === "NUMBER") return numberLiteralText(value);
|
|
25910
26195
|
if (value.type === "CASE_VALUE") return evalCaseWhen(value.expr, {});
|
|
@@ -26064,6 +26349,12 @@ async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
26064
26349
|
const rowKeyValues = records.map(
|
|
26065
26350
|
(record) => stmt.keyFields.map((key) => String(record[key]?.value ?? ""))
|
|
26066
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
|
+
}
|
|
26067
26358
|
if (importSourceByDmlStatement.has(stmt)) {
|
|
26068
26359
|
const numericKey = stmt.keyFields.map((key) => fieldTypes.get(key) === "NUMBER");
|
|
26069
26360
|
const sourceKeys = /* @__PURE__ */ new Set();
|
|
@@ -27115,7 +27406,7 @@ var EXPLAIN_FETCH_PLAN = /* @__PURE__ */ Symbol("ksql.explainFetchPlan");
|
|
|
27115
27406
|
function setExplainFetchPlan(result, plan) {
|
|
27116
27407
|
result[EXPLAIN_FETCH_PLAN] = plan;
|
|
27117
27408
|
}
|
|
27118
|
-
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" }) {
|
|
27119
27410
|
const asOfClock = createAsOfClock(asOf ?? /* @__PURE__ */ new Date(), timezone);
|
|
27120
27411
|
const recursiveLimits = resolveRecursiveCteLimits({
|
|
27121
27412
|
recursiveCteMaxDepth,
|
|
@@ -27214,10 +27505,15 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
|
|
|
27214
27505
|
maxRecords,
|
|
27215
27506
|
dmlMaxRows
|
|
27216
27507
|
) : [];
|
|
27508
|
+
const nativeUpsertPlan = await nativeUpsertExplainLines(
|
|
27509
|
+
planStmt,
|
|
27510
|
+
invocationCacheContext,
|
|
27511
|
+
nativeUpsertOptions
|
|
27512
|
+
);
|
|
27217
27513
|
plans.push({
|
|
27218
27514
|
index: i,
|
|
27219
27515
|
type: analysis.statements[i].statementType,
|
|
27220
|
-
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]
|
|
27221
27517
|
});
|
|
27222
27518
|
fetchStatements.push({
|
|
27223
27519
|
index: i,
|
|
@@ -27531,7 +27827,7 @@ var explainMaterializedTables = /* @__PURE__ */ new WeakMap();
|
|
|
27531
27827
|
function defaultRecursiveExplainContext() {
|
|
27532
27828
|
return { maxRecords: 1e4, recursiveLimits: resolveRecursiveCteLimits({}) };
|
|
27533
27829
|
}
|
|
27534
|
-
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" }) {
|
|
27535
27831
|
const recursiveLimits = resolveRecursiveCteLimits({
|
|
27536
27832
|
recursiveCteMaxDepth,
|
|
27537
27833
|
recursiveCteMaxRows,
|
|
@@ -27569,7 +27865,8 @@ async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxA
|
|
|
27569
27865
|
cursorMaxActive
|
|
27570
27866
|
)
|
|
27571
27867
|
];
|
|
27572
|
-
const
|
|
27868
|
+
const nativeUpsertPlan = await nativeUpsertExplainLines(stmt, cacheContext, nativeUpsertOptions);
|
|
27869
|
+
const lines = addFetchSummary([...planLines, ...nativeUpsertPlan], fetchCollector.sources);
|
|
27573
27870
|
const result = {
|
|
27574
27871
|
type: "SELECT",
|
|
27575
27872
|
columns: ["plan"],
|
|
@@ -29492,7 +29789,7 @@ var RequestGate = class {
|
|
|
29492
29789
|
}
|
|
29493
29790
|
};
|
|
29494
29791
|
function withRequestGate(client, gate) {
|
|
29495
|
-
|
|
29792
|
+
const wrapped = {
|
|
29496
29793
|
getRecords: (params) => gate.runReadOnly(() => client.getRecords(params)),
|
|
29497
29794
|
openCursor: async (params) => {
|
|
29498
29795
|
const handle = await gate.runCursorStep(() => client.openCursor(params));
|
|
@@ -29510,6 +29807,10 @@ function withRequestGate(client, gate) {
|
|
|
29510
29807
|
putRecords: (params) => gate.runMutation(() => client.putRecords(params)),
|
|
29511
29808
|
deleteRecords: (params) => gate.runMutation(() => client.deleteRecords(params))
|
|
29512
29809
|
};
|
|
29810
|
+
if ("upsertRecords" in client && typeof client.upsertRecords === "function") {
|
|
29811
|
+
wrapped.upsertRecords = (params) => gate.runMutation(() => client.upsertRecords(params));
|
|
29812
|
+
}
|
|
29813
|
+
return wrapped;
|
|
29513
29814
|
}
|
|
29514
29815
|
var globalGate = null;
|
|
29515
29816
|
function getGlobalRequestGate(options) {
|
|
@@ -30266,6 +30567,16 @@ function createNodeKintoneConnection(baseUrl, tokenResolver) {
|
|
|
30266
30567
|
_params.app
|
|
30267
30568
|
);
|
|
30268
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
|
+
},
|
|
30269
30580
|
async deleteRecords(_params) {
|
|
30270
30581
|
await requestJson(
|
|
30271
30582
|
`${apiBasePath}/records.json`,
|
|
@@ -30665,6 +30976,9 @@ var CLI_HELP_TEXT = HELP_TEXT.replace(
|
|
|
30665
30976
|
" --max-records <n> Max records to fetch (default: 500)",
|
|
30666
30977
|
` --max-records <n> Max records to fetch (default: 500)
|
|
30667
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"
|
|
30668
30982
|
);
|
|
30669
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";
|
|
30670
30984
|
function toCliImportError(error, importEnabled) {
|
|
@@ -30716,6 +31030,7 @@ function parseArgs(argv) {
|
|
|
30716
31030
|
debugHeaders: false,
|
|
30717
31031
|
exitOnEmpty: false,
|
|
30718
31032
|
allowDml: false,
|
|
31033
|
+
nativeUpsert: false,
|
|
30719
31034
|
yes: false,
|
|
30720
31035
|
allowWithoutWhere: false,
|
|
30721
31036
|
continueOnError: false,
|
|
@@ -30788,6 +31103,10 @@ function parseArgs(argv) {
|
|
|
30788
31103
|
out.allowDml = true;
|
|
30789
31104
|
continue;
|
|
30790
31105
|
}
|
|
31106
|
+
if (a === "--native-upsert") {
|
|
31107
|
+
out.nativeUpsert = true;
|
|
31108
|
+
continue;
|
|
31109
|
+
}
|
|
30791
31110
|
if (a === "--yes") {
|
|
30792
31111
|
out.yes = true;
|
|
30793
31112
|
continue;
|
|
@@ -31625,6 +31944,7 @@ function buildReplExecArgv(base, sql, dryRun, format) {
|
|
|
31625
31944
|
if (base.exitOnEmpty) argv.push("--exit-on-empty");
|
|
31626
31945
|
if (base.allowDml) argv.push("--yes");
|
|
31627
31946
|
if (base.allowDml) argv.push("--allow-dml");
|
|
31947
|
+
if (base.nativeUpsert) argv.push("--native-upsert");
|
|
31628
31948
|
if (base.allowWithoutWhere) argv.push("--allow-without-where");
|
|
31629
31949
|
if (base.continueOnError) argv.push("--continue-on-error");
|
|
31630
31950
|
return argv;
|
|
@@ -31801,7 +32121,8 @@ async function runConsole(base) {
|
|
|
31801
32121
|
` auth=${base.auth ?? "(auto)"}`,
|
|
31802
32122
|
` format=${format ?? "(default)"}`,
|
|
31803
32123
|
` dryrun=${dryRun ? "on" : "off"}`,
|
|
31804
|
-
` allow-dml=${base.allowDml ? "on" : "off"}
|
|
32124
|
+
` allow-dml=${base.allowDml ? "on" : "off"}`,
|
|
32125
|
+
` native-upsert=${base.nativeUpsert ? "on" : "off"}`
|
|
31805
32126
|
].join("\n") + "\n"
|
|
31806
32127
|
);
|
|
31807
32128
|
try {
|
|
@@ -31932,6 +32253,7 @@ async function runConsole(base) {
|
|
|
31932
32253
|
`app=${base.app ?? "(from SQL or config)"}`,
|
|
31933
32254
|
`resolved-app-profiles=${lastResolvedProfiles}`,
|
|
31934
32255
|
`allow-dml=${base.allowDml ? "on" : "off"}`,
|
|
32256
|
+
`native-upsert=${base.nativeUpsert ? "on" : "off"}`,
|
|
31935
32257
|
`dml-max-rows=${base.dmlMaxRows ?? "(default)"}`,
|
|
31936
32258
|
`dml-max-subtable-rows=${base.dmlMaxSubtableRows ?? "(default)"}`
|
|
31937
32259
|
];
|
|
@@ -32514,6 +32836,14 @@ async function run() {
|
|
|
32514
32836
|
if (!routed) throw new Error(`AuthError: profile "${pName}" is not resolved for APP${params.app}.`);
|
|
32515
32837
|
return routed.putRecords({ ...params, app: binding.appId });
|
|
32516
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
|
+
},
|
|
32517
32847
|
deleteRecords: (params) => {
|
|
32518
32848
|
const binding = appBindingByMappedApp.get(params.app) ?? { appId: params.app, profile: profileName.toLowerCase() };
|
|
32519
32849
|
const pName = binding.profile;
|
|
@@ -32567,7 +32897,14 @@ async function run() {
|
|
|
32567
32897
|
!dryRunUsesStaticTypedPlan,
|
|
32568
32898
|
recursiveCteMaxDepth,
|
|
32569
32899
|
recursiveCteMaxRows,
|
|
32570
|
-
recursiveCteMaxExpansions
|
|
32900
|
+
recursiveCteMaxExpansions,
|
|
32901
|
+
void 0,
|
|
32902
|
+
void 0,
|
|
32903
|
+
{
|
|
32904
|
+
surface: "CLI",
|
|
32905
|
+
enableNativeUpsert: args.nativeUpsert,
|
|
32906
|
+
clientHasNativeUpsert: true
|
|
32907
|
+
}
|
|
32571
32908
|
);
|
|
32572
32909
|
const out = [];
|
|
32573
32910
|
const restoredStatements = sqlDiagnosticContext ? restoreSqlDiagnosticValue(plans.statements, sqlDiagnosticContext.appBindingByMappedApp) : plans.statements;
|
|
@@ -32648,7 +32985,7 @@ query=${label}`);
|
|
|
32648
32985
|
return 2;
|
|
32649
32986
|
}
|
|
32650
32987
|
}
|
|
32651
|
-
let batchResult = await executeBatch(sql, client, {
|
|
32988
|
+
let batchResult = await executeBatch(sql, client, withNativeUpsertExecutionOption({
|
|
32652
32989
|
maxRecords,
|
|
32653
32990
|
fetchParallel,
|
|
32654
32991
|
onLimitReached: effectiveOnLimit,
|
|
@@ -32686,7 +33023,7 @@ query=${label}`);
|
|
|
32686
33023
|
}
|
|
32687
33024
|
return true;
|
|
32688
33025
|
} : void 0
|
|
32689
|
-
});
|
|
33026
|
+
}, args.nativeUpsert, true));
|
|
32690
33027
|
if (sqlDiagnosticContext) {
|
|
32691
33028
|
batchResult = {
|
|
32692
33029
|
...batchResult,
|
|
@@ -32703,7 +33040,7 @@ query=${label}`);
|
|
|
32703
33040
|
}
|
|
32704
33041
|
return writeBatchOutput(batchResult, { format, noHeader, pretty, displayOptions, outputPath, quiet });
|
|
32705
33042
|
}
|
|
32706
|
-
let result = args.dryRun ? await execute(`EXPLAIN ${sql}`, client, {
|
|
33043
|
+
let result = args.dryRun ? await execute(`EXPLAIN ${sql}`, client, withNativeUpsertExecutionOption({
|
|
32707
33044
|
maxRecords,
|
|
32708
33045
|
onLimitReached: onLimit,
|
|
32709
33046
|
cacheContext,
|
|
@@ -32715,7 +33052,7 @@ query=${label}`);
|
|
|
32715
33052
|
recursiveCteMaxDepth,
|
|
32716
33053
|
recursiveCteMaxRows,
|
|
32717
33054
|
recursiveCteMaxExpansions
|
|
32718
|
-
}) : await execute(sql, client, {
|
|
33055
|
+
}, args.nativeUpsert, true)) : await execute(sql, client, withNativeUpsertExecutionOption({
|
|
32719
33056
|
maxRecords,
|
|
32720
33057
|
fetchParallel,
|
|
32721
33058
|
onLimitReached: effectiveOnLimit,
|
|
@@ -32733,7 +33070,7 @@ query=${label}`);
|
|
|
32733
33070
|
dmlMaxSubtableRows
|
|
32734
33071
|
} : {},
|
|
32735
33072
|
...containsApplyMutation ? { allowApplyMutation: true } : {}
|
|
32736
|
-
});
|
|
33073
|
+
}, args.nativeUpsert, true));
|
|
32737
33074
|
if ((args.dryRun || parsedStatements[0]?.type === "EXPLAIN") && sqlDiagnosticContext) {
|
|
32738
33075
|
result = restoreSqlDiagnosticValue(result, sqlDiagnosticContext.appBindingByMappedApp);
|
|
32739
33076
|
}
|