@carllee1983/dbcli 1.52.0 → 1.53.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/.cursor/rules/dbcli.mdc +39 -28
- package/.cursor/skills/dbcli/reference.md +233 -21
- package/.github/skills/dbcli/SKILL.md +36 -25
- package/.github/skills/dbcli/reference.md +233 -21
- package/CHANGELOG.md +38 -0
- package/assets/SKILL.md +36 -25
- package/assets/SKILL.zh-TW.md +48 -30
- package/assets/reference.md +233 -21
- package/assets/ui-template.html +2 -2
- package/dist/cli-runtime.mjs +101686 -0
- package/dist/cli.mjs +140 -109572
- package/dist/core.d.ts +4 -0
- package/dist/core.mjs +157 -254
- package/dist/ui-style.css +2 -2
- package/package.json +4 -4
- package/plugins/dbcli-agent/skills/dbcli/SKILL.md +36 -25
- package/plugins/dbcli-agent/skills/dbcli/reference.md +233 -21
- package/skills/dbcli/SKILL.md +36 -25
- package/skills/dbcli/reference.md +233 -21
package/dist/core.d.ts
CHANGED
|
@@ -3010,6 +3010,10 @@ export declare class DataExecutor {
|
|
|
3010
3010
|
* Get identifier quote character (for table names and column names)
|
|
3011
3011
|
*/
|
|
3012
3012
|
private getQuoteChar;
|
|
3013
|
+
private checkBlacklist;
|
|
3014
|
+
private executeMutation;
|
|
3015
|
+
private successResult;
|
|
3016
|
+
private handleMutationError;
|
|
3013
3017
|
/**
|
|
3014
3018
|
* Build a parameterized UPDATE SQL statement
|
|
3015
3019
|
*/
|
package/dist/core.mjs
CHANGED
|
@@ -13639,6 +13639,23 @@ function mapError(error, system, options) {
|
|
|
13639
13639
|
]);
|
|
13640
13640
|
}
|
|
13641
13641
|
|
|
13642
|
+
// src/adapters/sql-adapter-utils.ts
|
|
13643
|
+
function requireConnected(connection) {
|
|
13644
|
+
if (!connection) {
|
|
13645
|
+
throw new ConnectionError("UNKNOWN", "Database connection not established", [
|
|
13646
|
+
"Call connect() to establish a connection"
|
|
13647
|
+
]);
|
|
13648
|
+
}
|
|
13649
|
+
return connection;
|
|
13650
|
+
}
|
|
13651
|
+
async function withMappedConnectionError(system, options, operation) {
|
|
13652
|
+
try {
|
|
13653
|
+
return await operation();
|
|
13654
|
+
} catch (error) {
|
|
13655
|
+
throw mapError(error, system, options);
|
|
13656
|
+
}
|
|
13657
|
+
}
|
|
13658
|
+
|
|
13642
13659
|
// src/adapters/postgresql-adapter.ts
|
|
13643
13660
|
import { Pool } from "pg";
|
|
13644
13661
|
// resources/lang/en/messages.json
|
|
@@ -14461,7 +14478,7 @@ class PostgreSQLAdapter {
|
|
|
14461
14478
|
}
|
|
14462
14479
|
}
|
|
14463
14480
|
async connect() {
|
|
14464
|
-
|
|
14481
|
+
await withMappedConnectionError("postgresql", this.options, async () => {
|
|
14465
14482
|
this.pool = new Pool({
|
|
14466
14483
|
host: this.options.host,
|
|
14467
14484
|
port: this.options.port,
|
|
@@ -14477,9 +14494,7 @@ class PostgreSQLAdapter {
|
|
|
14477
14494
|
const result = checkDbVersion(rawVersion, "postgresql");
|
|
14478
14495
|
warnIfUnsupported(result);
|
|
14479
14496
|
} catch {}
|
|
14480
|
-
}
|
|
14481
|
-
throw mapError(error, "postgresql", this.options);
|
|
14482
|
-
}
|
|
14497
|
+
});
|
|
14483
14498
|
}
|
|
14484
14499
|
async disconnect() {
|
|
14485
14500
|
try {
|
|
@@ -14494,45 +14509,29 @@ class PostgreSQLAdapter {
|
|
|
14494
14509
|
} catch {}
|
|
14495
14510
|
}
|
|
14496
14511
|
async testConnection() {
|
|
14497
|
-
|
|
14498
|
-
|
|
14499
|
-
"Call connect() to establish a connection"
|
|
14500
|
-
]);
|
|
14501
|
-
}
|
|
14502
|
-
try {
|
|
14512
|
+
requireConnected(this.pool);
|
|
14513
|
+
return withMappedConnectionError("postgresql", this.options, async () => {
|
|
14503
14514
|
const result = await this.execute("SELECT 1 as count");
|
|
14504
14515
|
return result.rows.length > 0;
|
|
14505
|
-
}
|
|
14506
|
-
throw mapError(error, "postgresql", this.options);
|
|
14507
|
-
}
|
|
14516
|
+
});
|
|
14508
14517
|
}
|
|
14509
14518
|
async execute(sql, params) {
|
|
14510
|
-
|
|
14511
|
-
|
|
14512
|
-
|
|
14513
|
-
]);
|
|
14514
|
-
}
|
|
14515
|
-
try {
|
|
14516
|
-
const result = params ? await this.pool.query(sql, params) : await this.pool.query(sql);
|
|
14519
|
+
const pool = requireConnected(this.pool);
|
|
14520
|
+
return withMappedConnectionError("postgresql", this.options, async () => {
|
|
14521
|
+
const result = params ? await pool.query(sql, params) : await pool.query(sql);
|
|
14517
14522
|
return {
|
|
14518
14523
|
rows: result.rows,
|
|
14519
14524
|
affectedRows: result.rowCount ?? 0
|
|
14520
14525
|
};
|
|
14521
|
-
}
|
|
14522
|
-
throw mapError(error, "postgresql", this.options);
|
|
14523
|
-
}
|
|
14526
|
+
});
|
|
14524
14527
|
}
|
|
14525
14528
|
async getServerVersion() {
|
|
14526
14529
|
const result = await this.execute("SHOW server_version");
|
|
14527
14530
|
return result.rows[0]?.server_version ?? "unknown";
|
|
14528
14531
|
}
|
|
14529
14532
|
async listTables() {
|
|
14530
|
-
|
|
14531
|
-
|
|
14532
|
-
"Call connect() to establish a connection"
|
|
14533
|
-
]);
|
|
14534
|
-
}
|
|
14535
|
-
try {
|
|
14533
|
+
requireConnected(this.pool);
|
|
14534
|
+
return withMappedConnectionError("postgresql", this.options, async () => {
|
|
14536
14535
|
const query = `
|
|
14537
14536
|
SELECT
|
|
14538
14537
|
n.nspname as schema_name,
|
|
@@ -14563,17 +14562,11 @@ class PostgreSQLAdapter {
|
|
|
14563
14562
|
estimatedRowCount: Math.max(0, row.estimated_rows || 0),
|
|
14564
14563
|
tableType: row.table_type
|
|
14565
14564
|
}));
|
|
14566
|
-
}
|
|
14567
|
-
throw mapError(error, "postgresql", this.options);
|
|
14568
|
-
}
|
|
14565
|
+
});
|
|
14569
14566
|
}
|
|
14570
14567
|
async getTableSchema(tableName) {
|
|
14571
|
-
|
|
14572
|
-
|
|
14573
|
-
"Call connect() to establish a connection"
|
|
14574
|
-
]);
|
|
14575
|
-
}
|
|
14576
|
-
try {
|
|
14568
|
+
requireConnected(this.pool);
|
|
14569
|
+
return withMappedConnectionError("postgresql", this.options, async () => {
|
|
14577
14570
|
const columnQuery = `
|
|
14578
14571
|
SELECT
|
|
14579
14572
|
c.column_name as name,
|
|
@@ -14760,9 +14753,7 @@ class PostgreSQLAdapter {
|
|
|
14760
14753
|
estimatedRowCount: Math.max(0, estimateResults[0]?.estimated_rows || 0)
|
|
14761
14754
|
};
|
|
14762
14755
|
return schema;
|
|
14763
|
-
}
|
|
14764
|
-
throw mapError(error, "postgresql", this.options);
|
|
14765
|
-
}
|
|
14756
|
+
});
|
|
14766
14757
|
}
|
|
14767
14758
|
}
|
|
14768
14759
|
|
|
@@ -14787,7 +14778,7 @@ class MySQLAdapter {
|
|
|
14787
14778
|
}
|
|
14788
14779
|
}
|
|
14789
14780
|
async connect() {
|
|
14790
|
-
|
|
14781
|
+
await withMappedConnectionError(this.system, this.options, async () => {
|
|
14791
14782
|
this.db = await mysql.createConnection({
|
|
14792
14783
|
host: this.options.host,
|
|
14793
14784
|
port: this.options.port,
|
|
@@ -14803,9 +14794,7 @@ class MySQLAdapter {
|
|
|
14803
14794
|
const result = checkDbVersion(rawVersion, this.system);
|
|
14804
14795
|
warnIfUnsupported(result);
|
|
14805
14796
|
} catch {}
|
|
14806
|
-
}
|
|
14807
|
-
throw mapError(error, this.system, this.options);
|
|
14808
|
-
}
|
|
14797
|
+
});
|
|
14809
14798
|
}
|
|
14810
14799
|
async disconnect() {
|
|
14811
14800
|
try {
|
|
@@ -14816,26 +14805,16 @@ class MySQLAdapter {
|
|
|
14816
14805
|
} catch {}
|
|
14817
14806
|
}
|
|
14818
14807
|
async testConnection() {
|
|
14819
|
-
|
|
14820
|
-
|
|
14821
|
-
"Call connect() to establish a connection"
|
|
14822
|
-
]);
|
|
14823
|
-
}
|
|
14824
|
-
try {
|
|
14808
|
+
requireConnected(this.db);
|
|
14809
|
+
return withMappedConnectionError(this.system, this.options, async () => {
|
|
14825
14810
|
const result = await this.execute("SELECT 1 as count");
|
|
14826
14811
|
return result.rows.length > 0;
|
|
14827
|
-
}
|
|
14828
|
-
throw mapError(error, this.system, this.options);
|
|
14829
|
-
}
|
|
14812
|
+
});
|
|
14830
14813
|
}
|
|
14831
14814
|
async execute(sql, params) {
|
|
14832
|
-
|
|
14833
|
-
|
|
14834
|
-
|
|
14835
|
-
]);
|
|
14836
|
-
}
|
|
14837
|
-
try {
|
|
14838
|
-
const [result] = params ? await this.db.execute(sql, params) : await this.db.execute(sql);
|
|
14815
|
+
const db = requireConnected(this.db);
|
|
14816
|
+
return withMappedConnectionError(this.system, this.options, async () => {
|
|
14817
|
+
const [result] = params ? await db.execute(sql, params) : await db.execute(sql);
|
|
14839
14818
|
if (Array.isArray(result)) {
|
|
14840
14819
|
return {
|
|
14841
14820
|
rows: result,
|
|
@@ -14848,21 +14827,15 @@ class MySQLAdapter {
|
|
|
14848
14827
|
affectedRows: header.affectedRows || 0,
|
|
14849
14828
|
lastInsertId: header.insertId
|
|
14850
14829
|
};
|
|
14851
|
-
}
|
|
14852
|
-
throw mapError(error, this.system, this.options);
|
|
14853
|
-
}
|
|
14830
|
+
});
|
|
14854
14831
|
}
|
|
14855
14832
|
async getServerVersion() {
|
|
14856
14833
|
const result = await this.execute("SELECT VERSION() as version");
|
|
14857
14834
|
return result.rows[0]?.version ?? "unknown";
|
|
14858
14835
|
}
|
|
14859
14836
|
async listTables() {
|
|
14860
|
-
|
|
14861
|
-
|
|
14862
|
-
"Call connect() to establish a connection"
|
|
14863
|
-
]);
|
|
14864
|
-
}
|
|
14865
|
-
try {
|
|
14837
|
+
requireConnected(this.db);
|
|
14838
|
+
return withMappedConnectionError(this.system, this.options, async () => {
|
|
14866
14839
|
const query = `
|
|
14867
14840
|
SELECT
|
|
14868
14841
|
t.TABLE_NAME as table_name,
|
|
@@ -14887,17 +14860,11 @@ class MySQLAdapter {
|
|
|
14887
14860
|
estimatedRowCount: row.row_count || 0,
|
|
14888
14861
|
tableType: row.table_type === "VIEW" ? "view" : "table"
|
|
14889
14862
|
}));
|
|
14890
|
-
}
|
|
14891
|
-
throw mapError(error, this.system, this.options);
|
|
14892
|
-
}
|
|
14863
|
+
});
|
|
14893
14864
|
}
|
|
14894
14865
|
async getTableSchema(tableName) {
|
|
14895
|
-
|
|
14896
|
-
|
|
14897
|
-
"Call connect() to establish a connection"
|
|
14898
|
-
]);
|
|
14899
|
-
}
|
|
14900
|
-
try {
|
|
14866
|
+
requireConnected(this.db);
|
|
14867
|
+
return withMappedConnectionError(this.system, this.options, async () => {
|
|
14901
14868
|
const columnQuery = `
|
|
14902
14869
|
SELECT
|
|
14903
14870
|
COLUMN_NAME as name,
|
|
@@ -14997,9 +14964,7 @@ class MySQLAdapter {
|
|
|
14997
14964
|
estimatedRowCount: estimateResults[0]?.estimated_rows || 0
|
|
14998
14965
|
};
|
|
14999
14966
|
return schema;
|
|
15000
|
-
}
|
|
15001
|
-
throw mapError(error, this.system, this.options);
|
|
15002
|
-
}
|
|
14967
|
+
});
|
|
15003
14968
|
}
|
|
15004
14969
|
}
|
|
15005
14970
|
|
|
@@ -22937,7 +22902,7 @@ DBCLI_PASSWORD=${password}
|
|
|
22937
22902
|
};
|
|
22938
22903
|
|
|
22939
22904
|
// src/utils/redaction.ts
|
|
22940
|
-
var SQL_SUBCOMMANDS = new Set(["query", "export", "lint"]);
|
|
22905
|
+
var SQL_SUBCOMMANDS = new Set(["query", "export", "lint", "assert", "verify"]);
|
|
22941
22906
|
var REDACTED_VALUE_FLAGS = new Set([
|
|
22942
22907
|
"--where",
|
|
22943
22908
|
"--set",
|
|
@@ -22951,6 +22916,12 @@ var REDACTED_VALUE_FLAGS = new Set([
|
|
|
22951
22916
|
"--secret",
|
|
22952
22917
|
"--bulk",
|
|
22953
22918
|
"--query-file",
|
|
22919
|
+
"--expect",
|
|
22920
|
+
"--vs",
|
|
22921
|
+
"--against",
|
|
22922
|
+
"--verification-subject",
|
|
22923
|
+
"--verification-summary",
|
|
22924
|
+
"--evidence-receipt",
|
|
22954
22925
|
"-f"
|
|
22955
22926
|
]);
|
|
22956
22927
|
var KEEP_VALUE_FLAGS = new Set([
|
|
@@ -22967,6 +22938,22 @@ var KEEP_VALUE_FLAGS = new Set([
|
|
|
22967
22938
|
]);
|
|
22968
22939
|
var LINT_BOOLEAN_FLAGS = new Set(["--no-schema", "--recovery"]);
|
|
22969
22940
|
var QUERY_BOOLEAN_FLAGS = new Set(["--ui", "--no-limit", "--no-truncate", "--recovery"]);
|
|
22941
|
+
var ASSERT_BOOLEAN_FLAGS = new Set(["--no-fail", "--write-verification-artifact"]);
|
|
22942
|
+
var VERIFY_REDACTED_VALUE_FLAGS = new Set([
|
|
22943
|
+
"--table",
|
|
22944
|
+
"--query",
|
|
22945
|
+
"--verify-query",
|
|
22946
|
+
"--ddl",
|
|
22947
|
+
"--statement",
|
|
22948
|
+
"--check",
|
|
22949
|
+
"--column",
|
|
22950
|
+
"--references",
|
|
22951
|
+
"--violation-query",
|
|
22952
|
+
"--subject-name",
|
|
22953
|
+
"--summary",
|
|
22954
|
+
"--baseline"
|
|
22955
|
+
]);
|
|
22956
|
+
var VERIFY_BOOLEAN_FLAGS = new Set(["--after-write", "--allow-preexisting"]);
|
|
22970
22957
|
function optionParts(token) {
|
|
22971
22958
|
if (token.startsWith("-f") && !token.startsWith("--") && token.length > 2) {
|
|
22972
22959
|
return { name: "-f", inlineValue: token.slice(2) };
|
|
@@ -22982,14 +22969,21 @@ function isKnownBooleanFlag(command, name) {
|
|
|
22982
22969
|
return LINT_BOOLEAN_FLAGS.has(name);
|
|
22983
22970
|
if (command === "query")
|
|
22984
22971
|
return QUERY_BOOLEAN_FLAGS.has(name);
|
|
22972
|
+
if (command === "assert")
|
|
22973
|
+
return ASSERT_BOOLEAN_FLAGS.has(name);
|
|
22974
|
+
if (command === "verify")
|
|
22975
|
+
return VERIFY_BOOLEAN_FLAGS.has(name);
|
|
22985
22976
|
return false;
|
|
22986
22977
|
}
|
|
22978
|
+
function isRedactedValueFlag(command, name) {
|
|
22979
|
+
return REDACTED_VALUE_FLAGS.has(name) || command === "verify" && VERIFY_REDACTED_VALUE_FLAGS.has(name);
|
|
22980
|
+
}
|
|
22987
22981
|
function findSensitiveSubcommand(argv) {
|
|
22988
22982
|
for (let index = 1;index < argv.length; index++) {
|
|
22989
22983
|
const token = argv[index];
|
|
22990
22984
|
if (isOptionToken(token)) {
|
|
22991
22985
|
const { name, inlineValue } = optionParts(token);
|
|
22992
|
-
if (inlineValue === undefined && (
|
|
22986
|
+
if (inlineValue === undefined && (isRedactedValueFlag(undefined, name) || KEEP_VALUE_FLAGS.has(name))) {
|
|
22993
22987
|
index++;
|
|
22994
22988
|
}
|
|
22995
22989
|
continue;
|
|
@@ -23003,6 +22997,7 @@ function sensitiveArgvValues(argv) {
|
|
|
23003
22997
|
const sensitiveCommand = findSensitiveSubcommand(argv);
|
|
23004
22998
|
const values = new Set;
|
|
23005
22999
|
let capturedSingleSql = false;
|
|
23000
|
+
let verifyScenarioSeen = false;
|
|
23006
23001
|
let afterEndOfOptions = false;
|
|
23007
23002
|
for (let index = 0;index < argv.length; index++) {
|
|
23008
23003
|
const token = argv[index];
|
|
@@ -23012,7 +23007,7 @@ function sensitiveArgvValues(argv) {
|
|
|
23012
23007
|
}
|
|
23013
23008
|
if (!afterEndOfOptions && isOptionToken(token)) {
|
|
23014
23009
|
const { name, inlineValue } = optionParts(token);
|
|
23015
|
-
if (
|
|
23010
|
+
if (isRedactedValueFlag(sensitiveCommand?.command, name)) {
|
|
23016
23011
|
const value = inlineValue ?? argv[index + 1];
|
|
23017
23012
|
if (value) {
|
|
23018
23013
|
values.add(value);
|
|
@@ -23039,6 +23034,10 @@ function sensitiveArgvValues(argv) {
|
|
|
23039
23034
|
}
|
|
23040
23035
|
continue;
|
|
23041
23036
|
}
|
|
23037
|
+
if (sensitiveCommand?.command === "verify" && index > sensitiveCommand.index && !verifyScenarioSeen) {
|
|
23038
|
+
verifyScenarioSeen = true;
|
|
23039
|
+
continue;
|
|
23040
|
+
}
|
|
23042
23041
|
if (sensitiveCommand && index > sensitiveCommand.index && (sensitiveCommand.command === "lint" || !capturedSingleSql)) {
|
|
23043
23042
|
values.add(token);
|
|
23044
23043
|
capturedSingleSql = true;
|
|
@@ -23052,6 +23051,7 @@ function redactArgv(argv) {
|
|
|
23052
23051
|
const sensitiveCommand = findSensitiveSubcommand(argv);
|
|
23053
23052
|
const out = [];
|
|
23054
23053
|
let redactedSingleSql = false;
|
|
23054
|
+
let verifyScenarioSeen = false;
|
|
23055
23055
|
let afterEndOfOptions = false;
|
|
23056
23056
|
for (let i = 0;i < argv.length; i++) {
|
|
23057
23057
|
const tok = argv[i];
|
|
@@ -23062,7 +23062,7 @@ function redactArgv(argv) {
|
|
|
23062
23062
|
}
|
|
23063
23063
|
if (!afterEndOfOptions && isOptionToken(tok)) {
|
|
23064
23064
|
const { name, inlineValue } = optionParts(tok);
|
|
23065
|
-
if (
|
|
23065
|
+
if (isRedactedValueFlag(sensitiveCommand?.command, name)) {
|
|
23066
23066
|
out.push(`${name} <redacted>`);
|
|
23067
23067
|
if (inlineValue === undefined)
|
|
23068
23068
|
i++;
|
|
@@ -23088,6 +23088,11 @@ function redactArgv(argv) {
|
|
|
23088
23088
|
continue;
|
|
23089
23089
|
}
|
|
23090
23090
|
}
|
|
23091
|
+
if (sensitiveCommand?.command === "verify" && i > sensitiveCommand.index && !verifyScenarioSeen) {
|
|
23092
|
+
out.push(tok);
|
|
23093
|
+
verifyScenarioSeen = true;
|
|
23094
|
+
continue;
|
|
23095
|
+
}
|
|
23091
23096
|
if (sensitiveCommand && i > sensitiveCommand.index && (sensitiveCommand.command === "lint" || !redactedSingleSql)) {
|
|
23092
23097
|
out.push("<sql>");
|
|
23093
23098
|
redactedSingleSql = true;
|
|
@@ -23650,141 +23655,27 @@ class DataExecutor {
|
|
|
23650
23655
|
async executeInsert(tableName, data, schema, options) {
|
|
23651
23656
|
const timestamp = new Date().toISOString();
|
|
23652
23657
|
try {
|
|
23653
|
-
|
|
23654
|
-
this.blacklistValidator.checkTableBlacklist("INSERT", tableName, []);
|
|
23655
|
-
}
|
|
23658
|
+
this.checkBlacklist("INSERT", tableName);
|
|
23656
23659
|
enforcePermission("INSERT INTO dummy", this.permission);
|
|
23657
|
-
|
|
23658
|
-
if (options?.dryRun) {
|
|
23659
|
-
return {
|
|
23660
|
-
status: "success",
|
|
23661
|
-
operation: "insert",
|
|
23662
|
-
rows_affected: 0,
|
|
23663
|
-
timestamp,
|
|
23664
|
-
sql
|
|
23665
|
-
};
|
|
23666
|
-
}
|
|
23667
|
-
if (!options?.force) {
|
|
23668
|
-
console.log(`
|
|
23669
|
-
Generated SQL:`);
|
|
23670
|
-
console.log(` ${sql}`);
|
|
23671
|
-
console.log(`
|
|
23672
|
-
Parameters:`);
|
|
23673
|
-
console.log(` ${JSON.stringify(params, null, 2)}`);
|
|
23674
|
-
const confirmed = await promptUser.confirm("Proceed with this operation?");
|
|
23675
|
-
if (!confirmed) {
|
|
23676
|
-
return {
|
|
23677
|
-
status: "success",
|
|
23678
|
-
operation: "insert",
|
|
23679
|
-
rows_affected: 0,
|
|
23680
|
-
timestamp,
|
|
23681
|
-
sql
|
|
23682
|
-
};
|
|
23683
|
-
}
|
|
23684
|
-
}
|
|
23685
|
-
const result = await this.adapter.execute(sql, params);
|
|
23686
|
-
return {
|
|
23687
|
-
status: "success",
|
|
23688
|
-
operation: "insert",
|
|
23689
|
-
rows_affected: result.affectedRows,
|
|
23690
|
-
timestamp,
|
|
23691
|
-
sql
|
|
23692
|
-
};
|
|
23660
|
+
return await this.executeMutation("insert", this.buildInsertSql(tableName, data, schema), timestamp, options, { prompt: "Proceed with this operation?" });
|
|
23693
23661
|
} catch (error) {
|
|
23694
|
-
|
|
23695
|
-
throw error;
|
|
23696
|
-
}
|
|
23697
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
23698
|
-
if (error instanceof PermissionError) {
|
|
23699
|
-
return {
|
|
23700
|
-
status: "error",
|
|
23701
|
-
operation: "insert",
|
|
23702
|
-
rows_affected: 0,
|
|
23703
|
-
timestamp,
|
|
23704
|
-
error: "Permission denied: Query-only mode only allows SELECT. Use Read-Write or Admin mode to execute INSERT."
|
|
23705
|
-
};
|
|
23706
|
-
}
|
|
23707
|
-
return {
|
|
23708
|
-
status: "error",
|
|
23709
|
-
operation: "insert",
|
|
23710
|
-
rows_affected: 0,
|
|
23711
|
-
timestamp,
|
|
23712
|
-
error: `INSERT failed: ${errorMessage}`
|
|
23713
|
-
};
|
|
23662
|
+
return this.handleMutationError("insert", timestamp, error);
|
|
23714
23663
|
}
|
|
23715
23664
|
}
|
|
23716
23665
|
async executeUpdate(tableName, data, where, schema, options) {
|
|
23717
23666
|
const timestamp = new Date().toISOString();
|
|
23718
23667
|
try {
|
|
23719
|
-
|
|
23720
|
-
this.blacklistValidator.checkTableBlacklist("UPDATE", tableName, []);
|
|
23721
|
-
}
|
|
23668
|
+
this.checkBlacklist("UPDATE", tableName);
|
|
23722
23669
|
enforcePermission("UPDATE dummy", this.permission);
|
|
23723
|
-
|
|
23724
|
-
if (options?.dryRun) {
|
|
23725
|
-
return {
|
|
23726
|
-
status: "success",
|
|
23727
|
-
operation: "update",
|
|
23728
|
-
rows_affected: 0,
|
|
23729
|
-
timestamp,
|
|
23730
|
-
sql
|
|
23731
|
-
};
|
|
23732
|
-
}
|
|
23733
|
-
if (!options?.force) {
|
|
23734
|
-
console.log(`
|
|
23735
|
-
Generated SQL:`);
|
|
23736
|
-
console.log(` ${sql}`);
|
|
23737
|
-
console.log(`
|
|
23738
|
-
Parameters:`);
|
|
23739
|
-
console.log(` ${JSON.stringify(params, null, 2)}`);
|
|
23740
|
-
const confirmed = await promptUser.confirm("Proceed with this operation?");
|
|
23741
|
-
if (!confirmed) {
|
|
23742
|
-
return {
|
|
23743
|
-
status: "success",
|
|
23744
|
-
operation: "update",
|
|
23745
|
-
rows_affected: 0,
|
|
23746
|
-
timestamp,
|
|
23747
|
-
sql
|
|
23748
|
-
};
|
|
23749
|
-
}
|
|
23750
|
-
}
|
|
23751
|
-
const result = await this.adapter.execute(sql, params);
|
|
23752
|
-
return {
|
|
23753
|
-
status: "success",
|
|
23754
|
-
operation: "update",
|
|
23755
|
-
rows_affected: result.affectedRows,
|
|
23756
|
-
timestamp,
|
|
23757
|
-
sql
|
|
23758
|
-
};
|
|
23670
|
+
return await this.executeMutation("update", this.buildUpdateSql(tableName, data, where, schema), timestamp, options, { prompt: "Proceed with this operation?" });
|
|
23759
23671
|
} catch (error) {
|
|
23760
|
-
|
|
23761
|
-
throw error;
|
|
23762
|
-
}
|
|
23763
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
23764
|
-
if (error instanceof PermissionError) {
|
|
23765
|
-
return {
|
|
23766
|
-
status: "error",
|
|
23767
|
-
operation: "update",
|
|
23768
|
-
rows_affected: 0,
|
|
23769
|
-
timestamp,
|
|
23770
|
-
error: "Permission denied: Query-only mode only allows SELECT. Use Read-Write or Admin mode to execute UPDATE."
|
|
23771
|
-
};
|
|
23772
|
-
}
|
|
23773
|
-
return {
|
|
23774
|
-
status: "error",
|
|
23775
|
-
operation: "update",
|
|
23776
|
-
rows_affected: 0,
|
|
23777
|
-
timestamp,
|
|
23778
|
-
error: `UPDATE failed: ${errorMessage}`
|
|
23779
|
-
};
|
|
23672
|
+
return this.handleMutationError("update", timestamp, error);
|
|
23780
23673
|
}
|
|
23781
23674
|
}
|
|
23782
23675
|
async executeDelete(tableName, where, schema, options) {
|
|
23783
23676
|
const timestamp = new Date().toISOString();
|
|
23784
23677
|
try {
|
|
23785
|
-
|
|
23786
|
-
this.blacklistValidator.checkTableBlacklist("DELETE", tableName, []);
|
|
23787
|
-
}
|
|
23678
|
+
this.checkBlacklist("DELETE", tableName);
|
|
23788
23679
|
if (this.permission !== "data-admin" && this.permission !== "admin") {
|
|
23789
23680
|
return {
|
|
23790
23681
|
status: "error",
|
|
@@ -23794,63 +23685,75 @@ Parameters:`);
|
|
|
23794
23685
|
error: "Permission denied: DELETE operation requires Data-Admin or Admin permission."
|
|
23795
23686
|
};
|
|
23796
23687
|
}
|
|
23797
|
-
|
|
23798
|
-
|
|
23799
|
-
|
|
23800
|
-
|
|
23801
|
-
|
|
23802
|
-
|
|
23803
|
-
|
|
23804
|
-
|
|
23805
|
-
|
|
23806
|
-
|
|
23807
|
-
|
|
23808
|
-
|
|
23809
|
-
|
|
23688
|
+
return await this.executeMutation("delete", this.buildDeleteSql(tableName, where, schema), timestamp, options, {
|
|
23689
|
+
warning: "\u26A0\uFE0F Warning: DELETE operation is destructive and cannot be undone!",
|
|
23690
|
+
prompt: "Are you sure you want to execute this DELETE operation? This cannot be undone."
|
|
23691
|
+
});
|
|
23692
|
+
} catch (error) {
|
|
23693
|
+
return this.handleMutationError("delete", timestamp, error);
|
|
23694
|
+
}
|
|
23695
|
+
}
|
|
23696
|
+
getSystemType() {
|
|
23697
|
+
return this.dbSystem;
|
|
23698
|
+
}
|
|
23699
|
+
getQuoteChar() {
|
|
23700
|
+
return this.dbSystem === "mysql" ? "`" : '"';
|
|
23701
|
+
}
|
|
23702
|
+
checkBlacklist(operation, tableName) {
|
|
23703
|
+
this.blacklistValidator?.checkTableBlacklist(operation, tableName, []);
|
|
23704
|
+
}
|
|
23705
|
+
async executeMutation(operation, statement, timestamp, options, confirmation) {
|
|
23706
|
+
if (options?.dryRun) {
|
|
23707
|
+
return this.successResult(operation, 0, timestamp, statement.sql);
|
|
23708
|
+
}
|
|
23709
|
+
if (!options?.force) {
|
|
23710
|
+
if (confirmation.warning) {
|
|
23810
23711
|
console.log(`
|
|
23712
|
+
${confirmation.warning}`);
|
|
23713
|
+
}
|
|
23714
|
+
console.log(`
|
|
23811
23715
|
Generated SQL:`);
|
|
23812
|
-
|
|
23813
|
-
|
|
23716
|
+
console.log(` ${statement.sql}`);
|
|
23717
|
+
console.log(`
|
|
23814
23718
|
Parameters:`);
|
|
23815
|
-
|
|
23816
|
-
|
|
23817
|
-
|
|
23818
|
-
return {
|
|
23819
|
-
status: "success",
|
|
23820
|
-
operation: "delete",
|
|
23821
|
-
rows_affected: 0,
|
|
23822
|
-
timestamp,
|
|
23823
|
-
sql
|
|
23824
|
-
};
|
|
23825
|
-
}
|
|
23719
|
+
console.log(` ${JSON.stringify(statement.params, null, 2)}`);
|
|
23720
|
+
if (!await promptUser.confirm(confirmation.prompt)) {
|
|
23721
|
+
return this.successResult(operation, 0, timestamp, statement.sql);
|
|
23826
23722
|
}
|
|
23827
|
-
|
|
23828
|
-
|
|
23829
|
-
|
|
23830
|
-
|
|
23831
|
-
|
|
23832
|
-
|
|
23833
|
-
|
|
23834
|
-
|
|
23835
|
-
|
|
23836
|
-
|
|
23837
|
-
|
|
23838
|
-
|
|
23839
|
-
|
|
23723
|
+
}
|
|
23724
|
+
const result = await this.adapter.execute(statement.sql, statement.params);
|
|
23725
|
+
return this.successResult(operation, result.affectedRows, timestamp, statement.sql);
|
|
23726
|
+
}
|
|
23727
|
+
successResult(operation, rowsAffected, timestamp, sql) {
|
|
23728
|
+
return {
|
|
23729
|
+
status: "success",
|
|
23730
|
+
operation,
|
|
23731
|
+
rows_affected: rowsAffected,
|
|
23732
|
+
timestamp,
|
|
23733
|
+
sql
|
|
23734
|
+
};
|
|
23735
|
+
}
|
|
23736
|
+
handleMutationError(operation, timestamp, error) {
|
|
23737
|
+
if (error instanceof BlacklistError) {
|
|
23738
|
+
throw error;
|
|
23739
|
+
}
|
|
23740
|
+
if (error instanceof PermissionError && operation !== "delete") {
|
|
23840
23741
|
return {
|
|
23841
23742
|
status: "error",
|
|
23842
|
-
operation
|
|
23743
|
+
operation,
|
|
23843
23744
|
rows_affected: 0,
|
|
23844
23745
|
timestamp,
|
|
23845
|
-
error: `
|
|
23746
|
+
error: `Permission denied: Query-only mode only allows SELECT. Use Read-Write or Admin mode to execute ${operation.toUpperCase()}.`
|
|
23846
23747
|
};
|
|
23847
23748
|
}
|
|
23848
|
-
|
|
23849
|
-
|
|
23850
|
-
|
|
23851
|
-
|
|
23852
|
-
|
|
23853
|
-
|
|
23749
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
23750
|
+
return {
|
|
23751
|
+
status: "error",
|
|
23752
|
+
operation,
|
|
23753
|
+
rows_affected: 0,
|
|
23754
|
+
timestamp,
|
|
23755
|
+
error: `${operation.toUpperCase()} failed: ${errorMessage}`
|
|
23756
|
+
};
|
|
23854
23757
|
}
|
|
23855
23758
|
buildUpdateSql(tableName, data, where, schema) {
|
|
23856
23759
|
const dataKeys = Object.keys(data);
|