fix(db): only cast text and binary compare columns to char on Oracle - #41783
Draft
DeepDiver1975 wants to merge 2 commits into
Draft
fix(db): only cast text and binary compare columns to char on Oracle#41783DeepDiver1975 wants to merge 2 commits into
DeepDiver1975 wants to merge 2 commits into
Conversation
Adapter::upsert() wrapped every compare column in to_char() on Oracle. The cast is needed for CLOB and BLOB columns, which Oracle refuses to compare with = at all (ORA-00932), but to_char(column) is not sargable, so an index on the column can no longer be used. Cache::put() compares storage and path_hash - exactly the columns of the unique index fs_storage_path_hash - so every upload, rename and file scan degraded into an index skip scan. The compare column types are resolved from the schema now, and only text and binary columns are cast. If the types cannot be resolved, every column is cast, which is the previous behaviour and can never raise ORA-00932. #41782 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com>
DeepDiver1975
force-pushed
the
fix/oracle-upsert-clob-only-cast
branch
from
August 21, 2026 11:47
d8240e9 to
3babcdc
Compare
AdapterTest has a constructor without arguments, so PHPUnit cannot hand a data set to an instance and the provider arguments were dropped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com>
DeepDiver1975
marked this pull request as draft
August 21, 2026 12:42
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
On Oracle,
Adapter::upsert()wrapped every compare column into_char(). The cast isrequired for CLOB and BLOB columns — Oracle refuses to compare those with
=at all(
ORA-00932) — butto_char(column) = 'literal'is not sargable, so an index on the columncan no longer be used.
Cache::put()comparesstorageandpath_hash, which is exactly the unique indexfs_storage_path_hash, so every upload, rename and file scan degraded from an index uniquescan into an index skip scan. On the installation where this was found the statement went
from ~0.0002 s / 8–20 buffer gets to 5.7–63.7 s / 91,000–122,000 buffer gets.
The compare column types are resolved from the schema now, and only text and binary columns
are cast:
Comparing a
NUMBERcolumn to a quoted literal is safe: Oracle converts the literal to anumber, so the column and its index stay usable.
How
Rather than hand-building the cast, the per-column type is handed to the expression builder.
OCIExpressionBuilder::eq()already emits exactlyto_char("col")forIQueryBuilder::PARAM_STR, and the baseExpressionBuilder::eq()ignores its type argumententirely, so this is a no-op on MySQL/MariaDB/PostgreSQL/SQLite — the generated SQL there is
byte-identical to before. The CLOB path on Oracle is likewise byte-identical; only non-LOB
columns change.
The resolution itself lives in
AdapterOCI8, via a new protectedAdapter::getCompareColumnTypes()seam that returns nothing on every other platform. Theschema lookup passes a quoted identifier, because ownCloud creates all tables quoted and
therefore in lower case while Oracle folds unquoted identifiers to upper case — the same
reason
OracleConnection::tableExists()already quotes. The result is memoized per table.If the types cannot be resolved (unknown table, schema manager throws), every compare column
is cast, i.e. exactly the previous behaviour: slow, but it can never raise
ORA-00932. Awarning is logged once per table.
Tests
tests/lib/DB/AdapterTest.phpgets six new tests that fake the platform, since there is noOracle job in CI. They pin both halves of the behaviour:
storage,path_hash) are not cast — the regression this fixesTextTypecompare column (configvalue) is still cast — the ORA-00932 caseThe existing
AdapterTestcall-count expectations are unchanged, which confirms there is nocollateral change to the generated queries.
Notes for the reviewer
Two pre-existing Oracle limitations are documented in
IDBConnection::upsert()butdeliberately not changed here:
to_char()on a CLOB longer than 4000 bytes raisesORA-22835, so comparing a long CLOBnever worked.
OC\AllConfigworks around this withdbms_lob.substr(configvalue, 4000, 1);adopting that changes behaviour for long values and belongs in its own PR.
to_char()does not accept a BLOB, so a binary compare column has never worked either way.Keeping
BlobTypein the cast list preserves today's behaviour rather than changing it.One behaviour does change on Oracle: a non-numeric string compared against a
NUMBERcolumnnow raises
ORA-01722instead of silently matching no rows. No core caller does this.This PR targets
10.16for a customer escalation; a forward-port tomasterfollowsseparately. The code is deliberately DBAL 2/3 agnostic (
getSchemaManager()), so thecherry-pick is clean.
Fixes #41782
🤖 Generated with Claude Code