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); }