mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Merge branch 'feature/1186-MultiValueBind'
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -3,15 +3,20 @@ package io.ebean;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.util.StringHelper;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.server.core.HelpCreateQueryRequest;
|
||||
import io.ebeaninternal.server.core.OrmQueryRequest;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import org.tests.model.basic.Country;
|
||||
import org.avaje.agentloader.AgentLoader;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.tests.model.basic.Country;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@RunWith(ConditionalTestRunner.class)
|
||||
public abstract class BaseTestCase {
|
||||
|
||||
@@ -81,7 +86,7 @@ public abstract class BaseTestCase {
|
||||
public boolean isDb2() {
|
||||
return Platform.DB2 == platform();
|
||||
}
|
||||
|
||||
|
||||
public boolean isPostgres() {
|
||||
return Platform.POSTGRES == platform();
|
||||
}
|
||||
@@ -127,4 +132,31 @@ public abstract class BaseTestCase {
|
||||
.setLoadBeanCache(true)
|
||||
.findList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform specific IN clause assert.
|
||||
*/
|
||||
protected void platformAssertIn(String sql, String containsIn) {
|
||||
if (isPostgres()) {
|
||||
assertThat(sql).contains(containsIn+" = any(");
|
||||
} else {
|
||||
assertThat(sql).contains(containsIn+" in ");
|
||||
}
|
||||
// H2 contains("where t0.name in (select * from table(x varchar = ?)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform specific NOT IN clause assert.
|
||||
*/
|
||||
protected void platformAssertNotIn(String sql, String containsIn) {
|
||||
if (isPostgres()) {
|
||||
assertThat(sql).contains(containsIn+" != all(");
|
||||
} else {
|
||||
assertThat(sql).contains(containsIn+" not in ");
|
||||
}
|
||||
}
|
||||
|
||||
protected <T> OrmQueryRequest<T> createQueryRequest(SpiQuery.Type type, Query<T> query, Transaction t) {
|
||||
return HelpCreateQueryRequest.create(server(), type, query, t);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.ebean;
|
||||
|
||||
import org.tests.model.basic.EBasicVer;
|
||||
import org.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.basic.EBasicVer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -29,7 +29,7 @@ public class EbeanServer_deleteAllByIdTest extends BaseTestCase {
|
||||
|
||||
List<String> loggedSql = LoggedSqlCollector.stop();
|
||||
assertThat(loggedSql).hasSize(1);
|
||||
assertThat(loggedSql.get(0)).contains("delete from e_basicver where id in (?,?,?)");
|
||||
platformAssertIn(loggedSql.get(0), "delete from e_basicver where id ");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -55,7 +55,7 @@ public class EbeanServer_deleteAllByIdTest extends BaseTestCase {
|
||||
}
|
||||
List<String> loggedSql = LoggedSqlCollector.stop();
|
||||
assertThat(loggedSql).hasSize(1);
|
||||
assertThat(loggedSql.get(0)).contains("delete from e_basicver where id in (?,?,?)");
|
||||
platformAssertIn(loggedSql.get(0), "delete from e_basicver where id ");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -75,7 +75,7 @@ public class EbeanServer_deleteAllByIdTest extends BaseTestCase {
|
||||
|
||||
List<String> loggedSql = LoggedSqlCollector.stop();
|
||||
assertThat(loggedSql).hasSize(1);
|
||||
assertThat(loggedSql.get(0)).contains("delete from e_basicver where id in (?,?,?)");
|
||||
platformAssertIn(loggedSql.get(0), "delete from e_basicver where id ");
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ public class EbeanServer_deleteAllByIdTest extends BaseTestCase {
|
||||
}
|
||||
List<String> loggedSql = LoggedSqlCollector.stop();
|
||||
assertThat(loggedSql).hasSize(1);
|
||||
assertThat(loggedSql.get(0)).contains("delete from e_basicver where id in (?,?,?)");
|
||||
platformAssertIn(loggedSql.get(0), "delete from e_basicver where id ");
|
||||
}
|
||||
|
||||
private List<EBasicVer> beans(int count) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
|
||||
public class HelpCreateQueryRequest {
|
||||
|
||||
public static <T> OrmQueryRequest<T> create(EbeanServer server, SpiQuery.Type type, Query<T> query, Transaction t) {
|
||||
|
||||
DefaultServer defaultServer = (DefaultServer) server;
|
||||
return (OrmQueryRequest<T>)defaultServer.createQueryRequest(type, query, t);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ public class IdInExpressionTest extends BaseExpressionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isSameByPlan_when_diffBindCount() {
|
||||
public void isSameByPlan_when_diffBindCount_notPrepared() {
|
||||
|
||||
different(exp(10), exp(10, 20));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package io.ebeaninternal.server.expression;
|
||||
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.event.BeanQueryRequest;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.basic.Customer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -10,6 +15,19 @@ import static org.assertj.core.api.StrictAssertions.assertThat;
|
||||
|
||||
public class InExpressionTest extends BaseExpressionTest {
|
||||
|
||||
/**
|
||||
* Request with Multi-Value support.
|
||||
*/
|
||||
private TDQueryRequest<Customer> multi() {
|
||||
return MULTI_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request with NO Multi-Value support.
|
||||
*/
|
||||
private TDQueryRequest<Customer> noMulti() {
|
||||
return NO_MULTI_VALUE;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryPlanHash_given_diffPropertyName_should_differentPlanHash() throws Exception {
|
||||
@@ -19,8 +37,8 @@ public class InExpressionTest extends BaseExpressionTest {
|
||||
InExpression ex1 = new InExpression("foo", values, false);
|
||||
InExpression ex2 = new InExpression("bar", values, false);
|
||||
|
||||
ex1.prepareExpression(null);
|
||||
ex2.prepareExpression(null);
|
||||
ex1.prepareExpression(multi());
|
||||
ex2.prepareExpression(multi());
|
||||
|
||||
different(ex1, ex2);
|
||||
}
|
||||
@@ -34,12 +52,25 @@ public class InExpressionTest extends BaseExpressionTest {
|
||||
InExpression ex1 = new InExpression("foo", values1, false);
|
||||
InExpression ex2 = new InExpression("foo", values2, false);
|
||||
|
||||
ex1.prepareExpression(null);
|
||||
ex2.prepareExpression(null);
|
||||
|
||||
ex1.prepareExpression(noMulti());
|
||||
ex2.prepareExpression(noMulti());
|
||||
different(ex1, ex2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryPlanHash_given_diffBindCount_withMultiSupport_samePlanHash() throws Exception {
|
||||
|
||||
List<Integer> values1 = values(42, 92);
|
||||
List<Integer> values2 = values(42, 92, 82);
|
||||
|
||||
InExpression ex1 = new InExpression("foo", values1, false);
|
||||
InExpression ex2 = new InExpression("foo", values2, false);
|
||||
|
||||
ex1.prepareExpression(multi());
|
||||
ex2.prepareExpression(multi());
|
||||
same(ex1, ex2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryPlanHash_given_diffNotFlag_should_differentPlanHash() throws Exception {
|
||||
|
||||
@@ -48,8 +79,8 @@ public class InExpressionTest extends BaseExpressionTest {
|
||||
InExpression ex1 = new InExpression("foo", values, true);
|
||||
InExpression ex2 = new InExpression("foo", values, false);
|
||||
|
||||
ex1.prepareExpression(null);
|
||||
ex2.prepareExpression(null);
|
||||
ex1.prepareExpression(multi());
|
||||
ex2.prepareExpression(multi());
|
||||
|
||||
different(ex1, ex2);
|
||||
}
|
||||
@@ -62,8 +93,8 @@ public class InExpressionTest extends BaseExpressionTest {
|
||||
InExpression ex1 = new InExpression("foo", values, true);
|
||||
InExpression ex2 = new InExpression("foo", values, true);
|
||||
|
||||
ex1.prepareExpression(null);
|
||||
ex2.prepareExpression(null);
|
||||
ex1.prepareExpression(multi());
|
||||
ex2.prepareExpression(multi());
|
||||
|
||||
same(ex1, ex2);
|
||||
}
|
||||
@@ -78,7 +109,13 @@ public class InExpressionTest extends BaseExpressionTest {
|
||||
|
||||
private InExpression exp(String propName, boolean not, Object... values) {
|
||||
InExpression ex = new InExpression(propName, Arrays.asList(values), not);
|
||||
ex.prepareExpression(null);
|
||||
ex.prepareExpression(multi());
|
||||
return ex;
|
||||
}
|
||||
|
||||
private InExpression expNoMulti(String propName, boolean not, Object... values) {
|
||||
InExpression ex = new InExpression(propName, Arrays.asList(values), not);
|
||||
ex.prepareExpression(noMulti());
|
||||
return ex;
|
||||
}
|
||||
|
||||
@@ -103,13 +140,15 @@ public class InExpressionTest extends BaseExpressionTest {
|
||||
@Test
|
||||
public void isSameByPlan_when_diffBind_same() {
|
||||
|
||||
different(exp("a", false, 10), exp("a", false, 10, 20));
|
||||
same(exp("a", false, 10), exp("a", false, 10, 20));
|
||||
different(expNoMulti("a", false, 10), expNoMulti("a", false, 10, 20));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isSameByPlan_when_diffBindCount() {
|
||||
|
||||
different(exp("a", false, 10), exp("a", false, 10, 20));
|
||||
same(exp("a", false, 10), exp("a", false, 10, 20));
|
||||
different(expNoMulti("a", false, 10), expNoMulti("a", false, 10, 20));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -142,4 +181,42 @@ public class InExpressionTest extends BaseExpressionTest {
|
||||
assertThat(exp("a", false, 10, "ABC").isSameByBind(exp("a", false, 10, "ABC", 30))).isFalse();
|
||||
}
|
||||
|
||||
|
||||
private static final TDQueryRequest<Customer> MULTI_VALUE= new TDQueryRequest<>(true);
|
||||
private static final TDQueryRequest<Customer> NO_MULTI_VALUE = new TDQueryRequest<>(false);
|
||||
|
||||
static class TDQueryRequest<T> implements BeanQueryRequest<T> {
|
||||
|
||||
final boolean supported;
|
||||
|
||||
TDQueryRequest(boolean supported) {
|
||||
this.supported = supported;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EbeanServer getEbeanServer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transaction getTransaction() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query<T> getQuery() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMultiValueIdSupported() {
|
||||
return supported;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMultiValueSupported(Class<?> valueType) {
|
||||
return supported;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -78,9 +78,14 @@ public class TDSpiExpressionRequest implements SpiExpressionRequest {
|
||||
public void appendLike() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String escapeLikeString(String value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendInExpression(boolean not, Object[] bindValues) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.Query;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.basic.Customer;
|
||||
import org.tests.model.basic.ResetBasicData;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -167,7 +167,7 @@ public class EqlParserTest extends BaseTestCase {
|
||||
Query<Customer> query = parse("where name in ('Rob','Jim')");
|
||||
query.findList();
|
||||
|
||||
assertThat(query.getGeneratedSql()).contains("where t0.name in (?, ? )");
|
||||
platformAssertIn(query.getGeneratedSql(),"where t0.name");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -178,7 +178,7 @@ public class EqlParserTest extends BaseTestCase {
|
||||
query.setParameter("two", "Bar");
|
||||
query.findList();
|
||||
|
||||
assertThat(query.getGeneratedSql()).contains("where t0.name in (?, ? )");
|
||||
platformAssertIn(query.getGeneratedSql(),"where t0.name");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -189,7 +189,7 @@ public class EqlParserTest extends BaseTestCase {
|
||||
query.setParameter("two", "Bar");
|
||||
query.findList();
|
||||
|
||||
assertThat(query.getGeneratedSql()).contains("where t0.name in (?, ? )");
|
||||
platformAssertIn(query.getGeneratedSql(),"where t0.name");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -200,7 +200,7 @@ public class EqlParserTest extends BaseTestCase {
|
||||
query.setParameter("two", "Bar");
|
||||
query.findList();
|
||||
|
||||
assertThat(query.getGeneratedSql()).contains("where t0.name in (?, ? )");
|
||||
platformAssertIn(query.getGeneratedSql(),"where t0.name");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -210,7 +210,7 @@ public class EqlParserTest extends BaseTestCase {
|
||||
query.setParameter("names", Arrays.asList("Baz", "Maz", "Jim"));
|
||||
query.findList();
|
||||
|
||||
assertThat(query.getGeneratedSql()).contains("where t0.name in (?, ?, ? )");
|
||||
platformAssertIn(query.getGeneratedSql(),"where t0.name");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -3,9 +3,11 @@ package io.ebeaninternal.server.querydefn;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.server.core.OrmQueryRequest;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.basic.Customer;
|
||||
import org.tests.model.basic.Order;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -91,7 +93,11 @@ public class DefaultOrmQueryTest extends BaseTestCase {
|
||||
}
|
||||
|
||||
private void prepare(DefaultOrmQuery<?> q1, DefaultOrmQuery<?> q2) {
|
||||
q1.prepare(null);
|
||||
q2.prepare(null);
|
||||
|
||||
OrmQueryRequest<?> r1 = createQueryRequest(SpiQuery.Type.LIST, q1, null);
|
||||
q1.prepare(r1);
|
||||
|
||||
OrmQueryRequest<?> r2 = createQueryRequest(SpiQuery.Type.LIST, q2, null);
|
||||
q2.prepare(r2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public class TestBatchLazyWithCacheHits extends BaseTestCase {
|
||||
// batch lazy loading into cache
|
||||
assertThat(sql).hasSize(2);
|
||||
assertThat(sql.get(0)).contains("from uuone t0 where t0.name like ");
|
||||
assertThat(sql.get(1)).contains("from uuone t0 where t0.id in (?,");
|
||||
platformAssertIn(sql.get(1), "from uuone t0 where t0.id");
|
||||
|
||||
statistics = beanCache.getStatistics(true);
|
||||
assertThat(statistics.getSize()).isGreaterThan(3);
|
||||
|
||||
@@ -36,7 +36,7 @@ public class TestQueryJoinToAssocOne extends BaseTestCase {
|
||||
String secondaryQuery = trimSql(loggedSql.get(1), 1);
|
||||
assertThat(secondaryQuery).contains("select t0.order_id, t0.id,");
|
||||
assertThat(secondaryQuery).contains(" from o_order_detail t0 left join o_product t1");
|
||||
assertThat(secondaryQuery).contains(" (t0.order_id) in (?");
|
||||
platformAssertIn(secondaryQuery, " (t0.order_id)");
|
||||
assertThat(secondaryQuery).contains(" order by t0.order_id, t0.id");
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class TestQueryJoinToAssocOne extends BaseTestCase {
|
||||
String secondaryQuery = trimSql(loggedSql.get(1), 1);
|
||||
assertThat(secondaryQuery).contains("select t0.order_id, t0.id,");
|
||||
assertThat(secondaryQuery).contains(" from o_order_detail t0 left join o_product t1");
|
||||
assertThat(secondaryQuery).contains(" (t0.order_id) in (?");
|
||||
platformAssertIn(secondaryQuery, " (t0.order_id)");
|
||||
assertThat(secondaryQuery).contains(" order by t0.order_id, t0.id");
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ public class TestQueryJoinToAssocOne extends BaseTestCase {
|
||||
String secondaryQuery = trimSql(loggedSql.get(1), 1);
|
||||
assertThat(secondaryQuery).contains("select t0.order_id, t0.id,");
|
||||
assertThat(secondaryQuery).contains(" from o_order_detail t0 left join o_product t1");
|
||||
assertThat(secondaryQuery).contains(" (t0.order_id) in (?");
|
||||
platformAssertIn(secondaryQuery, " (t0.order_id)");
|
||||
assertThat(secondaryQuery).contains(" order by t0.order_id, t0.id");
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@ public class TestSecondaryQueries extends BaseTestCase {
|
||||
|
||||
assertThat(sql).hasSize(2);
|
||||
assertThat(trimSql(sql.get(0), 2)).contains("select t0.id, t0.status, t0.kcustomer_id from o_order t0");
|
||||
assertThat(trimSql(sql.get(1), 2)).contains("select t0.id, t0.name from o_customer t0 where t0.id in");
|
||||
assertThat(trimSql(sql.get(1), 2)).contains("select t0.id, t0.name from o_customer t0 where t0.id");
|
||||
platformAssertIn(sql.get(1), " where t0.id");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -71,7 +72,8 @@ public class TestSecondaryQueries extends BaseTestCase {
|
||||
|
||||
sql = LoggedSqlCollector.stop();
|
||||
assertThat(sql).hasSize(1);
|
||||
assertThat(trimSql(sql.get(0), 1)).contains("select t0.id, t0.name from o_customer t0 where t0.id in");
|
||||
assertThat(trimSql(sql.get(0), 1)).contains("select t0.id, t0.name from o_customer t0 where t0.id");
|
||||
platformAssertIn(sql.get(0), " where t0.id");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -123,7 +125,8 @@ public class TestSecondaryQueries extends BaseTestCase {
|
||||
assertThat(generatedSql).contains("from o_customer t0 where t0.id = ?");
|
||||
|
||||
assertEquals(2, sql.size());
|
||||
assertThat(sql.get(1)).contains("from contact t0 where (t0.customer_id) in (?)");
|
||||
assertThat(sql.get(1)).contains("from contact t0 where (t0.customer_id) ");
|
||||
platformAssertIn(sql.get(1), " where (t0.customer_id)");
|
||||
}
|
||||
|
||||
|
||||
@@ -179,7 +182,8 @@ public class TestSecondaryQueries extends BaseTestCase {
|
||||
// from o_order_detail t0
|
||||
// where (t0.order_id) in (?,?,?,?,?) ; --bind(1,4,1,1,1)
|
||||
|
||||
assertThat(ordSecondarySql).contains(" from o_order_detail t0 where t0.id > 0 and (t0.order_id) in (?");
|
||||
assertThat(ordSecondarySql).contains(" from o_order_detail t0 where t0.id > 0 and (t0.order_id) ");
|
||||
platformAssertIn(ordSecondarySql, "and (t0.order_id)");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -118,9 +118,9 @@ public class DeleteById_SoftDelete_Tests extends BaseTestCase {
|
||||
List<String> sql = LoggedSqlCollector.stop();
|
||||
assertThat(sql).hasSize(1);
|
||||
if (isPlatformBooleanNative()) {
|
||||
assertThat(sql.get(0)).contains("update cover set deleted=true where id in (?,?)");
|
||||
assertThat(sql.get(0)).contains("update cover set deleted=true where id ");
|
||||
} else {
|
||||
assertThat(sql.get(0)).contains("update cover set deleted=1 where id in (?,?)");
|
||||
assertThat(sql.get(0)).contains("update cover set deleted=1 where id ");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,9 +144,9 @@ public class DeleteById_SoftDelete_Tests extends BaseTestCase {
|
||||
List<String> sql = LoggedSqlCollector.stop();
|
||||
assertThat(sql).hasSize(1);
|
||||
if (isPlatformBooleanNative()) {
|
||||
assertThat(sql.get(0)).contains("update cover set deleted=true where id in (?,?)");
|
||||
assertThat(sql.get(0)).contains("update cover set deleted=true where id");
|
||||
} else {
|
||||
assertThat(sql.get(0)).contains("update cover set deleted=1 where id in (?,?)");
|
||||
assertThat(sql.get(0)).contains("update cover set deleted=1 where id");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ public class DeleteById_SoftDelete_Tests extends BaseTestCase {
|
||||
|
||||
List<String> sql = LoggedSqlCollector.stop();
|
||||
assertThat(sql).hasSize(1);
|
||||
assertThat(sql.get(0)).contains("delete from cover where id in (?,?)");
|
||||
platformAssertIn(sql.get(0), "delete from cover where id ");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -178,7 +178,7 @@ public class DeleteById_SoftDelete_Tests extends BaseTestCase {
|
||||
|
||||
List<String> sql = LoggedSqlCollector.stop();
|
||||
assertThat(sql).hasSize(1);
|
||||
assertThat(sql.get(0)).contains("delete from cover where id in (?,?)");
|
||||
platformAssertIn(sql.get(0), "delete from cover where id ");
|
||||
}
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ public class DeleteById_SoftDelete_Tests extends BaseTestCase {
|
||||
|
||||
List<String> sql = LoggedSqlCollector.stop();
|
||||
assertThat(sql).hasSize(1);
|
||||
assertThat(sql.get(0)).contains("delete from cover where id in (?,?)");
|
||||
platformAssertIn(sql.get(0), "delete from cover where id ");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -57,7 +57,8 @@ public class TestQueryFetchManyTwoDeep extends BaseTestCase {
|
||||
|
||||
//SpiQuery<?> secondaryQuery = secondaryQueries.get(0);
|
||||
String secondarySql = sql.get(1);
|
||||
assertThat(secondarySql).contains("from o_order_detail t0 where t0.id > 0 and (t0.order_id) in");
|
||||
assertThat(secondarySql).contains("from o_order_detail t0 where t0.id > 0 and (t0.order_id) ");
|
||||
platformAssertIn(secondarySql, "(t0.order_id)");
|
||||
|
||||
// select t0.order_id c0, t0.id c1, t0.order_qty c2, t0.ship_qty c3, t0.unit_price c4, t0.cretime c5, t0.updtime c6, t0.order_id c7, t0.product_id c8
|
||||
// from o_order_detail t0
|
||||
|
||||
@@ -76,9 +76,9 @@ public class TestQueryFilterMany extends BaseTestCase {
|
||||
|
||||
assertThat(sql).hasSize(3);
|
||||
assertThat(sql.get(0)).contains(" from o_customer t0; --bind()");
|
||||
assertThat(sql.get(1)).contains(" from contact t0 where (t0.customer_id) in");
|
||||
platformAssertIn(sql.get(1), " from contact t0 where (t0.customer_id)");
|
||||
assertThat(sql.get(1)).contains(" and t0.first_name is not null");
|
||||
assertThat(sql.get(2)).contains(" from contact_note t0 where (t0.contact_id) in");
|
||||
platformAssertIn(sql.get(2), " from contact_note t0 where (t0.contact_id)");
|
||||
assertThat(sql.get(2)).contains(" and lower(t0.title) like");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ public class TestQueryInAssocOne extends BaseTestCase {
|
||||
String sql = query.getGeneratedSql();
|
||||
|
||||
assertThat(sql).contains("join o_customer t1 on t1.id = t0.kcustomer_id");
|
||||
assertThat(sql).contains("t0.kcustomer_id in (?");
|
||||
platformAssertIn(sql, "where t0.kcustomer_id");
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public class TestQueryInAssocOne extends BaseTestCase {
|
||||
String sql = query.getGeneratedSql();
|
||||
|
||||
assertThat(sql).contains("join o_customer t1 on t1.id = t0.kcustomer_id");
|
||||
assertThat(sql).contains("t0.kcustomer_id in (?");
|
||||
platformAssertIn(sql, "t0.kcustomer_id");
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,6 @@ public class TestQueryInAssocOne extends BaseTestCase {
|
||||
String sql = query.getGeneratedSql();
|
||||
|
||||
assertThat(sql).contains("join o_customer t1 on t1.id = t0.kcustomer_id");
|
||||
assertThat(sql).contains("t0.kcustomer_id not in (?");
|
||||
platformAssertNotIn(sql, "t0.kcustomer_id");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ public class TestSubQuery extends BaseTestCase {
|
||||
|
||||
List<Integer> productIds = new ArrayList<>();
|
||||
productIds.add(3);
|
||||
productIds.add(4);
|
||||
productIds.add(5);
|
||||
|
||||
Query<Order> sq = Ebean.createQuery(Order.class).select("id").where()
|
||||
.in("details.product.id", productIds).query();
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.tests.query;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.Query;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.basic.Country;
|
||||
import org.tests.model.basic.ResetBasicData;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestWhereIn extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void testInVarchar() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
Query<Country> query = Ebean.find(Country.class)
|
||||
.where().in("code", "NZ", "AU")
|
||||
.query();
|
||||
|
||||
query.findList();
|
||||
if (isPostgres()) {
|
||||
assertThat(sqlOf(query)).contains(" = any(");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNotInVarchar() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
Query<Country> query = Ebean.find(Country.class)
|
||||
.where().notIn("code", "NZ", "SA", "US")
|
||||
.query();
|
||||
|
||||
query.findList();
|
||||
if (isPostgres()) {
|
||||
assertThat(sqlOf(query)).contains(" != all(");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,8 @@ public class TestManyLazyLoadingQuery extends BaseTestCase {
|
||||
beanProperty.addWhereParentIdIn(query0, parentIds, false);
|
||||
|
||||
query0.findList();
|
||||
assertThat(query0.getGeneratedSql()).contains(" from o_order_detail t0 where (t0.order_id) in (");
|
||||
assertThat(query0.getGeneratedSql()).contains(" from o_order_detail t0 where (t0.order_id) ");
|
||||
platformAssertIn(query0.getGeneratedSql(), "where (t0.order_id)");
|
||||
|
||||
} finally {
|
||||
Ebean.endTransaction();
|
||||
|
||||
Reference in New Issue
Block a user