From 006b32f740cb9d38719eeb59bc8519101799bb16 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Tue, 04 Feb 2025 07:28:20 +0000
Subject: [PATCH] [#466] FIX compatibility jdbc backend: Postgres, Oracle, MySQL, MSSQL (#474)

---
 opendj-server-legacy/pom.xml                                                                     |    3 -
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/OnDiskMergeImporter.java |    2 
 opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/OracleTestCase.java           |    3 +
 opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/Storage.java                  |   88 +++++++++++++++++++++++++++++++------------
 4 files changed, 66 insertions(+), 30 deletions(-)

diff --git a/opendj-server-legacy/pom.xml b/opendj-server-legacy/pom.xml
index aabad0c..6d9d79f 100644
--- a/opendj-server-legacy/pom.xml
+++ b/opendj-server-legacy/pom.xml
@@ -305,19 +305,16 @@
       <groupId>com.mysql</groupId>
       <artifactId>mysql-connector-j</artifactId>
       <version>9.2.0</version>
-      <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>com.oracle.database.jdbc</groupId>
       <artifactId>ojdbc8</artifactId>
       <version>23.6.0.24.10</version>
-      <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>com.microsoft.sqlserver</groupId>
       <artifactId>mssql-jdbc</artifactId>
       <version>12.8.1.jre8</version>
-      <scope>test</scope>
     </dependency>
   </dependencies>
 
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/Storage.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/Storage.java
index 0c3a4fe..8e34eaf 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/Storage.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/Storage.java
@@ -34,7 +34,6 @@
 import org.opends.server.types.RestoreConfig;
 import org.opends.server.util.BackupManager;
 
-import java.math.BigInteger;
 import java.security.MessageDigest;
 import java.sql.*;
 import java.util.*;
@@ -89,8 +88,7 @@
 	}
 
     Connection getConnection() throws Exception {
-		final Connection con=CachedConnection.getConnection(config.getDBDirectory());
-		return con;
+		return CachedConnection.getConnection(config.getDBDirectory());
 	}
 
 
@@ -114,16 +112,17 @@
 		storageStatus = StorageStatus.lockedDown(LocalizableMessage.raw("closed"));
 	}
 
-	LoadingCache<TreeName,String> tree2table= CacheBuilder.newBuilder()
+	final LoadingCache<TreeName,String> tree2table= CacheBuilder.newBuilder()
 			.build(new CacheLoader<TreeName, String>() {
 				@Override
 				public String load(TreeName treeName) throws Exception {
 					final MessageDigest md = MessageDigest.getInstance("SHA-224");
 					final byte[] messageDigest = md.digest(treeName.toString().getBytes());
-					final BigInteger no = new BigInteger(1, messageDigest);
-					String hashtext = no.toString(16);
-					while (hashtext.length() < 32) {
-						hashtext = "0" + hashtext;
+					final StringBuilder hashtext = new StringBuilder();
+					for (byte b : messageDigest) {
+						String hex = Integer.toHexString(0xff & b);
+						if (hex.length() == 1) hashtext.append('0');
+						hashtext.append(hex);
 					}
 					return "opendj_"+hashtext;
 				}
@@ -195,6 +194,31 @@
 		}
 	}
 
+	final static byte[] NULL=new byte[]{(byte)0};
+
+	static byte[] real2db(byte[] real) {
+		return real.length==0?NULL:real;
+	}
+	static byte[] db2real(byte[] db) {
+		return Arrays.equals(NULL,db)?new byte[0]:db;
+	}
+
+	final LoadingCache<byte[],String> key2hash= CacheBuilder.newBuilder()
+			.maximumSize(32000)
+			.build(new CacheLoader<byte[], String>() {
+				@Override
+				public String load(byte[] key) throws Exception {
+					final MessageDigest md = MessageDigest.getInstance("SHA-512");
+					final byte[] messageDigest = md.digest(key);
+					final StringBuilder hashtext = new StringBuilder();
+					for (byte b : messageDigest) {
+						String hex = Integer.toHexString(0xff & b);
+						if (hex.length() == 1) hashtext.append('0');
+						hashtext.append(hex);
+					}
+					return hashtext.toString();
+				}
+			});
 	private class ReadableTransactionImpl implements ReadableTransaction {
 		final Connection con;
 		boolean isReadOnly=true;
@@ -205,12 +229,13 @@
 
 		@Override
 		public ByteString read(TreeName treeName, ByteSequence key) {
-			try (final PreparedStatement statement=con.prepareStatement("select v from "+getTableName(treeName)+" where k=?")){
-				statement.setBytes(1,key.toByteArray());
+			try (final PreparedStatement statement=con.prepareStatement("select v from "+getTableName(treeName)+" where h=? and k=?")){
+				statement.setString(1,key2hash.get(key.toByteArray()));
+				statement.setBytes(2,real2db(key.toByteArray()));
 				try(ResultSet rc=executeResultSet(statement)) {
 					return rc.next() ? ByteString.wrap(rc.getBytes("v")) : null;
 				}
-			}catch (SQLException e) {
+			}catch (SQLException|ExecutionException e) {
 				throw new StorageRuntimeException(e);
 			}
 		}
@@ -231,19 +256,19 @@
 		}
 	}
 	private final class WriteableTransactionTransactionImpl extends ReadableTransactionImpl implements WriteableTransaction {
-		
+
 		public WriteableTransactionTransactionImpl(Connection con) {
 			super(con);
 			if (!accessMode.isWriteable()) {
 				throw new ReadOnlyStorageException();
 			}
-			isReadOnly=false;
+			isReadOnly = false;
 		}
 
 		boolean isExistsTable(TreeName treeName) {
-			try(final ResultSet rs = con.getMetaData().getTables(null, null, tree2table.get(treeName), new String[]{"TABLE"})) {
+			try (final ResultSet rs = con.getMetaData().getTables(null, null, null, new String[]{"TABLE"})) {
 				while (rs.next()) {
-					if (tree2table.get(treeName).equals(rs.getString("TABLE_NAME"))){
+					if (tree2table.get(treeName).equalsIgnoreCase(rs.getString("TABLE_NAME"))) {
 						return true;
 					}
 				}
@@ -253,10 +278,21 @@
 			return false;
 		}
 
+		String getTableDialect() {
+			if (((CachedConnection) con).parent.getClass().getName().contains("oracle")) {
+				return "h char(128),k raw(2000),v blob,primary key(h,k)";
+			}else if (((CachedConnection) con).parent.getClass().getName().contains("mysql")) {
+				return "h char(128),k tinyblob,v longblob,primary key(h,k(255))";
+			}else if (((CachedConnection) con).parent.getClass().getName().contains("microsoft")) {
+				return "h char(128),k varbinary(max),v image,primary key(h)";
+			}
+			return "h char(128),k bytea,v bytea,primary key(h,k)";
+		}
+
 		@Override
 		public void openTree(TreeName treeName, boolean createOnDemand) {
 			if (createOnDemand && !isExistsTable(treeName)) {
-				try (final PreparedStatement statement=con.prepareStatement("create table "+getTableName(treeName)+" (k bytea primary key,v bytea)")){
+				try (final PreparedStatement statement=con.prepareStatement("create table "+getTableName(treeName)+" ("+getTableDialect()+")")){
 					execute(statement);
 					con.commit();
 				}catch (SQLException e) {
@@ -289,11 +325,12 @@
 		@Override
 		public void put(TreeName treeName, ByteSequence key, ByteSequence value) {
 			delete(treeName,key);
-			try (final PreparedStatement statement=con.prepareStatement("insert into "+getTableName(treeName)+" (k,v) values(?,?) ")){
-				statement.setBytes(1,key.toByteArray());
-				statement.setBytes(2,value.toByteArray());
+			try (final PreparedStatement statement=con.prepareStatement("insert into "+getTableName(treeName)+" (h,k,v) values(?,?,?) ")){
+				statement.setString(1,key2hash.get(key.toByteArray()));
+				statement.setBytes(2,real2db(key.toByteArray()));
+				statement.setBytes(3,value.toByteArray());
 				execute(statement);
-			}catch (SQLException e) {
+			}catch (SQLException|ExecutionException e) {
 				throw new StorageRuntimeException(e);
 			}
 		}
@@ -317,10 +354,11 @@
 
 		@Override
 		public boolean delete(TreeName treeName, ByteSequence key) {
-			try (final PreparedStatement statement=con.prepareStatement("delete from "+getTableName(treeName)+" where k=?")){
-				statement.setBytes(1,key.toByteArray());
+			try (final PreparedStatement statement=con.prepareStatement("delete from "+getTableName(treeName)+" where h=? and k=?")){
+				statement.setString(1,key2hash.get(key.toByteArray()));
+				statement.setBytes(2,real2db(key.toByteArray()));
 				execute(statement);
-			}catch (SQLException e) {
+			}catch (SQLException|ExecutionException e) {
 				throw new StorageRuntimeException(e);
 			}
 			return true;
@@ -337,7 +375,7 @@
 			this.treeName=treeName;
 			this.isReadOnly=isReadOnly;
 			try {
-				statement=con.prepareStatement("select k,v from "+getTableName(treeName)+" order by k",
+				statement=con.prepareStatement("select h,k,v from "+getTableName(treeName)+" order by k",
 						isReadOnly?ResultSet.TYPE_SCROLL_INSENSITIVE:ResultSet.TYPE_SCROLL_SENSITIVE,
 						isReadOnly?ResultSet.CONCUR_READ_ONLY:ResultSet.CONCUR_UPDATABLE);
 				rc=executeResultSet(statement);
@@ -370,7 +408,7 @@
 				throw new NoSuchElementException();
 			}
 			try{
-				return ByteString.wrap(rc.getBytes("k"));
+				return ByteString.wrap(db2real(rc.getBytes("k")));
 			}catch (SQLException e) {
 				throw new StorageRuntimeException(e);
 			}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/OnDiskMergeImporter.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/OnDiskMergeImporter.java
index 41f946b..c87e13d 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/OnDiskMergeImporter.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/OnDiskMergeImporter.java
@@ -3036,7 +3036,7 @@
 
       void writeByteSequence(int position, ByteSequence data)
       {
-        buffer.position(position);
+        ((ByteBuffer)buffer).position(position);
         data.copyTo(buffer);
       }
 
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/OracleTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/OracleTestCase.java
index c2232aa..d1fec01 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/OracleTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/OracleTestCase.java
@@ -30,7 +30,8 @@
                 .withExposedPorts(1521)
                 .withUsername("opendj")
                 .withPassword("password")
-                .withDatabaseName("database_name");
+                .withDatabaseName("database_name")
+                .withStartupAttempts(10);
     }
 
     @Override

--
Gitblit v1.10.0