# MySQL Driver

## Purpose

支持 MySQL 作为可选数据库后端，通过配置选择 sqlite 或 mysql 驱动，提供 MySQL 连接管理、初始化和方言迁移文件。

## Requirements

### Requirement: MySQL 数据库驱动支持

系统 SHALL 支持通过配置项 `database.driver` 选择 `sqlite` 或 `mysql` 数据库驱动，默认值为 `sqlite`。

#### Scenario: 默认使用 SQLite 驱动

- **WHEN** 配置中未指定 `database.driver`
- **THEN** SHALL 使用 `sqlite` 作为数据库驱动
- **THEN** SHALL 行为与现有逻辑完全一致

#### Scenario: 配置 MySQL 驱动

- **WHEN** 配置 `database.driver` 设置为 `mysql`
- **THEN** SHALL 使用 MySQL 驱动连接远程数据库
- **THEN** SHALL 使用 `gorm.io/driver/mysql` 打开连接
- **THEN** SHALL 构建 DSN 格式为 `{user}:{password}@tcp({host}:{port})/{dbname}?charset=utf8mb4&parseTime=true&loc=Local`

#### Scenario: driver 值不合法

- **WHEN** 配置 `database.driver` 不是 `sqlite` 或 `mysql`
- **THEN** SHALL 配置验证失败，拒绝启动

### Requirement: MySQL 连接配置

系统 SHALL 在 `DatabaseConfig` 中支持 MySQL 连接参数。

#### Scenario: MySQL 连接参数字段

- **WHEN** `database.driver` 为 `mysql`
- **THEN** SHALL 读取 `host`（MySQL 主机地址，必填）
- **THEN** SHALL 读取 `port`（MySQL 端口，默认 3306）
- **THEN** SHALL 读取 `user`（MySQL 用户名，必填）
- **THEN** SHALL 读取 `password`（MySQL 密码，选填）
- **THEN** SHALL 读取 `dbname`（数据库名，必填）

#### Scenario: SQLite 模式忽略 MySQL 参数

- **WHEN** `database.driver` 为 `sqlite`
- **THEN** SHALL 忽略 MySQL 相关配置字段（host/port/user/password/dbname）
- **THEN** SHALL 仅使用 `path` 字段作为数据库文件路径

#### Scenario: MySQL 模式忽略 SQLite 参数

- **WHEN** `database.driver` 为 `mysql`
- **THEN** SHALL 忽略 `path` 字段

### Requirement: 数据库初始化公共包

系统 SHALL 提供 `internal/database` 公共包，封装数据库初始化、迁移执行和连接关闭逻辑，供 `cmd/server` 和 `cmd/desktop` 共同调用。

#### Scenario: 公共包 Init 函数

- **WHEN** 调用 `database.Init(cfg, logger)`
- **THEN** SHALL 根据 `cfg.Driver` 选择对应的 GORM 驱动打开连接
- **THEN** SHALL 执行对应方言的 goose 迁移
- **THEN** SHALL 配置连接池参数
- **THEN** SHALL 在 `driver=sqlite` 时执行 `PRAGMA journal_mode=WAL`
- **THEN** SHALL 在 `driver=mysql` 时跳过 SQLite 专有 PRAGMA
- **THEN** SHALL 返回 `*gorm.DB` 实例

#### Scenario: 公共包 Close 函数

- **WHEN** 调用 `database.Close(db)`
- **THEN** SHALL 获取底层 `sql.DB` 并关闭连接

#### Scenario: 迁移目录选择

- **WHEN** 执行迁移
- **THEN** SHALL 在 `driver=sqlite` 时使用 `migrations/sqlite/` 目录，goose dialect 为 `sqlite3`
- **THEN** SHALL 在 `driver=mysql` 时使用 `migrations/mysql/` 目录，goose dialect 为 `mysql`

### Requirement: MySQL 方言迁移文件

系统 SHALL 提供 MySQL 方言的初始迁移文件 `migrations/mysql/20260421000001_initial_schema.sql`。

#### Scenario: providers 表

- **WHEN** 执行 MySQL 初始迁移
- **THEN** SHALL 创建 `providers` 表，字段：`id VARCHAR(36) PRIMARY KEY`、`name VARCHAR(255) NOT NULL`、`api_key VARCHAR(255) NOT NULL`、`base_url VARCHAR(255) NOT NULL`、`protocol VARCHAR(50) DEFAULT 'openai'`、`enabled BOOLEAN DEFAULT TRUE`、`created_at DATETIME(3)`、`updated_at DATETIME(3)`

#### Scenario: models 表

- **WHEN** 执行 MySQL 初始迁移
- **THEN** SHALL 创建 `models` 表，字段：`id VARCHAR(36) PRIMARY KEY`、`provider_id VARCHAR(36) NOT NULL`、`model_name VARCHAR(255) NOT NULL`、`enabled BOOLEAN DEFAULT TRUE`、`created_at DATETIME(3)`
- **THEN** SHALL 创建 `UNIQUE(provider_id, model_name)` 约束
- **THEN** SHALL 创建 `FOREIGN KEY (provider_id) REFERENCES providers(id) ON DELETE CASCADE` 约束
- **THEN** SHALL 创建 `idx_models_provider_id` 和 `idx_models_model_name` 索引

#### Scenario: usage_stats 表

- **WHEN** 执行 MySQL 初始迁移
- **THEN** SHALL 创建 `usage_stats` 表，字段：`id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY`、`provider_id VARCHAR(36) NOT NULL`、`model_name VARCHAR(255) NOT NULL`、`request_count INT DEFAULT 0`、`date DATE NOT NULL`
- **THEN** SHALL 创建 `UNIQUE(provider_id, model_name, date)` 约束
- **THEN** SHALL 创建 `idx_usage_stats_provider_model_date` 复合索引

#### Scenario: Down 迁移

- **WHEN** 执行 MySQL down 迁移
- **THEN** SHALL 按正确顺序删除索引和表（usage_stats → models → providers）
