fix(db): only cast text and binary compare columns to char on Oracle [full-ci] - #41818
Open
oc-tmueller wants to merge 3 commits into
Open
fix(db): only cast text and binary compare columns to char on Oracle [full-ci]#41818oc-tmueller wants to merge 3 commits into
oc-tmueller wants to merge 3 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>
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>
The tests added with the fix fake the platform, so they cannot prove that the schema lookup finds an ownCloud table on a live Oracle: the tables are created quoted and therefore in lower case, while Oracle folds an unquoted identifier to upper case. A lookup that comes up empty falls back to casting every compare column, which is exactly the regression that was fixed. Now that the PHPUnit DB suite runs against Oracle again, pin that on the real schema: the compare column types of oc_filecache and oc_appconfig, the generated SQL they lead to, and an upsert that has to match on an uncast VARCHAR2 column as well as on a CLOB column that is still cast. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com>
oc-tmueller
force-pushed
the
fix/oracle-upsert-clob-only-cast-master
branch
from
September 7, 2026 09:20
4023359 to
c593171
Compare
phil-davis
approved these changes
Sep 7, 2026
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 tests that fake the platform, so that every platform ispinned no matter which database the job runs against. They cover both halves of the behaviour:
storage,path_hash) are not cast — the regression this fixesTextTypecompare column (configvalue) is still cast — the ORA-00932 caseNow that #41808 has restored the Oracle job, three further tests run against a live Oracle
and are skipped everywhere else. They pin what the mocks cannot: that the schema lookup finds
an ownCloud table at all on Oracle, which is the one thing that decides between the fix and the
old behaviour.
oc_filecacheandoc_appconfigto_char()around the indexed columns, still one around the CLOBVARCHAR2column as well as on a cast CLOB columnThe existing
AdapterTestcall-count expectations are unchanged, which confirms there is nocollateral change to the generated queries.
Notes for the reviewer
This is the
masterforward-port of #41783, which carries the same fix for10.16where thecustomer escalation is.
lib/private/DB/AdapterOCI8.phpis byte-identical on both branches: thecode is deliberately DBAL 2/3 agnostic.
getSchemaManager()is deprecated on DBAL 3, but it iswhat core uses everywhere else (10+ call sites, no
createSchemaManager()usage), anddoctrine/deprecationsstays atTYPE_NONEunlessDOCTRINE_DEPRECATIONSis set, so nothingis emitted under PHPUnit either.
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.Fixes #41782
🤖 Generated with Claude Code