From 1e2e3c91eba5a0ed99b28ce2cc30a3651a534803 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Tue, 22 Sep 2026 09:07:31 +0000
Subject: [PATCH] [#902] Name the table to the index guard of the JDBC backend the way the database stores it (#1001)
---
opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/JDBCStorageRetryTest.java | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 51 insertions(+), 0 deletions(-)
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/JDBCStorageRetryTest.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/JDBCStorageRetryTest.java
index 0a20a37..bb0eeb0 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/JDBCStorageRetryTest.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/JDBCStorageRetryTest.java
@@ -40,6 +40,7 @@
import java.sql.SQLNonTransientConnectionException;
import java.sql.SQLRecoverableException;
import java.sql.Statement;
+import java.util.Locale;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -57,6 +58,7 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.startsWith;
@@ -1064,6 +1066,55 @@
}
/**
+ * The table the index guard names to the catalog is spelled the way the database stores it, and which way
+ * that is comes from the driver rather than from the name of the engine. An unquoted identifier is stored
+ * folded - upper case on oracle, lower case on postgresql - and {@link DatabaseMetaData#getIndexInfo} matches
+ * its argument against the stored form and not against the name as it was written, so a guard spelling it
+ * any other way finds no index of a table that carries one and reissues the create behind it: on the two
+ * engines whose {@code create index} has no {@code if not exists} that is the open of the tree failing
+ * (#902). It is the rule {@code isExistsTable()} takes {@code storedIdentifier()} for, and the guard of the
+ * index had it hard-coded in the oracle branch of its caller alone - the one engine of the three that was
+ * known to fold upwards.
+ */
+ @Test
+ public void testTheIndexGuardNamesTheTableAsTheDatabaseStoresIt() throws Exception
+ {
+ final JDBCStorage storage = storageOverAnEngine(postgresConnection.class, true);
+ final DatabaseMetaData metaData = engineConnection.getMetaData();
+ // a database of this driver that stores what it is given in upper case: what the guard has to ask about
+ // is then the folded name, whichever branch of openTree() the driver took to get here
+ when(metaData.storesUpperCaseIdentifiers()).thenReturn(true);
+
+ storage.write(txn -> txn.openTree(TREE, true));
+
+ verify(metaData).getIndexInfo(any(), any(), eq(storage.getTableName(TREE).toUpperCase(Locale.ROOT)),
+ anyBoolean(), anyBoolean());
+ }
+
+ /**
+ * The other arm of that rule: a driver saying it stores an unquoted identifier as it was written is asked
+ * about the name as it was written. The case above pins the fold alone, and a guard folding upwards
+ * whatever the driver answers would pass it: on oracle the two spellings are one, and on postgresql - where
+ * the stored form is the lower case name the guard was given - the lookup would report no index of a table
+ * that carries one, and the {@code create index if not exists} behind it would reissue in silence, taking
+ * every write that opens a tree out of the conflict replay it is guarded for. Nothing would fail and
+ * nothing would be logged, so what is pinned here is the question being put to the driver rather than the
+ * answer one engine gives.
+ */
+ @Test
+ public void testTheIndexGuardNamesTheTableAsWrittenWhenTheDatabaseStoresItSo() throws Exception
+ {
+ final JDBCStorage storage = storageOverAnEngine(postgresConnection.class, true);
+ final DatabaseMetaData metaData = engineConnection.getMetaData();
+ // a database of this driver storing what it is given: storesUpperCaseIdentifiers() and
+ // storesLowerCaseIdentifiers() both answer false, and the name to ask about is the one the caller wrote
+
+ storage.write(txn -> txn.openTree(TREE, true));
+
+ verify(metaData).getIndexInfo(any(), any(), eq(storage.getTableName(TREE)), anyBoolean(), anyBoolean());
+ }
+
+ /**
* postgresql runs DDL inside the transaction, so a create index the engine rolled back has committed nothing:
* {@code write()} rolls the attempt back whole and replays it. Raising the flag in front of the statement -
* which is what mysql and oracle need, since they commit before a DDL of their own accord - would turn a
--
Gitblit v1.10.0