From e252f71792f6299a820324b725b585c644af5369 Mon Sep 17 00:00:00 2001
From: Roland Praml
Date: Fri, 2 Mar 2018 00:04:56 +0100
Subject: [PATCH] ENH: Optimized binary form for UUID (Type 1) (#1308)
* ENH: Optimized binary form for UUID (Type 1)
* added missing parameter in test
---
.../java/io/ebean/config/ServerConfig.java | 31 +++++-
.../server/core/BasicTypeConverter.java | 4 +-
.../server/core/RelationalQueryRequest.java | 5 +-
.../server/query/DefaultSqlRow.java | 6 +-
.../server/type/DefaultTypeManager.java | 2 +-
.../server/type/ScalarTypeUUIDBase.java | 2 +-
.../server/type/ScalarTypeUUIDBinary.java | 105 ++++++++++++------
.../server/query/DefaultSqlRowTest.java | 2 +-
.../TestScalarTypeUUIDBinaryConversion.java | 16 ++-
9 files changed, 126 insertions(+), 47 deletions(-)
diff --git a/src/main/java/io/ebean/config/ServerConfig.java b/src/main/java/io/ebean/config/ServerConfig.java
index 28211d1a5..69a771ef3 100644
--- a/src/main/java/io/ebean/config/ServerConfig.java
+++ b/src/main/java/io/ebean/config/ServerConfig.java
@@ -3033,37 +3033,51 @@ public class ServerConfig {
return dataSource;
}
+ /**
/**
* Specify how UUID is stored.
*/
public enum DbUuid {
+
/**
* Store using native UUID in H2 and Postgres and otherwise fallback to VARCHAR(40).
*/
- AUTO_VARCHAR(true, false),
+ AUTO_VARCHAR(true, false, false),
/**
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16).
*/
- AUTO_BINARY(true, true),
+ AUTO_BINARY(true, true, false),
+
+ /**
+ * Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16) with optimized packing.
+ */
+ AUTO_BINARY_OPTIMIZED(true, true, true),
/**
* Store using DB VARCHAR(40).
*/
- VARCHAR(false, false),
+ VARCHAR(false, false, false),
/**
* Store using DB BINARY(16).
*/
- BINARY(false, true);
+ BINARY(false, true, false),
+
+ /**
+ * Store using DB BINARY(16).
+ */
+ BINARY_OPTIMIZED(false, true, true);
boolean nativeType;
boolean binary;
+ boolean binaryOptimized;
- DbUuid(boolean nativeType, boolean binary) {
+ DbUuid(boolean nativeType, boolean binary, boolean binaryOptimized) {
this.nativeType = nativeType;
this.binary = binary;
+ this.binaryOptimized = binaryOptimized;
}
/**
@@ -3079,5 +3093,12 @@ public class ServerConfig {
public boolean useBinary() {
return binary;
}
+
+ /**
+ * Return true, if optimized packing should be used.
+ */
+ public boolean useBinaryOptimized() {
+ return binaryOptimized;
+ }
}
}
diff --git a/src/main/java/io/ebeaninternal/server/core/BasicTypeConverter.java b/src/main/java/io/ebeaninternal/server/core/BasicTypeConverter.java
index 34beed7df..5a82baf0a 100644
--- a/src/main/java/io/ebeaninternal/server/core/BasicTypeConverter.java
+++ b/src/main/java/io/ebeaninternal/server/core/BasicTypeConverter.java
@@ -176,7 +176,7 @@ public final class BasicTypeConverter implements Serializable {
/**
* Convert the value to a UUID.
*/
- public static UUID toUUID(Object value) {
+ public static UUID toUUID(Object value, boolean optimizedBinary) {
if (value == null) {
return null;
@@ -188,7 +188,7 @@ public final class BasicTypeConverter implements Serializable {
return UUID.fromString((String) value);
}
if (value instanceof byte[]) {
- return ScalarTypeUUIDBinary.convertFromBytes((byte[]) value);
+ return ScalarTypeUUIDBinary.convertFromBytes((byte[]) value, optimizedBinary);
}
return UUID.fromString(value.toString());
}
diff --git a/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java b/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java
index 9461142f0..7f8ab2e8a 100644
--- a/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java
+++ b/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java
@@ -5,6 +5,7 @@ import io.ebean.SqlQuery;
import io.ebean.SqlRow;
import io.ebean.Transaction;
import io.ebean.util.JdbcClose;
+import io.ebean.config.ServerConfig;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlQuery;
@@ -173,8 +174,8 @@ public final class RelationalQueryRequest {
* Read and return the next SqlRow.
*/
public SqlRow createNewRow(String dbTrueValue) throws SQLException {
-
- SqlRow sqlRow = new DefaultSqlRow(estimateCapacity, 0.75f, dbTrueValue);
+ ServerConfig.DbUuid dbUuid = ebeanServer.getServerConfig().getDbTypeConfig().getDbUuid();
+ SqlRow sqlRow = new DefaultSqlRow(estimateCapacity, 0.75f, dbTrueValue, dbUuid.useBinaryOptimized());
int index = 0;
for (String propertyName : propertyNames) {
diff --git a/src/main/java/io/ebeaninternal/server/query/DefaultSqlRow.java b/src/main/java/io/ebeaninternal/server/query/DefaultSqlRow.java
index df7fe5d31..0ee1e1525 100644
--- a/src/main/java/io/ebeaninternal/server/query/DefaultSqlRow.java
+++ b/src/main/java/io/ebeaninternal/server/query/DefaultSqlRow.java
@@ -34,6 +34,7 @@ public class DefaultSqlRow implements SqlRow {
private static final long serialVersionUID = -3120927797041336242L;
private final String dbTrueValue;
+ private final boolean optimizedBinaryUUID;
/**
* The underlying map of property data.
@@ -50,9 +51,10 @@ public class DefaultSqlRow implements SqlRow {
* this map reaches its threshold (initialCapacity * loadFactor).
*
*/
- public DefaultSqlRow(int initialCapacity, float loadFactor, String dbTrueValue) {
+ public DefaultSqlRow(int initialCapacity, float loadFactor, String dbTrueValue, boolean optimizedBinaryUUID) {
this.map = new LinkedHashMap<>(initialCapacity, loadFactor);
this.dbTrueValue = dbTrueValue;
+ this.optimizedBinaryUUID = optimizedBinaryUUID;
}
@Override
@@ -93,7 +95,7 @@ public class DefaultSqlRow implements SqlRow {
@Override
public UUID getUUID(String name) {
- return BasicTypeConverter.toUUID(get(name));
+ return BasicTypeConverter.toUUID(get(name), optimizedBinaryUUID);
}
@Override
diff --git a/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java
index e0f2c6ac5..89b87bf35 100644
--- a/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java
+++ b/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java
@@ -931,7 +931,7 @@ public final class DefaultTypeManager implements TypeManager {
typeMap.put(UUID.class, new ScalarTypeUUIDNative());
} else {
// Store UUID as binary(16) or varchar(40)
- ScalarType> uuidType = dbUuid.useBinary() ? new ScalarTypeUUIDBinary() : new ScalarTypeUUIDVarchar();
+ ScalarType> uuidType = dbUuid.useBinary() ? new ScalarTypeUUIDBinary(dbUuid.useBinaryOptimized()) : new ScalarTypeUUIDVarchar();
typeMap.put(UUID.class, uuidType);
}
diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeUUIDBase.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeUUIDBase.java
index 1dfdfcc0f..ba14a710f 100644
--- a/src/main/java/io/ebeaninternal/server/type/ScalarTypeUUIDBase.java
+++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeUUIDBase.java
@@ -57,7 +57,7 @@ public abstract class ScalarTypeUUIDBase extends ScalarTypeBase implements
@Override
public UUID toBeanType(Object value) {
- return BasicTypeConverter.toUUID(value);
+ return BasicTypeConverter.toUUID(value, false);
}
@Override
diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeUUIDBinary.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeUUIDBinary.java
index 29e318120..19425f8a7 100644
--- a/src/main/java/io/ebeaninternal/server/type/ScalarTypeUUIDBinary.java
+++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeUUIDBinary.java
@@ -2,19 +2,18 @@ package io.ebeaninternal.server.type;
import io.ebeaninternal.server.core.BasicTypeConverter;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
import java.sql.SQLException;
import java.sql.Types;
import java.util.UUID;
public class ScalarTypeUUIDBinary extends ScalarTypeUUIDBase {
- protected ScalarTypeUUIDBinary() {
+ private final boolean optimized;
+
+
+ protected ScalarTypeUUIDBinary(boolean optimized) {
super(false, Types.BINARY);
+ this.optimized = optimized;
}
@Override
@@ -24,15 +23,15 @@ public class ScalarTypeUUIDBinary extends ScalarTypeUUIDBase {
@Override
public Object toJdbcType(Object value) {
- return convertToBytes(value);
+ return convertToBytes(value, optimized);
}
@Override
public UUID toBeanType(Object value) {
if (value instanceof byte[]) {
- return convertFromBytes((byte[]) value);
+ return convertFromBytes((byte[]) value, optimized);
} else {
- return BasicTypeConverter.toUUID(value);
+ return BasicTypeConverter.toUUID(value, optimized);
}
}
@@ -41,7 +40,7 @@ public class ScalarTypeUUIDBinary extends ScalarTypeUUIDBase {
if (value == null) {
b.setNull(Types.BINARY);
} else {
- b.setBytes(convertToBytes(value));
+ b.setBytes(convertToBytes(value, optimized));
}
}
@@ -51,14 +50,14 @@ public class ScalarTypeUUIDBinary extends ScalarTypeUUIDBase {
if (bytes == null) {
return null;
} else {
- return convertFromBytes(bytes);
+ return convertFromBytes(bytes, optimized);
}
}
/**
* Convert from byte[] to UUID.
*/
- public static UUID convertFromBytes(byte[] bytes) {
+ public static UUID convertFromBytes(byte[] bytes, boolean optimized) {
int usableBytes = Math.min(bytes.length, 16);
@@ -70,36 +69,80 @@ public class ScalarTypeUUIDBinary extends ScalarTypeUUIDBase {
barr[i] = bytes[j];
}
- ByteArrayInputStream bais = new ByteArrayInputStream(barr);
- DataInputStream inputStream = new DataInputStream(bais);
-
- try {
- long msb = inputStream.readLong();
- long lsb = inputStream.readLong();
- return new UUID(msb, lsb);
-
- } catch (IOException e) {
- throw new RuntimeException("Not Expecting this", e);
+ long msb;
+ if (optimized) {
+ msb = ((long)barr[4] << 56) + // XXXXXXXX-____-____-...
+ ((long)(barr[5] & 255) << 48) + // -> put at end 4..7 of buf
+ ((long)(barr[6] & 255) << 40) +
+ ((long)(barr[7] & 255) << 32) +
+ ((long)(barr[2] & 255) << 24) + // ________-XXXX-____-...
+ ((barr[3] & 255) << 16) + // put at 2..3 in buf
+ ((barr[0] & 255) << 8) + // ________-____-XXXX-...
+ ((barr[1] & 255) << 0); // put at 0..1 in buf
+ } else {
+ msb = ((long)barr[0] << 56) + // XXXXXXXX-____-____-...
+ ((long)(barr[1] & 255) << 48) +
+ ((long)(barr[2] & 255) << 40) +
+ ((long)(barr[3] & 255) << 32) +
+ ((long)(barr[4] & 255) << 24) + // ________-XXXX-____-...
+ ((barr[5] & 255) << 16) +
+ ((barr[6] & 255) << 8) + // ________-____-XXXX-...
+ ((barr[7] & 255) << 0);
}
+ long lsb = ((long)barr[8] << 56) +
+ ((long)(barr[9] & 255) << 48) +
+ ((long)(barr[10] & 255) << 40) +
+ ((long)(barr[11] & 255) << 32) +
+ ((long)(barr[12] & 255) << 24) +
+ ((barr[13] & 255) << 16) +
+ ((barr[14] & 255) << 8) +
+ ((barr[15] & 255) << 0);
+
+ return new UUID(msb, lsb);
}
/**
* Convert from UUID to byte[].
*/
- public static byte[] convertToBytes(Object value) {
+ public static byte[] convertToBytes(Object value, boolean optimized) {
+
UUID uuid = (UUID) value;
- ByteArrayOutputStream baos = new ByteArrayOutputStream(16);
- DataOutputStream outputStream = new DataOutputStream(baos);
+ byte[] ret = new byte[16];
+ long l = uuid.getMostSignificantBits();
- try {
- outputStream.writeLong(uuid.getMostSignificantBits());
- outputStream.writeLong(uuid.getLeastSignificantBits());
- } catch (IOException e) {
- throw new RuntimeException("Not Expecting this", e);
+ if (optimized) {
+ ret[0] = (byte) (l >>> 8); // was 6/7
+ ret[1] = (byte) (l >>> 0);
+
+ ret[2] = (byte) (l >>> 24); // was 4/5
+ ret[3] = (byte) (l >>> 16);
+
+ ret[4] = (byte) (l >>> 56); // was 0..3
+ ret[5] = (byte) (l >>> 48);
+ ret[6] = (byte) (l >>> 40);
+ ret[7] = (byte) (l >>> 32);
+ } else {
+ ret[0] = (byte) (l >>> 56);
+ ret[1] = (byte) (l >>> 48);
+ ret[2] = (byte) (l >>> 40);
+ ret[3] = (byte) (l >>> 32);
+ ret[4] = (byte) (l >>> 24);
+ ret[5] = (byte) (l >>> 16);
+ ret[6] = (byte) (l >>> 8);
+ ret[7] = (byte) (l >>> 0);
}
+ l = uuid.getLeastSignificantBits();
+ ret[8] = (byte) (l >>> 56);
+ ret[9] = (byte) (l >>> 48);
+ ret[10] = (byte) (l >>> 40);
+ ret[11] = (byte) (l >>> 32);
+ ret[12] = (byte) (l >>> 24);
+ ret[13] = (byte) (l >>> 16);
+ ret[14] = (byte) (l >>> 8);
+ ret[15] = (byte) (l >>> 0);
- return baos.toByteArray();
+ return ret;
}
}
diff --git a/src/test/java/io/ebeaninternal/server/query/DefaultSqlRowTest.java b/src/test/java/io/ebeaninternal/server/query/DefaultSqlRowTest.java
index 9888fa842..3ceb37305 100644
--- a/src/test/java/io/ebeaninternal/server/query/DefaultSqlRowTest.java
+++ b/src/test/java/io/ebeaninternal/server/query/DefaultSqlRowTest.java
@@ -9,7 +9,7 @@ public class DefaultSqlRowTest {
@Test
public void test_keyConversion() {
- DefaultSqlRow row = new DefaultSqlRow(16, 0.5f, "T");
+ DefaultSqlRow row = new DefaultSqlRow(16, 0.5f, "T", false);
row.put("Foo", "hello");
assertThat(row.containsKey("foo")).isTrue();
diff --git a/src/test/java/io/ebeaninternal/server/type/TestScalarTypeUUIDBinaryConversion.java b/src/test/java/io/ebeaninternal/server/type/TestScalarTypeUUIDBinaryConversion.java
index 29640283e..d5e5bd109 100644
--- a/src/test/java/io/ebeaninternal/server/type/TestScalarTypeUUIDBinaryConversion.java
+++ b/src/test/java/io/ebeaninternal/server/type/TestScalarTypeUUIDBinaryConversion.java
@@ -12,10 +12,22 @@ public class TestScalarTypeUUIDBinaryConversion {
UUID id = UUID.randomUUID();
- byte[] bytes = ScalarTypeUUIDBinary.convertToBytes(id);
+ byte[] bytes = ScalarTypeUUIDBinary.convertToBytes(id, false);
Assert.assertEquals(16, bytes.length);
- UUID id2 = ScalarTypeUUIDBinary.convertFromBytes(bytes);
+ UUID id2 = ScalarTypeUUIDBinary.convertFromBytes(bytes, false);
+ Assert.assertEquals(id, id2);
+ }
+
+ @Test
+ public void testConversionOptimized() {
+
+ UUID id = UUID.randomUUID();
+
+ byte[] bytes = ScalarTypeUUIDBinary.convertToBytes(id, true);
+ Assert.assertEquals(16, bytes.length);
+
+ UUID id2 = ScalarTypeUUIDBinary.convertFromBytes(bytes, true);
Assert.assertEquals(id, id2);
}