Describe the bug
On Oracle, OC\DB\Adapter::upsert() wraps every compare column in to_char():
https://github.com/owncloud/core/blob/v10.16.4/lib/private/DB/Adapter.php#L145-L152
to_char(col) = 'literal' is not sargable, so Oracle cannot use a B-tree index on col
and falls back to an index skip scan or a full scan.
OC\Files\Cache\Cache::put() upserts into oc_filecache with ['storage', 'path_hash']
as the compare columns, which is exactly the unique index fs_storage_path_hash. The
generated UPDATE therefore reads:
UPDATE "oc_filecache" SET ... WHERE to_char("storage") = '1' AND to_char("path_hash") = '…'
instead of
UPDATE "oc_filecache" SET ... WHERE "storage" = 1 AND "path_hash" = '…'
Impact
Every upload, rename, move and background scan goes through Cache::put(). On a large
oc_filecache the statement degrades from an index unique scan into an index skip scan.
On the installation where this was found the same statement went from ~0.0002 s and
8–20 buffer gets to 5.7–63.7 s and 91,000–122,000 buffer gets, which makes uploads and
renames effectively unusable.
Why the cast is there
It is deliberate — it was added in a990b75 ("Much oracle - many hack", #28698) to fix
ORA-00932: inconsistent datatypes: expected - got CLOB
upsert()'s documented default is $compare = array_keys($input), so a CLOB column such
as oc_appconfig.configvalue can end up in the WHERE clause, and Oracle cannot compare
a CLOB with = at all. to_char() is the classic workaround. The cast simply cannot be
removed outright without breaking that case — and there is no test covering it, because a
follow-up commit in the same PR (e754a10) narrowed the tests to explicit, non-CLOB
compare arrays.
Expected behaviour
Cast only the columns that need it. A TextType/BlobType compare column keeps its
to_char(); every other compare column is compared as it is, so storage and path_hash
become sargable again and fs_storage_path_hash is usable.
Steps to reproduce
- Run ownCloud on Oracle with a reasonably large
oc_filecache.
- Upload a file, or trigger
files:scan.
- Look at the plan for the
UPDATE "oc_filecache" statement in v$sql_plan — it is an
INDEX SKIP SCAN of fs_storage_path_hash, not a unique scan, and
dba_ind_expressions shows no function-based index that would match to_char(...).
Server configuration
- Database: Oracle
- ownCloud version: present in every release since v10.0.5 (the code has not changed
since a990b75), reported on 10.16.3
Notes
Two pre-existing limitations are worth writing down, but are out of scope here:
to_char() on a CLOB longer than 4000 bytes raises ORA-22835, so comparing a long
CLOB never worked. OC\AllConfig works around this with
dbms_lob.substr(configvalue, 4000, 1).
to_char() does not accept a BLOB, so a binary compare column has never worked on
Oracle either way.
Describe the bug
On Oracle,
OC\DB\Adapter::upsert()wraps every compare column into_char():https://github.com/owncloud/core/blob/v10.16.4/lib/private/DB/Adapter.php#L145-L152
to_char(col) = 'literal'is not sargable, so Oracle cannot use a B-tree index oncoland falls back to an index skip scan or a full scan.
OC\Files\Cache\Cache::put()upserts intooc_filecachewith['storage', 'path_hash']as the compare columns, which is exactly the unique index
fs_storage_path_hash. Thegenerated
UPDATEtherefore reads:instead of
Impact
Every upload, rename, move and background scan goes through
Cache::put(). On a largeoc_filecachethe statement degrades from an index unique scan into an index skip scan.On the installation where this was found the same statement went from ~0.0002 s and
8–20 buffer gets to 5.7–63.7 s and 91,000–122,000 buffer gets, which makes uploads and
renames effectively unusable.
Why the cast is there
It is deliberate — it was added in a990b75 ("Much oracle - many hack", #28698) to fix
upsert()'s documented default is$compare = array_keys($input), so a CLOB column suchas
oc_appconfig.configvaluecan end up in theWHEREclause, and Oracle cannot comparea CLOB with
=at all.to_char()is the classic workaround. The cast simply cannot beremoved outright without breaking that case — and there is no test covering it, because a
follow-up commit in the same PR (e754a10) narrowed the tests to explicit, non-CLOB
compare arrays.
Expected behaviour
Cast only the columns that need it. A
TextType/BlobTypecompare column keeps itsto_char(); every other compare column is compared as it is, sostorageandpath_hashbecome sargable again and
fs_storage_path_hashis usable.Steps to reproduce
oc_filecache.files:scan.UPDATE "oc_filecache"statement inv$sql_plan— it is anINDEX SKIP SCANoffs_storage_path_hash, not a unique scan, anddba_ind_expressionsshows no function-based index that would matchto_char(...).Server configuration
since a990b75), reported on 10.16.3
Notes
Two pre-existing limitations are worth writing down, but are out of scope here:
to_char()on a CLOB longer than 4000 bytes raisesORA-22835, so comparing a longCLOB never worked.
OC\AllConfigworks around this withdbms_lob.substr(configvalue, 4000, 1).to_char()does not accept a BLOB, so a binary compare column has never worked onOracle either way.