Skip to content

Commit

Permalink
SQLite3 driver
Browse files Browse the repository at this point in the history
  • Loading branch information
derrabus committed Oct 10, 2022
1 parent 00f2f7a commit 9297af3
Show file tree
Hide file tree
Showing 18 changed files with 542 additions and 29 deletions.
16 changes: 12 additions & 4 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,21 @@ jobs:
- "8.2"
dependencies:
- "highest"
extension:
- "pdo_sqlite"
include:
- os: "ubuntu-20.04"
php-version: "7.4"
dependencies: "lowest"
extension: "pdo_sqlite"
- os: "ubuntu-22.04"
php-version: "7.4"
dependencies: "highest"
extension: "sqlite3"
- os: "ubuntu-22.04"
php-version: "8.1"
dependencies: "highest"
extension: "sqlite3"

steps:
- name: "Checkout"
Expand All @@ -76,12 +84,12 @@ jobs:
php -r 'printf("Testing with libsqlite version %s\n", (new PDO("sqlite::memory:"))->query("select sqlite_version()")->fetch()[0]);'
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml --coverage-clover=coverage.xml"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"

- name: "Upload coverage file"
uses: "actions/upload-artifact@v3"
with:
name: "phpunit-sqlite-${{ matrix.deps }}-${{ matrix.php-version }}.coverage"
name: "phpunit-${{ matrix.extension }}-${{ matrix.deps }}-${{ matrix.php-version }}.coverage"
path: "coverage.xml"

phpunit-oci8:
Expand Down Expand Up @@ -551,7 +559,7 @@ jobs:
path: "coverage.xml"

development-deps:
name: "PHPUnit with SQLite and development dependencies"
name: "PHPUnit with PDO_SQLite and development dependencies"
runs-on: "ubuntu-22.04"

strategy:
Expand All @@ -577,7 +585,7 @@ jobs:
composer-options: "--prefer-dist"

- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml"
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_sqlite.xml"

upload_coverage:
name: "Upload coverage to Codecov"
Expand Down
30 changes: 30 additions & 0 deletions ci/github/phpunit/pdo_sqlite.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
failOnRisky="true"
failOnWarning="true"
convertDeprecationsToExceptions="true"
>
<php>
<ini name="error_reporting" value="-1" />

<var name="db_driver" value="pdo_sqlite"/>
<var name="db_memory" value="true"/>
<var name="db_event_subscribers" value="Doctrine\DBAL\Event\Listeners\SQLiteSessionInit"/>
</php>

<testsuites>
<testsuite name="Doctrine DBAL Test Suite">
<directory>../../../tests</directory>
</testsuite>
</testsuites>

<coverage>
<include>
<directory suffix=".php">../../../src</directory>
</include>
</coverage>
</phpunit>
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
>
<php>
<ini name="error_reporting" value="-1" />

<var name="db_driver" value="sqlite3"/>
<var name="db_memory" value="true"/>
<var name="db_event_subscribers" value="Doctrine\DBAL\Event\Listeners\SQLiteSessionInit"/>
</php>

<testsuites>
Expand Down
10 changes: 10 additions & 0 deletions docs/en/reference/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ interfaces to use. It can be configured in one of three ways:
- ``mysqli``: A MySQL driver that uses the mysqli extension.
- ``pdo_sqlite``: An SQLite driver that uses the pdo_sqlite PDO
extension.
- ``sqlite3``: An SQLite driver that uses the sqlite3 extension.
- ``pdo_pgsql``: A PostgreSQL driver that uses the pdo_pgsql PDO
extension.
- ``pdo_oci``: An Oracle driver that uses the pdo_oci PDO
Expand Down Expand Up @@ -155,6 +156,15 @@ pdo_sqlite
in-memory (non-persistent). Mutually exclusive with ``path``.
``path`` takes precedence.

sqlite3
^^^^^^^

- ``path`` (string): The filesystem path to the database file.
Mutually exclusive with ``memory``. ``path`` takes precedence.
- ``memory`` (boolean): True if the SQLite database should be
in-memory (non-persistent). Mutually exclusive with ``path``.
``path`` takes precedence.

pdo_mysql
^^^^^^^^^

Expand Down
6 changes: 6 additions & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@
<exclude-pattern>*/src/*</exclude-pattern>
</rule>

<rule ref="SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly">
<!-- ext-sqlite3 throws generic exceptions. -->
<!-- Catching \Exception is legit here, and we don't want to widen types to \Throwable. -->
<exclude-pattern>src/Driver/SQLite3/*</exclude-pattern>
</rule>

<rule ref="PSR1.Classes.ClassDeclaration.MultipleClasses">
<exclude-pattern>*/tests/*</exclude-pattern>
</rule>
Expand Down
23 changes: 23 additions & 0 deletions src/Driver/API/SQLite/UserDefinedFunctions.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

namespace Doctrine\DBAL\Driver\API\SQLite;

use Closure;
use Doctrine\DBAL\Platforms\SqlitePlatform;

use function array_merge;
use function strpos;

/**
Expand All @@ -11,6 +15,25 @@
*/
final class UserDefinedFunctions
{
private const DEFAULT_FUNCTIONS = [
'sqrt' => ['callback' => [SqlitePlatform::class, 'udfSqrt'], 'numArgs' => 1],
'mod' => ['callback' => [SqlitePlatform::class, 'udfMod'], 'numArgs' => 2],
'locate' => ['callback' => [SqlitePlatform::class, 'udfLocate'], 'numArgs' => -1],
];

/**
* @param Closure(string, callable, int): bool $callback
* @param array<string, array{callback: callable, numArgs: int}> $additionalFunctions
*/
public static function register(Closure $callback, array $additionalFunctions): void
{
$userDefinedFunctions = array_merge(self::DEFAULT_FUNCTIONS, $additionalFunctions);

foreach ($userDefinedFunctions as $function => $data) {
$callback($function, $data['callback'], $data['numArgs']);
}
}

/**
* User-defined function that implements MOD().
*
Expand Down
31 changes: 9 additions & 22 deletions src/Driver/PDO/SQLite/Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,26 @@

namespace Doctrine\DBAL\Driver\PDO\SQLite;

use Closure;
use Doctrine\DBAL\Driver\AbstractSQLiteDriver;
use Doctrine\DBAL\Driver\API\SQLite\UserDefinedFunctions;
use Doctrine\DBAL\Driver\PDO\Connection;
use Doctrine\DBAL\Driver\PDO\Exception;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use PDO;
use PDOException;

use function array_merge;

final class Driver extends AbstractSQLiteDriver
{
/** @var mixed[] */
private array $userDefinedFunctions = [
'sqrt' => ['callback' => [SqlitePlatform::class, 'udfSqrt'], 'numArgs' => 1],
'mod' => ['callback' => [SqlitePlatform::class, 'udfMod'], 'numArgs' => 2],
'locate' => ['callback' => [SqlitePlatform::class, 'udfLocate'], 'numArgs' => -1],
];

/**
* {@inheritdoc}
*
* @return Connection
*/
public function connect(array $params)
{
$driverOptions = $params['driverOptions'] ?? [];

if (isset($driverOptions['userDefinedFunctions'])) {
$this->userDefinedFunctions = array_merge(
$this->userDefinedFunctions,
$driverOptions['userDefinedFunctions'],
);
unset($driverOptions['userDefinedFunctions']);
}
$driverOptions = $params['driverOptions'] ?? [];
$userDefinedFunctions = $driverOptions['userDefinedFunctions'] ?? [];
unset($driverOptions['userDefinedFunctions']);

try {
$pdo = new PDO(
Expand All @@ -48,9 +34,10 @@ public function connect(array $params)
throw Exception::new($exception);
}

foreach ($this->userDefinedFunctions as $fn => $data) {
$pdo->sqliteCreateFunction($fn, $data['callback'], $data['numArgs']);
}
UserDefinedFunctions::register(
Closure::fromCallable([$pdo, 'sqliteCreateFunction']),
$userDefinedFunctions,
);

return new Connection($pdo);
}
Expand Down
107 changes: 107 additions & 0 deletions src/Driver/SQLite3/Connection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php

namespace Doctrine\DBAL\Driver\SQLite3;

use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\ParameterType;
use SQLite3;

use function assert;
use function sprintf;

final class Connection implements ServerInfoAwareConnection
{
private SQLite3 $connection;

/** @internal The connection can be only instantiated by its driver. */
public function __construct(SQLite3 $connection)
{
$this->connection = $connection;
}

public function prepare(string $sql): Statement
{
try {
$statement = $this->connection->prepare($sql);
} catch (\Exception $e) {
throw Exception::new($e);
}

assert($statement !== false);

return new Statement($this->connection, $statement);
}

public function query(string $sql): Result
{
try {
$result = $this->connection->query($sql);
} catch (\Exception $e) {
throw Exception::new($e);
}

assert($result !== false);

return new Result($result, $this->connection->changes());
}

/** @inheritdoc */
public function quote($value, $type = ParameterType::STRING): string
{
return sprintf('\'%s\'', SQLite3::escapeString($value));
}

public function exec(string $sql): int
{
try {
$this->connection->exec($sql);
} catch (\Exception $e) {
throw Exception::new($e);
}

return $this->connection->changes();
}

/** @inheritdoc */
public function lastInsertId($name = null): int
{
return $this->connection->lastInsertRowID();
}

public function beginTransaction(): bool
{
try {
return $this->connection->exec('BEGIN');
} catch (\Exception $e) {
return false;
}
}

public function commit(): bool
{
try {
return $this->connection->exec('COMMIT');
} catch (\Exception $e) {
return false;
}
}

public function rollBack(): bool
{
try {
return $this->connection->exec('ROLLBACK');
} catch (\Exception $e) {
return false;
}
}

public function getNativeConnection(): SQLite3
{
return $this->connection;
}

public function getServerVersion(): string
{
return SQLite3::version()['versionString'];
}
}
33 changes: 33 additions & 0 deletions src/Driver/SQLite3/Driver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Doctrine\DBAL\Driver\SQLite3;

use Closure;
use Doctrine\DBAL\Driver\AbstractSQLiteDriver;
use Doctrine\DBAL\Driver\API\SQLite\UserDefinedFunctions;
use SQLite3;

final class Driver extends AbstractSQLiteDriver
{
/**
* {@inheritdoc}
*/
public function connect(array $params): Connection
{
$isMemory = (bool) ($params['memory'] ?? false);
try {
$connection = new SQLite3($isMemory ? ':memory:' : $params['path']);
} catch (\Exception $e) {
throw Exception::new($e);
}

$connection->enableExceptions(true);

UserDefinedFunctions::register(
Closure::fromCallable([$connection, 'createFunction']),
$params['driverOptions']['userDefinedFunctions'] ?? [],
);

return new Connection($connection);
}
}
18 changes: 18 additions & 0 deletions src/Driver/SQLite3/Exception.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Doctrine\DBAL\Driver\SQLite3;

use Doctrine\DBAL\Driver\AbstractException;

/**
* @internal
*
* @psalm-immutable
*/
final class Exception extends AbstractException
{
public static function new(\Exception $exception): self
{
return new self($exception->getMessage(), null, (int) $exception->getCode(), $exception);
}
}
Loading

0 comments on commit 9297af3

Please sign in to comment.