@carllee1983/dbcli 1.52.1 → 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/dist/cli.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@carllee1983/dbcli",
6
- version: "1.52.1",
6
+ version: "1.53.0",
7
7
  description: "Database CLI for AI agents",
8
8
  type: "module",
9
9
  publishConfig: {
@@ -120,15 +120,15 @@ var package_default = {
120
120
  "dts-bundle-generator": "^9.5.1",
121
121
  eslint: "^10.4.0",
122
122
  "happy-dom": "^20.10.6",
123
- postcss: "^8.5.25",
123
+ postcss: "^8.5.26",
124
124
  prettier: "^3.8.3",
125
- tailwindcss: "3.4.1",
125
+ tailwindcss: "3.4.19",
126
126
  typescript: "^5.9.3",
127
127
  "typescript-eslint": "^8.59.4"
128
128
  },
129
129
  overrides: {
130
130
  "brace-expansion": "^5.0.9",
131
- postcss: "^8.5.25"
131
+ postcss: "^8.5.26"
132
132
  }
133
133
  };
134
134
 
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
- try {
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
- } catch (error) {
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
- if (!this.pool) {
14498
- throw new ConnectionError("UNKNOWN", "Database connection not established", [
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
- } catch (error) {
14506
- throw mapError(error, "postgresql", this.options);
14507
- }
14516
+ });
14508
14517
  }
14509
14518
  async execute(sql, params) {
14510
- if (!this.pool) {
14511
- throw new ConnectionError("UNKNOWN", "Database connection not established", [
14512
- "Call connect() to establish a connection"
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
- } catch (error) {
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
- if (!this.pool) {
14531
- throw new ConnectionError("UNKNOWN", "Database connection not established", [
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
- } catch (error) {
14567
- throw mapError(error, "postgresql", this.options);
14568
- }
14565
+ });
14569
14566
  }
14570
14567
  async getTableSchema(tableName) {
14571
- if (!this.pool) {
14572
- throw new ConnectionError("UNKNOWN", "Database connection not established", [
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
- } catch (error) {
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
- try {
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
- } catch (error) {
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
- if (!this.db) {
14820
- throw new ConnectionError("UNKNOWN", "Database connection not established", [
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
- } catch (error) {
14828
- throw mapError(error, this.system, this.options);
14829
- }
14812
+ });
14830
14813
  }
14831
14814
  async execute(sql, params) {
14832
- if (!this.db) {
14833
- throw new ConnectionError("UNKNOWN", "Database connection not established", [
14834
- "Call connect() to establish a connection"
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
- } catch (error) {
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
- if (!this.db) {
14861
- throw new ConnectionError("UNKNOWN", "Database connection not established", [
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
- } catch (error) {
14891
- throw mapError(error, this.system, this.options);
14892
- }
14863
+ });
14893
14864
  }
14894
14865
  async getTableSchema(tableName) {
14895
- if (!this.db) {
14896
- throw new ConnectionError("UNKNOWN", "Database connection not established", [
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
- } catch (error) {
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 && (REDACTED_VALUE_FLAGS.has(name) || KEEP_VALUE_FLAGS.has(name))) {
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 (REDACTED_VALUE_FLAGS.has(name)) {
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 (REDACTED_VALUE_FLAGS.has(name)) {
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
- if (this.blacklistValidator) {
23654
- this.blacklistValidator.checkTableBlacklist("INSERT", tableName, []);
23655
- }
23658
+ this.checkBlacklist("INSERT", tableName);
23656
23659
  enforcePermission("INSERT INTO dummy", this.permission);
23657
- const { sql, params } = this.buildInsertSql(tableName, data, schema);
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
- if (error instanceof BlacklistError) {
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
- if (this.blacklistValidator) {
23720
- this.blacklistValidator.checkTableBlacklist("UPDATE", tableName, []);
23721
- }
23668
+ this.checkBlacklist("UPDATE", tableName);
23722
23669
  enforcePermission("UPDATE dummy", this.permission);
23723
- const { sql, params } = this.buildUpdateSql(tableName, data, where, schema);
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
- if (error instanceof BlacklistError) {
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
- if (this.blacklistValidator) {
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
- const { sql, params } = this.buildDeleteSql(tableName, where, schema);
23798
- if (options?.dryRun) {
23799
- return {
23800
- status: "success",
23801
- operation: "delete",
23802
- rows_affected: 0,
23803
- timestamp,
23804
- sql
23805
- };
23806
- }
23807
- if (!options?.force) {
23808
- console.log(`
23809
- \u26A0\uFE0F Warning: DELETE operation is destructive and cannot be undone!`);
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
- console.log(` ${sql}`);
23813
- console.log(`
23716
+ console.log(` ${statement.sql}`);
23717
+ console.log(`
23814
23718
  Parameters:`);
23815
- console.log(` ${JSON.stringify(params, null, 2)}`);
23816
- const confirmed = await promptUser.confirm("Are you sure you want to execute this DELETE operation? This cannot be undone.");
23817
- if (!confirmed) {
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
- const result = await this.adapter.execute(sql, params);
23828
- return {
23829
- status: "success",
23830
- operation: "delete",
23831
- rows_affected: result.affectedRows,
23832
- timestamp,
23833
- sql
23834
- };
23835
- } catch (error) {
23836
- if (error instanceof BlacklistError) {
23837
- throw error;
23838
- }
23839
- const errorMessage = error instanceof Error ? error.message : String(error);
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: "delete",
23743
+ operation,
23843
23744
  rows_affected: 0,
23844
23745
  timestamp,
23845
- error: `DELETE failed: ${errorMessage}`
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
- getSystemType() {
23850
- return this.dbSystem;
23851
- }
23852
- getQuoteChar() {
23853
- return this.dbSystem === "mysql" ? "`" : '"';
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);