Skip to content

Oracle: upsert() casts every compare column to char, so filecache writes cannot use an index #41782

Description

@DeepDiver1975

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

  1. Run ownCloud on Oracle with a reasonably large oc_filecache.
  2. Upload a file, or trigger files:scan.
  3. 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.

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