From 774272578900c27258f497fd01da347de49e430f Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Tue, 22 Sep 2026 09:09:49 +0000
Subject: [PATCH] [#930] Build the names a JDBC backend gives its own trees once, from the id it was created with (#1007)
---
opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/JDBCStorage.java | 120 ++++++++++++++++++++++++++++++++++++++++++++++-------------
1 files changed, 93 insertions(+), 27 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/JDBCStorage.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/JDBCStorage.java
index fad5fa3..caacc46 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/JDBCStorage.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/JDBCStorage.java
@@ -1205,11 +1205,13 @@
* The catalog is per backend and named after the backend id alone: a process that has opened
* nothing can still find its table, and backends sharing one database URL - which nothing
* forbids (#873) - never name each other's trees. The id goes in escaped, for the reason {@link
- * #escapedBackendId} states: a name that does not survive being read back is a table of this
- * backend that its own clear cannot recognize.
+ * OwnNames#escapedBackendId} states: a name that does not survive being read back is a table of
+ * this backend that its own clear cannot recognize.
+ * <p>
+ * Built once and remembered; see {@link OwnNames}.
*/
TreeName getCatalogTree() {
- return new TreeName(CATALOG_BASE_DN, escapedBackendId());
+ return ownNames().catalogTree;
}
/**
@@ -3287,6 +3289,8 @@
leftovers.unreadable.addAll(standing);
return leftovers;
}
+ // normalized once for the whole scan and not per table: see isOwnTree()
+ final Set<String> ownBaseDNs=ownBaseDNs();
for (final String tableName : standing) {
final TreeName stamp;
try {
@@ -3306,7 +3310,7 @@
}
if (stamp==null) {
leftovers.unattributed.add(tableName);
- } else if (isOwnTree(stamp)) {
+ } else if (isOwnTree(stamp, ownBaseDNs)) {
leftovers.ours.add(tableName+" ("+stamp+")");
}
}
@@ -3347,23 +3351,75 @@
* here for the reason {@link #SHARED_COMPRESSED_SCHEMA_TREES} is: the prefix is built by a private
* method of {@code PersistentCompressedSchema}, escapes and all. A table stamped with one of these
* carries this backend's id in plain text, so a clear that finds one standing can say whose it is.
+ * <p>
+ * Built once and remembered; see {@link OwnNames}.
*/
- private String ownCompressedSchemaBaseDN() {
- return SHARED_COMPRESSED_SCHEMA_BASE_DN+"_"+escapedBackendId();
+ String ownCompressedSchemaBaseDN() {
+ return ownNames().compressedSchemaBaseDN;
}
/**
- * The backend id as one component of a tree name. A tree name is {@code /<base DN>/<id>} and is
- * read back by splitting on its slashes ({@code TreeName.valueOf}), so an id carrying one of them
- * would name a tree that parses into another tree than it was built from - and a table is stamped
- * with that name (#866), so a clear reading the stamp of a table of this backend's own would then
- * fail to recognize it and pass it over in silence. The escape is the one {@code
- * PersistentCompressedSchema} spells its own prefix with, percent first so that the escape of the
- * slash cannot be produced twice, and it leaves an id of the ordinary shape exactly as it is -
- * which is what keeps the table names of an installation unchanged.
+ * The names this backend gives the trees that are its own rather than a base DN's: the escaped
+ * id of {@link #escapedBackendId}, the catalog tree of {@link #getCatalogTree} and the base DN of
+ * {@link #ownCompressedSchemaBaseDN}. Held in one object so that the three can never come from
+ * two different ids.
+ * <p>
+ * Read from the configuration once rather than per call, for the reason {@link
+ * #poolConnectionString} is read once: {@code applyConfigurationChange()} replaces {@code config}
+ * whole, while the tables of this storage - the catalog table among them - stand under the id
+ * they were created with, and a name read again from a configuration that has moved would leave
+ * this storage naming a catalog nothing has ever written and its own tables attributed to
+ * nobody. The configuration framework holds backend-id read-only - "The backend ID may not be
+ * altered after the backend is created in the server" - so no change made through it renames a
+ * live backend, which is what makes reading the id once correct in the first place.
+ * <p>
+ * That it also takes the escape and the allocation off every enrolment, off every clear and off
+ * every table of a clear's leftover scan (#930) is the smaller half of it: every one of those
+ * call sites is already paying a round trip to the database.
*/
- private String escapedBackendId() {
- return config.getBackendId().replace("%", "%25").replace("/", "%2F");
+ private static final class OwnNames {
+ /**
+ * The backend id as one component of a tree name. A tree name is {@code /<base DN>/<id>} and is
+ * read back by splitting on its slashes ({@code TreeName.valueOf}), so an id carrying one of
+ * them would name a tree that parses into another tree than it was built from - and a table is
+ * stamped with that name (#866), so a clear reading the stamp of a table of this backend's own
+ * would then fail to recognize it and pass it over in silence. The escape is the one {@code
+ * PersistentCompressedSchema} spells its own prefix with, percent first so that the escape of
+ * the slash cannot be produced twice, and it leaves an id of the ordinary shape exactly as it
+ * is - which is what keeps the table names of an installation unchanged.
+ */
+ final String escapedBackendId;
+ final TreeName catalogTree;
+ final String compressedSchemaBaseDN;
+
+ OwnNames(String backendId) {
+ escapedBackendId=backendId.replace("%", "%25").replace("/", "%2F");
+ catalogTree=new TreeName(CATALOG_BASE_DN, escapedBackendId);
+ compressedSchemaBaseDN=SHARED_COMPRESSED_SCHEMA_BASE_DN+"_"+escapedBackendId;
+ }
+ }
+
+ private volatile OwnNames ownNames;
+
+ /**
+ * The names above, built at the first call that needs one, and not in the constructor: a storage
+ * is constructed by callers that go on to name no tree of it at all - the bounds of a statement
+ * are asked of one whose configuration carries no backend id whatsoever in the tests of {@code
+ * JDBCStatementBoundTestCase} - and a construction reading the id would fail there, where today
+ * nothing reads it.
+ * <p>
+ * Two callers arriving at once may each build one, and the names of both are the same names.
+ * Every field of {@link OwnNames} is final, so a caller reading the reference reads the names
+ * whole and not half-built.
+ */
+ private OwnNames ownNames() {
+ final OwnNames built=ownNames;
+ if (built!=null) {
+ return built;
+ }
+ final OwnNames names=new OwnNames(config.getBackendId());
+ ownNames=names;
+ return names;
}
/**
@@ -3374,23 +3430,33 @@
* backend id (#873) and so belongs to this backend as plainly as any tree of a base DN it serves -
* where the legacy pair, named from a literal, belongs to no backend in particular and is reported
* by nobody.
+ * <p>
+ * The base DNs are handed in rather than read here: they are the same for every table of one
+ * scan, and normalizing a DN builds its string from every RDN of it ({@code
+ * DN.toNormalizedUrlSafeString} memoizes nothing), which is a cost the caller pays once instead
+ * of once per table.
*/
- private boolean isOwnTree(TreeName treeName) {
- if (getCatalogTree().equals(treeName) || ownCompressedSchemaBaseDN().equals(treeName.getBaseDN())) {
- return true;
- }
+ private boolean isOwnTree(TreeName treeName, Set<String> ownBaseDNs) {
+ return getCatalogTree().equals(treeName)
+ || ownCompressedSchemaBaseDN().equals(treeName.getBaseDN())
+ || ownBaseDNs.contains(treeName.getBaseDN());
+ }
+
+ /**
+ * The base DNs this backend serves, in the form the trees of an entry container are named after:
+ * every one of them is named from the normalized base DN, which is what {@code EntryContainer}
+ * builds its tree names from.
+ */
+ private Set<String> ownBaseDNs() {
final SortedSet<DN> baseDNs=config.getBaseDN();
if (baseDNs==null) {
- return false;
+ return Collections.emptySet();
}
+ final Set<String> normalized=new HashSet<>();
for (final DN baseDN : baseDNs) {
- // every tree of an entry container is named after the normalized form of its base DN,
- // which is what EntryContainer builds its tree names from
- if (treeName.getBaseDN().equals(baseDN.toNormalizedUrlSafeString())) {
- return true;
- }
+ normalized.add(baseDN.toNormalizedUrlSafeString());
}
- return false;
+ return normalized;
}
/**
--
Gitblit v1.10.0