
# 数据库驱动

merql 将连接传输层与数据库方言分离。`DatabaseConnection` 适配器通过 PDO 或 mysqli 执行 SQL，而 `Driver` 则处理特定数据库引擎的元数据 SQL 和标识符引号处理。内置驱动支持 MySQL 和 SQLite。

```php
use Merql\Driver\DriverFactory;

$driver = DriverFactory::create($connection);
// Auto-detects MySQL or SQLite from the connection driver identity.
```

## Driver 接口

每个驱动都实现五个方法：

```php
use Merql\Driver\Driver;

interface Driver
{
    public function quoteIdentifier(string $name): string;
    public function listTables(DatabaseConnection $connection): array;
    public function readSchema(DatabaseConnection $connection, string $table): TableSchema;
    public function readForeignKeys(DatabaseConnection $connection): array;
    public function selectAll(string $table): string;
}
```

| 方法 | 用途 |
|---|---|
| `quoteIdentifier` | 为 SQL 中使用的表名或列名加引号。 |
| `listTables` | 返回当前数据库中的所有用户表。 |
| `readSchema` | 读取表的列名、类型、主键和唯一键。 |
| `readForeignKeys` | 返回子表到父表依赖关系的映射。 |
| `selectAll` | 为表构建 `SELECT *` 查询。 |

## MysqlDriver

使用 `INFORMATION_SCHEMA` 查询读取元数据，并使用反引号为标识符加引号。

```php
use Merql\Driver\MysqlDriver;

$driver = new MysqlDriver();

$driver->quoteIdentifier('posts');
// `posts`

$driver->quoteIdentifier('my`table');
// `my``table`

$tables = $driver->listTables($connection);
// ['comments', 'posts', 'users']

$schema = $driver->readSchema($connection, 'posts');
// TableSchema with columns, primaryKey, uniqueKeys

$fks = $driver->readForeignKeys($connection);
// ['comments' => ['posts', 'users']]
```

表列表查询使用 `INFORMATION_SCHEMA.TABLES`，并按当前数据库和 `BASE TABLE` 类型筛选。架构读取查询使用 `INFORMATION_SCHEMA.COLUMNS` 获取列信息，使用 `INFORMATION_SCHEMA.KEY_COLUMN_USAGE` 获取主键和唯一键信息。

## SqliteDriver

使用 `PRAGMA` 查询读取元数据，并使用双引号为标识符加引号。

```php
use Merql\Driver\SqliteDriver;

$driver = new SqliteDriver();

$driver->quoteIdentifier('posts');
// "posts"

$driver->quoteIdentifier('my"table');
// "my""table"

$tables = $driver->listTables($connection);
// Queries sqlite_master, excludes sqlite_* internal tables.

$schema = $driver->readSchema($connection, 'posts');
// Uses PRAGMA table_info() and PRAGMA index_list().

$fks = $driver->readForeignKeys($connection);
// Uses PRAGMA foreign_key_list() for each table.
```

## DriverFactory

`DriverFactory` 会根据 `DatabaseConnection::driverName()` 自动检测正确的驱动。

```php
use Merql\Connection;
use Merql\Driver\DriverFactory;

$connection = Connection::mysql('127.0.0.1', 'mydb', 'root', '');
$driver = DriverFactory::create($connection);
// Returns MysqlDriver

$connection = Connection::sqlite(':memory:');
$driver = DriverFactory::create($connection);
// Returns SqliteDriver
```

如果无法识别连接的驱动标识，则会抛出消息为 `Unsupported database driver: <name>` 的 `RuntimeException`。merql 未提供 PostgreSQL 驱动。

### 注册自定义驱动

对于 MySQL 和 SQLite 以外的数据库，请编写一个实现 `Driver` 的类并注册它。下方的 `PostgresDriver` 代表你自己的类：

```php
use Merql\Driver\DriverFactory;

DriverFactory::register('pgsql', PostgresDriver::class);

$connection = Connection::fromDsn('pgsql:host=localhost;dbname=mydb', 'user', 'pass');
$driver = DriverFactory::create($connection);
// Returns your PostgresDriver instance.
```

驱动类必须实现 `Driver` 接口。`register()` 的第一个参数是连接的驱动标识。

### 重置

测试时，将工厂重置为默认状态：

```php
DriverFactory::reset();
// Reverts to only MySQL and SQLite drivers.
```

## 连接辅助类

`Connection` 类使用合理的默认设置来构建适配器。PDO 适配器在出错时会抛出异常，使用关联数组提取模式、真正的预处理语句和字符串化提取结果。mysqli 适配器会设置 `utf8mb4`，使用 mysqli 异常模式和预处理语句，并将提取的值规范化为相同的字符串或 null 格式。

```php
use Merql\Connection;

// MySQL.
$connection = Connection::mysql('127.0.0.1', 'my_database', 'root', 'password');
$connection = Connection::mysql('db.example.com', 'mydb', 'user', 'pass', port: 3307);

// MySQLi.
$connection = Connection::mysqli('127.0.0.1', 'my_database', 'root', 'password');

// SQLite.
$connection = Connection::sqlite(':memory:');
$connection = Connection::sqlite('/var/data/app.sqlite');

// Any MySQL or SQLite PDO DSN.
$connection = Connection::fromDsn('mysql:host=127.0.0.1;dbname=mydb;charset=utf8mb4', 'user', 'pass');
```

`fromDsn()` 接受 PDO 可打开的任意 DSN，但 merql 只能通过已注册的驱动创建快照并应用变更。`pgsql:` 连接可以成功打开（前提是已安装 `pdo_pgsql`），但 `Merql::init()`、`Snapshotter`、`Applier` 和 `GuardedApplier` 构造函数都会调用 `DriverFactory::create()`；除非你[为它注册了驱动](#registering-custom-drivers)或传入一个驱动，否则会抛出 `RuntimeException: Unsupported database driver: pgsql`。

`Connection::sqlite()` 还会自动启用 `PRAGMA foreign_keys = ON`，SQLite 需要它来实施外键约束。

## 驱动的使用位置

驱动在 merql 中用途广泛：

- **Snapshotter** 使用 `listTables`、`readSchema` 和 `selectAll` 捕获数据库状态。
- **Applier** 使用 `readForeignKeys` 确定操作顺序，并将驱动传递给 `SqlGenerator` 以处理标识符引号。
- **SqlGenerator** 使用 `quoteIdentifier` 构建安全的 SQL。

如果将连接传递给 `Snapshotter` 或 `Applier` 时未指定驱动，则会自动调用 `DriverFactory::create()`。
