Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
.project
.vagrant
.phpunit.result.cache
*.cache
*-console.log
.crate-docs
/composer.lock
Expand Down
26 changes: 26 additions & 0 deletions .php-cs-fixer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
// PHP Coding Standards Fixer
// https://cs.symfony.com/
// https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/doc/config.rst

$finder = (new PhpCsFixer\Finder())
->in(__DIR__)
;

return (new PhpCsFixer\Config())
->setUnsupportedPhpVersionAllowed(true)
->setRules([
// '@auto' => true,
// '@PHP7x3Migration' => true,
// '@PSR1' => true,
// '@PSR2' => true,
// '@PSR12' => true,
// '@Symfony' => true,
// 'array_syntax' => ['syntax' => 'short'],
'ordered_imports' => true,
// 'strict_param' => true,
])
->setFinder($finder)
;

?>
2 changes: 2 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ CHANGES for crate-dbal
Unreleased
==========

- Added support for Doctrine 3, dropped support for Doctrine 2.
Copy link
Member Author

@amotl amotl Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The versioning of Doctrine ORM is different, so let's be closer to the truth here, by discriminating between Doctrine ORM and Doctrine DBAL.

Suggested change
- Added support for Doctrine 3, dropped support for Doctrine 2.
- Added support for Doctrine DBAL 3, dropped support for Doctrine DBAL 2.


2025/11/13 4.0.3
================

Expand Down
14 changes: 8 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"prefer-stable": true,
"require": {
"php": "^8.0|^8.1|^8.2|^8.3|^8.4|^8.5",
"doctrine/dbal": "^2",
"doctrine/dbal": "^3",
"crate/crate-pdo": "^2",
"ext-pdo": "*"
},
Expand All @@ -27,12 +27,14 @@
},
"require-dev": {
"phpunit/phpunit": "^9.0",
"squizlabs/php_codesniffer": "^3.5"
"friendsofphp/php-cs-fixer": "^3.89"
},
"autoload-dev": {
"psr-0": {
"Crate\\Test": "test",
"Doctrine\\Tests": "vendor/doctrine/dbal/tests"
"Crate\\Test": "test"
},
"psr-4": {
"Doctrine\\DBAL\\Tests\\": "vendor/doctrine/dbal/tests"
}
},
"config": {
Expand All @@ -42,7 +44,7 @@
},
"scripts": {
"test": "XDEBUG_MODE=coverage phpunit --coverage-clover build/logs/clover.xml",
"check-style": "phpcs",
"fix-style": "phpcbf"
"check-style": "php-cs-fixer check",
"fix-style": "php-cs-fixer fix"
}
}
4 changes: 2 additions & 2 deletions docs/appendices/data-types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ Here's an example of how the ``MapType`` can be used:
$objDefinition = array(
'type' => MapType::STRICT,
'fields' => array(
new Column('id', Type::getType('integer'), array()),
new Column('name', Type::getType('string'), array()),
new Column('id', Type::getType('integer'), array()),
new Column('name', Type::getType('string'), array()),
),
);
$table->addColumn(
Expand Down
4 changes: 2 additions & 2 deletions docs/appendices/table-options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Example:

$options = [];
$options['sharding_shards'] = 5;
$myTable = new Table('my_table', [], [], [], 0, $options);
$myTable = new Table('my_table', [], [], [], [], $options);


Sharding Options
Expand Down Expand Up @@ -66,7 +66,7 @@ Example on how to adjust the replicas:
$options = [];
$options['table_options'] = [];
$options['table_options']['number_of_replicas'] = '2';
$myTable = new Table('my_table', [], [], [], 0, $options);
$myTable = new Table('my_table', [], [], [], [], $options);


.. _CrateDB CREATE TABLE Documentation: https://cratedb.com/docs/crate/reference/en/latest/sql/statements/create-table.html
Expand Down
6 changes: 4 additions & 2 deletions docs/connect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@ If you plan to query CrateDB via DBAL, you can get a connection from the

.. code-block:: php

use Doctrine\DBAL\DriverManager;

$params = array(
'driverClass' => 'Crate\DBAL\Driver\PDOCrate\Driver',
'user' => 'crate',
'host' => 'localhost',
'port' => 4200
);
$connection = \Doctrine\DBAL\DriverManager::getConnection($params);
$schemaManager = $connection->getSchemaManager();
$connection = DriverManager::getConnection($params);
$schemaManager = $connection->createSchemaManager();

With these connection parameters, the ``DriverManager`` will attempt to
authenticate as ``crate`` with a CrateDB node listening on ``localhost:4200``.
Expand Down
62 changes: 62 additions & 0 deletions examples/objects.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

/*
* Basic example program about handling CrateDB OBJECTs with Doctrine DBAL.
* https://github.com/crate/crate-dbal
*/
require __DIR__ . '/../vendor/autoload.php';

use Crate\DBAL\Platforms\CratePlatform4;
use Crate\DBAL\Types\MapType;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\Table;

use Doctrine\DBAL\Tools\DsnParser;
use Doctrine\DBAL\Types\Type;

// Initialize machinery.
// This ensures that the 'map' type is registered in the type system from the beginning.
$platform = new CratePlatform4();

// Define table schema.
$table = new Table('example');
$objDefinition = array(
'type' => MapType::STRICT,
'fields' => array(
new Column('id', Type::getType('integer'), array()),
new Column('name', Type::getType('string'), array()),
),
);
$table->addColumn(
'data',
MapType::NAME,
array('platformOptions' => $objDefinition),
);

// Register driver.
$dsnParser = new DsnParser(array('crate' => 'Crate\DBAL\Driver\PDOCrate\Driver'));

// Connect to database.
$connectionParams = $dsnParser->parse('crate://crate:crate@localhost:4200/');
$connection = DriverManager::getConnection($connectionParams);
$schemaManager = $connection->createSchemaManager();

// Provision database table.
try {
$schemaManager->dropTable($table->getName());
} catch (TableNotFoundException) {
}
$schemaManager->createTable($table);

// Insert data.
$connection->insert('example', array('data' => array('id' => 42, 'name' => 'foo')), array('data' => 'map'));
$connection->insert('example', array('data' => array('id' => 43, 'name' => 'bar')), array('data' => 'map'));
$connection->executeStatement('REFRESH TABLE example');

// Query data.
$result = $connection->executeQuery('SELECT * FROM example');
print_r($result->fetchAllAssociative());

?>
120 changes: 117 additions & 3 deletions src/Crate/DBAL/Driver/PDOCrate/CrateStatement.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* Licensed to CRATE Technology GmbH("Crate") under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
Expand All @@ -22,14 +23,127 @@

namespace Crate\DBAL\Driver\PDOCrate;

use Crate\PDO\PDOInterface;
use Crate\PDO\PDOStatement;
use Doctrine\DBAL\Driver\PDO\Exception;
use Doctrine\DBAL\Driver\PDOStatementImplementations;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Crate\PDO\PDOStatement;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use PDO;

/**
* @internal
*/
class CrateStatement extends PDOStatement implements StatementInterface
final class CrateStatement implements StatementInterface
{
use PDOStatementImplementations;
private PDOInterface $pdo;
private PDOStatement $stmt;

/**
* @param string $sql
* @param array<string,mixed> $options
*/
public function __construct(PDOInterface $pdo, $sql, $options = [])
{
$this->pdo = $pdo;
$this->stmt = $pdo->prepare($sql, $options);
}
Comment on lines +41 to +52
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Constructor now correctly forwards options to the underlying PDO connection

Using composition with PDOInterface/PDOStatement and updating the constructor to accept $options and call $pdo->prepare($sql, $options) aligns CrateStatement with PDOConnection::prepare() and preserves driver options such as bulk mode. This is the right shape for DBAL 3’s PDO‑backed driver.

If you want an extra sanity check, you can grep for all new CrateStatement( call sites to ensure they always pass the expected three arguments:


🏁 Script executed:

#!/bin/bash
rg -n "new\s+CrateStatement\(" -C2

Length of output: 443



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for execute() method in CrateStatement
ast-grep --pattern $'class CrateStatement {
  $$$
  execute($$$) {
    $$$
  }
  $$$
}'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Also check with rg to see the actual execute() implementation
rg -n "public\s+function\s+execute" src/Crate/DBAL/Driver/PDOCrate/CrateStatement.php -A10

Length of output: 461



---

🏁 Script executed:

```shell
#!/bin/bash
# Get more context on execute() method — show full implementation
rg -n "public\s+function\s+execute" src/Crate/DBAL/Driver/PDOCrate/CrateStatement.php -A30

Length of output: 859


Constructor correctly forwards options, but execute() must catch and wrap PDOException

Constructor alignment with PDOConnection::prepare() is correct. However, execute() directly delegates to $this->stmt->execute($params) without catching PDOException. For consistency with PDOConnection and the DBAL driver contract, wrap the call:

public function execute($params = null): ResultInterface
{
    if ($params !== null) {
        Deprecation::trigger(
            'doctrine/dbal',
            'https://github.com/doctrine/dbal/pull/5556',
            'Passing $params to Statement::execute() is deprecated. Bind parameters using'
            . ' Statement::bindParam() or Statement::bindValue() instead.',
        );
    }
    try {
        $this->stmt->execute($params);
    } catch (PDOException $exception) {
        throw Exception::new($exception);
    }
    return new Result($this);
}

This ensures PDOException is converted to the driver-level Exception before reaching the caller, maintaining abstraction consistency.

🤖 Prompt for AI Agents
In src/Crate/DBAL/Driver/PDOCrate/CrateStatement.php around lines 41 to 52, the
constructor is fine but the execute() method currently calls
$this->stmt->execute($params) without catching PDOException; update execute() to
(1) keep the existing deprecation trigger when $params !== null, (2) wrap the
call to $this->stmt->execute($params) in a try/catch that catches PDOException
and rethrows it as the driver-level Exception via Exception::new($exception),
and (3) return the Result as before — this ensures PDOExceptions are converted
to the DBAL driver Exception before propagating.


/**
* {@inheritDoc}
*/
public function execute($params = null): ResultInterface
{

if ($params !== null) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5556',
'Passing $params to Statement::execute() is deprecated. Bind parameters using'
. ' Statement::bindParam() or Statement::bindValue() instead.',
);
}
$this->stmt->execute($params);
return new Result($this);
}

/**
* {@inheritDoc}
*/
public function columnCount(): int
{
return $this->stmt->columnCount();
}

/**
* {@inheritDoc}
*/
public function rowCount(): int
{
return $this->stmt->rowCount();
}

/**
* {@inheritDoc}
*/
public function bindValue($param, $value, $type = ParameterType::STRING)
{
return $this->stmt->bindValue($param, $value, $type);
}

/**
* {@inheritDoc}
*/
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null)
{
return $this->stmt->bindParam($param, $variable, $type, $length);
}

/**
* {@inheritDoc}
*/
public function fetch(
$fetch_style = PDO::FETCH_ASSOC,
$cursor_orientation = PDO::FETCH_ORI_NEXT,
$cursor_offset = 0,
) {
return $this->stmt->fetch($fetch_style, $cursor_orientation, $cursor_offset);
}

/**
* @phpstan-param PDO::FETCH_* $mode
*
* @return list<mixed>
*
* @throws Exception
*/
public function fetchAll(int $mode): array
{
return $this->stmt->fetchAll($mode);
}

public function fetchColumn($column_number = 0)
{
return $this->stmt->fetchColumn($column_number);
}

/**
* {@inheritDoc}
*/
public function closeCursor(): bool
{
return $this->stmt->closeCursor();
}

/**
* Gets the wrapped CrateDB PDOStatement.
*
* @return PDOStatement
*/
public function getWrappedStatement(): PDOStatement
{
return $this->stmt;
}
}
Loading