From add86d3c7c047215f886c51e2b29a4c7f1e86c0c Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Thu, 03 Sep 2026 08:21:14 +0000
Subject: [PATCH] [#877] Bound a statement of the JDBC backend by the class of the work it belongs to (#882)

---
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/VerifyJob.java |   49 +++++++++++++++++++++++++++++++++----------------
 1 files changed, 33 insertions(+), 16 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/VerifyJob.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/VerifyJob.java
index 6784d24..06d4b83 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/VerifyJob.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/VerifyJob.java
@@ -92,12 +92,15 @@
   /** Indicates whether the children count tree is to be verified. */
   private boolean verifyID2ChildrenCount;
 
+  // The trees below, and the iterate* methods that walk them, are visible to the test pinning the
+  // class of the cursors they open: every one of these walks a tree whole with nobody waiting on
+  // it, which a storage engine that bounds a statement must not bound as an operation (#877).
   /** The entry tree. */
-  private ID2Entry id2entry;
+  ID2Entry id2entry;
   /** The DN tree. */
-  private DN2ID dn2id;
+  DN2ID dn2id;
   /** The children tree. */
-  private ID2ChildrenCount id2childrenCount;
+  ID2ChildrenCount id2childrenCount;
 
   /** A list of the attribute indexes to be verified. */
   private final ArrayList<AttributeIndex> attrIndexList = new ArrayList<>();
@@ -343,9 +346,10 @@
    *
    * @throws StorageRuntimeException If an error occurs in the storage.
    */
-  private void iterateID2Entry(ReadableTransaction txn) throws StorageRuntimeException
+  void iterateID2Entry(ReadableTransaction txn) throws StorageRuntimeException
   {
-    try(final Cursor<ByteString, ByteString> cursor = txn.openCursor(id2entry.getName()))
+    // Every tree this job walks, it walks whole, and no client operation is waiting on it.
+    try(final Cursor<ByteString, ByteString> cursor = txn.openBulkCursor(id2entry.getName()))
     {
       long storedEntryCount = id2entry.getRecordCount(txn);
       while (cursor.next())
@@ -437,12 +441,12 @@
    *
    * @throws StorageRuntimeException If an error occurs in the storage.
    */
-  private void iterateDN2ID(ReadableTransaction txn) throws StorageRuntimeException
+  void iterateDN2ID(ReadableTransaction txn) throws StorageRuntimeException
   {
     final Deque<ChildrenCount> childrenCounters = new LinkedList<>();
     ChildrenCount currentNode = null;
 
-    try(final Cursor<ByteString, ByteString> cursor = txn.openCursor(dn2id.getName()))
+    try(final Cursor<ByteString, ByteString> cursor = txn.openBulkCursor(dn2id.getName()))
     {
       while (cursor.next())
       {
@@ -515,7 +519,10 @@
 
   private void verifyID2ChildrenCount(ReadableTransaction txn, ChildrenCount parent) {
     final long expected = parent.numberOfChildren;
-    final long currentValue = id2childrenCount.getCount(txn, parent.entryID);
+    // Part of the walk of dn2id above, and bounded as one: this runs once per DN of the tree, so
+    // reading it as a client operation would put the bound of an entry read over a read of a
+    // backend nobody is waiting on - which on the JDBC backend aborts a verify of a large one.
+    final long currentValue = id2childrenCount.getCount(txn, parent.entryID, true);
     if (expected != currentValue)
     {
       errorCount++;
@@ -525,7 +532,7 @@
 
   private void iterateID2ChildrenCount(ReadableTransaction txn) throws StorageRuntimeException
   {
-    try (final SequentialCursor<EntryID, Void> cursor = id2childrenCount.openCursor(txn))
+    try (final SequentialCursor<EntryID, Void> cursor = id2childrenCount.openBulkCursor(txn))
     {
       while (cursor.next())
       {
@@ -599,7 +606,7 @@
    * @throws StorageRuntimeException If an error occurs in the storage.
    * @throws DirectoryException If an error occurs reading values in the index.
    */
-  private void iterateVLVIndex(ReadableTransaction txn, VLVIndex vlvIndex, boolean verifyID)
+  void iterateVLVIndex(ReadableTransaction txn, VLVIndex vlvIndex, boolean verifyID)
       throws StorageRuntimeException, DirectoryException
   {
     if(vlvIndex == null || !verifyID)
@@ -607,7 +614,7 @@
       return;
     }
 
-    try(final Cursor<ByteString, ByteString> cursor = txn.openCursor(vlvIndex.getName()))
+    try(final Cursor<ByteString, ByteString> cursor = txn.openBulkCursor(vlvIndex.getName()))
     {
       while (cursor.next())
       {
@@ -655,7 +662,7 @@
       return;
     }
 
-    try(final Cursor<ByteString,EntryIDSet> cursor = index.openCursor(txn))
+    try(final Cursor<ByteString,EntryIDSet> cursor = index.openBulkCursor(txn))
     {
       while (cursor.next())
       {
@@ -985,8 +992,13 @@
     }
   }
 
-  /** This class reports progress of the verify job at fixed intervals. */
-  private final class ProgressTask extends TimerTask
+  /**
+   * This class reports progress of the verify job at fixed intervals.
+   * <p>
+   * Visible, with its constructor, to the test pinning the class of the reads it makes to size
+   * that report: they belong to the walk they measure rather than to a client operation (#877).
+   */
+  final class ProgressTask extends TimerTask
   {
     /** The total number of records to process. */
     private long totalCount;
@@ -1001,7 +1013,7 @@
      * through indexes or the entries.
      * @throws StorageRuntimeException An error occurred while accessing the storage.
      */
-    private ProgressTask(boolean indexIterator, ReadableTransaction txn) throws StorageRuntimeException
+    ProgressTask(boolean indexIterator, ReadableTransaction txn) throws StorageRuntimeException
     {
       previousTime = System.currentTimeMillis();
 
@@ -1031,7 +1043,12 @@
       }
       else
       {
-        totalCount = rootContainer.getEntryContainer(verifyConfig.getBaseDN()).getNumberOfEntriesInBaseDN0(txn);
+        // Part of this walk, like the counts of the branch above: it sizes a verify of the whole
+        // backend, and the branch above is only reached by "verify-index --clean" - a plain
+        // verify-index, with or without an index named, comes here. Read as a client operation it
+        // would take the bound of one on a storage engine that bounds a statement, which on a
+        // backend large enough aborts the verify before its first record (#877).
+        totalCount = rootContainer.getEntryContainer(verifyConfig.getBaseDN()).getNumberOfEntriesInBaseDN0(txn, true);
       }
     }
 

--
Gitblit v1.10.0