Skip to content

OracleSchemaManager and OracleMetadataProvider ignore platform UnquotedIdentifierFolding when querying Oracle data dictionary, causing introspectTableByUnquotedName() to fail #7330

@k0n3r

Description

@k0n3r

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'):

  1. introspectTableObjects() calls $tableName->getUnqualifiedName()->toNormalizedValue($folding) — with LOWER folding this produces 'migrations'
  2. That value is passed to selectTableColumns() / OracleMetadataProvider::buildTableQueryPredicate()
  3. SQL ends up as WHERE C.TABLE_NAME = 'migrations'
  4. 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions