Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New driver: SQLite3 #5737

Merged
merged 1 commit into from
Oct 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 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 @@ -74,14 +82,20 @@ jobs:
- name: "Print SQLite version"
run: >
php -r 'printf("Testing with libsqlite version %s\n", (new PDO("sqlite::memory:"))->query("select sqlite_version()")->fetch()[0]);'
if: "${{ matrix.extension == 'pdo_sqlite' }}"

- name: "Print SQLite version"
run: >
php -r 'printf("Testing with libsqlite version %s\n", SQLite3::version()["versionString"]);'
if: "${{ matrix.extension == 'sqlite3' }}"

- 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 +565,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 +591,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" />
morozov marked this conversation as resolved.
Show resolved Hide resolved

<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
9 changes: 9 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,14 @@ 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``.
- ``memory`` (boolean): True if the SQLite database should be
in-memory (non-persistent). Mutually exclusive with ``path``.

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
22 changes: 22 additions & 0 deletions src/Driver/API/SQLite/UserDefinedFunctions.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace Doctrine\DBAL\Driver\API\SQLite;

use Doctrine\DBAL\Platforms\SqlitePlatform;

use function array_merge;
use function strpos;

/**
Expand All @@ -11,6 +14,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 callable(string, callable, int): bool $callback
* @param array<string, array{callback: callable, numArgs: int}> $additionalFunctions
*/
public static function register(callable $callback, array $additionalFunctions = []): void
{
$userDefinedFunctions = array_merge(self::DEFAULT_FUNCTIONS, $additionalFunctions);
morozov marked this conversation as resolved.
Show resolved Hide resolved

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

/**
* User-defined function that implements MOD().
*
Expand Down
26 changes: 8 additions & 18 deletions src/Driver/PDO/SQLite/Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,24 @@
namespace Doctrine\DBAL\Driver\PDO\SQLite;

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 Doctrine\Deprecations\Deprecation;
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'] ?? [];
$driverOptions = $params['driverOptions'] ?? [];
$userDefinedFunctions = [];

if (isset($driverOptions['userDefinedFunctions'])) {
Deprecation::trigger(
Expand All @@ -38,10 +30,7 @@ public function connect(array $params)
. ' Register function directly on the native connection instead.',
);

$this->userDefinedFunctions = array_merge(
$this->userDefinedFunctions,
$driverOptions['userDefinedFunctions'],
);
$userDefinedFunctions = $driverOptions['userDefinedFunctions'];
unset($driverOptions['userDefinedFunctions']);
}

Expand All @@ -56,9 +45,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(
[$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'];
}
}
46 changes: 46 additions & 0 deletions src/Driver/SQLite3/Driver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace Doctrine\DBAL\Driver\SQLite3;

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);

if (isset($params['path'])) {
if ($isMemory) {
throw new Exception(
'Invalid connection settings: specifying both parameters "path" and "memory" ambiguous.',
derrabus marked this conversation as resolved.
Show resolved Hide resolved
);
}

$filename = $params['path'];
} elseif ($isMemory) {
$filename = ':memory:';
} else {
throw new Exception(
'Invalid connection settings: specify either the "path" or the "memory" parameter for SQLite3.',
);
}

try {
$connection = new SQLite3($filename);
} catch (\Exception $e) {
throw Exception::new($e);
}

$connection->enableExceptions(true);

UserDefinedFunctions::register([$connection, 'createFunction']);
derrabus marked this conversation as resolved.
Show resolved Hide resolved

return new Connection($connection);
}
}
Loading