Skip to content

JDBC backend: the catalog reads that guard its create table and create index are not narrowed by schema, so an object of another schema suppresses the create #902

Description

@vharseko

The JDBC backend asks the catalog whether its table and its index exist, and narrows neither question by schema. Both reads pass null where the JDBC contract takes a schema — "null means that the schema name should not be used to narrow the search" — so an object of another schema of the same database answers the question, and the object the backend was about to create is never created.

try (final ResultSet rs = metaData.getTables(null, null,
        storedIdentifier(metaData, tableName), new String[]{"TABLE"})) {

opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/JDBCStorage.java:1069-1070 (isExistsTable)

try (final ResultSet rs = con.getMetaData().getIndexInfo(null, null, tableName, false, true)) {

opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/JDBCStorage.java:1150 (isExistsIndex)

Identifier folding is not the concern here — storedIdentifier already covers it, and the names are 63-character lowercase hex with no truncation. What is missing is the schema.

Why two schemas hold the same name

The table name is a hash of the tree name — getTableName is "opendj_" plus a digest of <baseDN>/<treeName> — so it is a pure function of the configuration, not of the instance. Two OpenDJ instances sharing one database, each with its own schema and its own search_path (a routine way to host two directories on one server), hold a table of the same name and an index of the same name in two different schemas. Nothing about the names distinguishes them.

What each guard then does

  • isExistsIndex — the guard around the create index of every engine. Backend B finds A's k_<hash> in A's schema and skips the create. B's table keeps only its (h,k) primary key, and the where k>? order by k batches of CursorImpl — the access path that index exists for — run unindexed for the life of the deployment. Nothing fails and nothing is logged: the symptom is a search that reads the whole table.
  • isExistsTable — the guard around the create table. Backend B finds A's table and skips the create, then issues its first statement against a table that does not exist in its own schema. That one is loud (relation "opendj_<hash>" does not exist on postgresql), but the backend does not open.

The single-instance deployment is unaffected: one schema, one answer.

What a fix has to deal with

Passing the connection's own schema to both calls is the shape of it, but not a one-liner:

  • Connection.getSchema() is JDBC 4.1 and not every driver answers it usefully — oracle returns the login user, and a driver that does not support it throws SQLFeatureNotSupportedException.
  • The schema of getTables is a pattern and the one of getIndexInfo is a plain name; an unquoted name is stored folded, so it needs storedIdentifier around it exactly as the table name does.
  • mysql has no schema separate from its database: Connector/J reports the database as the catalog, and nullCatalogMeansCurrent decides what null means there. The right narrowing is per engine, which is why this deserves a change of its own rather than a line in another PR.
  • A deployment whose account cannot see other schemas is already narrow by privilege, so whatever is done must not make the common case ask for more than it does today.

Provenance

Found while reviewing #883, which changes neither call and only adds a second caller of isExistsIndex (the postgres branch of openTree, which now guards its create index if not exists to keep the transaction replayable). Pre-existing on master; both guards were introduced with the index and the catalog reads themselves.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugjavaPull requests that update java codejdbc

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions