mirror of
https://github.com/doctrine/orm.git
synced 2026-03-23 22:42:18 +01:00
SQLResultCasing ignores UnquotedIdentifierFolding and breaks hydration when using DBAL portability (Oracle) #7575
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @k0n3r on GitHub (Nov 14, 2025).
Bug Report
Version: ORM 3.5.7, DBAL 4.3.4
Summary
The
SQLResultCasingtrait generates SQL aliases in UPPERCASE for Oracle, but when using DBAL's portability layer withColumnCase::LOWER, the hydration fails because it expects lowercase keys while receiving uppercase aliases in the ResultSetMapping.Current behavior
When using Oracle with DBAL's portability configuration:
The ORM hydration fails with this setup because:
SQL Generation:
SQLResultCasinggenerates aliases in UPPERCASE:ResultSetMapping: The ORM builds the RSM with lowercase keys (before SQL generation):
Result Fetching: DBAL portability converts the result keys to lowercase:
Important: The portability layer does NOT modify the platform or its
UnquotedIdentifierFoldingsetting. It only normalizes the result set keys after the database returns them. The platform still reportsUnquotedIdentifierFolding::UPPER.Hydration Fails: The ORM has no way to know that portability is being applied at the connection level. It builds the RSM expecting
IDPAIS_0(uppercase, matching the SQL it generated), but receivesidpais_0(lowercase, after portability processing). WhenAbstractHydrator::hydrateColumnInfo('idpais_0')looks for this key in the RSM, it fails because the RSM only contains the uppercase version.The root cause
The
SQLResultCasingtrait uses hardcoded platform checks:This ignores:
UnquotedIdentifierFoldingAPI (introduced in https://github.com/doctrine/dbal/pull/6823)Historical context: The
UnquotedIdentifierFoldingenum was introduced in DBAL 4.x specifically to standardize how platforms handle identifier casing. However, the ORM'sSQLResultCasingtrait was never updated to use this new API, continuing to rely on hardcodedinstanceofchecks instead.The core issue: The ORM has no way to detect that portability middleware is being applied at the connection level. The portability layer (configured via
ColumnCase::LOWER) normalizes result keys to lowercase, but the ORM still generates uppercase aliases because it only checks the platform type, not the actual portability configuration. This creates a mismatch between what the ORM expects (uppercase keys in the RSM) and what it receives (lowercase keys from the portability layer).How to reproduce
Step 1: Configure Oracle connection with portability
Step 2: Create a simple entity
Step 3: Execute a repository query
Step 4: Debug the issue
Why it fails:
AS IDPAIS_0(uppercase fromSQLResultCasingbased oninstanceof OraclePlatform)IDPAIS_0(uppercase key)IDPAIS_0→idpais_0(lowercase)AbstractHydrator::hydrateColumnInfo('idpais_0')looks foridpais_0in RSMIDPAIS_0→ lookup fails → returnsnullKey insight: The ORM cannot detect that portability is being applied because the portability layer operates at the DBAL connection level, transparently wrapping the driver. The platform still reports
UnquotedIdentifierFolding::UPPER, but the actual results come back in lowercase.Expected behavior
The ideal solution would allow users to create a custom platform that properly declares its identifier folding behavior, and have the ORM respect it:
However, this approach doesn't work because
SQLResultCasingignores theUnquotedIdentifierFoldingsetting and uses hardcoded checks instead.The
SQLResultCasingshould respect the platform's identifier folding configuration:This would allow:
Impact
SQLResultCasinguses hardcoded checksProposed solution
Update
SQLResultCasing::getSQLResultCasing()to use the modern API:This would:
Additional notes
The ORM has no mechanism to detect that portability middleware is being applied. While
OraclePlatformdeclaresUnquotedIdentifierFolding::UPPERby default (which is correct), users should be able to:The current hardcoded approach in
SQLResultCasingprevents all of these use cases.Note: The
UnquotedIdentifierFoldingAPI was specifically introduced in DBAL 4.x (via https://github.com/doctrine/dbal/pull/6823) to provide a unified way to handle identifier casing across different platforms. The ORM should leverage this API instead of maintaining its own hardcoded platform checks. This would allow users to create custom platforms that declare their folding strategy (e.g.,LOWERto match portability configuration), and have the ORM generate SQL accordingly.Related files and references
vendor/doctrine/orm/src/Internal/SQLResultCasing.php(line 18)vendor/doctrine/dbal/src/Platforms/AbstractPlatform.php(getUnquotedIdentifierFolding())vendor/doctrine/orm/src/Internal/Hydration/AbstractHydrator.php(hydrateColumnInfo())UnquotedIdentifierFolding)@beberlei commented on GitHub (Jan 4, 2026):
This issue is clearly AI generated, you had me at "The root cause" and the description of the issue. Please rewrite the issue to just have the minimum required facts to what you think a core contributor that knows the code to understand.
@k0n3r commented on GitHub (Jan 16, 2026):
Cuando uso la capa de portabilidad de DBAL (ColumnCase::LOWER) con Oracle, las consultas generadas por DQL no funcionan.
La raíz es que SQLResultCasing genera alias en MAYÚSCULAS (por ser Oracle), pero como la portabilidad convierte las claves del resultado a minúsculas, el ResultSetMapping no encuentra las columnas y la hidratación falla en silencio, devolviendo un array vacío.
Mi configuración:
ORM 3.5.7 y DBAL 4.3.4
Tengo una conexión personalizada que envuelve el driver con PortableMiddleware y ColumnCase::LOWER.
Lo que pasa:
El ORM genera el SQL con alias como AS IDPAIS_0.
El ResultSetMapping interno espera la clave IDPAIS_0.
La portabilidad de DBAL convierte las claves del resultado a idpais_0 (minúsculas).
El AbstractHydrator busca idpais_0 en el RSM, no lo encuentra, y la hidratación falla.
------Version Ingles-------
When I use the DBAL portability layer (ColumnCase::LOWER) with Oracle, the DQL-generated queries fail.
The core issue is that SQLResultCasing generates uppercase aliases (because it's Oracle), but since the portability layer converts the result keys to lowercase, the ResultSetMapping can't find the columns. This causes hydration to fail silently, returning an empty array.
My configuration:
ORM 3.5.7 and DBAL 4.3.4
I have a custom connection that wraps the driver with PortableMiddleware and ColumnCase::LOWER.
What happens:
The ORM generates SQL with aliases like AS IDPAIS_0.
The internal ResultSetMapping expects the key IDPAIS_0.
The DBAL portability layer converts the result keys to idpais_0 (lowercase).
The AbstractHydrator looks for idpais_0 in the RSM, doesn't find it, and hydration fails.