-
Notifications
You must be signed in to change notification settings - Fork 10
Support for Doctrine DBAL 3 #122
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
base: main
Are you sure you want to change the base?
Conversation
c0dddcc to
f1a7a40
Compare
|
Other than many deprecation warnings, because crate/crate-pdo#146 has not been merged and released yet, running the tests here 1 raise this fatal error 2: Fatal error: Declaration of Crate\PDO\PDO::prepare($statement, $options = null) must be compatible
with Doctrine\DBAL\Driver\Connection::prepare(string $sql): Doctrine\DBAL\Driver\Statement
in /path/to/crate-dbal/vendor/crate/crate-pdo/src/Crate/PDO/PDO.php on line 193Footnotes |
|
It will make no sense to continue working on this before crate/crate-pdo#146 has been merged. Otherwise, there are too many type errors to work around. In other words, fa8ef49 should be removed again. |
|
Hi @amotl I came across this and was wondering if this will be merge soon now that PR holding you back is merged? |
|
Dear Rico, thank you for writing in. I am not sure if this patch in its current state is enough to add support for Doctrine 3. Is it?
Ah, I see. Yeah, this would probably need a refresh. Thanks for the reminder! With kind regards, |
|
Hi @amotl, regards, |
|
Hi @andythedandy. Thank you for writing in and for offering support. This patch certainly needs more love to satisfy CI. Would you be able to spend a few cycles on it? On the other hand, GH-136 by @JulianMar may also be very relevant in this regard, and could make this patch obsolete, by skipping support for Doctrine 3 altogether. |
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... 📒 Files selected for processing (5)
WalkthroughUpgrades support from Doctrine DBAL 2 to DBAL 3. Refactors core driver classes to implement new DBAL 3 interfaces. Replaces inheritance-based statement implementation with composition. Adds exception converter and result wrapper classes. Updates type hints across platform and type classes. Modifies all functional tests to use new DBAL 3 APIs and execution methods. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes
Areas requiring extra attention:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
It also has a ruleset to order `use` statements. - https://github.com/PHP-CS-Fixer/PHP-CS-Fixer - https://cs.symfony.com/doc/rules/import/ordered_imports.html Before: PHP_CodeSniffer - https://github.com/PHPCSStandards/PHP_CodeSniffer
- 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
seut
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the efforts, left some suggestions and questions.
| . ' Statement::bindParam() or Statement::bindValue() instead.', | ||
| ); | ||
| } | ||
| $this->stmt->execute($params); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't all calls against $this->stmt wrapped into a try/catch block to convert a PDOException into the newly added Exception which provides an sqlState? See https://github.com/doctrine/dbal/blob/3.10.x/src/Driver/PDO/Statement.php.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Apparently this was part of the stacked GH-147 already. I am now merging that branch into this one to make it complete. Sorry for the confusion / review overhead.
| use PDOException; | ||
|
|
||
| /** @internal */ | ||
| final class Exception extends AbstractException |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this class needed? Looks the same as https://github.com/doctrine/dbal/blob/3.10.x/src/Driver/PDO/Exception.php.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice spot, fixed with ec6eef9.
|
|
||
| public function lastInsertId($name = null): string|false | ||
| { | ||
| return $this->connection->lastInsertId($name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrap into try/catch to return the Exception instead of an PDOException?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea: 05f85d5. Thanks again.
| $table->setPrimaryKey(array('id')); | ||
|
|
||
| // OBJECT schema definition via platform options | ||
| // Those intentionally use the `Type::getType()` notation and resolve to DBAL types. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we add "why/reason"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried with 24d1767.
| // TODO: Could work by inheriting. | ||
| // 'wrapperClass' => 'Crate\DBAL\Driver\PDOCrate\PDOConnection', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please clarify this? Why would it be needed, why could? Sorry if this relates to my lack of current DBAL knowledge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed the comment with b07a4a4. It originated from the same lack of DBAL knowledge on my end: I had spotted this parameter somewhere and wanted to investigate what it means.
| $sql = "SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?"; | ||
| $testString = $this->_conn->fetchColumn($sql, array(1, 'foo'), 1); | ||
|
|
||
| $stmt->executeQuery(array(1, 'foo')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it needed to execute the query again to fetch the 2nd col?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Crate\PDO\PDOStatement::fetchColumn looks like this, ....
#[\ReturnTypeWillChange]
public function fetchColumn($column_number = 0)
{
if (!is_int($column_number)) {
throw new Exception\InvalidArgumentException('column_number must be a valid integer');
}
if (!$this->hasExecuted()) {
$this->execute();
}
if (!$this->isSuccessful()) {
return false;
}
if (!$this->collection->valid()) {
return false;
}
$row = $this->collection->current();
$this->collection->next();
if ($column_number >= count($row)) {
throw new Exception\OutOfBoundsException(
sprintf('The column "%d" with the zero-based does not exist', $column_number)
);
}
return $row[$column_number];
}... and without that change, it would now trip on the check for $this->collection->valid(), returning false and failing the test case.
1) Crate\Test\DBAL\Functional\DataAccessTest::testFetchColumn
Failed asserting that false matches expected 'foo'.Can you figure out what I am doing wrong?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation says:
Returns a single column from the next row of a result set or
falseif there are no more rows.
In this spirit, I think the amendment to the test case is correct if it wants to fetch another column from the same record like before. Apparently, this spot has been more graceful before, I don't know why, and didn't bother to investigate closer after stumbling upon this anomaly like you did. 🤷
|
|
||
| public function testQuotesAlterTableRenameIndex() : void | ||
| { | ||
| $this->markTestSkipped('Platform does not support ALTER TABLE.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| $this->markTestSkipped('Platform does not support ALTER TABLE.'); | |
| testAlterTableRenameIndex(); |
or
| $this->markTestSkipped('Platform does not support ALTER TABLE.'); | |
| $this->markTestSkipped('Platform does not support renaming indexes.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've adjusted this and a few other skip messages with ee38c2f, thanks.
| * @param string $password | ||
| * @param array|null $options | ||
| */ | ||
| public function __construct($dsn, $user = null, $password = null, ?array $options = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this is using an old abstraction layer? When looking into the shipped implementations, it looks like they only implement the Driver class and uses the built-in Connection.
Maybe something to consider cleaning up in future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. @coderabbitai: Please create a dedicated issue to investigate this on behalf of a subsequent iteration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Create a GitHub issue to investigate simplifying PDOConnection abstraction
gh issue create \
--title "Investigate simplifying PDOConnection to use built-in Doctrine DBAL Connection" \
--body "## Context
In PR #122, @seut observed that the current \`PDOConnection\` implementation may be using an old abstraction layer. When reviewing shipped Doctrine DBAL implementations, they noted that these typically only implement the \`Driver\` class and use the built-in [Connection](https://github.com/doctrine/dbal/blob/3.10.x/src/Driver/PDO/Connection.php) from Doctrine DBAL.
## Task
Investigate whether the custom \`PDOConnection\` class in \`src/Crate/DBAL/Driver/PDOCrate/PDOConnection.php\` can be simplified or replaced by leveraging Doctrine DBAL's built-in Connection implementation, similar to how other DBAL drivers are structured.
## References
- PR: https://github.com/crate/crate-dbal/pull/122
- Comment thread: https://github.com/crate/crate-dbal/pull/122#discussion_r2541563710
- Requested by: @amotl
This investigation is intended for a future iteration after PR #122 is completed." \
--assignee amotl
Length of output: 46
@amotl I've created issue #151 to investigate simplifying the PDOConnection abstraction layer: #151
The issue documents @seut's observation that the current implementation may be using an old abstraction pattern, and suggests investigating whether the custom PDOConnection class can be simplified by leveraging Doctrine DBAL's built-in Connection implementation, similar to how other DBAL drivers are structured. The issue has been assigned to you for a future iteration after this PR is completed.
🧬 Code Graph Analysis Results
<src/Crate/DBAL/Driver/PDOCrate/Driver.php, lines 35-130>
class Driver implements \Doctrine\DBAL\Driver
{
public const VERSION = '4.0.3';
public const NAME = 'crate';
private const VERSION_057 = '0.57.0';
private const VERSION_4 = '4.0.0';
/**
* {@inheritDoc}
* @return PDOConnection The database connection.
*/
public function connect(
#[SensitiveParameter]
array $params
): PDOConnection {
$username = $params['user'] ?? null;
$password = $params['password'] ?? null;
$driverOptions = $params['driver_options'] ?? [];
return new PDOConnection($this->constructPdoDsn($params), $username, $password, $driverOptions);
}
/**
* Constructs the Crate PDO DSN.
*
* @return string The DSN.
*/
private function constructPdoDsn(array $params): string
{
$dsn = self::NAME . ':';
if (isset($params['host']) && $params['host'] != '') {
$dsn .= $params['host'];
} else {
$dsn .= 'localhost';
}
$dsn .= ':' . (isset($params['port']) ? $params['port'] : '4200');
return $dsn;
}
/**
* {@inheritDoc}
*/
public function getDatabasePlatform(): AbstractPlatform
{
return new CratePlatform4();
}
/**
* {@inheritDoc}
*/
public function getSchemaManager(Connection $conn, AbstractPlatform $platform): AbstractSchemaManager
{
// Added by Doctrine 3.
return new CrateSchemaManager($conn, $platform);
}
/**
* {@inheritDoc}
*/
public function getName(): string
{
return self::NAME;
}
/**
* {@inheritDoc}
*/
public function getDatabase(Connection $conn): string|null
{
return null;
}
/**
* {@inheritDoc}
*/
public function createDatabasePlatformForVersion($version): AbstractPlatform
{
if (version_compare($version, self::VERSION_057, "<")) {
return new CratePlatform();
} elseif (version_compare($version, self::VERSION_4, "<")) {
return new CratePlatform1();
} else {
return new CratePlatform4();
}
}
/**
* {@inheritDoc}
*/
public function getExceptionConverter(): ExceptionConverter
{
// Added by Doctrine 3.
return new ExceptionConverter();
}
}
<src/Crate/DBAL/Driver/PDOCrate/CrateStatement.php, lines 39-147>
final class CrateStatement implements StatementInterface
{
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);
}
/**
* {@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::bindValue() instead.',
);
}
try {
$this->stmt->execute($params);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
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): void
{
$this->stmt->bindValue($param, $value, $type);
}
/**
* @deprecated Use bindValue() instead.
* {@inheritDoc}
*/
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null)
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5563',
'Statement::bindParam() was deprecated. Please use Statement::bindValue() instead.',
);
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);
}
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;
}
}
<src/Crate/DBAL/Driver/PDOCrate/CrateStatement.php, lines 48-52>
public function __construct(PDOInterface $pdo, $sql, $options = [])
{
$this->pdo = $pdo;
$this->stmt = $pdo->prepare($sql, $options);
}
<src/Crate/DBAL/Driver/PDOCrate/CrateStatement.php, lines 57-74>
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::bindValue() instead.',
);
}
try {
$this->stmt->execute($params);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
return new Result($this);
}
<src/Crate/DBAL/Driver/PDOCrate/Result.php, lines 12-108>
final class Result implements ResultInterface
{
private CrateStatement $statement;
/** @internal The result can be only instantiated by its driver connection or statement. */
public function __construct(CrateStatement $statement)
{
$this->statement = $statement;
}
/**
* {@inheritDoc}
*/
public function fetchNumeric(): false|array
{
return $this->fetch(PDO::FETCH_NUM);
}
/**
* {@inheritDoc}
*/
public function fetchAssociative(): false|array
{
return $this->fetch(PDO::FETCH_ASSOC);
}
/**
* {@inheritDoc}
*/
public function fetchOne(): mixed
{
return FetchUtils::fetchOne($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllNumeric(): array
{
return FetchUtils::fetchAllNumeric($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllAssociative(): array
{
return FetchUtils::fetchAllAssociative($this);
}
/**
* {@inheritDoc}
*/
public function fetchFirstColumn(): array
{
return FetchUtils::fetchFirstColumn($this);
}
public function rowCount(): int
{
try {
return $this->statement->rowCount();
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
public function columnCount(): int
{
try {
return $this->statement->columnCount();
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
public function free(): void
{
$this->statement->closeCursor();
}
/**
* @phpstan-param PDO::FETCH_* $mode
*
* @return mixed
*
* @throws Exception
*/
private function fetch(int $mode)
{
try {
return $this->statement->fetch($mode);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (4)
test/Crate/Test/DBAL/Platforms/CratePlatformTest.php (2)
157-170: Rename/ALTER-related tests being skipped is consistent with platform capabilitiesMarking the various rename-column, change-column-length, and rename-index tests as skipped (with clear messages) matches Crate’s actual DDL limitations and aligns with earlier review suggestions to skip these cases instead of faking SQL support.
Also applies to: 174-177, 182-185, 199-202, 219-222, 525-533
516-523: Rename-index helper and tests match skip-based strategyProviding
getAlterTableRenameIndexSQL()while keepingtestAlterTableRenameIndex()andtestAlterTableRenameIndexInSchema()skipped keeps this test class compatible with the baseAbstractPlatformTestCasewithout pretending Crate supportsALTER INDEX ... RENAME. This also implements what prior reviews suggested (prefer skipping over partial SQL).Also applies to: 525-533
src/Crate/DBAL/Driver/PDOCrate/CrateStatement.php (1)
95-98: Return the boolean result frombindValue()to comply with Doctrine DBAL interface contractDoctrine DBAL 3.0–3.6 driver-level interface declares
Doctrine\DBAL\Driver\Statement::bindValue()with aboolreturn type, and implementations must return this value. The current code inCrateStatement::bindValue()declaresvoidand discards the underlyingPDOStatement::bindValue()result, breaking interface compliance.Apply the suggested fix:
- public function bindValue($param, $value, $type = ParameterType::STRING): void - { - $this->stmt->bindValue($param, $value, $type); - } + public function bindValue($param, $value, $type = ParameterType::STRING): bool + { + return $this->stmt->bindValue($param, $value, $type); + }File:
src/Crate/DBAL/Driver/PDOCrate/CrateStatement.phplines 95–98src/Crate/DBAL/Platforms/CratePlatform.php (1)
803-807: Varchar fallback logic compares type name to SQL snippet.The condition compares
$columnData['type']->getName()(DBAL type name like'string') with$platform->getVarcharTypeDeclarationSQLSnippet(0, false)(SQL keyword'STRING'). While the case-insensitive comparison may work, this mixes semantic levels and makes the intent unclear.Consider comparing against the DBAL type name directly:
- if (strtolower($columnData['type']->getName()) == - strtolower($platform->getVarcharTypeDeclarationSQLSnippet(0, false)) + if (strtolower($columnData['type']->getName()) === 'string' && $columnData['length'] === null) {Based on past review comments.
🧹 Nitpick comments (8)
src/Crate/DBAL/Platforms/Keywords/CrateKeywords.php (1)
35-35: Good addition of return type, but consider consistency.The
: arrayreturn type correctly aligns with Doctrine DBAL 3's stricter typing requirements. However, for consistency, thegetName()method (line 30) should also declare its return type as: string.Apply this diff to add the missing return type to
getName():- public function getName() + public function getName(): string { return 'Crate'; }src/Crate/DBAL/Types/TimestampType.php (2)
28-37: Import and public constants are consistent with other typesThe explicit
Typeimport and makingNAMEpublic align this implementation with your other DBAL type classes (e.g.,ArrayType,MapType). KeepingS_TO_MSpublic is harmless, though you could narrow it toprivateif you want to signal it as an implementation detail only.
49-53: Conversion methods: types look good; consider small robustness tweaksThe added
: mixedreturn types and the existing conversion logic look fine. Two optional refinements you might consider:
- Accept
DateTimeInterfaceinstead of onlyDateTimein theinstanceofchecks if you want to supportDateTimeImmutabletransparently.- Cast before calling
setTimestampto avoid passing a float from$value / self::S_TO_MS:- $val->setTimestamp($value / self::S_TO_MS); + $val->setTimestamp((int) ($value / self::S_TO_MS));Static analysis: PHPMD’s
UnusedFormalParameterwarnings for$platformhere are expected — the parameter is required by the Doctrine type signature and should not be removed. If the warnings are noisy in CI, a suppression annotation is safer than changing the signature.Please verify these suggestions against your supported PHP versions and the Doctrine DBAL 3
TypeAPI.Also applies to: 55-69
test/Crate/Test/DBAL/Platforms/CratePlatformTest.php (2)
379-380: Consider usingTypes::INTEGERinstead of raw'integer'stringsYou already import
Typesand use it elsewhere; for consistency and to reduce typo risk, you might prefer:- $tableDiff->addedColumns['quota'] = new Column('quota', Type::getType('integer'), array('notnull' => false)); + $tableDiff->addedColumns['quota'] = new Column('quota', Type::getType(Types::INTEGER), array('notnull' => false)); @@ - $tableDiff->addedColumns['added'] = new Column('added', Type::getType('integer'), array()); + $tableDiff->addedColumns['added'] = new Column('added', Type::getType(Types::INTEGER), array());Purely stylistic; current code is functionally fine.
Also applies to: 407-409
543-575: Avoid leaking global Doctrine type overrides across tests
testCaseInsensitiveDoctrineTypeMappingFromType()overrides the global Doctrine type'map'viaType::overrideType()(oraddType()), but never restores the original mapping. Because the DoctrineTyperegistry is static, this can affect other tests that rely on the realMapType, depending on execution order.Consider capturing the original type and restoring it in a
finallyblock:- $type = new class () extends Type { + $type = new class () extends Type { @@ - }; - - if (Type::hasType($type->getName())) { - Type::overrideType($type->getName(), get_class($type)); - } else { - Type::addType($type->getName(), get_class($type)); - } - - self::assertSame($type->getName(), $this->platform->getDoctrineTypeMapping('ObJecT')); + }; + + $typeName = $type->getName(); + $originalType = Type::getType($typeName); + + Type::overrideType($typeName, get_class($type)); + + try { + self::assertSame($typeName, $this->platform->getDoctrineTypeMapping('ObJecT')); + } finally { + Type::overrideType($typeName, get_class($originalType)); + }This keeps the test behavior while preventing side effects on subsequent tests that use the
'map'type.src/Crate/DBAL/Types/ArrayType.php (1)
37-66: ArrayType: sequential array validation and DBAL 3-style SQL declaration look consistent
convertToDatabaseValue()correctly returnsnullunless$valueis a sequential (0..n-1) array; empty array handling is correct, and the logic mirrorsMapTypein reverse.- Using
Types::STRINGas the default and then callingType::getType($type)->getSQLDeclaration()is sound, since the constant resolves to the expected type name string.- The
AbstractPlatform $platformparameter being unused inconvertToDatabaseValue()/convertToPHPValue()is expected due to the base type contract; PHPMD warnings here can safely be ignored or suppressed if desired.Also applies to: 81-101
src/Crate/DBAL/Types/MapType.php (1)
39-42: MapType: associative-array validation and SQL declaration remain coherent
- Public constants for
NAME,STRICT,DYNAMIC, andIGNOREDmake the type and object policies easier to reference externally without changing behavior.convertToDatabaseValue()correctly returnsnullunless$valueis an associative array (non 0..n-1 keys), complementingArrayType’s sequential-array handling.convertToPHPValue()’s explicitnullcheck plus(array)cast ensures you never accidentally turnnullinto an empty array.getSQLDeclaration()/getMapTypeDeclarationSQL()keep the existing OBJECT DDL generation while adding properstringreturn types and updated exception annotations.The unused
$platformparameter in the convert* methods is expected due to the base type contract; PHPMD warnings here can be safely ignored or suppressed.Also applies to: 65-72, 74-80, 90-119
src/Crate/DBAL/Driver/PDOCrate/PDOConnection.php (1)
120-130: Document or throw exceptions for unsupported transactions.Empty transaction methods silently accept but ignore transaction boundaries, which can lead to subtle bugs if application code expects ACID semantics. Consider either:
- Adding doc comments explaining CrateDB's lack of transaction support, or
- Throwing
Exception::notSupported('Transactions are not supported by CrateDB')to fail fast.Would you like me to generate an implementation that throws explicit "not supported" exceptions?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (24)
.php-cs-fixer.php(1 hunks)CHANGES.txt(1 hunks)src/Crate/DBAL/Driver/PDOCrate/CrateStatement.php(2 hunks)src/Crate/DBAL/Driver/PDOCrate/PDOConnection.php(3 hunks)src/Crate/DBAL/Driver/PDOCrate/Result.php(1 hunks)src/Crate/DBAL/Platforms/CratePlatform.php(24 hunks)src/Crate/DBAL/Platforms/CratePlatform4.php(3 hunks)src/Crate/DBAL/Platforms/Keywords/CrateKeywords.php(3 hunks)src/Crate/DBAL/Schema/CrateSchemaManager.php(6 hunks)src/Crate/DBAL/Types/ArrayType.php(5 hunks)src/Crate/DBAL/Types/MapType.php(6 hunks)src/Crate/DBAL/Types/TimestampType.php(5 hunks)test/Crate/Test/DBAL/DBALFunctionalTest.php(2 hunks)test/Crate/Test/DBAL/Functional/BindingTest.php(1 hunks)test/Crate/Test/DBAL/Functional/ConnectionTest.php(3 hunks)test/Crate/Test/DBAL/Functional/DataAccessTest.php(24 hunks)test/Crate/Test/DBAL/Functional/ModifyLimitQueryTest.php(7 hunks)test/Crate/Test/DBAL/Functional/NamedParametersTest.php(4 hunks)test/Crate/Test/DBAL/Functional/Schema/SchemaManagerTest.php(6 hunks)test/Crate/Test/DBAL/Functional/TableOptionsTest.php(4 hunks)test/Crate/Test/DBAL/Functional/TypeConversionTest.php(6 hunks)test/Crate/Test/DBAL/Functional/Types/MapTypeTest.php(2 hunks)test/Crate/Test/DBAL/Functional/WriteTest.php(6 hunks)test/Crate/Test/DBAL/Platforms/CratePlatformTest.php(11 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
- test/Crate/Test/DBAL/Functional/NamedParametersTest.php
- .php-cs-fixer.php
- CHANGES.txt
- src/Crate/DBAL/Driver/PDOCrate/Result.php
- test/Crate/Test/DBAL/Functional/TableOptionsTest.php
- test/Crate/Test/DBAL/Functional/TypeConversionTest.php
- test/Crate/Test/DBAL/Functional/Schema/SchemaManagerTest.php
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-14T11:14:50.172Z
Learnt from: amotl
Repo: crate/crate-dbal PR: 122
File: docs/appendices/table-options.rst:21-21
Timestamp: 2025-11-14T11:14:50.172Z
Learning: In Doctrine DBAL 3, the Table constructor signature changed from DBAL 2. The `int $idGeneratorType` parameter (previously 5th position) was removed and replaced with `array $uniqueConstraints` as the 4th parameter. The new signature is: __construct($name, array $columns = [], array $indexes = [], array $uniqueConstraints = [], array $fkConstraints = [], array $options = [])
Applied to files:
test/Crate/Test/DBAL/Functional/WriteTest.phpsrc/Crate/DBAL/Platforms/CratePlatform.php
🧬 Code graph analysis (16)
test/Crate/Test/DBAL/DBALFunctionalTest.php (1)
src/Crate/DBAL/Driver/PDOCrate/Result.php (1)
Result(12-108)
src/Crate/DBAL/Types/TimestampType.php (2)
src/Crate/DBAL/Types/ArrayType.php (3)
convertToDatabaseValue(60-66)convertToPHPValue(68-71)getSQLDeclaration(81-86)src/Crate/DBAL/Types/MapType.php (3)
convertToDatabaseValue(65-72)convertToPHPValue(74-80)getSQLDeclaration(90-96)
test/Crate/Test/DBAL/Functional/WriteTest.php (4)
test/Crate/Test/DBAL/DBALFunctionalTest.php (3)
DBALFunctionalTest(32-124)run_sql(110-113)refresh(115-118)src/Crate/DBAL/Driver/PDOCrate/PDOConnection.php (2)
prepare(72-79)quote(106-109)src/Crate/DBAL/Driver/PDOCrate/CrateStatement.php (3)
getWrappedStatement(143-146)fetchColumn(125-128)bindValue(95-98)src/Crate/DBAL/Driver/PDOCrate/Result.php (1)
fetchAllAssociative(57-60)
src/Crate/DBAL/Driver/PDOCrate/CrateStatement.php (2)
src/Crate/DBAL/Driver/PDOCrate/Result.php (5)
Result(12-108)__construct(17-20)columnCount(79-86)rowCount(70-77)fetch(100-107)src/Crate/DBAL/Driver/PDOCrate/PDOConnection.php (2)
__construct(45-50)prepare(72-79)
test/Crate/Test/DBAL/Functional/BindingTest.php (3)
test/Crate/Test/DBAL/DBALFunctionalTest.php (3)
DBALFunctionalTest(32-124)prepareStatement(120-123)run_sql(110-113)src/Crate/DBAL/Driver/PDOCrate/CrateStatement.php (2)
bindValue(95-98)rowCount(87-90)src/Crate/DBAL/Driver/PDOCrate/Result.php (2)
rowCount(70-77)fetchAllAssociative(57-60)
test/Crate/Test/DBAL/Functional/Types/MapTypeTest.php (1)
test/Crate/Test/DBAL/DBALFunctionalTest.php (1)
DBALFunctionalTest(32-124)
test/Crate/Test/DBAL/Platforms/CratePlatformTest.php (1)
src/Crate/DBAL/Platforms/CratePlatform.php (1)
getDoctrineTypeMapping(552-559)
src/Crate/DBAL/Types/ArrayType.php (1)
src/Crate/DBAL/Types/MapType.php (3)
convertToDatabaseValue(65-72)convertToPHPValue(74-80)getSQLDeclaration(90-96)
src/Crate/DBAL/Schema/CrateSchemaManager.php (2)
src/Crate/DBAL/Platforms/CratePlatform.php (2)
CratePlatform(41-877)getTableOptionsSQL(862-867)src/Crate/DBAL/Driver/PDOCrate/Result.php (1)
fetchAssociative(33-36)
src/Crate/DBAL/Platforms/CratePlatform4.php (1)
src/Crate/DBAL/Platforms/CratePlatform.php (6)
initializeDoctrineTypeMappings(531-547)getBigIntTypeDeclarationSQL(374-377)getSmallIntTypeDeclarationSQL(382-385)getFloatDeclarationSQL(390-393)getVarcharTypeDeclarationSQLSnippet(449-452)getClobTypeDeclarationSQL(457-460)
src/Crate/DBAL/Driver/PDOCrate/PDOConnection.php (3)
src/Crate/DBAL/Driver/PDOCrate/Driver.php (1)
Driver(35-130)src/Crate/DBAL/Driver/PDOCrate/Result.php (1)
Result(12-108)src/Crate/DBAL/Driver/PDOCrate/CrateStatement.php (2)
CrateStatement(39-147)execute(57-74)
test/Crate/Test/DBAL/Functional/ConnectionTest.php (4)
test/Crate/Test/DBAL/DBALFunctionalTest.php (1)
DBALFunctionalTest(32-124)src/Crate/DBAL/Driver/PDOCrate/Driver.php (1)
connect(47-55)src/Crate/DBAL/Driver/PDOCrate/PDOConnection.php (2)
getNativeConnection(59-62)prepare(72-79)src/Crate/DBAL/Driver/PDOCrate/CrateStatement.php (1)
getWrappedStatement(143-146)
test/Crate/Test/DBAL/Functional/ModifyLimitQueryTest.php (2)
test/Crate/Test/DBAL/DBALFunctionalTest.php (1)
DBALFunctionalTest(32-124)src/Crate/DBAL/Driver/PDOCrate/Result.php (1)
fetchAllAssociative(57-60)
test/Crate/Test/DBAL/Functional/DataAccessTest.php (4)
test/Crate/Test/DBAL/DBALFunctionalTest.php (2)
DBALFunctionalTest(32-124)refresh(115-118)src/Crate/DBAL/Driver/PDOCrate/Result.php (6)
fetchAssociative(33-36)fetchAllAssociative(57-60)fetchNumeric(25-28)fetchOne(41-44)fetchAllNumeric(49-52)fetchFirstColumn(65-68)src/Crate/DBAL/Driver/PDOCrate/CrateStatement.php (3)
bindValue(95-98)getWrappedStatement(143-146)fetchColumn(125-128)src/Crate/DBAL/Driver/PDOCrate/PDOConnection.php (2)
prepare(72-79)query(95-104)
src/Crate/DBAL/Platforms/CratePlatform.php (3)
src/Crate/DBAL/Types/MapType.php (2)
MapType(37-120)getName(49-52)src/Crate/DBAL/Types/TimestampType.php (2)
TimestampType(34-82)getName(44-47)src/Crate/DBAL/Types/ArrayType.php (1)
getName(44-47)
src/Crate/DBAL/Types/MapType.php (1)
src/Crate/DBAL/Types/ArrayType.php (3)
convertToDatabaseValue(60-66)convertToPHPValue(68-71)getSQLDeclaration(81-86)
🪛 PHPMD (2.15.0)
src/Crate/DBAL/Types/TimestampType.php
49-49: Avoid unused parameters such as '$platform'. (undefined)
(UnusedFormalParameter)
55-55: Avoid unused parameters such as '$platform'. (undefined)
(UnusedFormalParameter)
src/Crate/DBAL/Types/ArrayType.php
60-60: Avoid unused parameters such as '$platform'. (undefined)
(UnusedFormalParameter)
68-68: Avoid unused parameters such as '$platform'. (undefined)
(UnusedFormalParameter)
src/Crate/DBAL/Types/MapType.php
65-65: Avoid unused parameters such as '$platform'. (undefined)
(UnusedFormalParameter)
74-74: Avoid unused parameters such as '$platform'. (undefined)
(UnusedFormalParameter)
🔇 Additional comments (40)
src/Crate/DBAL/Types/TimestampType.php (1)
78-81: String return type ongetSQLDeclarationmatches usageAdding the
stringreturn type here matches how the method is used (delegating directly to$platform->getDateTimeTypeDeclarationSQL) and keeps the behavior unchanged.If you maintain compatibility with multiple DBAL versions, double‑check that this signature still matches the parent contract on all supported versions.
test/Crate/Test/DBAL/Platforms/CratePlatformTest.php (4)
32-39: DBAL imports and aliases are consistent with usageThe new
DBALExceptionalias,AbstractPlatformTestCase, andTypesimports all line up with how the test class uses these symbols further down; no issues spotted here.
464-465: Use ofTypes::*in nested object/array declarations looks correctSwitching nested
num/textfields and the arraytypeoption toTypes::INTEGER/Types::STRINGis consistent with how Doctrine DBAL 3 represents type names and matches the expectations of the surrounding SQL assertion.Also applies to: 480-481
508-515: Static, typed data provider is appropriateMaking
asciiStringSqlDeclarationDataProvider()public staticwith an explicitarrayreturn type is consistent with modern PHPUnit / DBAL test style and doesn’t rely on instance state.
535-541: Commented-type mapping test is a good guard forregisterDoctrineTypeMappingbehavior
testRegistersCommentedDoctrineMappingTypeImplicitly()usefully asserts that registering a DB type ('foo') to map toTypes::ARRAYdoes not mark the type as “commented,” which protectsCratePlatform::getDoctrineTypeMapping()/ comment-handling from regressions. No changes needed here.src/Crate/DBAL/Platforms/CratePlatform4.php (1)
31-52: CratePlatform4: added return types align with base platform methodsThe added
void/stringreturn types oninitializeDoctrineTypeMappings()and the*TypeDeclarationSQL()overrides match the base platform signatures and don’t change behavior. This is a straightforward typing improvement.Also applies to: 58-93
src/Crate/DBAL/Schema/CrateSchemaManager.php (2)
44-59: Portable index/column helpers: normalization logic looks solid
_getPortableTableIndexesList()correctly rewrites the raw index rows into the array shape expected by the parent implementation (key_name,column_name,non_unique,primary,where), then defers to the parent for final normalization._getPortableTableColumnDefinition()’s handling ofis_nullable(defaulting missing values to'YES'and normalizing booleans to'YES'/'NO') provides backward compatibility for different CrateDB versions and feeds a consistentnotnullflag into theColumnoptions.- Using
Type::getType($type)for theColumninstance is consistent with the existing platform/type system.Also applies to: 63-96
119-130: Let me search for CrateDB documentation about the information_schema.tables.settings column:Let me search more specifically for the information_schema.tables structure:
Let me search specifically for how crate-pdo maps CrateDB data types:
Based on my verification, the review comment is valid and addresses a real concern. Here's what I found:
Key findings:
The
information_schema.tablesvirtual table contains table settings as configuration parameters, returned by the query inCratePlatform.phpline 864-865.The code shows that other columns like
clustered_byandpartitioned_bycan be NULL (as seen in VIEWs in the official documentation), suggesting that schema columns from CrateDB can return NULL values.The problem:
flatten()now has a strictarraytype hint, but no NULL check exists before passing$s['settings']to it at line 146. If thesettingscolumn ever returns NULL or a scalar value, aTypeErrorwould be raised instead of the previous silent behavior.The other fields like
clustered_by,number_of_shards, etc. are used directly without array type enforcement, butsettingsis specifically passed to a function with strict array typing.The concern is valid and actionable. The suggested mitigations are sound:
- Cast with
(array) $s['settings']ensures NULL becomes[]- Or relax the parameter type and add internal guards
Rewritten review comment:
Add NULL-safety guard for
settingscolumn before passing toflatten()Line 146 in
CrateSchemaManager::listTableDetails()passes$s['settings']directly toflatten(array $array)without checking if it's NULL. While CrateDB'sinformation_schema.tablestypically returns settings as a map/object, defensive coding suggests guarding against potential NULL values. If NULL occurs, the strict type hint will raise a TypeError. Consider either:- $options['table_options'] = self::flatten($s['settings']); + $options['table_options'] = self::flatten((array) $s['settings']);or relax the
flatten()parameter type hint and add internal NULL handling.The same applies at line 147 (and similar usage patterns elsewhere in the method).
src/Crate/DBAL/Platforms/CratePlatform.php (2)
43-45: LGTM: Constants visibility update for DBAL 3 compatibility.The constants are now public, making them accessible to external code. This aligns with Doctrine DBAL 3's approach to platform configuration and is consistent with the broader API modernization in this PR.
869-876: LGTM: Doctrine 3 required method implementation.The
getCurrentDatabaseExpression()method is required by Doctrine DBAL 3'sAbstractPlatforminterface. The implementation correctly returns the CrateDB-specific expression.test/Crate/Test/DBAL/Functional/Types/MapTypeTest.php (1)
28-34: LGTM: Updated to Doctrine DBAL 3 APIs.The changes correctly migrate to DBAL 3:
- Base class updated to
DBALFunctionalTest- Type constants usage updated from
Type::INTEGER/STRINGtoTypes::INTEGER/STRINGThese align with Doctrine DBAL 3's type system refactoring.
test/Crate/Test/DBAL/Functional/ConnectionTest.php (2)
52-56: LGTM: Authentication retrieval updated for DBAL 3.The test now explicitly calls
connect()and retrieves credentials from the native connection usinggetNativeConnection()->getAttribute(), which correctly adapts to DBAL 3's connection architecture.
70-84: LGTM: Improved documentation of statement wrapping layers.The added PHPDoc and inline comments clearly document the three-layer statement wrapping architecture:
- Outer:
Doctrine\DBAL\Statement- Middle:
Crate\DBAL\Driver\PDOCrate\CrateStatement- Inner:
Crate\PDO\PDOStatementThis makes the test's intent and the architectural design more explicit.
test/Crate/Test/DBAL/DBALFunctionalTest.php (2)
26-32: LGTM: Updated test base class for DBAL 3.The changes correctly adapt the test infrastructure:
- New
ResultandStatementimports from DBAL 3- Class renamed to
DBALFunctionalTest- Grammar fix in docblock
110-123: LGTM: Test helper methods updated for DBAL 3 APIs.The helper methods are correctly updated:
run_sql(): UsesexecuteQuery()and returns typedResultrefresh(): UsesexecuteStatement()withvoidreturn typeprepareStatement(): Returns typedStatementAll changes align with Doctrine DBAL 3's execution model.
test/Crate/Test/DBAL/Functional/ModifyLimitQueryTest.php (3)
25-29: LGTM: Test class updated for DBAL 3.The imports and class declaration correctly migrate to DBAL 3:
- Base class updated to
DBALFunctionalTest- Added
TableandTypesimports- Exception class updated to
Exception as DBALException
48-49: LGTM: Schema manager API updated.Correctly replaced
getSchemaManager()withcreateSchemaManager(), which is the DBAL 3 API for obtaining a schema manager instance.Also applies to: 59-59
120-120: LGTM: Fetch method updated to DBAL 3 API.Replaced the legacy fetch pattern with
fetchAllAssociative(), which is the typed fetch method in Doctrine DBAL 3'sResultclass.test/Crate/Test/DBAL/Functional/WriteTest.php (4)
26-33: LGTM: Test imports and class updated for DBAL 3.The changes correctly migrate the test infrastructure:
- Base class updated to
DBALFunctionalTestTableandTypesimports added- Prepared for DBAL 3 API usage
44-62: LGTM: Table definition migrated to DBAL 3 type system.Column definitions now use
Typesconstants (Types::INTEGER,Types::STRING, etc.) instead of string literals, andType::getType(Types::...)for platform options. This aligns with Doctrine DBAL 3's type system.
82-96: LGTM: Updated to DBAL 3 execution and fetch APIs.The test correctly uses:
executeStatement()for DML operationsexecuteQuery()on prepared statementsgetWrappedStatement()->fetchColumn()for low-level column accessThis properly adapts to DBAL 3's separation of query vs. statement execution methods.
200-204: LGTM: Fetch methods updated to DBAL 3.Replaced direct fetch calls with
fetchAllAssociative(), which is the typed method in DBAL 3'sResultAPI.test/Crate/Test/DBAL/Functional/BindingTest.php (3)
24-26: LGTM: Test class updated for DBAL 3.Base class correctly updated to
DBALFunctionalTest, aligning with the test infrastructure changes for DBAL 3 compatibility.
34-43: LGTM: Binding and execution updated to DBAL 3 APIs.The test correctly uses:
bindValue()for parameter bindingexecuteQuery()to execute and get aResultrowCount()on theResultobjectThis aligns with Doctrine DBAL 3's statement and result APIs.
91-115: LGTM: Schema management and timestamp binding updated.The test correctly:
- Uses
createSchemaManager()for DBAL 3- Uses
run_sql()helper for DDL/DML- Calls
executeQuery()->fetchAllAssociative()for result retrievalAll changes align with DBAL 3 patterns.
test/Crate/Test/DBAL/Functional/DataAccessTest.php (8)
26-37: LGTM: Test class and imports updated for DBAL 3.The changes correctly set up the test for DBAL 3:
- Base class updated to
DBALFunctionalTestConnection,Table, andTypesimports added- Exception import updated to
Exception as DBALException
48-48: LGTM: Schema manager API updated.Correctly replaced
getSchemaManager()withcreateSchemaManager(), matching the DBAL 3 API.Also applies to: 83-83
94-100: LGTM: Prepared statement execution updated to DBAL 3.The test correctly:
- Calls
executeQuery()to get aResult- Uses
fetchAssociative()on theResultThis properly adapts to DBAL 3's separation of statement execution and result fetching.
145-148: LGTM: Execute before fetchColumn pattern fixed.The test now correctly calls
executeStatement()before accessingfetchColumn()viagetWrappedStatement(). This addresses the past review comment about missing execution.
203-203: LGTM: Connection-level fetch methods updated.All connection-level fetch calls are correctly updated to DBAL 3 typed methods:
fetchAllAssociative()for associative arraysfetchAssociative()for single associative rowfetchNumeric()for numeric arrayAlso applies to: 218-218, 231-231
259-260: LGTM: DateTime type handling updated to DBAL 3.The tests correctly use:
Types::DATETIME_MUTABLEconstant instead of string literalsfetchOne()for scalar resultsexecuteStatement()for DML operationsAll changes align with DBAL 3's type system and execution APIs.
Also applies to: 275-276, 283-284, 294-297
313-322: LGTM: Array parameter binding and numeric fetch updated.The test correctly uses
fetchAllNumeric()to retrieve numeric arrays from theResult, aligning with DBAL 3's typed fetch methods.
495-496: LGTM: Empty result handling updated.Both assertions correctly use:
fetchOne()on the connection for short-hand executionfetchOne()on theResultobject fromexecuteQuery()Both should return
falsewhen the result set is empty, matching PDO semantics.src/Crate/DBAL/Driver/PDOCrate/PDOConnection.php (7)
27-37: LGTM: Clean DBAL 3 interface adoption.The shift from inheritance to composition (implementing
ConnectionInterfacewith an internalPDOCrateDBinstance) aligns with DBAL 3's architecture.
59-62: LGTM: Standard native connection accessor.
72-79: LGTM:prepare()correctly forwards options.The
$optionsparameter is now properly passed toCrateStatement, and exception wrapping is appropriate.
84-93: LGTM:exec()implementation is sound.The assertion on line 88 prevents returning
falseas anint, and exception handling is consistent.
106-109: LGTM: Simple delegation.
111-118: LGTM: Consistent exception wrapping.
95-104: Remove unnecessary try-catch block.Both
prepare()(line 77) andCrateStatement::execute()already convertPDOExceptiontoDoctrine\DBAL\Driver\PDO\Exception, so thecatch (PDOException $exception)block here will never trigger—it's dead code.Apply this diff:
public function query(string $sql): ResultInterface { - try { - $stmt = $this->prepare($sql); - $stmt->execute(); - return new Result($stmt); - } catch (PDOException $exception) { - throw Exception::new($exception); - } + $stmt = $this->prepare($sql); + $stmt->execute(); + return new Result($stmt); }Likely an incorrect or invalid review comment.
| $this->setAttribute(\PDO::ATTR_STATEMENT_CLASS, CrateStatement::class); | ||
| $this->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); | ||
| $this->connection = new PDOCrateDB($dsn, $user, $password, $options); | ||
| $this->connection->setAttribute(PDO::ATTR_STATEMENT_CLASS, PDOStatement::class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify ATTR_STATEMENT_CLASS requires array format.
PDO::setAttribute(PDO::ATTR_STATEMENT_CLASS, ...) typically expects [ClassName::class, [constructor_args]] rather than just ClassName::class. Confirm that PDOCrateDB accepts the bare class string.
🏁 Script executed:
#!/bin/bash
# Check how ATTR_STATEMENT_CLASS is used elsewhere in the codebase
rg -nP 'ATTR_STATEMENT_CLASS' -A 2 -B 2Length of output: 522
🌐 Web query:
PDO ATTR_STATEMENT_CLASS setAttribute syntax format
💡 Result:
Use an array: [className, constructor_args_array]. Example:
$pdo->setAttribute(PDO::ATTR_STATEMENT_CLASS, [MyStatement::class, [$pdo]]);
MyStatement must extend PDOStatement and its constructor will be called with the provided args (note: cannot be used with persistent PDO). [1][2]
Sources:
[1] PHP manual — PDO::setAttribute / PDO::ATTR_STATEMENT_CLASS.
[2] PHP manual — example showing custom PDOStatement usage.
Fix ATTR_STATEMENT_CLASS to use required array format.
PDO::ATTR_STATEMENT_CLASS requires an array: [className, constructor_args_array]. Line 48 incorrectly passes a bare class string; it should be:
$this->connection->setAttribute(PDO::ATTR_STATEMENT_CLASS, [PDOStatement::class, []]);🤖 Prompt for AI Agents
In src/Crate/DBAL/Driver/PDOCrate/PDOConnection.php around line 48, the call to
setAttribute uses a bare class string for PDO::ATTR_STATEMENT_CLASS which is
invalid; change it to pass an array containing the statement class and an array
of constructor arguments (i.e., provide the class name and an empty array for
args) so the attribute is set in the required [className,
constructor_args_array] format.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following your evaluation, those statements might be wrong in crate-pdo's PDOCrateDB.php? /cc @coderabbitai
'statementClass' => PDOStatement::classcase self::ATTR_STATEMENT_CLASS:
$this->attributes['statementClass'] = $value;
break;$className = $this->attributes['statementClass'];Otherwise, when amending the DBAL code like suggested:
--- a/src/Crate/DBAL/Driver/PDOCrate/PDOConnection.php
+++ b/src/Crate/DBAL/Driver/PDOCrate/PDOConnection.php
@@ -45,7 +45,7 @@ class PDOConnection implements ConnectionInterface
public function __construct($dsn, $user = null, $password = null, ?array $options = null)
{
$this->connection = new PDOCrateDB($dsn, $user, $password, $options);
- $this->connection->setAttribute(PDO::ATTR_STATEMENT_CLASS, PDOStatement::class);
+ $this->connection->setAttribute(PDO::ATTR_STATEMENT_CLASS, [PDOStatement::class, []]);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}This exception will be raised:
Error: Class name must be a valid object or a string
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for PDOCrateDB's setAttribute implementation and ATTR_STATEMENT_CLASS usage
rg -nP 'ATTR_STATEMENT_CLASS|statementClass' --type php -A 5 -B 5
Length of output: 1024
🏁 Script executed:
#!/bin/bash
# Check if there's any documentation or comments about the statement class handling
rg -nP 'statement.*class|ATTR_STATEMENT' --type php -C 3 | head -100
Length of output: 721
🏁 Script executed:
#!/bin/bash
# Look for PDOCrateDB class definition and how it handles attributes
fd -e php -x grep -l "class PDOCrateDB"
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check how prepare() is called on the connection and what it returns
rg -nP '\$this->connection->prepare|\$pdo->prepare' --type php -B 2 -A 3
Length of output: 419
🏁 Script executed:
#!/bin/bash
# Look for any usage of the PDOStatement class that might rely on ATTR_STATEMENT_CLASS
rg -nP 'PDOStatement' --type php -C 2
Length of output: 2943
@amotl You're absolutely right, and I apologize for the incorrect suggestion. The code in both PDOConnection.php and PDOCrateDB.php is correct as-is.
Here's why:
The Architecture:
CrateStatementconstructor calls$pdo->prepare($sql, $options)where$pdoisPDOCrateDBPDOCrateDB.prepare()uses the class set viaATTR_STATEMENT_CLASSto instantiate the statement- This creates a three-layer wrapper (confirmed by your test):
- Outer:
Doctrine\DBAL\Statement - Middle:
Crate\DBAL\Driver\PDOCrate\CrateStatement - Inner:
Crate\PDO\PDOStatement
- Outer:
Why Bare String is Correct:
PDOCrateDB is a custom PDO implementation that intentionally diverges from standard PDO's API. While standard PDO requires [ClassName::class, [constructor_args]], PDOCrateDB accepts just the class name string for simplicity. The three code snippets you highlighted from PDOCrateDB.php demonstrate this design choice:
'statementClass' => PDOStatement::class, // stores as string
$this->attributes['statementClass'] = $value; // accepts string value
$className = $this->attributes['statementClass']; // uses as stringConclusion:
No changes are needed. The current implementation using PDOStatement::class (bare string) is correct for PDOCrateDB's custom API. My original suggestion to use array format would break compatibility with PDOCrateDB's design.
🧬 Code Graph Analysis Results
-
File: src/Crate/DBAL/Driver/PDOCrate/PDOConnection.php
-
Snippet (lines around the statement class attribute setup)
- Overview of the current behavior:
- The constructor sets the underlying PDOCrateDB connection and configures the PDO statement class and error mode.
- Current code:
- public function __construct($dsn, $user = null, $password = null, ?array $options = null)
{
$this->connection = new PDOCrateDB($dsn, $user, $password, $options);
$this->connection->setAttribute(PDO::ATTR_STATEMENT_CLASS, PDOStatement::class);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
- Overview of the current behavior:
-
Rationale/Impact (summary):
- The code registers PDOStatement as the statement class for the underlying Crate PDO connection. If Doctrine DBAL expects CrateStatement (or a custom class implemented for Crate) to be used as the statement class, using PDOStatement::class here could lead to mismatches when DBAL expects CrateStatement instances. The user suggestion to switch to a class/constructor-args form ([PDOStatement::class, []]) would be inappropriate if CrateStatement is intended to wrap or replace the statement, since CrateStatement is implemented in this repository and is designed to wrap the Crate PDO statement.
-
-
File: src/Crate/DBAL/Driver/PDOCrate/CrateStatement.php
-
Snippet (constructor)
- public function __construct(PDOInterface $pdo, $sql, $options = [])
{
$this->pdo = $pdo;
$this->stmt = $pdo->prepare($sql, $options);
}
- public function __construct(PDOInterface $pdo, $sql, $options = [])
-
Snippet (getWrappedStatement)
- public function getWrappedStatement(): PDOStatement
{
return $this->stmt;
}
- public function getWrappedStatement(): PDOStatement
-
-
File: src/Crate/DBAL/Driver/PDOCrate/CrateStatement.php
- Snippet (execute path and exception handling)
- public function execute($params = null): ResultInterface
{
if ($params !== null) {
Deprecation::trigger(
'doctrine/dbal',
'Deprecate passing parameters to Statement::execute*() doctrine/dbal#5556',
'Passing $params to Statement::execute() is deprecated. Bind parameters using'
. ' Statement::bindValue() instead.',
);
}
try {
$this->stmt->execute($params);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
return new Result($this);
}
- public function execute($params = null): ResultInterface
- Snippet (execute path and exception handling)
-
File: src/Crate/DBAL/Driver/PDOCrate/Result.php
-
Snippet (row/column handling and wrapping)
- public function __construct(CrateStatement $statement)
{
$this->statement = $statement;
}
- public function __construct(CrateStatement $statement)
-
Snippet (fetch wrappers)
- public function fetchNumeric(): false|array
{
return $this->fetch(PDO::FETCH_NUM);
} - public function fetchAssociative(): false|array
{
return $this->fetch(PDO::FETCH_ASSOC);
} - public function fetchAllNumeric(): array
{
return FetchUtils::fetchAllNumeric($this);
}
- public function fetchNumeric(): false|array
-
Snippet (row/column count with exception conversion)
- public function rowCount(): int
{
try {
return $this->statement->rowCount();
} catch (PDOException $exception) {
throw Exception::new($exception);
}
} - public function columnCount(): int
{
try {
return $this->statement->columnCount();
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
- public function rowCount(): int
-
-
File: src/Crate/DBAL/Driver/PDOCrate/Driver.php
-
Snippet (connect method/documentation)
- public function connect(array $params): PDOConnection
{
$username = $params['user'] ?? null;
$password = $params['password'] ?? null;
$driverOptions = $params['driver_options'] ?? [];
return new PDOConnection($this->constructPdoDsn($params), $username, $password, $driverOptions);
}
- public function connect(array $params): PDOConnection
-
Snippet (getName)
- public function getName(): string
{
return self::NAME;
}
- public function getName(): string
-
Note: If there are no other relevant code snippets directly tied to how PDOStatement is chosen or instantiated, the provided snippets above cover the key areas where statement class configuration and statement wrapping are implemented, which are central to understanding the user’s concern about ATTR_STATEMENT_CLASS and the resulting exception. If you want, I can extract specific line-numbered blocks exactly as in the files you provided.
✏️ Learnings added
Learnt from: amotl
Repo: crate/crate-dbal PR: 122
File: src/Crate/DBAL/Driver/PDOCrate/PDOConnection.php:48-48
Timestamp: 2025-11-19T23:30:55.274Z
Learning: In crate-dbal's PDOConnection.php, PDO::ATTR_STATEMENT_CLASS is set using a bare class string (PDOStatement::class) rather than the standard PDO array format ([ClassName::class, [args]]) because PDOCrateDB is a custom PDO implementation that accepts a class name string directly for this attribute.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@coderabbitai: It is not a bad idea to adhere to driver and adapter standards and conventions, to make it easier for users to switch implementations? Why not create an issue at crate/crate-pdo about this little flaw?
| public function getServerVersion(): string | ||
| { | ||
| // Unable to detect platform version. | ||
| // TODO: Need to retrieve and propagate CrateDB server version here? | ||
| return "0.0.0"; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded version always selects oldest platform.
Returning "0.0.0" will cause Driver::createDatabasePlatformForVersion() to always select CratePlatform() (the pre-0.57 variant) instead of the appropriate CratePlatform1 or CratePlatform4. Consider retrieving the actual server version via a query like SELECT version() or document why detection is not feasible.
Would you like me to generate a query-based implementation to fetch the actual CrateDB version?
About
Doctrine DBAL 3.0 was released on November 17, 2020.
Details
-- https://www.doctrine-project.org/2020/11/17/dbal-3.0.0.html