
# Datenbanktreiber

merql trennt Verbindungstransport und Datenbankdialekt. Ein `DatabaseConnection`-Adapter führt SQL über PDO oder mysqli aus, während ein `Driver` datenbankmaschinenspezifische Metadaten SQL und das Quoting von Bezeichnern übernimmt. Integrierte Treiber gibt es für MySQL und SQLite.

```php
use Merql\Driver\DriverFactory;

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

## Die Driver-Schnittstelle

Jeder Treiber implementiert fünf Methoden:

```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;
}
```

| Methode | Zweck |
|---|---|
| `quoteIdentifier` | Quoted einen Tabellen- oder Spaltennamen zur Verwendung in SQL. |
| `listTables` | Gibt alle Benutzertabellen der aktuellen Datenbank zurück. |
| `readSchema` | Liest Spaltennamen, Typen, Primärschlüssel und eindeutige Schlüssel einer Tabelle. |
| `readForeignKeys` | Gibt eine Zuordnung von untergeordneten Tabellen zu übergeordneten Tabellen zurück. |
| `selectAll` | Erstellt eine `SELECT *`-Abfrage für eine Tabelle. |

## MysqlDriver

Verwendet `INFORMATION_SCHEMA`-Abfragen für Metadaten und Backticks zum Quoting von Bezeichnern.

```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']]
```

Tabellenlistenabfragen nutzen `INFORMATION_SCHEMA.TABLES`, gefiltert nach aktueller Datenbank und Typ `BASE TABLE`. Schemaabfragen nutzen `INFORMATION_SCHEMA.COLUMNS` für Spalten und `INFORMATION_SCHEMA.KEY_COLUMN_USAGE` für Primär- und eindeutige Schlüssel.

## SqliteDriver

Verwendet `PRAGMA`-Abfragen für Metadaten und doppelte Anführungszeichen zum Quoting von Bezeichnern.

```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` erkennt den passenden Treiber anhand von `DatabaseConnection::driverName()` automatisch.

```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
```

Wenn die Treiberkennung der Verbindung nicht erkannt wird, wird ein `RuntimeException` mit der Meldung `Unsupported database driver: <name>` ausgelöst. merql enthält keinen PostgreSQL-Treiber.

### Benutzerdefinierte Treiber registrieren

Für andere Datenbanken als MySQL und SQLite schreibst du eine Klasse, die `Driver` implementiert, und registrierst sie. `PostgresDriver` steht unten für deine eigene Klasse:

```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.
```

Die Treiberklasse muss die Schnittstelle `Driver` implementieren. Das erste Argument von `register()` ist die Treiberkennung der Verbindung.

### Zurücksetzen

Setze die Factory zum Testen auf ihren Standardzustand zurück:

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

## Verbindungshilfsfunktion

Die Klasse `Connection` erstellt Adapter mit sinnvollen Standardeinstellungen. PDO-Adapter verwenden Exceptions bei Fehlern, assoziativen Abrufmodus, echte vorbereitete Anweisungen und Zeichenfolgen für abgerufene Werte. Der mysqli-Adapter setzt `utf8mb4`, verwendet den mysqli-Exception-Modus und vorbereitete Anweisungen und normalisiert abgerufene Werte auf dasselbe Format aus Zeichenfolge oder 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()` akzeptiert jeden DSN, den PDO öffnen kann, aber merql kann nur über einen registrierten Treiber Snapshots erstellen und anwenden. Eine `pgsql:`-Verbindung wird geöffnet (wenn `pdo_pgsql` installiert ist), aber die Konstruktoren von `Merql::init()`, `Snapshotter`, `Applier` und `GuardedApplier` rufen `DriverFactory::create()` auf, das `RuntimeException: Unsupported database driver: pgsql` auslöst, sofern du dafür keinen Treiber [registriert hast](#registering-custom-drivers) oder einen übergibst.

`Connection::sqlite()` aktiviert außerdem automatisch `PRAGMA foreign_keys = ON`, was SQLite für die Durchsetzung von Fremdschlüsseln benötigt.

## Wo Treiber verwendet werden

Treiber kommen überall in merql zum Einsatz:

- **Snapshotter** verwendet `listTables`, `readSchema` und `selectAll`, um den Datenbankzustand zu erfassen.
- **Applier** verwendet `readForeignKeys`, um die Reihenfolge der Operationen zu bestimmen, und übergibt den Treiber an `SqlGenerator` zum Quoting von Bezeichnern.
- **SqlGenerator** verwendet `quoteIdentifier`, um sichere SQL zu erstellen.

Wenn du eine Verbindung an `Snapshotter` oder `Applier` übergibst, ohne einen Treiber anzugeben, wird `DriverFactory::create()` automatisch aufgerufen.
