Merge branch 'feature/1186-MultiValueBind'

This commit is contained in:
rob bygrave
2017-10-31 22:11:30 +13:00
54 changed files with 817 additions and 168 deletions
@@ -24,4 +24,13 @@ public interface BeanQueryRequest<T> {
*/
Query<T> getQuery();
/**
* Return true if multi-value binding using Array or Table Values is supported.
*/
boolean isMultiValueIdSupported();
/**
* Return true if multi-value binding is supported for this value type.
*/
boolean isMultiValueSupported(Class<?> valueType);
}
@@ -103,7 +103,8 @@ public class LoadBeanRequest extends LoadRequest {
idList.add(desc.getId(bean));
}
if (!idList.isEmpty()) {
if (!desc.isMultiValueIdSupported() && !idList.isEmpty()) {
int extraIds = batchSize - batch.size();
if (extraIds > 0) {
// for performance make up the Id's to the batch size
@@ -107,11 +107,13 @@ public class LoadManyRequest extends LoadRequest {
for (BeanCollection<?> bc : batch) {
idList.add(many.getParentId(bc.getOwnerBean()));
}
int extraIds = batchSize - batch.size();
if (extraIds > 0) {
Object firstId = idList.get(0);
for (int i = 0; i < extraIds; i++) {
idList.add(firstId);
if (!many.getTargetDescriptor().isMultiValueIdSupported()) {
int extraIds = batchSize - batch.size();
if (extraIds > 0) {
Object firstId = idList.get(0);
for (int i = 0; i < extraIds; i++) {
idList.add(firstId);
}
}
}
@@ -70,4 +70,9 @@ public interface SpiExpressionRequest {
* Escapes a string to use it as exact match in Like clause.
*/
String escapeLikeString(String value);
/**
* Append IN expression taking into account platform and type support for Multi-value.
*/
void appendInExpression(boolean not, Object[] bindValues);
}
@@ -12,7 +12,7 @@ import org.slf4j.LoggerFactory;
*/
public abstract class BeanRequest {
private static final Logger log = LoggerFactory.getLogger(BeanRequest.class);
static final Logger log = LoggerFactory.getLogger(BeanRequest.class);
/**
* The server processing the request.
@@ -182,7 +182,6 @@ public class DefaultBeanLoader {
}
}
/**
* Load a batch of beans for +query or +lazy loading.
*/
@@ -7,9 +7,9 @@ import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ExternalTransactionManager;
import io.ebean.config.ProfilingConfig;
import io.ebean.config.ServerConfig;
import io.ebean.config.SlowQueryListener;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbHistorySupport;
import io.ebean.config.SlowQueryListener;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
@@ -43,6 +43,9 @@ import io.ebeaninternal.server.deploy.parse.DeployUtil;
import io.ebeaninternal.server.expression.DefaultExpressionFactory;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.persist.DefaultPersister;
import io.ebeaninternal.server.persist.platform.H2MultiValueBind;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
import io.ebeaninternal.server.persist.platform.PostgresMultiValueBind;
import io.ebeaninternal.server.query.CQueryEngine;
import io.ebeaninternal.server.query.DefaultOrmQueryEngine;
import io.ebeaninternal.server.query.DefaultRelationalQueryEngine;
@@ -122,6 +125,8 @@ public class InternalConfiguration {
*/
private final List<Plugin> plugins = new ArrayList<>();
private final MultiValueBind multiValueBind;
public InternalConfiguration(ClusterManager clusterManager,
SpiCacheManager cacheManager, SpiBackgroundExecutor backgroundExecutor,
ServerConfig serverConfig, BootupClasses bootupClasses) {
@@ -138,6 +143,7 @@ public class InternalConfiguration {
this.expressionFactory = initExpressionFactory(serverConfig, databasePlatform);
this.typeManager = new DefaultTypeManager(serverConfig, bootupClasses);
this.multiValueBind = createMultiValueBind(databasePlatform.getPlatform());
this.deployInherit = new DeployInherit(bootupClasses);
this.deployCreateProperties = new DeployCreateProperties(typeManager);
@@ -245,9 +251,9 @@ public class InternalConfiguration {
DbHistorySupport historySupport = databasePlatform.getHistorySupport();
if (historySupport == null) {
return new Binder(typeManager, 0, false, jsonHandler, dataTimeZone);
return new Binder(typeManager, 0, false, jsonHandler, dataTimeZone, multiValueBind);
}
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isStandardsBased(), jsonHandler, dataTimeZone);
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isStandardsBased(), jsonHandler, dataTimeZone, multiValueBind);
}
/**
@@ -267,6 +273,21 @@ public class InternalConfiguration {
}
}
private MultiValueBind createMultiValueBind(Platform platform) {
switch (platform) {
case POSTGRES:
return new PostgresMultiValueBind();
// case H2:
// return new H2MultiValueBind();
// case SQLSERVER:
// return new SqlServerTvpMultiValueHelp();
// case ORACLE:
// return new OracleTvpMultiValueHelp();
default:
return new MultiValueBind();
}
}
public SpiJsonContext createJsonContext(SpiEbeanServer server) {
return new DJsonContext(server, jsonFactory, typeManager);
}
@@ -280,7 +301,7 @@ public class InternalConfiguration {
}
public OrmQueryEngine createOrmQueryEngine() {
return new DefaultOrmQueryEngine(cQueryEngine);
return new DefaultOrmQueryEngine(cQueryEngine, binder);
}
public Persister createPersister(SpiEbeanServer server) {
@@ -478,4 +499,11 @@ public class InternalConfiguration {
}
return listener;
}
/**
* Return the platform specific MultiValue bind support.
*/
public MultiValueBind getMultiValueBind() {
return multiValueBind;
}
}
@@ -62,4 +62,9 @@ public interface OrmQueryEngine {
* Translate the SQLException to a specific persistence exception type if possible.
*/
<T> PersistenceException translate(OrmQueryRequest<T> request, String bindLog, String sql, SQLException e);
/**
* Return true if multi-value bind is supported for this type (and current platform).
*/
boolean isMultiValueSupported(Class<?> valueType);
}
@@ -89,6 +89,16 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
return queryEngine.translate(this, bindLog, sql, e);
}
@Override
public boolean isMultiValueIdSupported() {
return beanDescriptor.isMultiValueIdSupported();
}
@Override
public boolean isMultiValueSupported(Class<?> valueType) {
return queryEngine.isMultiValueSupported(valueType);
}
/**
* Mark the transaction as not being query only.
*/
@@ -49,6 +49,7 @@ import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.core.PersistRequest;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.deploy.id.IdBinder;
import io.ebeaninternal.server.deploy.id.IdBinderSimple;
import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyLists;
@@ -116,6 +117,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
private final short profileBeanId;
private final boolean multiValueSupported;
public enum EntityType {
ORM, EMBEDDED, VIEW, SQL, DOC
}
@@ -402,6 +405,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
public BeanDescriptor(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy) {
this.owner = owner;
this.multiValueSupported = owner.isMultiValueSupported();
this.serverName = owner.getServerName();
this.entityType = deploy.getEntityType();
this.properties = deploy.getProperties();
@@ -935,9 +939,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
sb.append(inClause);
DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString());
for (Object anIdList : idList) {
idBinder.bindId(delete, anIdList);
}
idBinder.addIdInBindValues(delete, idList);
return delete;
}
@@ -1661,7 +1663,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
* Return a raw expression for 'where parent id in ...' clause.
*/
public String getParentIdInExpr(int parentIdSize, String rawWhere) {
String inClause = idBinder.getIdInValueExpr(parentIdSize);
String inClause = idBinder.getIdInValueExpr(false, parentIdSize);
return idBinder.isIdInExpandedForm() ? inClause : rawWhere + inClause;
}
@@ -1672,6 +1674,20 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return idBinder;
}
/**
* Return true if this bean type has a simple single Id property.
*/
public boolean isSimpleId() {
return idBinder instanceof IdBinderSimple;
}
/**
* Return true if this type has a simple Id and the platform supports mutli-value binding.
*/
public boolean isMultiValueIdSupported() {
return multiValueSupported && isSimpleId();
}
/**
* Return the sql for binding an id. This is the columns with table alias that
* make up the id.
@@ -45,6 +45,7 @@ import io.ebeaninternal.server.deploy.parse.DeployInherit;
import io.ebeaninternal.server.deploy.parse.DeployUtil;
import io.ebeaninternal.server.deploy.parse.ReadAnnotations;
import io.ebeaninternal.server.deploy.parse.TransientProperties;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
import io.ebeaninternal.server.properties.BeanPropertiesReader;
import io.ebeaninternal.server.properties.BeanPropertyAccess;
import io.ebeaninternal.server.properties.EnhanceBeanPropertyAccess;
@@ -131,6 +132,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final DocStoreFactory docStoreFactory;
private final MultiValueBind multiValueBind;
private int entityBeanCount;
private final boolean updateChangesOnly;
@@ -201,7 +204,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
this.dataSource = serverConfig.getDataSource();
this.encryptKeyManager = serverConfig.getEncryptKeyManager();
this.databasePlatform = serverConfig.getDatabasePlatform();
this.idBinderFactory = new IdBinderFactory(databasePlatform.isIdInExpandedForm());
this.multiValueBind = config.getMultiValueBind();
this.idBinderFactory = new IdBinderFactory(databasePlatform.isIdInExpandedForm(), multiValueBind);
this.eagerFetchLobs = serverConfig.isEagerFetchLobs();
this.asOfViewSuffix = getAsOfViewSuffix(databasePlatform, serverConfig);
@@ -252,6 +256,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return (historySupport == null) ? serverConfig.getAsOfViewSuffix() : historySupport.getVersionsBetweenSuffix(serverConfig.getAsOfViewSuffix());
}
@Override
public boolean isMultiValueSupported() {
return multiValueBind.isSupported();
}
@Override
public ServerConfig getServerConfig() {
return serverConfig;
@@ -36,6 +36,11 @@ public interface BeanDescriptorMap {
*/
NamingConvention getNamingConvention();
/**
* Return true if multiple values can be bound as Array or Table Value and hence share the same query plan.
*/
boolean isMultiValueSupported();
/**
* Return the BeanDescriptor for a given class.
*/
@@ -137,7 +137,7 @@ public final class BeanFkeyProperty implements ElPropertyValue {
* Returns null as not an AssocOne.
*/
@Override
public String getAssocIdInValueExpr(int size) {
public String getAssocIdInValueExpr(boolean not, int size) {
return null;
}
@@ -909,7 +909,7 @@ public class BeanProperty implements ElPropertyValue, Property {
}
@Override
public String getAssocIdInValueExpr(int size) {
public String getAssocIdInValueExpr(boolean not, int size) {
// Returns null as not an AssocOne.
return null;
}
@@ -18,6 +18,7 @@ import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.persist.MultiValueWrapper;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
@@ -366,8 +367,11 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
// Flatten the bind values if needed (embeddedId)
List<Object> bindValues = getBindParentIds(parentIds);
query.where().raw(expr, bindValues.toArray());
if (descriptor.isSimpleId()) {
query.where().raw(expr, new MultiValueWrapper(bindValues));
} else {
query.where().raw(expr, bindValues.toArray());
}
}
private List<Object> findIdsByParentIdList(List<Object> parentIdList, Transaction t, ArrayList<Object> excludeDetailIds) {
@@ -383,10 +387,12 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
}
EbeanServer server = getBeanDescriptor().getEbeanServer();
Query<?> q = server.find(getPropertyType())
.where()
.raw(expr, bindValues.toArray())
.query();
Query<?> q = server.find(getPropertyType());
if (descriptor.isSimpleId()) {
q.where().raw(expr, new MultiValueWrapper(bindValues));
} else {
q.where().raw(expr, bindValues.toArray());
}
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
Expression idIn = q.getExpressionFactory().idIn(excludeDetailIds);
@@ -405,10 +411,13 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
sb.append(inClause);
DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString());
for (Object aParentIdist : parentIdist) {
bindWhereParendId(delete, aParentIdist);
if (exportedProperties.length == 1) {
bindWhereParendId(delete, new MultiValueWrapper(parentIdist));
} else {
for (Object aParentIdist : parentIdist) {
bindWhereParendId(delete, aParentIdist);
}
}
return delete;
}
@@ -430,9 +439,11 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
private String buildInClauseBinding(int size, String bindProto) {
if (descriptor.isSimpleId()) {
return descriptor.getIdBinder().getIdInValueExpr(false, size);
}
StringBuilder sb = new StringBuilder(10 + (size * (bindProto.length() + 1)));
sb.append(" in");
sb.append(" (");
for (int i = 0; i < size; i++) {
if (i > 0) {
@@ -534,8 +545,8 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
* Return the logical id value expression taking into account embedded id's.
*/
@Override
public String getAssocIdInValueExpr(int size) {
return targetDescriptor.getIdBinder().getIdInValueExpr(size);
public String getAssocIdInValueExpr(boolean not, int size) {
return targetDescriptor.getIdBinder().getIdInValueExpr(not, size);
}
/**
@@ -207,7 +207,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
StringBuilder sb = new StringBuilder(100);
sb.append(deleteByParentIdInSql);
String inClause = targetIdBinder.getIdInValueExpr(parentIdist.size());
String inClause = targetIdBinder.getIdInValueExpr(false, parentIdist.size());
sb.append(inClause);
DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString());
@@ -256,7 +256,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
private List<Object> findIdsByParentIdList(List<Object> parentIdList, Transaction t) {
String rawWhere = deriveWhereParentIdSql(true);
String inClause = targetIdBinder.getIdInValueExpr(parentIdList.size());
String inClause = targetIdBinder.getIdInValueExpr(false, parentIdList.size());
String expr = rawWhere + inClause;
@@ -445,8 +445,8 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
* Return the logical id value expression taking into account embedded id's.
*/
@Override
public String getAssocIdInValueExpr(int size) {
return targetDescriptor.getIdBinder().getIdInValueExpr(size);
public String getAssocIdInValueExpr(boolean not, int size) {
return targetDescriptor.getIdBinder().getIdInValueExpr(not, size);
}
/**
@@ -12,6 +12,7 @@ import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
/**
@@ -134,7 +135,15 @@ public interface IdBinder {
*/
void bindId(DefaultSqlUpdate sqlUpdate, Object value);
void addIdInBindValue(SpiExpressionRequest request, Object value);
/**
* Binds multiple id value to an update.
*/
void addIdInBindValues(DefaultSqlUpdate sqlUpdate, Collection<?> ids);
/**
* Binds multiple id value to a request.
*/
void addIdInBindValues(SpiExpressionRequest request, Collection<?> ids);
/**
* Return the sql for binding the id using an IN clause.
@@ -144,7 +153,7 @@ public interface IdBinder {
/**
* Return the binding expression (like "?" or "(?,?)")for the Id.
*/
String getIdInValueExpr(int size);
String getIdInValueExpr(boolean not, int size);
/**
* Same as getIdInValueExpr but for delete by id.
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.deploy.id;
import io.ebean.bean.EntityBean;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
import io.ebeaninternal.server.deploy.BeanDescriptor;
@@ -8,13 +9,13 @@ import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebean.util.SplitName;
import io.ebeaninternal.server.type.DataBind;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -163,20 +164,13 @@ public final class IdBinderEmbedded implements IdBinder {
return props;
}
@Override
public void addIdInBindValue(SpiExpressionRequest request, Object value) {
for (BeanProperty prop : props) {
request.addBindValue(prop.getValue((EntityBean) value));
}
}
@Override
public String getIdInValueExprDelete(int size) {
if (size <= 0) {
throw new IndexOutOfBoundsException("The size must be at least 1");
}
if (!idInExpandedForm) {
return getIdInValueExpr(size);
return getIdInValueExpr(false, size);
}
StringBuilder sb = new StringBuilder();
@@ -201,12 +195,14 @@ public final class IdBinderEmbedded implements IdBinder {
}
@Override
public String getIdInValueExpr(int size) {
public String getIdInValueExpr(boolean not, int size) {
if (size <= 0) {
throw new IndexOutOfBoundsException("The size must be at least 1");
}
StringBuilder sb = new StringBuilder();
if (not) {
sb.append(" not");
}
if (!idInExpandedForm) {
sb.append(" in");
}
@@ -294,6 +290,22 @@ public final class IdBinderEmbedded implements IdBinder {
}
}
@Override
public void addIdInBindValues(DefaultSqlUpdate sqlUpdate, Collection<?> values) {
for (Object value : values) {
bindId(sqlUpdate, value);
}
}
@Override
public void addIdInBindValues(SpiExpressionRequest request, Collection<?> values) {
for (Object value : values) {
for (BeanProperty prop : props) {
request.addBindValue(prop.getValue((EntityBean) value));
}
}
}
@Override
public Object readData(DataInput dataInput) throws IOException {
@@ -12,6 +12,7 @@ import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
/**
@@ -85,18 +86,13 @@ public final class IdBinderEmpty implements IdBinder {
return null;
}
@Override
public void addIdInBindValue(SpiExpressionRequest request, Object value) {
}
@Override
public String getIdInValueExprDelete(int size) {
return getIdInValueExpr(size);
return getIdInValueExpr(false, size);
}
@Override
public String getIdInValueExpr(int size) {
public String getIdInValueExpr(boolean not, int size) {
return "";
}
@@ -135,6 +131,16 @@ public final class IdBinderEmpty implements IdBinder {
}
@Override
public void addIdInBindValues(DefaultSqlUpdate sqlUpdate, Collection<?> ids) {
}
@Override
public void addIdInBindValues(SpiExpressionRequest request, Collection<?> ids) {
}
@Override
public void loadIgnore(DbReadContext ctx) {
}
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy.id;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
/**
* Creates the appropriate IdConvertSet depending on the type of Id property(s).
@@ -12,8 +13,11 @@ public class IdBinderFactory {
private final boolean idInExpandedForm;
public IdBinderFactory(boolean idInExpandedForm) {
private final MultiValueBind multiValueBind;
public IdBinderFactory(boolean idInExpandedForm, MultiValueBind multiValueBind) {
this.idInExpandedForm = idInExpandedForm;
this.multiValueBind = multiValueBind;
}
/**
@@ -29,7 +33,7 @@ public class IdBinderFactory {
if (id.isEmbedded()) {
return new IdBinderEmbedded(idInExpandedForm, (BeanPropertyAssocOne<?>) id);
} else {
return new IdBinderSimple(id);
return new IdBinderSimple(id, multiValueBind);
}
}
@@ -7,6 +7,8 @@ import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.persist.MultiValueWrapper;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.ScalarType;
@@ -14,6 +16,8 @@ import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
@@ -27,14 +31,18 @@ public final class IdBinderSimple implements IdBinder {
private final Class<?> expectedType;
private final MultiValueBind multiValueBind;
@SuppressWarnings("rawtypes")
private final ScalarType scalarType;
public IdBinderSimple(BeanProperty idProperty) {
public IdBinderSimple(BeanProperty idProperty, MultiValueBind multiValueBind) {
this.idProperty = idProperty;
this.scalarType = idProperty.getScalarType();
this.expectedType = idProperty.getPropertyType();
bindIdSql = InternString.intern(idProperty.getDbColumn() + " = ? ");
this.multiValueBind = multiValueBind;
}
@Override
@@ -125,28 +133,29 @@ public final class IdBinderSimple implements IdBinder {
@Override
public String getIdInValueExprDelete(int size) {
return getIdInValueExpr(size);
return getIdInValueExpr(false, size);
}
@Override
public String getIdInValueExpr(int size) {
public String getIdInValueExpr(boolean not, int size) {
if (size <= 0) {
throw new IndexOutOfBoundsException("The size must be at least 1");
}
StringBuilder sb = new StringBuilder(2 * size + 10);
sb.append(" in");
sb.append(" (?");
for (int i = 1; i < size; i++) {
sb.append(",?");
}
sb.append(") ");
return sb.toString();
return multiValueBind.getInExpression(not, scalarType, size);
}
@Override
public void addIdInBindValue(SpiExpressionRequest request, Object value) {
value = convertSetId(value, null);
request.addBindValue(value);
public void addIdInBindValues(DefaultSqlUpdate sqlUpdate, Collection<?> ids) {
sqlUpdate.addParameter(new MultiValueWrapper(ids));
}
@Override
public void addIdInBindValues(SpiExpressionRequest request, Collection<?> values) {
List<Object> copy = new ArrayList<>(values);
for (int i = 0; i < copy.size(); i++) {
copy.set(i, convertSetId(copy.get(i), null));
}
request.addBindValue(new MultiValueWrapper(copy));
}
@Override
@@ -192,8 +192,8 @@ public class ElPropertyChain implements ElPropertyValue {
}
@Override
public String getAssocIdInValueExpr(int size) {
return lastElPropertyValue.getAssocIdInValueExpr(size);
public String getAssocIdInValueExpr(boolean not, int size) {
return lastElPropertyValue.getAssocIdInValueExpr(not, size);
}
@Override
@@ -14,7 +14,7 @@ public interface ElPropertyValue extends ElPropertyDeploy, ExpressionPath {
/**
* Return the logical id value expression taking into account embedded id's.
*/
String getAssocIdInValueExpr(int size);
String getAssocIdInValueExpr(boolean not, int size);
/**
* Return the logical id in expression taking into account embedded id's.
@@ -160,4 +160,8 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
return bindValues;
}
@Override
public void appendInExpression(boolean not, Object[] bindValues) {
append(binder.getInExpression(not, bindValues));
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.expression;
import io.ebean.event.BeanQueryRequest;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
@@ -18,10 +19,17 @@ public class IdInExpression extends NonPrepareExpression {
private final Collection<?> idCollection;
private boolean multiValueIdSupported;
public IdInExpression(Collection<?> idCollection) {
this.idCollection = idCollection;
}
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
multiValueIdSupported = request.isMultiValueIdSupported();
}
@Override
public String nestedPath(BeanDescriptor<?> desc) {
return null;
@@ -49,10 +57,7 @@ public class IdInExpression extends NonPrepareExpression {
DefaultExpressionRequest r = (DefaultExpressionRequest) request;
BeanDescriptor<?> descriptor = r.getBeanDescriptor();
IdBinder idBinder = descriptor.getIdBinder();
for (Object id : idCollection) {
idBinder.addIdInBindValue(request, id);
}
idBinder.addIdInBindValues(request, idCollection);
}
/**
@@ -67,7 +72,7 @@ public class IdInExpression extends NonPrepareExpression {
request.append("1=0"); // append false for this stage
} else {
request.append(descriptor.getIdBinder().getBindIdInSql(null));
String inClause = idBinder.getIdInValueExpr(idCollection.size());
String inClause = idBinder.getIdInValueExpr(false, idCollection.size());
request.append(inClause);
}
}
@@ -82,7 +87,7 @@ public class IdInExpression extends NonPrepareExpression {
request.append("1=0"); // append false for this stage
} else {
request.append(descriptor.getIdBinderInLHSSql());
String inClause = idBinder.getIdInValueExpr(idCollection.size());
String inClause = idBinder.getIdInValueExpr(false, idCollection.size());
request.append(inClause);
}
}
@@ -92,7 +97,12 @@ public class IdInExpression extends NonPrepareExpression {
*/
@Override
public void queryPlanHash(StringBuilder builder) {
builder.append("IdIn[").append("?").append(idCollection.size()).append("]");
builder.append("IdIn[?");
if (!multiValueIdSupported) {
// query plan specific to the number of parameters in the IN clause
builder.append(idCollection.size());
}
builder.append("]");
}
@Override
@@ -5,11 +5,13 @@ import io.ebean.event.BeanQueryRequest;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.persist.MultiValueWrapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
class InExpression extends AbstractExpression {
@@ -20,6 +22,8 @@ class InExpression extends AbstractExpression {
private Object[] bindValues;
private boolean multiValueSupported;
InExpression(String propertyName, Collection<?> sourceValues, boolean not) {
super(propertyName);
this.sourceValues = sourceValues;
@@ -43,6 +47,9 @@ class InExpression extends AbstractExpression {
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
bindValues = values();
if (bindValues.length > 0) {
multiValueSupported = request.isMultiValueSupported((bindValues[0]).getClass());
}
}
@Override
@@ -58,19 +65,24 @@ class InExpression extends AbstractExpression {
prop = null;
}
for (Object bindValue : bindValues) {
if (prop == null) {
request.addBindValue(bindValue);
} else {
if (prop == null) {
if (bindValues.length > 0) {
// if we have no property, we wrap them in a multi value wrapper.
// later the binder will decide, which bind strategy to use.
request.addBindValue(new MultiValueWrapper(Arrays.asList(bindValues)));
}
} else {
List<Object> idList = new ArrayList<>();
for (Object bindValue : bindValues) {
// extract the id values from the bean
Object[] ids = prop.getAssocIdValues((EntityBean) bindValue);
if (ids != null) {
for (Object id : ids) {
request.addBindValue(id);
}
Collections.addAll(idList, ids);
}
}
if (!idList.isEmpty()) {
request.addBindValue(new MultiValueWrapper(idList));
}
}
}
@@ -90,23 +102,12 @@ class InExpression extends AbstractExpression {
if (prop != null) {
request.append(prop.getAssocIdInExpr(propName));
String inClause = prop.getAssocIdInValueExpr(bindValues.length);
if (not) {
request.append(" not");
}
String inClause = prop.getAssocIdInValueExpr(not, bindValues.length);
request.append(inClause);
} else {
request.append(propName);
if (not) {
request.append(" not");
}
request.append(" in (?");
for (int i = 1; i < bindValues.length; i++) {
request.append(", ").append("?");
}
request.append(" ) ");
request.appendInExpression(not, bindValues);
}
}
@@ -121,7 +122,12 @@ class InExpression extends AbstractExpression {
builder.append("In[");
}
builder.append(propName);
builder.append(" ?").append(bindValues.length).append("]");
builder.append(" ?");
if (!multiValueSupported) {
// query plan specific to the number of parameters in the IN clause
builder.append(bindValues.length);
}
builder.append("]");
}
@Override
@@ -5,6 +5,7 @@ import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.server.core.DbExpressionHandler;
import io.ebeaninternal.server.core.Message;
import io.ebeaninternal.server.core.timezone.DataTimeZone;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.TypeManager;
@@ -19,6 +20,7 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
@@ -38,17 +40,20 @@ public class Binder {
private final DataTimeZone dataTimeZone;
private final MultiValueBind multiValueBind;
/**
* Set the PreparedStatement with which to bind variables to.
*/
public Binder(TypeManager typeManager, int asOfBindCount, boolean asOfStandardsBased,
DbExpressionHandler dbExpressionHandler, DataTimeZone dataTimeZone) {
DbExpressionHandler dbExpressionHandler, DataTimeZone dataTimeZone, MultiValueBind multiValueBind) {
this.typeManager = typeManager;
this.asOfBindCount = asOfBindCount;
this.asOfStandardsBased = asOfStandardsBased;
this.dbExpressionHandler = dbExpressionHandler;
this.dataTimeZone = dataTimeZone;
this.multiValueBind = multiValueBind;
}
/**
@@ -168,6 +173,26 @@ public class Binder {
}
}
/**
* Return true if MultiValue binding is supported for the given type.
*/
public boolean isMultiValueSupported(Class<?> cls) {
try {
ScalarType<?> scalarType = getScalarType(cls);
return multiValueBind.isTypeSupported(scalarType.getJdbcType());
} catch (PersistenceException e) {
return false;
}
}
private ScalarType<?> getScalarType(Class<?> clazz) {
ScalarType<?> type = typeManager.getScalarType(clazz);
if (type == null) {
throw new PersistenceException("No ScalarType registered for " + clazz);
}
return type;
}
/**
* Bind an Object with unknown data type.
*/
@@ -178,15 +203,19 @@ public class Binder {
bindObject(dataBind, null, Types.OTHER);
return null;
} else if (value instanceof MultiValueWrapper) {
MultiValueWrapper wrapper = (MultiValueWrapper) value;
Collection<?> values = wrapper.getValues();
ScalarType<?> type = getScalarType(wrapper.getType());
int dbType = type.getJdbcType();
// let the multiValueBind decide what to do with the value
multiValueBind.bindMultiValues(dataBind, values, type, one -> bindObject(dataBind, one, dbType));
return values;
} else {
ScalarType<?> type = typeManager.getScalarType(value.getClass());
if (type == null) {
// the type is not registered with the TypeManager.
String msg = "No ScalarType registered for " + value.getClass();
throw new PersistenceException(msg);
} else if (!type.isJdbcNative()) {
ScalarType<?> type = getScalarType(value.getClass());
if (!type.isJdbcNative()) {
// convert to a JDBC native type
value = type.toJdbcType(value);
}
@@ -197,6 +226,14 @@ public class Binder {
}
}
/**
* Return the SQL in clause taking into account Multi-value support.
*/
public String getInExpression(boolean not, Object[] bindValues) {
ScalarType<?> type = getScalarType(bindValues[0].getClass());
return multiValueBind.getInExpression(not, type, bindValues.length);
}
/**
* bind a single value.
* <p>
@@ -0,0 +1,40 @@
package io.ebeaninternal.server.persist;
import java.util.Collection;
/**
* Wraps the multi values that are used for "property in (...)" queries
* @author Roland Praml, FOCONIS AG
*/
public class MultiValueWrapper {
private final Collection<?> values;
private Class<?> type;
public MultiValueWrapper(Collection<?> values) {
this.values = values;
this.type = values.iterator().next().getClass();
}
public Collection<?> getValues() {
return values;
}
public Class<?> getType() {
return type;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("Array[" + values.size() + "]={");
for (Object value : values) {
sb.append(value).append(',');
if (sb.length() > 50) {
sb.append("...}");
return sb.toString();
}
}
sb.setLength(sb.length() - 1);
sb.append('}');
return sb.toString();
}
}
@@ -0,0 +1,93 @@
package io.ebeaninternal.server.persist.platform;
import io.ebean.config.dbplatform.ExtraDbTypes;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.ScalarType;
import java.sql.SQLException;
import java.util.Collection;
import static java.sql.Types.BIGINT;
import static java.sql.Types.BIT;
import static java.sql.Types.BOOLEAN;
import static java.sql.Types.CHAR;
import static java.sql.Types.DATE;
import static java.sql.Types.DECIMAL;
import static java.sql.Types.DOUBLE;
import static java.sql.Types.FLOAT;
import static java.sql.Types.INTEGER;
import static java.sql.Types.NCHAR;
import static java.sql.Types.NUMERIC;
import static java.sql.Types.NVARCHAR;
import static java.sql.Types.REAL;
import static java.sql.Types.SMALLINT;
import static java.sql.Types.TIMESTAMP;
import static java.sql.Types.TIMESTAMP_WITH_TIMEZONE;
import static java.sql.Types.TIME_WITH_TIMEZONE;
import static java.sql.Types.TINYINT;
import static java.sql.Types.VARCHAR;
/**
* Base MultiValueBind for platform specific support.
*/
abstract class AbstractMultiValueBind extends MultiValueBind {
@Override
public boolean isSupported() {
return true;
}
@Override
public boolean isTypeSupported(int jdbcType) {
return getArrayType(jdbcType) != null;
}
@Override
public void bindMultiValues(DataBind dataBind, Collection<?> values, ScalarType<?> type, BindOne bindOne) throws SQLException {
String arrayType = getArrayType(type.getJdbcType());
if (arrayType == null) {
super.bindMultiValues(dataBind, values, type, bindOne);
} else {
dataBind.setArray(arrayType, toArray(values, type));
}
}
protected String getArrayType(int dbType) {
switch(dbType) {
case TINYINT:
case SMALLINT:
case INTEGER:
case BIGINT:
case DECIMAL: // TODO: we have no info about precision here
case NUMERIC:
return "bigint";
case REAL:
case FLOAT:
case DOUBLE:
return "float";
case BIT:
case BOOLEAN:
return "bit";
case DATE:
return "date";
case TIMESTAMP:
case TIME_WITH_TIMEZONE:
case TIMESTAMP_WITH_TIMEZONE:
return "timestamp";
//case LONGVARCHAR:
//case CLOB:
case CHAR:
case VARCHAR:
//case LONGNVARCHAR:
//case NCLOB:
case NCHAR:
case NVARCHAR:
return "varchar";
case ExtraDbTypes.UUID: // Db Native UUID
return "varchar";
default:
return null;
}
}
}
@@ -0,0 +1,23 @@
package io.ebeaninternal.server.persist.platform;
import io.ebeaninternal.server.type.ScalarType;
/**
* Multi value binder that uses SqlServers Table-value parameters
* @author Roland Praml, FOCONIS AG
*
*/
public class H2MultiValueBind extends AbstractMultiValueBind {
@Override
public String getInExpression(boolean not, ScalarType<?> type, int size) {
String arrayType = getArrayType(type.getJdbcType());
if (arrayType == null) {
return super.getInExpression(not, type, size);
} else {
StringBuilder sb = new StringBuilder(50);
sb.append(" in (select * from table(x ").append(arrayType).append(" = ?)) ");
return sb.toString();
}
}
}
@@ -0,0 +1,70 @@
package io.ebeaninternal.server.persist.platform;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.ScalarType;
import java.sql.SQLException;
import java.util.Collection;
/**
* Default implementation for multi value help.
*/
public class MultiValueBind {
@FunctionalInterface
public interface BindOne {
void bind(Object value) throws SQLException;
}
protected Object[] toArray(Collection<?> values, ScalarType<?> type) {
Object[] array = new Object[values.size()];
int i = 0;
for (Object value : values) {
array[i++] = type.toJdbcType(value);
}
return array;
}
/**
* Defaults to not supported and using a bind value per element.
*/
public boolean isSupported() {
return false;
}
/**
* Defaults to not supported and using a bind value per element.
*/
public boolean isTypeSupported(int jdbcType) {
return false;
}
/**
* Default for multi values. They are appended one by one.
*/
public void bindMultiValues(DataBind dataBind, Collection<?> values, ScalarType<?> type, BindOne bindOne) throws SQLException {
for (Object value : values) {
if (!type.isJdbcNative()) {
value = type.toJdbcType(value);
}
bindOne.bind(value);
}
}
/**
* Appends the 'in' expression to the request. Must add leading & trailing space!
*/
public String getInExpression(boolean not, ScalarType<?> type, int size) {
StringBuilder sb = new StringBuilder();
if (not) {
sb.append(" not");
}
sb.append(" in (?");
for (int i = 1; i < size; i++) {
sb.append(", ").append("?");
}
sb.append(" ) ");
return sb.toString();
}
}
@@ -0,0 +1,25 @@
package io.ebeaninternal.server.persist.platform;
import io.ebean.config.dbplatform.ExtraDbTypes;
import io.ebeaninternal.server.type.ScalarType;
/**
* Multi value binder that uses Postgres Array.
*/
public class PostgresMultiValueBind extends AbstractMultiValueBind {
@Override
public String getInExpression(boolean not, ScalarType<?> type, int size) {
int dbType = type.getJdbcType();
if (dbType == ExtraDbTypes.UUID) {
return (not) ? " != all(?::uuid[])" : " = any(?::uuid[])";
}
String arrayType = getArrayType(dbType);
if (arrayType == null) {
return super.getInExpression(not, type, size);
} else {
return (not) ? " != all(?)" : " = any(?)";
}
}
}
@@ -331,7 +331,7 @@ public class CQueryEngine {
* deemed to be a be a paging query - check that the order by contains the id
* property to ensure unique row ordering for predicable paging but only in
* case, this is not a distinct query
*
*
* @param request
*/
private <T> void prepareForPaging(OrmQueryRequest<T> request) {
@@ -10,6 +10,7 @@ import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.core.OrmQueryEngine;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.persist.Binder;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
@@ -28,11 +29,14 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
*/
private final CQueryEngine queryEngine;
private final Binder binder;
/**
* Create the Finder.
*/
public DefaultOrmQueryEngine(CQueryEngine queryEngine) {
public DefaultOrmQueryEngine(CQueryEngine queryEngine, Binder binder) {
this.queryEngine = queryEngine;
this.binder = binder;
}
@Override
@@ -40,6 +44,11 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
return queryEngine.translate(request, bindLog, sql, e);
}
@Override
public boolean isMultiValueSupported(Class<?> cls) {
return binder.isMultiValueSupported(cls);
}
/**
* Flushes the jdbc batch by default unless explicitly turned off on the transaction.
*/
@@ -2,12 +2,12 @@ package io.ebeaninternal.server.querydefn;
import io.ebean.OrderBy;
import io.ebean.Query;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.CQueryPlanKey;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.rawsql.SpiRawSql;
/**
* Query plan key for ORM queries.
@@ -109,6 +109,10 @@ class OrmQueryPlanKey implements CQueryPlanKey {
return planHash;
}
public String toString() {
return description + " maxRows:" + maxRows + " firstRow:" + firstRow + " rawSqlKey:" + rawSqlKey + " planHash:" + planHash;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;