Merge remote-tracking branch 'ebean/master' into fix-limit-constraint-names

# Conflicts:
#	src/test/resources/dbmigration/migrationtest/db2/1.0__initial.sql
#	src/test/resources/dbmigration/migrationtest/db2/1.1.sql
#	src/test/resources/dbmigration/migrationtest/db2/1.3.sql
This commit is contained in:
Roland Praml
2018-03-02 01:03:36 +01:00
88 changed files with 2752 additions and 64 deletions
+10
View File
@@ -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
@@ -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<IdGenerator> idGenerators = new ArrayList<>();
private List<BeanFindController> findControllers = new ArrayList<>();
private List<BeanPersistController> 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);
@@ -2836,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);
}
@@ -3033,37 +3091,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 +3151,18 @@ public class ServerConfig {
public boolean useBinary() {
return binary;
}
/**
* Return true, if optimized packing should be used.
*/
public boolean useBinaryOptimized() {
return binaryOptimized;
}
}
public enum UuidVersion {
VERSION4,
VERSION1,
VERSION1RND
};
}
+22 -6
View File
@@ -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;
}
}
@@ -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;
}
@@ -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 {
@@ -6,6 +6,9 @@ public class SplitColumns {
* Return as an array of string column names.
*/
public static String[] split(String columns) {
if (columns == null || columns.isEmpty()) {
return new String[0];
}
return columns.split(",");
}
@@ -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());
}
@@ -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());
@@ -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) {
@@ -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<T> {
*/
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;
}
}
/**
@@ -40,6 +40,7 @@ class InExpression extends AbstractExpression {
private List<Object> values() {
List<Object> vals = new ArrayList<>(sourceValues.size());
for (Object sourceValue : sourceValues) {
assert sourceValue != null : "null is not allowed in in-queries";
NamedParamHelp.valueAdd(vals, sourceValue);
}
return vals;
@@ -66,7 +67,11 @@ class InExpression extends AbstractExpression {
@Override
public void addBindValues(SpiExpressionRequest request) {
for (Object value : bindValues) {
if (value == null) {
throw new NullPointerException("null values in 'in(...)' queries must be handled separately!");
}
}
ElPropertyValue prop = getElProp(request);
if (prop != null && !prop.isAssocId()) {
prop = null;
@@ -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<File, UuidV1IdGenerator> 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<NetworkInterface> 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;
}
}
@@ -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
}
}
@@ -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();
@@ -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).
* </p>
*/
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
@@ -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, false);
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;
}
}
@@ -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);
}
@@ -70,11 +70,12 @@ public class ScalarTypeJsonList {
@Override
public List read(DataReader dataReader) throws SQLException {
String json = dataReader.getString();
try {
// parse JSON into modifyAware list
return EJson.parseList(dataReader.getString(), true);
return EJson.parseList(json, true);
} catch (IOException e) {
throw new SQLException("Failed to parse JSON content as List: [" + dataReader.getString() + "]", e);
throw new SQLException("Failed to parse JSON content as List: [" + json + "]", e);
}
}
@@ -73,11 +73,12 @@ public class ScalarTypeJsonSet {
@Override
public Set read(DataReader dataReader) throws SQLException {
String json = dataReader.getString();
try {
// parse JSON into modifyAware list
return EJson.parseSet(dataReader.getString(), true);
return EJson.parseSet(json, true);
} catch (IOException e) {
throw new SQLException("Failed to parse JSON content as List: [" + dataReader.getString() + "]", e);
throw new SQLException("Failed to parse JSON content as List: [" + json + "]", e);
}
}
@@ -57,7 +57,7 @@ public abstract class ScalarTypeUUIDBase extends ScalarTypeBase<UUID> implements
@Override
public UUID toBeanType(Object value) {
return BasicTypeConverter.toUUID(value);
return BasicTypeConverter.toUUID(value, false);
}
@Override
@@ -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;
}
}