# MysqlClient 增删改查测试计划

## 背景

本次修复针对 `src/tools/db-client/impl/MysqlClient.ts` 中 MySQL 执行结果的识别逻辑。

修复前：

- `select` 日志正常，如：`Query finish:rows: 1 use time: 9 ms`
- `insert/update/delete` 因为 `mysql2/promise` 返回的是结果对象，不是数组或数字，日志会出现：`Unknown result type:object`
- `batchUpdate()` 事务路径没有真正复用同一个连接，存在事务行为不符合预期的风险

修复后需要验证：

- 查询日志继续正确打印 `rows`
- 插入、修改、删除日志正确打印 `affectedRows`
- `update()` / `insertRow()` / `updateRow()` / `deleteRow()` 返回值正确
- `batchUpdate()` 成功时提交，失败时回滚

## 测试目标

本轮数据库联调完成后，至少确认以下结论：

1. 不再出现 `Unknown result type:object`
2. `select` 语句日志格式正确
3. `insert/update/delete` 语句日志格式正确
4. 框架对外返回的受影响行数正确
5. 批量事务在成功和失败两条路径上都符合预期

## 需要你后续提供的信息

联调前请提供：

- MySQL 连接信息：`host`、`port`、`user`、`password`、`database`
- MySQL 版本
- 是否允许创建和删除测试表
- 是否允许事务测试
- 一个独立测试库，或一个可清理的测试表前缀

建议使用独立测试库，避免污染业务数据。

## 建议测试表

建议使用下面这张表，既能覆盖普通增删改查，也能覆盖唯一键冲突和事务回滚：

```sql
DROP TABLE IF EXISTS tb_ts_boot_test_dml;

CREATE TABLE tb_ts_boot_test_dml (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  biz_code VARCHAR(64) NOT NULL,
  user_name VARCHAR(64) NOT NULL,
  age INT NOT NULL DEFAULT 0,
  status TINYINT NOT NULL DEFAULT 1,
  remark VARCHAR(255) DEFAULT '',
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uk_biz_code (biz_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

初始化数据：

```sql
INSERT INTO tb_ts_boot_test_dml (biz_code, user_name, age, status, remark) VALUES
('INIT_001', 'Alice', 18, 1, 'seed 1'),
('INIT_002', 'Bob', 20, 1, 'seed 2');
```

## 建议执行方式

拿到数据库后，新增一个独立测试脚本，例如：

- `test/mysql-client-dml.ts`

脚本内建议直接使用：

- `new DataBaseClient(new MysqlClient(config))`
- 顺序执行本文档中的测试用例
- 每个用例打印：
  - 用例名称
  - 实际返回值
  - 关键查询结果
  - 最终是否通过

如果希望观察 SQL，也可以打开：

```ts
(client.instance as MysqlClient).enablePrintSql();
```

## 测试用例

### 用例 1：查询日志正常

操作：

```ts
const list = await client.getList(null, 'select * from tb_ts_boot_test_dml order by id', []);
```

预期：

- 返回数组长度为 `2`
- 控制台日志包含 `Query finish: rows: 2`
- 不出现 `Unknown result type:object`

### 用例 2：直接执行插入 SQL 的日志正常

操作：

```ts
const result = await client.executeSql(
  'insert into tb_ts_boot_test_dml (biz_code, user_name, age, status, remark) values (?, ?, ?, ?, ?)',
  ['INS_001', 'CaseInsert', 21, 1, 'insert by executeSql']
);
```

预期：

- `result.affectedRows === 1`
- `result.insertId > 0`
- 控制台日志包含 `Query finish: affectedRows: 1`
- 控制台日志不再出现 `Unknown result type:object`

### 用例 3：`update()` 执行插入时返回受影响行数

操作：

```ts
const affected = await client.update(
  'insert into tb_ts_boot_test_dml (biz_code, user_name, age, status, remark) values (?, ?, ?, ?, ?)',
  ['INS_002', 'CaseUpdateInsert', 22, 1, 'insert by update']
);
```

预期：

- `affected === 1`
- 控制台日志包含 `Query finish: affectedRows: 1`

### 用例 4：`insertRow()` 返回值正确

操作：

```ts
const affected = await client.insertRow('tb_ts_boot_test_dml', {
  biz_code: 'INS_003',
  user_name: 'CaseInsertRow',
  age: 23,
  status: 1,
  remark: 'insert by insertRow'
});
```

预期：

- `affected === 1`
- 数据库中存在 `biz_code = 'INS_003'` 的记录
- 控制台日志包含 `Query finish: affectedRows: 1`

### 用例 5：`update()` 普通更新返回值正确

操作：

```ts
const affected = await client.update(
  'update tb_ts_boot_test_dml set user_name = ?, age = ? where biz_code = ?',
  ['CaseUpdated', 30, 'INS_001']
);
```

预期：

- `affected === 1`
- 控制台日志包含 `Query finish: affectedRows: 1`
- 若字段值实际发生变化，日志中通常还会出现 `changedRows: 1`

### 用例 6：`updateRow()` 返回值正确

操作：

```ts
const affected = await client.updateRow(
  'tb_ts_boot_test_dml',
  { remark: 'updated by updateRow', status: 2 },
  { biz_code: 'INS_002' }
);
```

预期：

- `affected === 1`
- 目标记录的 `remark` 和 `status` 已更新
- 控制台日志包含 `Query finish: affectedRows: 1`

### 用例 7：`deleteRow()` 返回值正确

操作：

```ts
const affected = await client.deleteRow('tb_ts_boot_test_dml', {
  biz_code: 'INS_003'
});
```

预期：

- `affected === 1`
- 数据库中已查不到 `biz_code = 'INS_003'` 的记录
- 控制台日志包含 `Query finish: affectedRows: 1`

### 用例 8：`batchUpdate()` 成功提交

操作：

```ts
const result = await client.instance.batchUpdate([
  {
    sql: 'insert into tb_ts_boot_test_dml (biz_code, user_name, age, status, remark) values (?, ?, ?, ?, ?)',
    arrayValues: ['BATCH_OK_001', 'Batch1', 24, 1, 'batch insert 1']
  },
  {
    sql: 'insert into tb_ts_boot_test_dml (biz_code, user_name, age, status, remark) values (?, ?, ?, ?, ?)',
    arrayValues: ['BATCH_OK_002', 'Batch2', 25, 1, 'batch insert 2']
  }
]);
```

预期：

- `result` 为 `[1, 1]`
- 两条数据都成功写入
- 每条 SQL 的日志都包含 `Query finish: affectedRows: 1`

### 用例 9：`batchUpdate()` 失败回滚

操作：

第二条语句故意制造唯一键冲突：

```ts
let errorCaught = false;
try {
  await client.instance.batchUpdate([
    {
      sql: 'insert into tb_ts_boot_test_dml (biz_code, user_name, age, status, remark) values (?, ?, ?, ?, ?)',
      arrayValues: ['BATCH_ROLLBACK_001', 'Rollback1', 26, 1, 'batch rollback 1']
    },
    {
      sql: 'insert into tb_ts_boot_test_dml (biz_code, user_name, age, status, remark) values (?, ?, ?, ?, ?)',
      arrayValues: ['INIT_001', 'Rollback2', 27, 1, 'duplicate biz_code']
    }
  ]);
} catch (e) {
  errorCaught = true;
}
```

预期：

- `errorCaught === true`
- `biz_code = 'BATCH_ROLLBACK_001'` 的数据不存在
- 说明第一条插入已经被事务回滚

这个用例是本次修复里最关键的一条，因为它能证明 `batchUpdate()` 确实使用了同一个事务连接。

## 额外检查项

除以上用例外，还应额外确认：

- 所有 DML 日志都不再出现 `Unknown result type:object`
- 没有连接未释放导致的进程挂住
- 重复执行测试脚本时，初始化 SQL 可以稳定清理并重建测试数据

## 通过标准

满足以下条件即可视为本次修复通过：

1. 9 个用例全部通过
2. 查询日志显示 `rows`
3. 插入、修改、删除日志显示 `affectedRows`
4. `batchUpdate()` 成功时提交、失败时回滚
5. 全程不再出现 `Unknown result type:object`

## 联调后的下一步

你提供数据库后，我会继续完成两件事：

1. 把上述文档转换成实际可执行的测试脚本
2. 在真实数据库上跑完用例并给出结果
