From e252f71792f6299a820324b725b585c644af5369 Mon Sep 17 00:00:00 2001
From: Roland Praml
Date: Fri, 2 Mar 2018 00:04:56 +0100
Subject: [PATCH 1/7] 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);
}
From 56a742ad7037e2a985638ea4a6d5510545955ecc Mon Sep 17 00:00:00 2001
From: Roland Praml
Date: Fri, 2 Mar 2018 00:09:21 +0100
Subject: [PATCH 2/7] NEW: UUID version 1 generator (#1309)
* NEW: Uuid Type 1 generator
* Remove parameter (from other PR)
* FIX: Mac detection
* Have to use nodeId in generation
---
.gitignore | 1 +
.../java/io/ebean/config/ServerConfig.java | 48 +++++
.../deploy/meta/DeployBeanDescriptor.java | 22 +-
.../server/idgen/UuidV1IdGenerator.java | 197 ++++++++++++++++++
.../server/idgen/UuidV1RndIdGenerator.java | 179 ++++++++++++++++
...dGenerator.java => UuidV4IdGenerator.java} | 8 +-
.../server/idgen/TestUuidGenerator.java | 141 +++++++++++++
7 files changed, 591 insertions(+), 5 deletions(-)
create mode 100644 src/main/java/io/ebeaninternal/server/idgen/UuidV1IdGenerator.java
create mode 100644 src/main/java/io/ebeaninternal/server/idgen/UuidV1RndIdGenerator.java
rename src/main/java/io/ebeaninternal/server/idgen/{UuidIdGenerator.java => UuidV4IdGenerator.java} (68%)
create mode 100644 src/test/java/io/ebeaninternal/server/idgen/TestUuidGenerator.java
diff --git a/.gitignore b/.gitignore
index 0d96b2f95..1b88dd830 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@ profiling/
*.ipr
*.iws
.idea/
+*uuid.state
\ No newline at end of file
diff --git a/src/main/java/io/ebean/config/ServerConfig.java b/src/main/java/io/ebean/config/ServerConfig.java
index 69a771ef3..0b42551b1 100644
--- a/src/main/java/io/ebean/config/ServerConfig.java
+++ b/src/main/java/io/ebean/config/ServerConfig.java
@@ -372,6 +372,16 @@ public class ServerConfig {
*/
private DbTypeConfig dbTypeConfig = new DbTypeConfig();
+ /**
+ * The UUID version to use.
+ */
+ private UuidVersion uuidVersion = UuidVersion.VERSION4;
+
+ /**
+ * The UUID state file (for Version 1 UUIDs).
+ */
+ private String uuidStateFile = "ebean-uuid.state";
+
private List idGenerators = new ArrayList<>();
private List findControllers = new ArrayList<>();
private List persistControllers = new ArrayList<>();
@@ -1891,6 +1901,34 @@ public class ServerConfig {
this.dbTypeConfig.setDbUuid(dbUuid);
}
+ /**
+ * Returns the UUID version mode.
+ */
+ public UuidVersion getUuidVersion() {
+ return uuidVersion;
+ }
+
+ /**
+ * Sets the UUID version mode.
+ */
+ public void setUuidVersion(UuidVersion uuidVersion) {
+ this.uuidVersion = uuidVersion;
+ }
+
+ /**
+ * Return the UUID state file.
+ */
+ public String getUuidStateFile() {
+ return uuidStateFile;
+ }
+
+ /**
+ * Set the UUID state file.
+ */
+ public void setUuidStateFile(String uuidStateFile) {
+ this.uuidStateFile = uuidStateFile;
+ }
+
/**
* Return true if LocalTime should be persisted with nanos precision.
*/
@@ -2816,6 +2854,10 @@ public class ServerConfig {
if (p.getBoolean("uuidStoreAsBinary", false)) {
dbTypeConfig.setDbUuid(DbUuid.BINARY);
}
+
+ uuidVersion = p.getEnum(UuidVersion.class, "uuidVersion", uuidVersion);
+ uuidStateFile = p.get("uuidStateFile", uuidStateFile);
+
localTimeWithNanos = p.getBoolean("localTimeWithNanos", localTimeWithNanos);
jodaLocalTimeMode = p.get("jodaLocalTimeMode", jodaLocalTimeMode);
@@ -3101,4 +3143,10 @@ public class ServerConfig {
return binaryOptimized;
}
}
+
+ public enum UuidVersion {
+ VERSION4,
+ VERSION1,
+ VERSION1RND
+ };
}
diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java
index 83416486a..cfd837967 100644
--- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java
+++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java
@@ -29,7 +29,9 @@ import io.ebeaninternal.server.deploy.IndexDefinition;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.deploy.parse.DeployBeanInfo;
-import io.ebeaninternal.server.idgen.UuidIdGenerator;
+import io.ebeaninternal.server.idgen.UuidV1IdGenerator;
+import io.ebeaninternal.server.idgen.UuidV1RndIdGenerator;
+import io.ebeaninternal.server.idgen.UuidV4IdGenerator;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import javax.persistence.Entity;
@@ -844,8 +846,22 @@ public class DeployBeanDescriptor {
*/
public void setUuidGenerator() {
this.idType = IdType.EXTERNAL;
- this.idGeneratorName = UuidIdGenerator.AUTO_UUID;
- this.idGenerator = UuidIdGenerator.INSTANCE;
+ this.idGeneratorName = PlatformIdGenerator.AUTO_UUID;
+
+ switch (serverConfig.getUuidVersion()) {
+ case VERSION1:
+ this.idGenerator = UuidV1IdGenerator.getInstance(serverConfig.getUuidStateFile());
+ break;
+
+ case VERSION1RND:
+ this.idGenerator = UuidV1RndIdGenerator.INSTANCE;
+ break;
+
+ case VERSION4:
+ default:
+ this.idGenerator = UuidV4IdGenerator.INSTANCE;
+ break;
+ }
}
/**
diff --git a/src/main/java/io/ebeaninternal/server/idgen/UuidV1IdGenerator.java b/src/main/java/io/ebeaninternal/server/idgen/UuidV1IdGenerator.java
new file mode 100644
index 000000000..0202ab816
--- /dev/null
+++ b/src/main/java/io/ebeaninternal/server/idgen/UuidV1IdGenerator.java
@@ -0,0 +1,197 @@
+package io.ebeaninternal.server.idgen;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * IdGenerator for java util UUID.
+ *
+ * It extends the UuidV1RndIdGenerator so that it can generate rfc4122 compliant Type 1 UUIDs.
+ *
+ * This generator produces real Type 1 UUIDs (best for sqlserver) - You should use this generator only,
+ * if you can guarantee, that mac addess is uniqe.
+ */
+public class UuidV1IdGenerator extends UuidV1RndIdGenerator {
+
+ private static final Logger logger = LoggerFactory.getLogger("io.ebean.IDGEN");
+
+ private final File stateFile;
+
+ private byte[] nodeId = null;
+
+ private boolean canSaveState = true;
+
+ private static final Map INSTANCES = new ConcurrentHashMap<>();
+
+ /**
+ * Returns an instance for given file.
+ */
+ public static UuidV1IdGenerator getInstance(String file) {
+ return INSTANCES.computeIfAbsent(new File(file), UuidV1IdGenerator::new);
+ }
+
+ /**
+ * Returns an instance for given file.
+ */
+ public static UuidV1IdGenerator getInstance(File file) {
+ return INSTANCES.computeIfAbsent(file, UuidV1IdGenerator::new);
+ }
+
+ /**
+ * Returns an alternative node id - set with the 'ebean.uuid.nodeId' system property.
+ */
+ private static byte[] getAlternativeNodeId() {
+ try {
+ String altNodeId = System.getProperty("ebean.uuid.nodeId");
+ if (altNodeId != null) {
+ String[] components = altNodeId.split("-");
+ if (components.length != 5) {
+ throw new IllegalArgumentException("Invalid nodeId string: " + altNodeId);
+ }
+ byte[] nodeId = new byte[6];
+ for (int i=0; i<5; i++) {
+ nodeId[i] = Byte.decode("0x"+components[i]).byteValue();
+ }
+ return nodeId;
+ }
+ } catch (SecurityException se) {
+ // ignore
+ }
+ return null;
+ }
+
+ /**
+ * Find hardware ID.
+ */
+ private static byte[] getHardwareId() throws SocketException {
+ final Enumeration e = NetworkInterface.getNetworkInterfaces();
+ while (e.hasMoreElements()) {
+ NetworkInterface network = e.nextElement();
+ if (!network.isLoopback()) {
+ return network.getHardwareAddress();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Creates a new instance of UuidGenerator. Note that there should not be more
+ * than one instance per stateFile.
+ */
+ private UuidV1IdGenerator(final File stateFile) {
+ super();
+ this.stateFile = stateFile;
+ try {
+ // See, if there is an alternative MAC address set.
+ nodeId = getAlternativeNodeId();
+ if (nodeId != null) {
+ logger.info("Using alternative MAC {} to generate Type 1 UUIDs", getNodeIdentifier());
+ } else {
+ nodeId = getHardwareId();
+ logger.info("Using MAC {} to generate Type 1 UUIDs", getNodeIdentifier());
+ }
+ if (nodeId == null) {
+ canSaveState = false;
+ // RFC 4.5 use random portion for node
+ nodeId = super.getNodeIdBytes();
+ logger.error("Have to fall back to random node identifier {} (Reason: No suitable network interface found)", getNodeIdentifier());
+
+ } else {
+ boolean flag = restoreState();
+ UUID uuid = nextId(null);
+ long ts = timeStamp.get();
+ ts -= UUID_EPOCH_OFFSET;
+ ts /= MILLIS_TO_UUID;
+
+ saveState();
+ logger.debug("RestoreState: {}, ClockSeq {}, Timestamp {}, uuid {}, stateFile: {})", flag, clockSeq.get(),
+ new Date(ts), uuid, stateFile);
+ }
+ } catch (IOException e) {
+ canSaveState = false;
+ // RFC 4.5 use random portion for node
+ nodeId = super.getNodeIdBytes();
+ logger.error("Have to fall back to random node identifier {} (Reason: {} )", getNodeIdentifier(), e.getMessage());
+ }
+ }
+
+ /**
+ * Returns the Node-identifier (=MAC address) as string
+ */
+ public String getNodeIdentifier() {
+ if (nodeId == null) {
+ return "none";
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < nodeId.length; i++) {
+ sb.append(String.format("%02X%s", nodeId[i], (i < nodeId.length - 1) ? "-" : ""));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Restores the state from the state file.
+ */
+ private boolean restoreState() throws IOException {
+ Properties prop = new Properties();
+ if (stateFile.exists()) {
+ try (InputStream is = new FileInputStream(stateFile)) {
+ prop.load(is);
+ }
+ }
+ if (getNodeIdentifier().equals(prop.getProperty("nodeId"))) {
+ try {
+ Integer seq = Integer.valueOf(prop.getProperty("clockSeq")) & 0x3FFF;
+ Long ts = Long.valueOf(prop.getProperty("timeStamp"));
+ clockSeq.set(seq);
+ timeStamp.set(ts);
+ logger.debug("Restored state from '{}'", stateFile);
+ return true;
+ } catch (NumberFormatException nfe) {
+ // nop
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Saves the state to the state file;
+ */
+ @Override
+ protected void saveState() {
+ if (!canSaveState) {
+ return;
+ }
+ Properties prop = new Properties();
+ prop.setProperty("nodeId", getNodeIdentifier());
+ prop.setProperty("clockSeq", String.valueOf(clockSeq.get()));
+ prop.setProperty("timeStamp", String.valueOf(timeStamp.get()));
+ try (OutputStream os = new FileOutputStream(stateFile)) {
+ prop.store(os, "ebean uuid state file");
+ logger.debug("Persisted state to '{}'", stateFile);
+ } catch (IOException e) {
+ logger.error("Could not persist uuid state to '{}'", stateFile, e);
+ }
+ }
+
+ @Override
+ protected byte[] getNodeIdBytes() {
+ return nodeId;
+ }
+
+}
diff --git a/src/main/java/io/ebeaninternal/server/idgen/UuidV1RndIdGenerator.java b/src/main/java/io/ebeaninternal/server/idgen/UuidV1RndIdGenerator.java
new file mode 100644
index 000000000..4db84223d
--- /dev/null
+++ b/src/main/java/io/ebeaninternal/server/idgen/UuidV1RndIdGenerator.java
@@ -0,0 +1,179 @@
+package io.ebeaninternal.server.idgen;
+
+import io.ebean.Transaction;
+import io.ebean.config.dbplatform.PlatformIdGenerator;
+
+import java.security.SecureRandom;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * IdGenerator for (pseudo) type 1 UUIDs.
+ *
+ * This implementation generates a type 1 UUID according to
+ * https://tools.ietf.org/html/rfc4122.html#section-4.2
+ * but has no persistence storage. It generates a new random 47 bit node ID with every
+ * UUID.
+ *
+ * Use this, if you want randomness in your UUIDs but want to take advantage of index
+ * optimizations of the database. It may be good with AUTO_BINARY_OPTIMIZED and MySql.
+ *
+ * See: https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/
+ */
+public class UuidV1RndIdGenerator implements PlatformIdGenerator {
+
+ protected static final Logger logger = LoggerFactory.getLogger("io.ebean.IDGEN");
+
+ // UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
+ protected static final long UUID_EPOCH_OFFSET = 0x01B21DD213814000L;
+
+ // the resolution is 100ns
+ protected static final long MILLIS_TO_UUID = 10000;
+
+ public static final UuidV1RndIdGenerator INSTANCE = new UuidV1RndIdGenerator();
+
+ protected final AtomicInteger clockSeq = new AtomicInteger((int) (Math.random() * 0x3FFF));
+
+ private final SecureRandom numberGenerator = new SecureRandom();
+
+ protected AtomicLong timeStamp = new AtomicLong(currentUuidTime());
+
+ private AtomicLong nanoToMilliOffset = new AtomicLong(currentUuidTime());
+
+
+ /**
+ * Returns the uuid epoch.
+ *
+ * This is the number of 100ns intervals since 1582-10-15 00:00:00
+ */
+ private static long currentUuidTime() {
+ return (System.currentTimeMillis() * MILLIS_TO_UUID) + UUID_EPOCH_OFFSET;
+ }
+
+ public UuidV1RndIdGenerator() {
+ computeNanoOffset();
+ }
+
+ /**
+ * Computes the internal offset between System.nanoTime() and System.currentTimeMillis().
+ */
+ protected void computeNanoOffset() {
+ long currentTime = currentUuidTime();
+ long fromNanos = System.nanoTime() / 100L + nanoToMilliOffset.get();
+ long offset = currentTime - fromNanos;
+ if (Math.abs(offset) > MILLIS_TO_UUID * 1000 ) {
+ nanoToMilliOffset.addAndGet(offset);
+ }
+ }
+
+ /**
+ * Method to overwrite to save the state in a non volatile place.
+ */
+ protected void saveState() {
+
+ }
+
+ /**
+ * returns a random 47 bit value according to https://tools.ietf.org/html/rfc4122.html#section-4.5
+ */
+ protected byte[] getNodeIdBytes() {
+ byte[] idBytes = new byte[6];
+ numberGenerator.nextBytes(idBytes);
+ idBytes[0] |= 0x01; // set multicast bit.
+ return idBytes;
+ }
+
+ /**
+ * Return UUID from UUID.randomUUID();
+ */
+ @Override
+ public UUID nextId(Transaction t) {
+ long current = System.nanoTime() / 100L + nanoToMilliOffset.get();
+
+ long delta;
+ int seq;
+ do {
+ seq = clockSeq.get();
+ while (true) {
+ long last = timeStamp.get();
+
+ delta = current - last;
+ if (delta < -10000 * 20000) {
+ logger.info("Clock skew of {} ms detected", delta / -10000);
+ // The clock was adjusted back about 2 seconds, or we were generating a lot of ids too fast
+ // if so, we try to set the current as last and also increment the clockSeq.
+ synchronized (this) {
+ if (clockSeq.compareAndSet(seq, seq + 1)) {
+ timeStamp.set(current);
+ saveState();
+ computeNanoOffset();
+ } else {
+ continue;
+ }
+ }
+ }
+ // If current is in the future (most of the cases) try to set it.
+ if (delta > 0 && timeStamp.compareAndSet(last, current)) {
+ break;
+ } else if (timeStamp.compareAndSet(last, last + 1)) {
+ // here we go, if we pull IDs too fast.
+ current = last + 1;
+ break;
+ }
+ }
+ // verify, if this timestamp is for this clock sequence
+ } while (seq != clockSeq.get());
+
+ // save state every 60 seconds
+ if (delta > 60000 * 10000) {
+ saveState();
+ computeNanoOffset();
+ }
+
+ long msb = current << 32; // time low
+ msb |= (current & 0xFFFF00000000L) >> 16; // time mid
+ msb |= 0x1000 | ((current >> 48) & 0x0FFF); // time hi and version 1
+
+ byte[] idBytes = getNodeIdBytes();
+
+ long lsb = 0;
+ for (int i = 0; i < 6; i++) {
+ lsb = (lsb << 8) | (idBytes[i] & 0xff);
+ }
+
+ // RFC 4.1.1. Variant: is set with bits 10xx
+ seq = seq & 0x3FFF | 0x8000;
+ lsb |= (long) seq << 48;
+
+ return new UUID(msb, lsb);
+ }
+
+ /**
+ * Returns "uuid".
+ */
+ @Override
+ public String getName() {
+ return "uuid";
+ }
+
+ /**
+ * Returns false.
+ */
+ @Override
+ public boolean isDbSequence() {
+ return false;
+ }
+
+ /**
+ * Ignored for UUID as not required as a performance optimisation.
+ */
+ @Override
+ public void preAllocateIds(int allocateSize) {
+ // ignored
+ }
+
+}
diff --git a/src/main/java/io/ebeaninternal/server/idgen/UuidIdGenerator.java b/src/main/java/io/ebeaninternal/server/idgen/UuidV4IdGenerator.java
similarity index 68%
rename from src/main/java/io/ebeaninternal/server/idgen/UuidIdGenerator.java
rename to src/main/java/io/ebeaninternal/server/idgen/UuidV4IdGenerator.java
index 06a0f8edc..c7d1d8093 100644
--- a/src/main/java/io/ebeaninternal/server/idgen/UuidIdGenerator.java
+++ b/src/main/java/io/ebeaninternal/server/idgen/UuidV4IdGenerator.java
@@ -7,10 +7,14 @@ import java.util.UUID;
/**
* IdGenerator for java util UUID.
+ *
+ * This generator generates a 60bit random UUID according to
+ * https://tools.ietf.org/html/rfc4122.html#section-4.4
+ * Use this generator if you want truly random UUIDs
*/
-public class UuidIdGenerator implements PlatformIdGenerator {
+public class UuidV4IdGenerator implements PlatformIdGenerator {
- public static final UuidIdGenerator INSTANCE = new UuidIdGenerator();
+ public static final UuidV4IdGenerator INSTANCE = new UuidV4IdGenerator();
/**
* Return UUID from UUID.randomUUID();
diff --git a/src/test/java/io/ebeaninternal/server/idgen/TestUuidGenerator.java b/src/test/java/io/ebeaninternal/server/idgen/TestUuidGenerator.java
new file mode 100644
index 000000000..0f27c5a89
--- /dev/null
+++ b/src/test/java/io/ebeaninternal/server/idgen/TestUuidGenerator.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed Materials - Property of FOCONIS AG
+ * (C) Copyright FOCONIS AG.
+ */
+
+package io.ebeaninternal.server.idgen;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.junit.Test;
+
+import io.ebean.config.dbplatform.PlatformIdGenerator;
+
+
+/**
+ * Run some simple tests for the UUID generator.
+ *
+ * @author Roland Praml, FOCONIS AG
+ *
+ */
+public class TestUuidGenerator {
+
+
+/**
+ * Worker to generate UUIDs
+ *
+ * @author Roland Praml, FOCONIS AG
+ *
+ */
+ public static class IdTest implements Runnable {
+ private final Map map;
+ private final PlatformIdGenerator idGen;
+ private final int ids;
+ private final AtomicBoolean failFlag;
+
+ public IdTest(PlatformIdGenerator idGen, int ids, Map map, AtomicBoolean failFlag) {
+ this.idGen = idGen;
+ this.ids = ids;
+ this.map = map;
+ this.failFlag = failFlag;
+ }
+
+ public IdTest(PlatformIdGenerator idGen, int ids) {
+ this.idGen = idGen;
+ this.ids = ids;
+ this.map = null;
+ this.failFlag = null;
+ }
+ @Override
+ public void run() {
+ //System.out.println("Start " + Thread.currentThread());
+ for (int i = 0; i < ids; i++) {
+ UUID uuid = (UUID) idGen.nextId(null);
+ if (map != null && map.put(uuid, uuid) != null) {
+ System.err.println("UUID already in set " + uuid);
+ failFlag.set(true);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test takes ~0.3 sec
+ */
+ @Test
+ public void testUuidV1SingleThread() throws Exception {
+ testGenerator(1, 1_000_000, UuidV1IdGenerator.getInstance("ebean-test-uuid.state"));
+ }
+
+ /**
+ * Test takes ~1.4 sec
+ */
+ @Test
+ public void testUuidV1MultiThread() throws Exception {
+ testGenerator(10, 100_000, UuidV1IdGenerator.getInstance("ebean-test-uuid.state"));
+ }
+
+ /**
+ * Test takes ~3.1 sec
+ */
+ @Test
+ public void testUuidV1RndMultiThread() throws Exception {
+ testGenerator(10, 100_000, UuidV1RndIdGenerator.INSTANCE);
+ }
+
+ /**
+ * Test takes ~2.0 sec
+ */
+ @Test
+ public void testUuidType1RndSingleThread() throws Exception {
+ testGenerator(1, 1_000_000, UuidV1RndIdGenerator.INSTANCE);
+ }
+
+ /**
+ * Test takes ~4.9 sec
+ */
+ @Test
+ public void testUuidType4MultiThread() throws Exception {
+ testGenerator(10, 100_000, UuidV4IdGenerator.INSTANCE);
+ }
+
+ /**
+ * Test takes ~3.1 sec
+ */
+ @Test
+ public void testUuidType4SingleThread() throws Exception {
+ testGenerator(1, 1_000_000, UuidV4IdGenerator.INSTANCE);
+ }
+
+
+
+
+ private void testGenerator(int threadCount, int count, PlatformIdGenerator generator) throws Exception {
+ System.out.println("Printing 5 consecutive IDs of " + generator);
+ for (int i = 0; i < 5; i++) {
+ System.out.println(generator.nextId(null));
+ }
+
+ AtomicBoolean failFlag = new AtomicBoolean();
+ ConcurrentHashMap map = new ConcurrentHashMap<>();
+ Thread[] threads = new Thread[threadCount];
+ for (int i = 0; i < threads.length; i++) {
+ threads[i] = new Thread(new IdTest(generator, count, map, failFlag));
+ }
+ for (int i = 0; i < threads.length; i++) {
+ threads[i].start();
+ }
+ for (int i = 0; i < threads.length; i++) {
+ threads[i].join();
+ }
+ assertThat(failFlag.get()).isFalse();
+ assertThat(map.size()).isEqualTo(threadCount * count);
+
+ }
+}
From 78628ffad0b9c021c46ca8feb6b1d0ee284a47c6 Mon Sep 17 00:00:00 2001
From: Roland Praml
Date: Fri, 2 Mar 2018 00:12:34 +0100
Subject: [PATCH 3/7] NEW: DB-with-Master option (#1311)
---
.../java/io/ebean/config/ServerConfig.java | 16 +++++++++++
src/main/java/io/ebean/config/TenantMode.java | 28 +++++++++++++++----
.../dbmigration/DdlGenerator.java | 2 +-
.../server/core/InternalConfiguration.java | 1 +
4 files changed, 40 insertions(+), 7 deletions(-)
diff --git a/src/main/java/io/ebean/config/ServerConfig.java b/src/main/java/io/ebean/config/ServerConfig.java
index 0b42551b1..679692b88 100644
--- a/src/main/java/io/ebean/config/ServerConfig.java
+++ b/src/main/java/io/ebean/config/ServerConfig.java
@@ -2878,6 +2878,22 @@ public class ServerConfig {
ddlInitSql = p.get("ddl.initSql", ddlInitSql);
ddlSeedSql = p.get("ddl.seedSql", ddlSeedSql);
+ // read tenant-configuration from config:
+ // tenant.mode = NONE | DB | SCHEMA | CATALOG | PARTITION
+ String mode = p.get("tenant.mode");
+ if (mode != null) {
+ for (TenantMode value : TenantMode.values()) {
+ if (value.name().equalsIgnoreCase(mode)) {
+ tenantMode = value;
+ break;
+ }
+ }
+ }
+
+ currentTenantProvider = createInstance(p, CurrentTenantProvider.class, "tenant.currentTenantProvider", currentTenantProvider);
+ tenantCatalogProvider = createInstance(p, TenantCatalogProvider.class, "tenant.catalogProvider", tenantCatalogProvider);
+ tenantSchemaProvider = createInstance(p, TenantSchemaProvider.class, "tenant.schemaProvider", tenantSchemaProvider);
+ tenantPartitionColumn = p.get("tenant.partitionColumn", tenantPartitionColumn);
classes = getClasses(p);
}
diff --git a/src/main/java/io/ebean/config/TenantMode.java b/src/main/java/io/ebean/config/TenantMode.java
index 1205fca58..2f659704a 100644
--- a/src/main/java/io/ebean/config/TenantMode.java
+++ b/src/main/java/io/ebean/config/TenantMode.java
@@ -8,32 +8,40 @@ public enum TenantMode {
/**
* No multi-tenancy.
*/
- NONE(false),
+ NONE(false, true),
/**
* Each Tenant has their own Database (javax.sql.DataSource)
*/
- DB(true),
+ DB(true, false),
/**
* Each Tenant has their own Database schema.
*/
- SCHEMA(true),
+ SCHEMA(true, false),
/**
* Each Tenant has their own Database but with in connection pool
*/
- CATALOG(true),
+ CATALOG(true, false),
/**
* Tenants share tables but have a discriminator/partition column that partitions the data.
*/
- PARTITION(false);
+ PARTITION(false, true),
+ /**
+ * Each Tenant has their own Database (javax.sql.DataSource), and there is also one master-database
+ * (that holds configuration e.g.)
+ */
+ DB_WITH_MASTER(true, true);
+
boolean dynamicDataSource;
+ boolean ddlEnabled;
- TenantMode(boolean dynamicDataSource) {
+ TenantMode(boolean dynamicDataSource, boolean ddlEnabled) {
this.dynamicDataSource = dynamicDataSource;
+ this.ddlEnabled = ddlEnabled;
}
/**
@@ -42,4 +50,12 @@ public enum TenantMode {
public boolean isDynamicDataSource() {
return dynamicDataSource;
}
+
+ /**
+ * Returns true, if DDL is enabled.
+ */
+ public boolean isDdlEnabled() {
+ return ddlEnabled;
+ }
+
}
diff --git a/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java b/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java
index 2a54e6f9a..ae1ef4d08 100644
--- a/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java
+++ b/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java
@@ -47,7 +47,7 @@ public class DdlGenerator {
this.jaxbPresent = serverConfig.getClassLoadConfig().isJavaxJAXBPresent();
this.generateDdl = serverConfig.isDdlGenerate();
this.createOnly = serverConfig.isDdlCreateOnly();
- if (serverConfig.getTenantMode().isDynamicDataSource() && serverConfig.isDdlRun()) {
+ if (!serverConfig.getTenantMode().isDdlEnabled() && serverConfig.isDdlRun()) {
log.warn("DDL can't be run on startup with TenantMode " + serverConfig.getTenantMode());
this.runDdl = false;
} else {
diff --git a/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java b/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java
index 3702d1984..a65dd2194 100644
--- a/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java
+++ b/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java
@@ -409,6 +409,7 @@ public class InternalConfiguration {
private DataSourceSupplier dataSource() {
switch (serverConfig.getTenantMode()) {
case DB:
+ case DB_WITH_MASTER:
return new MultiTenantDbSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getTenantDataSourceProvider());
case SCHEMA:
return new MultiTenantDbSchemaSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getDataSource(), serverConfig.getReadOnlyDataSource(), serverConfig.getTenantSchemaProvider());
From aea7d937ebe15ef9576bd7267c215cbb921ef6c0 Mon Sep 17 00:00:00 2001
From: Roland Praml
Date: Fri, 2 Mar 2018 00:13:16 +0100
Subject: [PATCH 4/7] ENH: added SqlRow factory method to RawSqlService (#1312)
* ENH: added SqlRow factory method to RawSqlService
* compile fixes
* ADD: sqlRow factory also to RawSqlBuilder
---
src/main/java/io/ebean/RawSqlBuilder.java | 10 ++++++++++
.../io/ebean/service/SpiRawSqlService.java | 8 ++++++++
.../server/rawsql/DRawSqlService.java | 20 +++++++++++++++++++
3 files changed, 38 insertions(+)
diff --git a/src/main/java/io/ebean/RawSqlBuilder.java b/src/main/java/io/ebean/RawSqlBuilder.java
index 3d1e4f4ca..44121962c 100644
--- a/src/main/java/io/ebean/RawSqlBuilder.java
+++ b/src/main/java/io/ebean/RawSqlBuilder.java
@@ -1,6 +1,7 @@
package io.ebean;
import java.sql.ResultSet;
+import java.sql.SQLException;
/**
* Builds RawSql instances from a SQL string and column mappings.
@@ -24,6 +25,15 @@ public interface RawSqlBuilder {
return XServiceProvider.rawSql().resultSet(resultSet, propertyNames);
}
+ /**
+ * Create and return a SqlRow based on the resultSet and the dbTrueValue.
+ * @throws SQLException
+ */
+ static SqlRow sqlRow(ResultSet resultSet, final String dbTrueValue) throws SQLException {
+ return XServiceProvider.rawSql().sqlRow(resultSet, dbTrueValue);
+
+ }
+
/**
* Return an unparsed RawSqlBuilder. Unlike a parsed one this query can not be
* modified - so no additional WHERE or HAVING expressions can be added to
diff --git a/src/main/java/io/ebean/service/SpiRawSqlService.java b/src/main/java/io/ebean/service/SpiRawSqlService.java
index aa668486a..4d201fc58 100644
--- a/src/main/java/io/ebean/service/SpiRawSqlService.java
+++ b/src/main/java/io/ebean/service/SpiRawSqlService.java
@@ -2,8 +2,10 @@ package io.ebean.service;
import io.ebean.RawSql;
import io.ebean.RawSqlBuilder;
+import io.ebean.SqlRow;
import java.sql.ResultSet;
+import java.sql.SQLException;
/**
* Service provided by Ebean for parsing and column mapping raw SQL queries.
@@ -24,4 +26,10 @@ public interface SpiRawSqlService {
* Unparsed SQL so explicit column mapping expected.
*/
RawSqlBuilder unparsed(String sql);
+
+ /**
+ * Create based on a JDBC ResultSet.
+ * @throws SQLException
+ */
+ SqlRow sqlRow(ResultSet resultSet, String dbTrueValue) throws SQLException;
}
diff --git a/src/main/java/io/ebeaninternal/server/rawsql/DRawSqlService.java b/src/main/java/io/ebeaninternal/server/rawsql/DRawSqlService.java
index fe33d1bbc..27f90a017 100644
--- a/src/main/java/io/ebeaninternal/server/rawsql/DRawSqlService.java
+++ b/src/main/java/io/ebeaninternal/server/rawsql/DRawSqlService.java
@@ -2,9 +2,13 @@ package io.ebeaninternal.server.rawsql;
import io.ebean.RawSql;
import io.ebean.RawSqlBuilder;
+import io.ebean.SqlRow;
import io.ebean.service.SpiRawSqlService;
+import io.ebeaninternal.server.query.DefaultSqlRow;
import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
public class DRawSqlService implements SpiRawSqlService {
@@ -28,4 +32,20 @@ public class DRawSqlService implements SpiRawSqlService {
SpiRawSql.Sql s = new SpiRawSql.Sql(sql);
return new DRawSqlBuilder(s, new SpiRawSql.ColumnMapping());
}
+
+ @Override
+ public SqlRow sqlRow(ResultSet resultSet, String dbTrueValue) throws SQLException {
+ ResultSetMetaData meta = resultSet.getMetaData();
+ int estCap = (int) (meta.getColumnCount() / 0.7f) + 1;
+ DefaultSqlRow ret = new DefaultSqlRow(estCap, 0.75f, dbTrueValue);
+
+ for (int i = 1; i <= meta.getColumnCount(); i++) {
+ String name = meta.getColumnLabel(i);
+ if (name == null) {
+ name = meta.getColumnName(i);
+ }
+ ret.put(name, resultSet.getObject(i));
+ }
+ return ret;
+ }
}
From 83b463040fd8aa9f615923fcc94deee95e951c48 Mon Sep 17 00:00:00 2001
From: Roland Praml
Date: Fri, 2 Mar 2018 00:14:09 +0100
Subject: [PATCH 5/7] ENH: Add sanity check to InExpression (#1313)
---
.../io/ebeaninternal/server/expression/InExpression.java | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/main/java/io/ebeaninternal/server/expression/InExpression.java b/src/main/java/io/ebeaninternal/server/expression/InExpression.java
index 8c2d5f55d..c1101b9a2 100644
--- a/src/main/java/io/ebeaninternal/server/expression/InExpression.java
+++ b/src/main/java/io/ebeaninternal/server/expression/InExpression.java
@@ -40,6 +40,7 @@ class InExpression extends AbstractExpression {
private List