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