Skip to content

Commit 2d084bb

Browse files
amotlJulianMar
andauthored
Support for Doctrine DBAL 3 (#122)
* DBAL3: Update to `doctrine/dbal` version 3 * DBAL3: Adjust interfaces and autoloading to match implementation * DBAL3: Start populating PDOConnection, detouring from PDOCrateDB * DBAL3: Code-style fixes * DBAL3: Making actual progress * DBAL3: Apply suggestions by CodeRabbit, and more copy editing * DBAL3: Minor wording updates * DBAL3: More copy editing with CodeRabbit * Chore: Switch linter/formatter to PHP Coding Standards Fixer * DBAL3: Rename more symbols * DBAL3: Towards DBAL4 * DBAL3: Implement suggestions by CodeRabbit * DBAL3: More generic type hinting * Chore: Naming things `s/TestCase/Test/` * Chore: Implement suggestions by CodeRabbit - Remove redundant test function `testPrepareWithBindParam` - Remove deprecated test function about `PDO::FETCH_CLASS` - Complete function signature type hinting - Improve MapType::convertToPHPValue about accounting for `null` values * DBAL3: Use `Doctrine\DBAL\Driver\PDO\Exception` instead of copying * DBAL3: At `lastInsertId`, catch `PDOException` and wrap into `DBAL` one * DBAL3: Clarify remark about differences of the new DBAL3 Type API * DBAL3: Remove comment about `wrapperClass` * Tests: Adjust skip messages instead of blatantly using `ALTER TABLE` * DBAL3: Implement suggestions by CodeRabbit * DBAL3: Use CrateDB 5.0.0 in `getServerVersion` to select modern dialect * DBAL3: Implement suggestions by CodeRabbit * Connection: Make statement class registration more compliant with PDO * Use `TransactionIsolationLevel::READ_UNCOMMITTED` as new default * Forward beginTransaction, commit, rollBack to PDO driver * DBAL3: Implement `getServerVersion()`, forwarding to `PDOCrateDB` * Example: Update procedure how to properly select the platform * Documentation: Educate about CrateDB and eventual consistency vs. ACID * Documentation: Make example program independent of `CratePlatform4` * DBAL3: Remove `assert($result !== false)` in `PDOConnection.exec()` * DBAL3: Apply optimization to `PDOConnection.query()` by CodeRabbit --------- Co-authored-by: Julian Martin <[email protected]>
1 parent 073b77e commit 2d084bb

33 files changed

+1096
-441
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
.project
44
.vagrant
55
.phpunit.result.cache
6+
*.cache
67
*-console.log
78
.crate-docs
89
/composer.lock

.php-cs-fixer.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
// PHP Coding Standards Fixer
3+
// https://cs.symfony.com/
4+
// https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/doc/config.rst
5+
6+
$finder = (new PhpCsFixer\Finder())
7+
->in(__DIR__)
8+
->exclude('build')
9+
->exclude('vendor')
10+
;
11+
12+
return (new PhpCsFixer\Config())
13+
->setUnsupportedPhpVersionAllowed(true)
14+
->setRules([
15+
// '@auto' => true,
16+
// '@PHP7x3Migration' => true,
17+
// '@PSR1' => true,
18+
// '@PSR2' => true,
19+
// '@PSR12' => true,
20+
// '@Symfony' => true,
21+
// 'array_syntax' => ['syntax' => 'short'],
22+
'ordered_imports' => true,
23+
// 'strict_param' => true,
24+
])
25+
->setFinder($finder)
26+
;
27+
28+
?>

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ CHANGES for crate-dbal
55
Unreleased
66
==========
77

8+
- Added support for Doctrine DBAL 3, dropped support for Doctrine DBAL 2.
9+
- Made `TransactionIsolationLevel::READ_UNCOMMITTED` the new default.
10+
811
2025/11/13 4.0.3
912
================
1013

composer.json

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
"prefer-stable": true,
1717
"require": {
1818
"php": "^8.0|^8.1|^8.2|^8.3|^8.4|^8.5",
19-
"doctrine/dbal": "^2",
20-
"crate/crate-pdo": "^2",
19+
"doctrine/dbal": "^3",
20+
"crate/crate-pdo": "^2.2.4",
2121
"ext-pdo": "*"
2222
},
2323
"autoload": {
@@ -27,12 +27,14 @@
2727
},
2828
"require-dev": {
2929
"phpunit/phpunit": "^9.0",
30-
"squizlabs/php_codesniffer": "^3.5"
30+
"friendsofphp/php-cs-fixer": "^3.89"
3131
},
3232
"autoload-dev": {
3333
"psr-0": {
34-
"Crate\\Test": "test",
35-
"Doctrine\\Tests": "vendor/doctrine/dbal/tests"
34+
"Crate\\Test": "test"
35+
},
36+
"psr-4": {
37+
"Doctrine\\DBAL\\Tests\\": "vendor/doctrine/dbal/tests"
3638
}
3739
},
3840
"config": {
@@ -42,7 +44,7 @@
4244
},
4345
"scripts": {
4446
"test": "XDEBUG_MODE=coverage phpunit --coverage-clover build/logs/clover.xml",
45-
"check-style": "phpcs",
46-
"fix-style": "phpcbf"
47+
"check-style": "php-cs-fixer check",
48+
"fix-style": "php-cs-fixer fix"
4749
}
4850
}

docs/appendices/data-types.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ Here's an example of how the ``MapType`` can be used:
8989
$objDefinition = array(
9090
'type' => MapType::STRICT,
9191
'fields' => array(
92-
new Column('id', Type::getType('integer'), array()),
93-
new Column('name', Type::getType('string'), array()),
92+
new Column('id', Type::getType('integer'), array()),
93+
new Column('name', Type::getType('string'), array()),
9494
),
9595
);
9696
$table->addColumn(

docs/appendices/table-options.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Example:
1818
1919
$options = [];
2020
$options['sharding_shards'] = 5;
21-
$myTable = new Table('my_table', [], [], [], 0, $options);
21+
$myTable = new Table('my_table', [], [], [], [], $options);
2222
2323
2424
Sharding Options
@@ -66,7 +66,7 @@ Example on how to adjust the replicas:
6666
$options = [];
6767
$options['table_options'] = [];
6868
$options['table_options']['number_of_replicas'] = '2';
69-
$myTable = new Table('my_table', [], [], [], 0, $options);
69+
$myTable = new Table('my_table', [], [], [], [], $options);
7070
7171
7272
.. _CrateDB CREATE TABLE Documentation: https://cratedb.com/docs/crate/reference/en/latest/sql/statements/create-table.html

docs/connect.rst

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,16 @@ If you plan to query CrateDB via DBAL, you can get a connection from the
2929

3030
.. code-block:: php
3131
32+
use Doctrine\DBAL\DriverManager;
33+
3234
$params = array(
3335
'driverClass' => 'Crate\DBAL\Driver\PDOCrate\Driver',
3436
'user' => 'crate',
3537
'host' => 'localhost',
3638
'port' => 4200
3739
);
38-
$connection = \Doctrine\DBAL\DriverManager::getConnection($params);
39-
$schemaManager = $connection->getSchemaManager();
40+
$connection = DriverManager::getConnection($params);
41+
$schemaManager = $connection->createSchemaManager();
4042
4143
With these connection parameters, the ``DriverManager`` will attempt to
4244
authenticate as ``crate`` with a CrateDB node listening on ``localhost:4200``.
@@ -84,6 +86,16 @@ Here's what we changed in the above example:
8486
:ref:`data types <data-types>` appendix for more information about type
8587
maps and column type definitions.
8688

89+
Eventual consistency
90+
====================
91+
92+
Note that CrateDB is primarily an analytical database for big data, so it
93+
implements `eventual consistency`_ and does not support traditional ACID
94+
transactions.
95+
96+
Please use `REFRESH TABLE`_ for establishing read-after-write consistency
97+
when applicable.
98+
8799
Next steps
88100
==========
89101

@@ -96,4 +108,6 @@ your setup process.
96108
.. _Doctrine provided example: https://www.doctrine-project.org/projects/doctrine-orm/en/3.0/reference/configuration.html#obtaining-an-entitymanager
97109
.. _Object-Relational Mapping: https://www.doctrine-project.org/projects/orm.html
98110
.. _Doctrine ORM documentation: https://www.doctrine-project.org/projects/doctrine-orm/en/3.0/index.html
111+
.. _eventual consistency: https://community.cratedb.com/t/fundamentals-of-eventual-consistency-in-cratedb/1235
112+
.. _REFRESH TABLE: https://cratedb.com/docs/crate/reference/en/5.10/general/dql/refresh.html
99113
.. _standard DBAL parameters: https://www.doctrine-project.org/projects/doctrine-dbal/en/3.0/reference/configuration.html

examples/objects.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
/*
4+
* Basic example program about handling CrateDB OBJECTs with Doctrine DBAL.
5+
* https://github.com/crate/crate-dbal
6+
*/
7+
require __DIR__ . '/../vendor/autoload.php';
8+
9+
use Crate\DBAL\Driver\PDOCrate\Driver as CrateDBDriver;
10+
use Crate\DBAL\Types\MapType;
11+
use Doctrine\DBAL\DriverManager;
12+
use Doctrine\DBAL\Exception\TableNotFoundException;
13+
use Doctrine\DBAL\Schema\Column;
14+
use Doctrine\DBAL\Schema\Table;
15+
16+
use Doctrine\DBAL\Tools\DsnParser;
17+
use Doctrine\DBAL\Types\Type;
18+
19+
20+
// Register driver and select platform.
21+
// Because DBAL can't connect to the database to determine its version dynamically,
22+
// it is the obligation of the user to provide the CrateDB version number.
23+
// Remark: Please let us know if you find a way how to set up more ergonomically.
24+
// https://www.doctrine-project.org/projects/doctrine-dbal/en/3.10/reference/platforms.html
25+
$driver = new CrateDBDriver();
26+
$dsnParser = new DsnParser(["crate" => $driver::class]);
27+
$driver->createDatabasePlatformForVersion('6.0.0');
28+
29+
// Create connection options from data source URL.
30+
$options = $dsnParser->parse('crate://crate:crate@localhost:4200/');
31+
32+
// Connect to database.
33+
$connection = DriverManager::getConnection($options);
34+
35+
// Define table schema.
36+
$table = new Table('example');
37+
$objDefinition = array(
38+
'type' => MapType::STRICT,
39+
'fields' => array(
40+
new Column('id', Type::getType('integer'), array()),
41+
new Column('name', Type::getType('string'), array()),
42+
),
43+
);
44+
$table->addColumn(
45+
'data',
46+
MapType::NAME,
47+
array('platformOptions' => $objDefinition),
48+
);
49+
50+
// Provision database table.
51+
$schemaManager = $connection->createSchemaManager();
52+
try {
53+
$schemaManager->dropTable($table->getName());
54+
} catch (TableNotFoundException) {
55+
}
56+
$schemaManager->createTable($table);
57+
58+
// Insert data.
59+
$connection->insert('example', array('data' => array('id' => 42, 'name' => 'foo')), array('data' => 'map'));
60+
$connection->insert('example', array('data' => array('id' => 43, 'name' => 'bar')), array('data' => 'map'));
61+
$connection->executeStatement('REFRESH TABLE example');
62+
63+
// Query data.
64+
$result = $connection->executeQuery('SELECT * FROM example');
65+
print_r($result->fetchAllAssociative());
66+
67+
?>

src/Crate/DBAL/Driver/PDOCrate/CrateStatement.php

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
23
/**
34
* Licensed to CRATE Technology GmbH("Crate") under one or more contributor
45
* license agreements. See the NOTICE file distributed with this work for
@@ -22,14 +23,129 @@
2223

2324
namespace Crate\DBAL\Driver\PDOCrate;
2425

25-
use Doctrine\DBAL\Driver\PDOStatementImplementations;
26-
use Doctrine\DBAL\Driver\Statement as StatementInterface;
26+
use Crate\PDO\PDOInterface;
2727
use Crate\PDO\PDOStatement;
28+
use Doctrine\DBAL\Driver\PDO\Exception;
29+
use Doctrine\DBAL\Driver\Result as ResultInterface;
30+
use Doctrine\DBAL\Driver\Statement as StatementInterface;
31+
use Doctrine\DBAL\ParameterType;
32+
use Doctrine\Deprecations\Deprecation;
33+
use PDO;
34+
use PDOException;
2835

2936
/**
3037
* @internal
3138
*/
32-
class CrateStatement extends PDOStatement implements StatementInterface
39+
final class CrateStatement implements StatementInterface
3340
{
34-
use PDOStatementImplementations;
41+
private PDOInterface $pdo;
42+
private PDOStatement $stmt;
43+
44+
/**
45+
* @param string $sql
46+
* @param array<string,mixed> $options
47+
*/
48+
public function __construct(PDOInterface $pdo, $sql, $options = [])
49+
{
50+
$this->pdo = $pdo;
51+
$this->stmt = $pdo->prepare($sql, $options);
52+
}
53+
54+
/**
55+
* {@inheritDoc}
56+
*/
57+
public function execute($params = null): ResultInterface
58+
{
59+
60+
if ($params !== null) {
61+
Deprecation::trigger(
62+
'doctrine/dbal',
63+
'https://github.com/doctrine/dbal/pull/5556',
64+
'Passing $params to Statement::execute() is deprecated. Bind parameters using'
65+
. ' Statement::bindValue() instead.',
66+
);
67+
}
68+
try {
69+
$this->stmt->execute($params);
70+
} catch (PDOException $exception) {
71+
throw Exception::new($exception);
72+
}
73+
return new Result($this);
74+
}
75+
76+
/**
77+
* {@inheritDoc}
78+
*/
79+
public function columnCount(): int
80+
{
81+
return $this->stmt->columnCount();
82+
}
83+
84+
/**
85+
* {@inheritDoc}
86+
*/
87+
public function rowCount(): int
88+
{
89+
return $this->stmt->rowCount();
90+
}
91+
92+
/**
93+
* {@inheritDoc}
94+
*/
95+
public function bindValue($param, $value, $type = ParameterType::STRING): bool
96+
{
97+
return $this->stmt->bindValue($param, $value, $type);
98+
}
99+
100+
/**
101+
* @deprecated Use bindValue() instead.
102+
* {@inheritDoc}
103+
*/
104+
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool
105+
{
106+
Deprecation::trigger(
107+
'doctrine/dbal',
108+
'https://github.com/doctrine/dbal/pull/5563',
109+
'Statement::bindParam() was deprecated. Please use Statement::bindValue() instead.',
110+
);
111+
return $this->stmt->bindParam($param, $variable, $type, $length);
112+
}
113+
114+
/**
115+
* {@inheritDoc}
116+
*/
117+
public function fetch(
118+
$fetch_style = PDO::FETCH_ASSOC,
119+
$cursor_orientation = PDO::FETCH_ORI_NEXT,
120+
$cursor_offset = 0,
121+
) {
122+
return $this->stmt->fetch($fetch_style, $cursor_orientation, $cursor_offset);
123+
}
124+
125+
public function fetchColumn($column_number = 0)
126+
{
127+
return $this->stmt->fetchColumn($column_number);
128+
}
129+
130+
/**
131+
* {@inheritDoc}
132+
*/
133+
public function closeCursor(): bool
134+
{
135+
try {
136+
return $this->stmt->closeCursor();
137+
} catch (PDOException $exception) {
138+
throw Exception::new($exception);
139+
}
140+
}
141+
142+
/**
143+
* Gets the wrapped CrateDB PDOStatement.
144+
*
145+
* @return PDOStatement
146+
*/
147+
public function getWrappedStatement(): PDOStatement
148+
{
149+
return $this->stmt;
150+
}
35151
}

0 commit comments

Comments
 (0)