Skip to content

[#885] Ask the catalog for the table of a tree by name - #886

Merged
vharseko merged 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issues/885-jdbc-catalog-lookup
Aug 20, 2026
Merged

[#885] Ask the catalog for the table of a tree by name#886
vharseko merged 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issues/885-jdbc-catalog-lookup

Conversation

@vharseko

@vharseko vharseko commented Aug 19, 2026

Copy link
Copy Markdown
Member

Problem

isExistsTable() asked the catalog for every table of the database and looked for a match in Java — no catalog filter, no schema filter, no table name:

// opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/JDBCStorage.java:269
try (final ResultSet rs = con.getMetaData().getTables(null, null, null, new String[]{"TABLE"})) {
    while (rs.next()) {
        if (tree2table.get(treeName).equalsIgnoreCase(rs.getString("TABLE_NAME"))) {

It is called from openTree(createOnDemand) — through AbstractTree.open(), which every tree of the backend goes through — and from deleteTree(). Opening a backend therefore walked the whole catalog once per tree: about 25 times for a stock suffix, on a database this backend may well be sharing with something else, and the cost is O(trees × tables) in something that ought to be a single indexed lookup.

It was first written up as a missing timeout, in what was then the third section of #885. It is not: the call is unbounded and needlessly expensive, and making it cheap is the fix that matters, so it was split out as #887. (What remains unbounded there can only be reached by the read timeout #885 still asks for, since DatabaseMetaData takes no setQueryTimeout at all.)

Change

The table name is passed as the pattern, so the driver asks its catalog for one row instead of all of them.

The name has to be passed in the form the catalog stores it: an unquoted identifier is folded when it is stored — to upper case on Oracle, to lower case on PostgreSQL — and a metadata pattern is matched against the stored form, not against the name as it was written. Which way a driver folds is asked of the driver, through storesUpperCaseIdentifiers() / storesLowerCaseIdentifiers(), rather than by matching its class name the way the dialect switches elsewhere in this class do: that pair of methods is exactly what the JDBC contract exposes for this, and it costs no round trip.

The comparison of the returned name is kept rather than dropped as redundant. _ is a single-character wildcard in a metadata pattern, so opendj_<hash> also matches a table called opendjX<hash>; escaping it through getSearchStringEscape() is the kind of thing drivers disagree about, and re-checking the answer is both cheaper and surer.

isExistsIndex() is left alone: it already passes the table name to getIndexInfo(), so it is a targeted lookup already. Its caller still uppercases the name for Oracle by driver-name check, which storedIdentifier() could now replace — a tidy-up worth doing on its own rather than inside this one.

Tests

testTableOfATreeIsFoundByName exercises both answers of the lookup on all four dialects: a tree is opened, then opened again — the second open has to find the table rather than create it a second time, which is where a wrong identifier folding shows up as ORA-00955 on Oracle — the entry written before is read back to confirm the tree was not replaced, and the tree is then deleted twice, the second delete having to find nothing rather than drop what is no longer there.

All four container suites pass with no skips: PgSql 38/38, MySql 38/38, MsSql 38/38, Oracle 38/38, plus the JDBC EncryptedTestCase 34/34. Every one of them runs openTree() for every tree of the backend on each open, so the lookup is exercised far beyond the test written for it.

Scope

#885 keeps what this does not touch — a read timeout for an established connection as a setting of its own, a lock timeout for the pooled connections, and the bound of the catalog reads and of commit(). The first two live in CachedConnection, in code that #876 introduces, so no branch off master can reach them yet.

Fixes #887

… name

isExistsTable() listed every table of the database and looked for a match
in Java: no catalog filter, no schema filter, no table name. It is called
from openTree(createOnDemand) through AbstractTree.open() for every tree of
the backend - about 25 of them for a stock suffix - so opening a backend
walked the whole catalog once per tree, on a database this backend may well
be sharing with something else.

The name is passed as the pattern now. It has to be passed in the form the
catalog stores it, since an unquoted identifier is folded when it is stored
- to upper case on oracle, to lower case on postgresql - and a metadata
pattern is matched against the stored form rather than against the name as
it was written. Which way a driver folds is asked of the driver itself,
through the two methods the JDBC contract exposes for it, rather than by
matching its class name as the dialect switches elsewhere in this class do.

The comparison of the name is kept: "_" is a single-character wildcard in a
metadata pattern, so "opendj_<hash>" also matches a table called
"opendjX<hash>", and the result of the lookup is still verified rather than
trusted.

testTableOfATreeIsFoundByName covers both answers on every dialect: a
second open has to find the table rather than create it again - getting the
folding wrong would raise ORA-00955 on oracle - and a second delete has to
find nothing rather than drop what is not there.
@vharseko
vharseko requested a review from maximthomas August 19, 2026 15:50
@vharseko vharseko added bug jdbc java Pull requests that update java code performance Performance / concurrency / lock-contention work tests Test suites: fixing, enabling, un-disabling labels Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug java Pull requests that update java code jdbc performance Performance / concurrency / lock-contention work tests Test suites: fixing, enabling, un-disabling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JDBC backend: isExistsTable lists every table of the database, once per tree, on every backend open

2 participants