-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Bug Report
| Q | A |
|---|---|
| Version | 4.4.1 |
| Previous Version if the bug is a regression | unknown |
Summary
OracleSchemaManager and OracleMetadataProvider pass table names as-is to Oracle's data dictionary queries without normalizing to uppercase. When the platform's
UnquotedIdentifierFolding is LOWER, Doctrine passes lowercase names like 'migrations' to WHERE TABLE_NAME = :TABLE_NAME, but Oracle's data dictionary always stores unquoted
identifiers as uppercase (MIGRATIONS), so the query returns nothing and introspectTableByUnquotedName() throws TableDoesNotExist.
Worth noting: OraclePlatform::__construct() hardcodes UnquotedIdentifierFolding::UPPER and doesn't accept it as a parameter, so the only way to use LOWER folding is by
subclassing and overriding the private property via reflection — which is what exposes this bug.
Current behavior
tablesExist(['migrations']) returns true, but introspectTableByUnquotedName('migrations') throws TableDoesNotExist immediately after on the same connection.
The call chain for introspectTableByUnquotedName('migrations'):
- introspectTableObjects() calls $tableName->getUnqualifiedName()->toNormalizedValue($folding) — with LOWER folding this produces 'migrations'
- That value is passed to selectTableColumns() / OracleMetadataProvider::buildTableQueryPredicate()
- SQL ends up as WHERE C.TABLE_NAME = 'migrations'
- Oracle's data dictionary has MIGRATIONS — no match, TableDoesNotExist is thrown
tablesExist() doesn't hit this because AbstractSchemaManager line 184 lowercases both sides before comparing, so migrations == strtolower('MIGRATIONS') passes — but the
introspection path has no equivalent normalization.
Affected methods:
- OracleSchemaManager::selectTableColumns()
- OracleSchemaManager::selectIndexColumns()
- OracleSchemaManager::selectForeignKeyColumns()
- OracleSchemaManager::fetchTableOptionsByTable()
- OracleMetadataProvider::buildTableQueryPredicate()
Expected behavior
introspectTableByUnquotedName('migrations') should find the table. Oracle's data dictionary is always uppercase for unquoted identifiers — that's an Oracle fact, independent of
the platform's folding setting. The catalog query parameters should always go through strtoupper() before being bound, regardless of what getUnquotedIdentifierFolding() returns.
Also, OraclePlatform::__construct() should ideally accept UnquotedIdentifierFolding as an optional parameter instead of hardcoding UPPER, so users don't have to reach for
reflection to configure it.
How to reproduce
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Schema\Name\UnquotedIdentifierFolding;
// OraclePlatform::__construct() hardcodes UPPER and accepts no parameters.
// The only way to get LOWER folding is via subclass + reflection:
class CustomOraclePlatform extends OraclePlatform
{
public function __construct()
{
parent::__construct();
$reflection = new \ReflectionClass(\Doctrine\DBAL\Platforms\AbstractPlatform::class);
$property = $reflection->getProperty('unquotedIdentifierFolding');
$property->setAccessible(true);
$property->setValue($this, UnquotedIdentifierFolding::LOWER);
}
}
// With a connection using this platform and a table MIGRATIONS existing in Oracle:
$schemaManager->tablesExist(['migrations']); // true
$schemaManager->introspectTableByUnquotedName('migrations'); // throws TableDoesNotExist
This is related to #7328, which hits a similar inconsistency between tablesExist() and the OracleMetadataProvider path from a different angle. The same pattern also appears in
doctrine/orm — SQLResultCasing hardcodes strtoupper() for OraclePlatform without consulting getUnquotedIdentifierFolding(), breaking hydration when ColumnCase::LOWER is used on
the connection (reported in doctrine/orm#12272).