Fix for #37 - disjunction expression should not produce inner join - left outer join instead

This commit is contained in:
Rob Bygrave
2014-05-18 21:17:19 +12:00
parent 5b98d82f58
commit 08602afcff
45 changed files with 956 additions and 331 deletions
@@ -2,8 +2,8 @@ package com.avaje.ebeaninternal.api;
import java.util.List;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
/**
* Request for loading ManyToOne and OneToOne relationships.
@@ -18,10 +18,12 @@ public class LoadBeanRequest extends LoadRequest {
private final boolean loadCache;
public LoadBeanRequest(LoadBeanBuffer LoadBuffer, Transaction transaction, boolean lazy, String lazyLoadProperty,
boolean loadCache) {
super(transaction, lazy);
public LoadBeanRequest(LoadBeanBuffer LoadBuffer, boolean lazy, String lazyLoadProperty, boolean loadCache) {
this(LoadBuffer, null, lazy, lazyLoadProperty, loadCache);
}
public LoadBeanRequest(LoadBeanBuffer LoadBuffer, OrmQueryRequest<?> parentRequest, boolean lazy, String lazyLoadProperty, boolean loadCache) {
super(parentRequest, lazy);
this.LoadBuffer = LoadBuffer;
this.batch = LoadBuffer.getBatch();
this.lazyLoadProperty = lazyLoadProperty;
@@ -2,69 +2,71 @@ package com.avaje.ebeaninternal.api;
import java.util.List;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
/**
* Request for loading Associated One Beans.
* Request for loading Associated Many Beans.
*/
public class LoadManyRequest extends LoadRequest {
private final List<BeanCollection<?>> batch;
private final List<BeanCollection<?>> batch;
private final LoadManyBuffer loadContext;
private final LoadManyBuffer loadContext;
private final boolean onlyIds;
private final boolean onlyIds;
private final boolean loadCache;
public LoadManyRequest(LoadManyBuffer loadContext, Transaction transaction, int batchSize, boolean lazy,
private final boolean loadCache;
public LoadManyRequest(LoadManyBuffer loadContext, int batchSize, boolean lazy,boolean onlyIds, boolean loadCache) {
this(loadContext, null, batchSize, lazy, onlyIds, loadCache);
}
public LoadManyRequest(LoadManyBuffer loadContext, OrmQueryRequest<?> parentRequest, int batchSize, boolean lazy,
boolean onlyIds, boolean loadCache) {
super(transaction, lazy);
this.loadContext = loadContext;
this.batch = loadContext.getBatch();
this.onlyIds = onlyIds;
this.loadCache = loadCache;
}
super(parentRequest, lazy);
this.loadContext = loadContext;
this.batch = loadContext.getBatch();
this.onlyIds = onlyIds;
this.loadCache = loadCache;
}
public String getDescription() {
return "path:" + loadContext.getFullPath() + " size:"+ batch.size();
}
public String getDescription() {
return "path:" + loadContext.getFullPath() + " size:" + batch.size();
}
/**
* Return the batch of collections to actually load.
*/
public List<BeanCollection<?>> getBatch() {
return batch;
}
/**
* Return the batch of collections to actually load.
*/
public List<BeanCollection<?>> getBatch() {
return batch;
}
/**
* Return the load context.
*/
public LoadManyBuffer getLoadContext() {
return loadContext;
}
/**
* Return the load context.
*/
public LoadManyBuffer getLoadContext() {
return loadContext;
}
/**
* Return true if lazy loading should only load the id values.
* <p>
* This for use when lazy loading is invoked on methods such
* as clear() and removeAll() where it generally makes sense to
* only fetch the Id values as the other property information is
* not used.
* </p>
*/
public boolean isOnlyIds() {
return onlyIds;
}
/**
* Return true if lazy loading should only load the id values.
* <p>
* This for use when lazy loading is invoked on methods such as clear() and removeAll() where it
* generally makes sense to only fetch the Id values as the other property information is not
* used.
* </p>
*/
public boolean isOnlyIds() {
return onlyIds;
}
/**
* Return true if we should load the Collection ids into the cache.
*/
public boolean isLoadCache() {
return loadCache;
}
/**
* Return true if we should load the Collection ids into the cache.
*/
public boolean isLoadCache() {
return loadCache;
}
}
@@ -1,22 +1,36 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.Transaction;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
/**
* Request for loading Associated One Beans.
*/
public abstract class LoadRequest {
protected final boolean lazy;
protected final OrmQueryRequest<?> parentRequest;
protected final Transaction transaction;
protected final Transaction transaction;
public LoadRequest(Transaction transaction, boolean lazy) {
protected final boolean lazy;
this.transaction = transaction;
public LoadRequest(OrmQueryRequest<?> parentRequest, boolean lazy) {
this.parentRequest = parentRequest;
this.transaction = parentRequest == null ? null : parentRequest.getTransaction();
this.lazy = lazy;
}
/**
* Log the just executed secondary query with the 'root' query if 'logSecondaryQuery' is set to
* true. This is for testing purposes to confirm the secondary query executes etc.
*/
public void logSecondaryQuery(SpiQuery<?> query) {
if (parentRequest != null && parentRequest.isLogSecondaryQuery()) {
parentRequest.getQuery().logSecondaryQuery(query);
}
}
/**
* Return true if this is a lazy load and false if it is a secondary query.
*/
@@ -1,13 +1,15 @@
package com.avaje.ebeaninternal.api;
import java.io.Serializable;
import java.util.Set;
import java.util.Collection;
import java.util.TreeMap;
import java.util.TreeSet;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
import com.avaje.ebeaninternal.server.query.SplitName;
import com.avaje.ebeaninternal.server.query.SqlJoinType;
/**
* Holds the joins needs to support the many where predicates.
@@ -17,12 +19,32 @@ public class ManyWhereJoins implements Serializable {
private static final long serialVersionUID = -6490181101871795417L;
private final TreeSet<String> joins = new TreeSet<String>();
private final TreeMap<String,PropertyJoin> joins = new TreeMap<String,PropertyJoin>();
private StringBuilder formulaProperties = new StringBuilder();
private boolean formulaWithJoin;
/**
* 'Mode' indicating that joins added while this is true are required to be outer joins.
*/
private boolean requireOuterJoins;
/**
* Return the current 'mode' indicating if outer joins are currently required or not.
*/
public boolean isRequireOuterJoins() {
return requireOuterJoins;
}
/**
* Set the 'mode' to be that joins added are required to be outer joins.
* This is set during the evaluation of disjunction predicates.
*/
public void setRequireOuterJoins(boolean requireOuterJoins) {
this.requireOuterJoins = requireOuterJoins;
}
/**
* Add a many where join.
*/
@@ -34,10 +56,10 @@ public class ManyWhereJoins implements Serializable {
join = addManyToJoin(join, p.getName());
}
if (join != null){
joins.add(join);
addJoin(join);
String secondaryTableJoinPrefix = p.getSecondaryTableJoinPrefix();
if (secondaryTableJoinPrefix != null) {
joins.add(join+"."+secondaryTableJoinPrefix);
addJoin(join+"."+secondaryTableJoinPrefix);
}
addParentJoins(join);
}
@@ -58,11 +80,16 @@ public class ManyWhereJoins implements Serializable {
private void addParentJoins(String join) {
String[] split = SplitName.split(join);
if (split[0] != null){
joins.add(split[0]);
addJoin(split[0]);
addParentJoins(split[0]);
}
}
private void addJoin(String property) {
SqlJoinType joinType = (requireOuterJoins) ? SqlJoinType.OUTER: SqlJoinType.INNER;
joins.put(property, new PropertyJoin(property, joinType));
}
/**
* Return true if there are no extra many where joins.
*/
@@ -73,10 +100,22 @@ public class ManyWhereJoins implements Serializable {
/**
* Return the set of many where joins.
*/
public Set<String> getJoins() {
return joins;
public Collection<PropertyJoin> getPropertyJoins() {
return joins.values();
}
/**
* Return the set of property names for the many where joins.
*/
public TreeSet<String> getPropertyNames() {
TreeSet<String> propertyNames = new TreeSet<String>();
for (PropertyJoin join : joins.values()) {
propertyNames.add(join.getProperty());
}
return propertyNames;
}
/**
* In findRowCount query found a formula property with a join clause so building a select clause
* specifically for the findRowCount query.
@@ -0,0 +1,39 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebeaninternal.server.query.SqlJoinType;
/**
* Represents a join required for a given property and whether than needs to be an outer join.
*/
public class PropertyJoin {
/**
* The property name.
*/
private final String property;
/**
* Set to true if the property needs to be an outer join.
*/
private final SqlJoinType joinType;
public PropertyJoin(String property, SqlJoinType joinType) {
this.property = property;
this.joinType = joinType;
}
/**
* Return the property that should be joined.
*/
public String getProperty() {
return property;
}
/**
* Return true if this join is required to be an outer join.
*/
public SqlJoinType getSqlJoinType() {
return joinType;
}
}
@@ -10,13 +10,12 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
*/
public interface SpiExpression extends Expression {
/**
* Process "Many" properties populating ManyWhereJoins.
* <p>
* Predicates on Many properties require an extra independent
* join clause.
* </p>
*/
/**
* Process "Many" properties populating ManyWhereJoins.
* <p>
* Predicates on Many properties require an extra independent join clause.
* </p>
*/
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins whereManyJoins);
/**
@@ -260,10 +260,25 @@ public interface SpiQuery<T> extends Query<T> {
*/
public Boolean isAutofetch();
// /**
// * Return explicit forUpdate setting or null.
// */
// public boolean isForUpdate();
/**
* Set to true if you want to capture executed secondary queries.
*/
public void setLogSecondaryQuery(boolean logSecondaryQuery);
/**
* Return true if executed secondary queries should be captured.
*/
public boolean isLogSecondaryQuery();
/**
* Return the list of secondary queries that were executed.
*/
public List<SpiQuery<?>> getLoggedSecondaryQueries();
/**
* Log an executed secondary query.
*/
public void logSecondaryQuery(SpiQuery<?> query);
/**
* If return null then no autoFetch profiling for this query. If a
@@ -140,6 +140,9 @@ public class DefaultBeanLoader {
desc.cacheManyPropPut(many, bc, parentId);
}
}
// log the query (for testing secondary queries)
loadRequest.logSecondaryQuery(query);
}
public void loadMany(BeanCollection<?> bc, boolean onlyIds) {
@@ -322,6 +325,9 @@ public class DefaultBeanLoader {
// necessary but allow processing to continue until it is accessed by client code
ebis[i].checkLazyLoadFailure();
}
// log the query (for testing secondary queries)
loadRequest.logSecondaryQuery(query);
}
public void refresh(EntityBean bean) {
@@ -54,7 +54,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
private HashQuery cacheKey;
private HashQueryPlan queryPlanHash;
/**
* Create the InternalQueryRequest.
*/
@@ -363,4 +363,11 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
beanDescriptor.flushPersistenceContextOnIterate(persistenceContext);
}
/**
* Return true if the request wants to log the secondary queries (test purpose).
*/
public boolean isLogSecondaryQuery() {
return query.isLogSecondaryQuery();
}
}
@@ -27,6 +27,7 @@ import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
import com.avaje.ebeaninternal.server.query.SqlBeanLoad;
import com.avaje.ebeaninternal.server.query.SqlJoinType;
import com.avaje.ebeaninternal.server.reflect.BeanReflectGetter;
import com.avaje.ebeaninternal.server.reflect.BeanReflectSetter;
import com.avaje.ebeaninternal.server.text.json.ReadJsonContext;
@@ -509,14 +510,14 @@ public class BeanProperty implements ElPropertyValue {
* Add any extra joins required to support this property. Generally a no
* operation except for a OneToOne exported.
*/
public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) {
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
if (formula && sqlFormulaJoin != null) {
ctx.appendFormulaJoin(sqlFormulaJoin, forceOuterJoin);
ctx.appendFormulaJoin(sqlFormulaJoin, joinType);
} else if (secondaryTableJoin != null) {
String relativePrefix = ctx.getRelativePrefix(secondaryTableJoinPrefix);
secondaryTableJoin.addJoin(forceOuterJoin, relativePrefix, ctx);
secondaryTableJoin.addJoin(joinType, relativePrefix, ctx);
}
}
@@ -16,6 +16,7 @@ import com.avaje.ebeaninternal.server.deploy.id.ImportedIdSimple;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc;
import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.query.SqlJoinType;
/**
* Abstract base for properties mapped to an associated bean, list, set or map.
@@ -129,22 +130,15 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
/**
* Add table join with table alias based on prefix.
*/
public boolean addJoin(boolean forceOuterJoin, String prefix, DbSqlContext ctx) {
return tableJoin.addJoin(forceOuterJoin, prefix, ctx);
public SqlJoinType addJoin(SqlJoinType joinType, String prefix, DbSqlContext ctx) {
return tableJoin.addJoin(joinType, prefix, ctx);
}
/**
* Add table join with explicit table alias.
*/
public boolean addJoin(boolean forceOuterJoin, String a1, String a2, DbSqlContext ctx) {
return tableJoin.addJoin(forceOuterJoin, a1, a2, ctx);
}
/**
* Add table join with explicit table alias.
*/
public void addInnerJoin(String a1, String a2, DbSqlContext ctx) {
tableJoin.addInnerJoin(a1, a2, ctx);
public SqlJoinType addJoin(SqlJoinType joinType, String a1, String a2, DbSqlContext ctx) {
return tableJoin.addJoin(joinType, a1, a2, ctx);
}
/**
@@ -23,6 +23,7 @@ import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.query.SplitName;
import com.avaje.ebeaninternal.server.query.SqlBeanLoad;
import com.avaje.ebeaninternal.server.query.SqlJoinType;
import com.avaje.ebeaninternal.server.text.json.ReadJsonContext;
import com.avaje.ebeaninternal.server.text.json.WriteJsonContext;
@@ -508,9 +509,9 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
}
@Override
public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) {
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
if (!isTransient) {
localHelp.appendFrom(ctx, forceOuterJoin);
localHelp.appendFrom(ctx, joinType);
}
}
@@ -587,7 +588,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
abstract void appendSelect(DbSqlContext ctx, boolean subQuery);
abstract void appendFrom(DbSqlContext ctx, boolean forceOuterJoin);
abstract void appendFrom(DbSqlContext ctx, SqlJoinType joinType);
}
@@ -633,7 +634,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
}
@Override
void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) {
void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
}
@Override
@@ -729,11 +730,11 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
}
@Override
void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) {
void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
if (targetInheritInfo != null) {
// add join to support the discriminator column
String relativePrefix = ctx.getRelativePrefix(name);
tableJoin.addJoin(forceOuterJoin, relativePrefix, ctx);
tableJoin.addJoin(joinType, relativePrefix, ctx);
}
}
@@ -822,10 +823,10 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
}
@Override
void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) {
void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
String relativePrefix = ctx.getRelativePrefix(getName());
tableJoin.addJoin(forceOuterJoin, relativePrefix, ctx);
tableJoin.addJoin(joinType, relativePrefix, ctx);
}
}
@@ -1,5 +1,7 @@
package com.avaje.ebeaninternal.server.deploy;
import com.avaje.ebeaninternal.server.query.SqlJoinType;
/**
* Used to provide context during sql construction.
*/
@@ -72,7 +74,7 @@ public interface DbSqlContext {
* Append a Sql Formula join. This converts the "${ta}" keyword to the current
* table alias.
*/
public void appendFormulaJoin(String sqlFormulaJoin, boolean forceOuterJoin);
public void appendFormulaJoin(String sqlFormulaJoin, SqlJoinType joinType);
/**
* Return the current content length.
@@ -10,6 +10,7 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoin;
import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoinColumn;
import com.avaje.ebeaninternal.server.query.SplitName;
import com.avaje.ebeaninternal.server.query.SqlBeanLoad;
import com.avaje.ebeaninternal.server.query.SqlJoinType;
/**
* Represents a join to another table.
@@ -32,9 +33,9 @@ public final class TableJoin {
private final String table;
/**
* The type of join. LEFT OUTER etc.
* The type of join as per deployment (cardinality and optionality).
*/
private final String type;
private final SqlJoinType type;
/**
* The persist cascade info.
@@ -60,7 +61,7 @@ public final class TableJoin {
this.importedPrimaryKey = deploy.isImportedPrimaryKey();
this.table = InternString.intern(deploy.getTable());
this.type = InternString.intern(deploy.getType());
this.type = deploy.getType();
this.cascadeInfo = deploy.getCascadeInfo();
this.inheritInfo = deploy.getInheritInfo();
@@ -149,7 +150,7 @@ public final class TableJoin {
/**
* Return the type of join. LEFT OUTER JOIN etc.
*/
public String getType() {
public SqlJoinType getType() {
return type;
}
@@ -157,32 +158,26 @@ public final class TableJoin {
* Return true if this join is a left outer join.
*/
public boolean isOuterJoin() {
return type.equals(LEFT_OUTER);
return type == SqlJoinType.OUTER;
}
public boolean addJoin(boolean forceOuterJoin, String prefix, DbSqlContext ctx) {
public SqlJoinType addJoin(SqlJoinType joinType, String prefix, DbSqlContext ctx) {
String[] names = SplitName.split(prefix);
String a1 = ctx.getTableAlias(names[0]);
String a2 = ctx.getTableAlias(prefix);
return addJoin(forceOuterJoin, a1, a2, ctx);
return addJoin(joinType, a1, a2, ctx);
}
public boolean addJoin(boolean forceOuterJoin, String a1, String a2, DbSqlContext ctx) {
public SqlJoinType addJoin(SqlJoinType joinType, String a1, String a2, DbSqlContext ctx) {
String inheritance = inheritInfo != null ? inheritInfo.getWhere() : null;
ctx.addJoin(forceOuterJoin?LEFT_OUTER:type, table, columns(), a1, a2, inheritance);
String joinLiteral = joinType.getLiteral(type);
ctx.addJoin(joinLiteral, table, columns(), a1, a2, inheritance);
return forceOuterJoin || LEFT_OUTER.equals(type);
}
/**
* Explicitly add a (non-outer) join.
*/
public void addInnerJoin(String a1, String a2, DbSqlContext ctx) {
String inheritance = inheritInfo != null ? inheritInfo.getWhere() : null;
ctx.addJoin(JOIN, table, columns(), a1, a2, inheritance);
return joinType.autoToOuter(type);
}
}
@@ -9,6 +9,7 @@ import com.avaje.ebeaninternal.server.deploy.BeanCascadeInfo;
import com.avaje.ebeaninternal.server.deploy.BeanTable;
import com.avaje.ebeaninternal.server.deploy.InheritInfo;
import com.avaje.ebeaninternal.server.deploy.TableJoin;
import com.avaje.ebeaninternal.server.query.SqlJoinType;
/**
* Represents a join to another table during deployment phase.
@@ -32,7 +33,7 @@ public class DeployTableJoin {
/**
* The type of join. LEFT OUTER etc.
*/
private String type = TableJoin.JOIN;
private SqlJoinType type = SqlJoinType.INNER;
/**
* The list of properties mapped to this joined table.
@@ -162,7 +163,7 @@ public class DeployTableJoin {
/**
* Return the type of join. LEFT OUTER JOIN etc.
*/
public String getType() {
public SqlJoinType getType() {
return type;
}
@@ -170,7 +171,11 @@ public class DeployTableJoin {
* Return true if this join is a left outer join.
*/
public boolean isOuterJoin() {
return type.equals(TableJoin.LEFT_OUTER);
return type == SqlJoinType.OUTER;
}
private void setType(SqlJoinType type) {
this.type = type;
}
/**
@@ -179,13 +184,13 @@ public class DeployTableJoin {
public void setType(String joinType) {
joinType = joinType.toUpperCase();
if (joinType.equalsIgnoreCase(TableJoin.JOIN)) {
type = TableJoin.JOIN;
type = SqlJoinType.INNER;
} else if (joinType.indexOf("LEFT") > -1) {
type = TableJoin.LEFT_OUTER;
type = SqlJoinType.OUTER;
} else if (joinType.indexOf("OUTER") > -1) {
type = TableJoin.LEFT_OUTER;
type = SqlJoinType.OUTER;
} else if (joinType.indexOf("INNER") > -1) {
type = TableJoin.JOIN;
type = SqlJoinType.INNER;
} else {
throw new RuntimeException(Message.msg("join.type.unknown", joinType));
}
@@ -40,7 +40,7 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
private static final long serialVersionUID = -645619859900030678L;
Conjunction(com.avaje.ebean.Query<T> query, ExpressionList<T> parent) {
super(AND, query, parent);
super(false, AND, query, parent);
}
}
@@ -49,17 +49,21 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
private static final long serialVersionUID = -8464470066692221413L;
Disjunction(com.avaje.ebean.Query<T> query, ExpressionList<T> parent) {
super(OR, query, parent);
super(true, OR, query, parent);
}
}
// private final ArrayList<SpiExpression> list = new
// ArrayList<SpiExpression>();
private final DefaultExpressionList<T> exprList;
private final String joinType;
JunctionExpression(String joinType, com.avaje.ebean.Query<T> query, ExpressionList<T> parent) {
/**
* If true then a disjunction which means outer joins are required.
*/
private final boolean disjunction;
JunctionExpression(boolean disjunction, String joinType, com.avaje.ebean.Query<T> query, ExpressionList<T> parent) {
this.disjunction = disjunction;
this.joinType = joinType;
this.exprList = new DefaultExpressionList<T>(query, parent);
}
@@ -68,9 +72,20 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
List<SpiExpression> list = exprList.internalList();
// get the current state for 'require outer joins'
boolean parentOuterJoins = manyWhereJoin.isRequireOuterJoins();
if (disjunction) {
// turn on outer joins required for disjunction expressions
manyWhereJoin.setRequireOuterJoins(true);
}
for (int i = 0; i < list.size(); i++) {
list.get(i).containsMany(desc, manyWhereJoin);
}
if (disjunction && !parentOuterJoins) {
// restore state to not forcing outer joins
manyWhereJoin.setRequireOuterJoins(false);
}
}
public Junction<T> add(Expression item) {
@@ -56,6 +56,13 @@ public abstract class DLoadBaseContext {
if (queryProps == null) {
return batchSize;
}
int queryFetchBatch = queryProps.getQueryFetchBatch();
if (queryFetchBatch > 0) {
// property join was automatically set to a 'query join'
return queryFetchBatch;
}
FetchConfig fetchConfig = queryProps.getFetchConfig();
if (fetchConfig == null) {
return batchSize;
@@ -29,8 +29,8 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
super(parent, desc, path, defaultBatchSize, queryProps);
this.bufferList = new ArrayList<DLoadBeanContext.LoadBuffer>();
this.currentBuffer = createBuffer(firstBatchSize);
this.bufferList = queryFetch ? new ArrayList<DLoadBeanContext.LoadBuffer>() : null;
}
protected void configureQuery(SpiQuery<?> query, String lazyLoadProperty) {
@@ -78,7 +78,7 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
for (LoadBuffer loadBuffer : bufferList) {
if (!loadBuffer.list.isEmpty()) {
boolean loadCache = false;
LoadBeanRequest req = new LoadBeanRequest(loadBuffer, parentRequest.getTransaction(), false, null, loadCache);
LoadBeanRequest req = new LoadBeanRequest(loadBuffer, parentRequest, false, null, loadCache);
parent.getEbeanServer().loadBean(req);
if (!queryProps.isQueryFetchAll()) {
@@ -173,7 +173,7 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
}
}
LoadBeanRequest req = new LoadBeanRequest(this, null, true, ebi.getLazyLoadProperty(), context.hitCache);
LoadBeanRequest req = new LoadBeanRequest(this, true, ebi.getLazyLoadProperty(), context.hitCache);
context.desc.getEbeanServer().loadBean(req);
}
@@ -91,7 +91,7 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
if (bufferList != null) {
for (LoadBuffer loadBuffer : bufferList) {
if (!loadBuffer.list.isEmpty()) {
LoadManyRequest req = new LoadManyRequest(loadBuffer, parentRequest.getTransaction(), requestedBatchSize, false, false, false);
LoadManyRequest req = new LoadManyRequest(loadBuffer, parentRequest, requestedBatchSize, false, false, false);
parent.getEbeanServer().loadMany(req);
if (!queryProps.isQueryFetchAll()) {
// Stop - only fetch the first batch ... the rest will be lazy loaded
@@ -187,7 +187,7 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
// Should reduce the list by checking each beanCollection in the L2 first before executing the query
LoadManyRequest req = new LoadManyRequest(this, null, batchSize, true, onlyIds, useCache);
LoadManyRequest req = new LoadManyRequest(this, batchSize, true, onlyIds, useCache);
context.parent.getEbeanServer().loadMany(req);
}
}
@@ -175,12 +175,11 @@ public class DefaultDbSqlContext implements DbSqlContext {
return this;
}
public void appendFormulaJoin(String sqlFormulaJoin, boolean forceOuterJoin) {
public void appendFormulaJoin(String sqlFormulaJoin, SqlJoinType joinType) {
// replace ${ta} place holder with the real table alias...
String tableAlias = tableAliasStack.peek();
String converted = StringHelper
.replaceString(sqlFormulaJoin, tableAliasPlaceHolder, tableAlias);
String converted = StringHelper.replaceString(sqlFormulaJoin, tableAliasPlaceHolder, tableAlias);
if (formulaJoins == null) {
formulaJoins = new HashSet<String>();
@@ -195,7 +194,7 @@ public class DefaultDbSqlContext implements DbSqlContext {
formulaJoins.add(converted);
sb.append(" ");
if (forceOuterJoin) {
if (joinType == SqlJoinType.OUTER) {
if ("join".equals(sqlFormulaJoin.substring(0, 4).toLowerCase())) {
// prepend left outer as we are in the 'many' part
append(" left outer ");
@@ -0,0 +1,70 @@
package com.avaje.ebeaninternal.server.query;
/**
* Inner join, Outer join or automatic determination based on cardinality and optionality.
*/
public enum SqlJoinType {
/**
* It is an inner join.
*/
INNER("join"),
/**
* It is an outer join.
*/
OUTER("left outer join"),
/**
* It is automatically determined based on cardinality and optionality.
*/
AUTO("JOIN-TYPE-AUTO-LITERAL-NOT-USED");
String literal;
SqlJoinType(String literal) {
this.literal = literal;
}
/**
* Return the SQL join literal.
*/
public String getLiteral() {
return literal;
}
/**
* Return the actual SQL join literal taking into account the current join type and the 'default
* join type as per deployment cardinality and optionality'.
*/
public String getLiteral(SqlJoinType deploymentJoinType) {
if (this == SqlJoinType.AUTO) {
return deploymentJoinType.getLiteral();
}
return this.getLiteral();
}
/**
* If this is an AUTO join set it to OUTER as we are joining to a Many.
*/
public SqlJoinType autoToOuter() {
if (this == AUTO) {
return OUTER;
} else {
return this;
}
}
/**
* If join is AUTO but deploymentJoinType is OUTER then go into OUTER join mode.
*/
public SqlJoinType autoToOuter(SqlJoinType deploymentJoinType) {
if (this == AUTO && deploymentJoinType == OUTER) {
return OUTER;
} else {
return this;
}
}
}
@@ -10,7 +10,11 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.PropertyJoin;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiQuery.Type;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
@@ -25,9 +29,6 @@ import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Factory for SqlTree.
*/
@@ -169,7 +170,7 @@ public class SqlTreeBuilder {
if (rawSql) {
return "Not Used";
}
rootNode.appendFrom(ctx, false);
rootNode.appendFrom(ctx, SqlJoinType.AUTO);
return ctx.getContent();
}
@@ -181,7 +182,7 @@ public class SqlTreeBuilder {
if (!rawSql) {
alias.addJoin(queryDetail.getIncludes(), desc);
alias.addJoin(predicates.getPredicateIncludes(), desc);
alias.addManyWhereJoins(manyWhereJoins.getJoins());
alias.addManyWhereJoins(manyWhereJoins.getPropertyNames());
// build set of table alias
alias.buildAlias();
@@ -237,10 +238,10 @@ public class SqlTreeBuilder {
*/
private void addManyWhereJoins(List<SqlTreeNode> myJoinList) {
Set<String> includes = manyWhereJoins.getJoins();
for (String joinProp : includes) {
BeanPropertyAssoc<?> beanProperty = (BeanPropertyAssoc<?>) desc.getBeanPropertyFromPath(joinProp);
SqlTreeNodeManyWhereJoin nodeJoin = new SqlTreeNodeManyWhereJoin(joinProp, beanProperty);
Collection<PropertyJoin> includes = manyWhereJoins.getPropertyJoins();
for (PropertyJoin joinProp : includes) {
BeanPropertyAssoc<?> beanProperty = (BeanPropertyAssoc<?>) desc.getBeanPropertyFromPath(joinProp.getProperty());
SqlTreeNodeManyWhereJoin nodeJoin = new SqlTreeNodeManyWhereJoin(joinProp.getProperty(), beanProperty, joinProp.getSqlJoinType());
myJoinList.add(nodeJoin);
}
}
@@ -289,12 +290,11 @@ public class SqlTreeBuilder {
// support the predicates or order by clauses.
// remove ManyWhereJoins from the predicateIncludes
predicateIncludes.removeAll(manyWhereJoins.getJoins());
predicateIncludes.removeAll(manyWhereJoins.getPropertyNames());
// look for predicateIncludes that are not in selectIncludes and add
// them as extra joins to the query
IncludesDistiller extraJoinDistill = new IncludesDistiller(desc, selectIncludes,
predicateIncludes);
IncludesDistiller extraJoinDistill = new IncludesDistiller(desc, selectIncludes, predicateIncludes);
Collection<SqlTreeNodeExtraJoin> extraJoins = extraJoinDistill.getExtraJoinRootNodes();
if (extraJoins.isEmpty()) {
@@ -28,7 +28,7 @@ public interface SqlTreeNode {
/**
* Append to the FROM part of the sql.
*/
public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin);
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType);
/**
* Append any where predicates for inheritance.
@@ -28,50 +28,47 @@ public class SqlTreeNodeBean implements SqlTreeNode {
private static final SqlTreeNode[] NO_CHILDREN = new SqlTreeNode[0];
final BeanDescriptor<?> desc;
protected final BeanDescriptor<?> desc;
final IdBinder idBinder;
protected final IdBinder idBinder;
/**
* The children which will be other SelectBean or SelectProxyBean.
*/
final SqlTreeNode[] children;
protected final SqlTreeNode[] children;
final boolean readOnlyLeaf;
protected final boolean readOnlyLeaf;
/**
* Set to true if this is a partial object fetch.
*/
final boolean partialObject;
protected final boolean partialObject;
final BeanProperty[] properties;
protected final BeanProperty[] properties;
/**
* Extra where clause added by Where annotation on associated many.
*/
final String extraWhere;
protected final String extraWhere;
final BeanPropertyAssoc<?> nodeBeanProp;
protected final BeanPropertyAssoc<?> nodeBeanProp;
final TableJoin[] tableJoins;
protected final TableJoin[] tableJoins;
/**
* False if report bean and has no id property.
*/
final boolean readId;
protected final boolean readId;
final boolean disableLazyLoad;
protected final boolean disableLazyLoad;
final InheritInfo inheritInfo;
protected final InheritInfo inheritInfo;
final String prefix;
final Map<String, String> pathMap;
protected final String prefix;
protected final Map<String, String> pathMap;
final BeanPropertyAssocMany<?> lazyLoadParent;
protected final BeanPropertyAssocMany<?> lazyLoadParent;
public SqlTreeNodeBean(String prefix, BeanPropertyAssoc<?> beanProp, SqlTreeProperties props,
List<SqlTreeNode> myChildren, boolean withId) {
@@ -452,20 +449,21 @@ public class SqlTreeNodeBean implements SqlTreeNode {
/**
* Append to the FROM clause for this node.
*/
public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) {
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
ctx.pushJoin(prefix);
ctx.pushTableAlias(prefix);
forceOuterJoin = appendFromBaseTable(ctx, forceOuterJoin);
// join and return SqlJoinType to use for child joins
joinType = appendFromBaseTable(ctx, joinType);
for (int i = 0; i < properties.length; i++) {
// usually nothing... except for 1-1 Exported
properties[i].appendFrom(ctx, forceOuterJoin);
properties[i].appendFrom(ctx, joinType);
}
for (int i = 0; i < children.length; i++) {
children[i].appendFrom(ctx, forceOuterJoin);
children[i].appendFrom(ctx, joinType);
}
ctx.popTableAlias();
@@ -476,7 +474,7 @@ public class SqlTreeNodeBean implements SqlTreeNode {
* Join to base table for this node. This includes a join to the intersection
* table if this is a ManyToMany node.
*/
public boolean appendFromBaseTable(DbSqlContext ctx, boolean forceOuterJoin) {
public SqlJoinType appendFromBaseTable(DbSqlContext ctx, SqlJoinType joinType) {
if (nodeBeanProp instanceof BeanPropertyAssocMany<?>) {
BeanPropertyAssocMany<?> manyProp = (BeanPropertyAssocMany<?>) nodeBeanProp;
@@ -488,14 +486,14 @@ public class SqlTreeNodeBean implements SqlTreeNode {
String alias2 = alias + "z_";
TableJoin manyToManyJoin = manyProp.getIntersectionTableJoin();
manyToManyJoin.addJoin(forceOuterJoin, parentAlias, alias2, ctx);
manyToManyJoin.addJoin(joinType, parentAlias, alias2, ctx);
return nodeBeanProp.addJoin(forceOuterJoin, alias2, alias, ctx);
return nodeBeanProp.addJoin(joinType, alias2, alias, ctx);
}
}
return nodeBeanProp.addJoin(forceOuterJoin, prefix, ctx);
return nodeBeanProp.addJoin(joinType, prefix, ctx);
}
/**
@@ -22,13 +22,13 @@ import com.avaje.ebeaninternal.server.deploy.TableJoin;
public class SqlTreeNodeExtraJoin implements SqlTreeNode {
final BeanPropertyAssoc<?> assocBeanProperty;
private final BeanPropertyAssoc<?> assocBeanProperty;
final String prefix;
private final String prefix;
final boolean manyJoin;
private final boolean manyJoin;
List<SqlTreeNodeExtraJoin> children;
private List<SqlTreeNodeExtraJoin> children;
public SqlTreeNodeExtraJoin(String prefix, BeanPropertyAssoc<?> assocBeanProperty) {
this.prefix = prefix;
@@ -66,7 +66,7 @@ public class SqlTreeNodeExtraJoin implements SqlTreeNode {
children.add(child);
}
public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) {
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
boolean manyToMany = false;
@@ -82,29 +82,29 @@ public class SqlTreeNodeExtraJoin implements SqlTreeNode {
String alias2 = alias+"z_";
TableJoin manyToManyJoin = manyProp.getIntersectionTableJoin();
manyToManyJoin.addJoin(forceOuterJoin, parentAlias, alias2, ctx);
manyToManyJoin.addJoin(joinType, parentAlias, alias2, ctx);
assocBeanProperty.addJoin(forceOuterJoin, alias2, alias, ctx);
assocBeanProperty.addJoin(joinType, alias2, alias, ctx);
}
}
if (!manyToMany){
assocBeanProperty.addJoin(forceOuterJoin, prefix, ctx);
assocBeanProperty.addJoin(joinType, prefix, ctx);
}
if (children != null){
if (manyJoin){
// make sure all decendants use OUTER JOIN
forceOuterJoin = true;
}
for (int i = 0; i < children.size(); i++) {
SqlTreeNodeExtraJoin child = children.get(i);
child.appendFrom(ctx, forceOuterJoin);
}
}
}
if (children != null) {
if (manyJoin) {
// if AUTO then make all decendants use OUTER JOIN
joinType = joinType.autoToOuter();
}
for (int i = 0; i < children.size(); i++) {
SqlTreeNodeExtraJoin child = children.get(i);
child.appendFrom(ctx, joinType);
}
}
}
/**
* Does nothing.
@@ -34,8 +34,8 @@ public final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
* Force outer join for everything after the many property.
*/
@Override
public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) {
super.appendFrom(ctx, true);
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
super.appendFrom(ctx, joinType.autoToOuter());
}
}
@@ -14,20 +14,30 @@ import com.avaje.ebeaninternal.server.deploy.TableJoin;
/**
* Join to Many (or child of a many) to support where clause predicates on many properties.
*
* @author rbygrave
*/
public class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
private final String parentPrefix;
private final String prefix;
private final BeanPropertyAssoc<?> nodeBeanProp;
private final SqlTreeNode[] children;
public SqlTreeNodeManyWhereJoin(String prefix, BeanPropertyAssoc<?> prop) {
private final BeanPropertyAssoc<?> nodeBeanProp;
/**
* Child joins.
*/
private final SqlTreeNode[] children;
/**
* The many where join which is either INNER or OUTER.
*/
private final SqlJoinType manyJoinType;
public SqlTreeNodeManyWhereJoin(String prefix, BeanPropertyAssoc<?> prop, SqlJoinType manyJoinType) {
this.nodeBeanProp = prop;
this.prefix = prefix;
this.manyJoinType = manyJoinType;
String[] split = SplitName.split(prefix);
this.parentPrefix = split[0];
@@ -39,12 +49,15 @@ public class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
/**
* Append to the FROM clause for this node.
*/
public void appendFrom(DbSqlContext ctx, boolean forceOuterJoin) {
@Override
public void appendFrom(DbSqlContext ctx, SqlJoinType currentJoinType) {
appendFromBaseTable(ctx, forceOuterJoin);
// always use the join type as per this many where join
// (OUTER for disjunction and otherwise INNER)
appendFromBaseTable(ctx, manyJoinType);
for (int i = 0; i < children.length; i++) {
children[i].appendFrom(ctx, forceOuterJoin);
children[i].appendFrom(ctx, manyJoinType);
}
}
@@ -52,25 +65,25 @@ public class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
* Join to base table for this node. This includes a join to the
* intersection table if this is a ManyToMany node.
*/
public void appendFromBaseTable(DbSqlContext ctx, boolean forceOuterJoin) {
public void appendFromBaseTable(DbSqlContext ctx, SqlJoinType joinType) {
String alias = ctx.getTableAliasManyWhere(prefix);
String parentAlias = ctx.getTableAliasManyWhere(parentPrefix);
if (nodeBeanProp instanceof BeanPropertyAssocOne<?>){
nodeBeanProp.addInnerJoin(parentAlias, alias, ctx);
nodeBeanProp.addJoin(joinType, parentAlias, alias, ctx);
} else {
BeanPropertyAssocMany<?> manyProp = (BeanPropertyAssocMany<?>)nodeBeanProp;
if (!manyProp.isManyToMany()) {
manyProp.addInnerJoin(parentAlias, alias, ctx);
manyProp.addJoin(joinType, parentAlias, alias, ctx);
} else {
String alias2 = alias + "z_";
TableJoin manyToManyJoin = manyProp.getIntersectionTableJoin();
manyToManyJoin.addInnerJoin(parentAlias, alias2, ctx);
manyProp.addInnerJoin(alias2, alias, ctx);
manyToManyJoin.addJoin(joinType, parentAlias, alias2, ctx);
manyProp.addJoin(joinType, alias2, alias, ctx);
}
}
}
@@ -40,7 +40,7 @@ public final class SqlTreeNodeRoot extends SqlTreeNodeBean {
* For the root node there is no join type or on clause etc.
*/
@Override
public boolean appendFromBaseTable(DbSqlContext ctx, boolean forceOuterJoin) {
public SqlJoinType appendFromBaseTable(DbSqlContext ctx, SqlJoinType joinType) {
ctx.append(desc.getBaseTable());
ctx.append(" ").append(ctx.getTableAlias(null));
@@ -48,10 +48,10 @@ public final class SqlTreeNodeRoot extends SqlTreeNodeBean {
if (includeJoin != null) {
String a1 = ctx.getTableAlias(null);
String a2 = "int_"; // unique alias for intersection join
includeJoin.addJoin(forceOuterJoin, a1, a2, ctx);
includeJoin.addJoin(joinType, a1, a2, ctx);
}
return forceOuterJoin;
return joinType;
}
}
@@ -191,6 +191,8 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
*/
private boolean autoFetchTuned;
private boolean logSecondaryQuery;
/**
* The node of the bean or collection that fired lazy loading. Not null if
* profiling is on and this query is for lazy loading. Used to hook back a
@@ -585,8 +587,31 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
public void setUsageProfiling(boolean usageProfiling) {
this.usageProfiling = usageProfiling;
}
public void setLogSecondaryQuery(boolean logSecondaryQuery) {
this.logSecondaryQuery = logSecondaryQuery;
}
public boolean isLogSecondaryQuery() {
return logSecondaryQuery;
}
public void setParentNode(ObjectGraphNode parentNode) {
private List<SpiQuery<?>> loggedSecondaryQueries;
@Override
public List<SpiQuery<?>> getLoggedSecondaryQueries() {
return loggedSecondaryQueries;
}
public void logSecondaryQuery(SpiQuery<?> query) {
if (loggedSecondaryQueries == null) {
loggedSecondaryQueries = new ArrayList<SpiQuery<?>>();
}
loggedSecondaryQueries.add(query);
}
public void setParentNode(ObjectGraphNode parentNode) {
this.parentNode = parentNode;
}
@@ -24,13 +24,14 @@ public class TestOrderTotalAmountFormula extends BaseTestCase {
.findList();
for (Customer c0 : l0) {
System.out.println("customer: " + c0.getId());
c0.getId();
List<Order> orders = c0.getOrders();
for (Order order : orders) {
System.out.println("... order:" + order);
order.getId();
}
}
}
}
@@ -7,6 +7,9 @@ import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.FetchConfig;
import com.avaje.ebean.Query;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.tests.model.basic.Customer;
import com.avaje.tests.model.basic.Order;
import com.avaje.tests.model.basic.ResetBasicData;
@@ -14,23 +17,93 @@ import com.avaje.tests.model.basic.ResetBasicData;
public class TestSecondaryQueries extends BaseTestCase {
@Test
public void testQueries() {
public void testSecQueryOneToMany() {
ResetBasicData.reset();
Order testOrder = ResetBasicData.createOrderCustAndOrder("testSecQry10");
Integer custId = testOrder.getCustomer().getId();
Customer cust = Ebean.find(Customer.class).select("name").fetch("contacts", "+query")
.setId(custId).findUnique();
Query<Customer> query = Ebean.find(Customer.class)
.select("name")
.fetch("contacts", "+query")
.setId(custId);
SpiQuery<?> spiQuery = (SpiQuery<?>)query;
spiQuery.setLogSecondaryQuery(true);
Customer cust = query.findUnique();
Assert.assertNotNull(cust);
String generatedSql = query.getGeneratedSql();
Assert.assertTrue(generatedSql.contains("from o_customer t0 where t0.id = ?"));
List<SpiQuery<?>> loggedSecondaryQueries = spiQuery.getLoggedSecondaryQueries();
Assert.assertEquals(1, loggedSecondaryQueries.size());
SpiQuery<?> secondaryQuery = loggedSecondaryQueries.get(0);
String secondarySql = secondaryQuery.getGeneratedSql();
Assert.assertTrue(secondarySql.contains("from contact t0 where (t0.customer_id) in (?)"));
}
@Test
public void testManyToOneWithManyPlusOneToMany() {
List<Order> list = Ebean.find(Order.class).select("status").fetch("details", "+query(10)")
.fetch("customer", "+query name, status").fetch("customer.contacts").where()
.eq("status", Order.Status.NEW).findList();
ResetBasicData.reset();
Query<Order> query = Ebean.find(Order.class)
.select("status")
.fetch("customer", "name, status", new FetchConfig().query())
.fetch("customer.contacts")
.fetch("details", new FetchConfig().query())
.where().eq("status", Order.Status.NEW)
.query();
// .fetch("customer", "+query name, status")
// .fetch("details", "+query(10)")
SpiQuery<?> spiQuery = (SpiQuery<?>)query;
spiQuery.setLogSecondaryQuery(true);
List<Order> list = query.findList();
Assert.assertTrue(list.size() > 0);
for (Order order : list) {
order.getCustomer().getStatus();
}
String generatedSql = spiQuery.getGeneratedSql();
//select t0.id c0, t0.status c1, t0.kcustomer_id c2 from o_order t0 where t0.status = ? ; --bind(NEW)
Assert.assertEquals("select t0.id c0, t0.status c1, t0.kcustomer_id c2 from o_order t0 where t0.status = ? ", generatedSql);
List<SpiQuery<?>> secondaryQueries = spiQuery.getLoggedSecondaryQueries();
Assert.assertEquals(2, secondaryQueries.size());
SpiQuery<?> custSecondaryQuery = secondaryQueries.get(0);
String custSecondarySql = custSecondaryQuery.getGeneratedSql();
// select t0.id c0, t0.name c1, t0.status c2,
// t1.id c3, t1.first_name c4, t1.last_name c5, t1.phone c6, t1.mobile c7, t1.email c8, t1.cretime c9, t1.updtime c10, t1.customer_id c11, t1.group_id c12
// from o_customer t0
// left outer join contact t1 on t1.customer_id = t0.id
// where t0.id = ? order by t0.id; --bind(1)
Assert.assertTrue(custSecondarySql.contains("from o_customer t0 "));
Assert.assertTrue(custSecondarySql.contains("left outer join contact t1 on t1.customer_id = t0.id "));
Assert.assertTrue(custSecondarySql.contains("where t0.id "));
SpiQuery<?> orderDetailsSecondaryQuery = secondaryQueries.get(1);
String ordSecondarySql = orderDetailsSecondaryQuery.getGeneratedSql();
// select ...
// from o_order_detail t0
// where (t0.order_id) in (?,?,?,?,?) ; --bind(1,4,1,1,1)
Assert.assertTrue(ordSecondarySql.contains(" from o_order_detail t0 where (t0.order_id) in (?"));
}
}
@@ -28,7 +28,7 @@ public class Contact {
String mobile;
String email;
@ManyToOne
@ManyToOne(optional=false)
Customer customer;
@ManyToOne(optional=true)
@@ -1,7 +1,7 @@
package com.avaje.tests.query;
import junit.framework.Assert;
import org.junit.Assert;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
@@ -18,18 +18,18 @@ public class TestIContains extends BaseTestCase {
ResetBasicData.reset();
// case insensitive
Query<Customer> q0 = Ebean.find(Customer.class).where().icontains("name", "Rob").query();
Query<Customer> query = Ebean.find(Customer.class).where().icontains("name", "Rob").query();
q0.findList();
String generatedSql = q0.getGeneratedSql();
query.findList();
String generatedSql = query.getGeneratedSql();
Assert.assertTrue(generatedSql.contains("lower(t0.name)"));
// not case insensitive
q0 = Ebean.find(Customer.class).where().contains("name", "Rob").query();
query = Ebean.find(Customer.class).where().contains("name", "Rob").query();
q0.findList();
generatedSql = q0.getGeneratedSql();
query.findList();
generatedSql = query.getGeneratedSql();
Assert.assertTrue(generatedSql.contains(" t0.name "));
@@ -0,0 +1,63 @@
package com.avaje.tests.query;
import org.junit.Assert;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Query;
import com.avaje.tests.model.basic.Customer;
import com.avaje.tests.model.basic.ResetBasicData;
public class TestImplicitJoinOnParentRelatonship extends BaseTestCase {
@Test
public void test() {
ResetBasicData.reset();
Query<Customer> query = Ebean.find(Customer.class)
.select("id, name")
.where().eq("orders.details.product.name", "Desk")
.query();
query.findList();
String expectedSql = "select distinct t0.id c0, t0.name c1 from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id join o_order_detail u2 on u2.order_id = u1.id join o_product u3 on u3.id = u2.product_id where u3.name = ? ";
Assert.assertEquals(expectedSql, query.getGeneratedSql());
// select distinct t0.id c0, t0.name c1
// from o_customer t0
// join o_order u1 on u1.kcustomer_id = t0.id
// join o_order_detail u2 on u2.order_id = u1.id
// join o_product u3 on u3.id = u2.product_id
// where u3.name = ?
}
@Test
public void testWithDisjunction() {
ResetBasicData.reset();
Query<Customer> query = Ebean.find(Customer.class)
.select("id, name")
.where().disjunction().eq("orders.details.product.name", "Desk").eq("id", 4).endJunction()
.query();
query.findList();
String expectedSql = "select distinct t0.id c0, t0.name c1 from o_customer t0 left outer join o_order u1 on u1.kcustomer_id = t0.id left outer join o_order_detail u2 on u2.order_id = u1.id left outer join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ? ) ";
Assert.assertEquals(expectedSql, query.getGeneratedSql());
// select distinct t0.id c0, t0.name c1
// from o_customer t0 select distinct t0.id c0, t0.name c1 from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id join o_order_detail u2 on u2.order_id = u1.id join o_product u3 on u3.id = u2.product_id where u3.name = ?
// left outer join o_order u1 on u1.kcustomer_id = t0.id
// left outer join o_order_detail u2 on u2.order_id = u1.id
// left outer join o_product u3 on u3.id = u2.product_id
// where (u3.name = ? or t0.id = ? ) ; --bind(Desk,4)
}
}
@@ -1,38 +0,0 @@
package com.avaje.tests.query;
import java.util.List;
import junit.framework.Assert;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Query;
import com.avaje.tests.model.basic.Customer;
import com.avaje.tests.model.basic.OrderShipment;
import com.avaje.tests.model.basic.ResetBasicData;
public class TestJoinQuery extends BaseTestCase {
@Test
public void test() {
ResetBasicData.reset();
// test that join to order.details is not included
Query<Customer> query = Ebean.find(Customer.class).setAutofetch(false).fetch("orders")
.fetch("orders.details");
List<Customer> list = query.findList();
Assert.assertTrue("has rows", list.size() > 0);
// test that join to order.details is not included
Query<OrderShipment> shipQuery = Ebean.find(OrderShipment.class).setAutofetch(false)
.fetch("order").fetch("order.details");
List<OrderShipment> shipList = shipQuery.findList();
Assert.assertTrue("has rows", shipList.size() > 0);
}
}
@@ -1,48 +0,0 @@
package com.avaje.tests.query;
import java.util.concurrent.ExecutionException;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.PagingList;
import com.avaje.tests.model.basic.Customer;
import com.avaje.tests.model.basic.ResetBasicData;
public class TestPagingListLoop extends BaseTestCase {
@Test
public void test() throws InterruptedException, ExecutionException {
// boolean autoRunTest = false;
// if (!autoRunTest){
// // we only want to run this test manually.
// return;
// }
try {
ResetBasicData.reset();
for (int i = 0; i < 50; i++) {
PagingList<Customer> pagingList = Ebean.find(Customer.class).findPagingList(10);
pagingList.getFutureRowCount();// .get();
// createLeak();
Thread.sleep(10);
}
} catch (Exception e) {
e.printStackTrace();
}
}
// private void createLeak() {
//
// // create a transaction we never close ...
// // ... a connection pool leak
// Ebean.getServer(null).createTransaction();
// }
}
@@ -0,0 +1,162 @@
package com.avaje.tests.query;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Query;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.tests.model.basic.Contact;
import com.avaje.tests.model.basic.Customer;
import com.avaje.tests.model.basic.OrderShipment;
import com.avaje.tests.model.basic.ResetBasicData;
public class TestQueryFetchManyTwoDeep extends BaseTestCase {
@Test
public void testFetchOneToManyWithChildOneToMany() {
ResetBasicData.reset();
// test that join to order.details is not included in the initial query (included in query join)
Query<Customer> query = Ebean.find(Customer.class)
.setAutofetch(false)
.fetch("orders")
.fetch("orders.details");
SpiQuery<?> spiQuery = (SpiQuery<?>)query;
spiQuery.setLogSecondaryQuery(true);
List<Customer> list = query.findList();
Assert.assertTrue("has rows", list.size() > 0);
Assert.assertTrue(query.getGeneratedSql().contains("from o_customer t0 "));
Assert.assertTrue(query.getGeneratedSql().contains("left outer join o_order t1 on t1.kcustomer_id = t0.id"));
Assert.assertTrue(query.getGeneratedSql().contains("left outer join o_customer t2 on t2.id = t1.kcustomer_id"));
Assert.assertFalse(query.getGeneratedSql().contains("join or_order_ship"));
//select t0.id c0, t0.status c1, t0.name c2, t0.smallnote c3, t0.anniversary c4, t0.cretime c5, t0.updtime c6, t0.billing_address_id c7, t0.shipping_address_id c8, t1.id c9, t1.status c10, t1.order_date c11, t1.ship_date c12,
// t2.name c13, t1.cretime c14, t1.updtime c15, t1.kcustomer_id c16
// from o_customer t0
// left outer join o_order t1 on t1.kcustomer_id = t0.id
// left outer join o_customer t2 on t2.id = t1.kcustomer_id
// where t1.order_date is not null order by t0.id; --bind()
List<SpiQuery<?>> secondaryQueries = spiQuery.getLoggedSecondaryQueries();
Assert.assertNotNull(secondaryQueries);
Assert.assertEquals(1, secondaryQueries.size());
SpiQuery<?> secondaryQuery = secondaryQueries.get(0);
String secondarySql = secondaryQuery.getGeneratedSql();
Assert.assertTrue(secondarySql.contains("from o_order_detail t0 where (t0.order_id) in"));
// 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
// where (t0.order_id) in (?,?,?,?,?)
}
@Test
public void testFetchOptionalManyToOneThenDownToMany() {
ResetBasicData.reset();
// test that join to order.details is not included
Query<OrderShipment> shipQuery = Ebean.find(OrderShipment.class)
.setAutofetch(false)
.fetch("order")
.fetch("order.details");
List<OrderShipment> shipList = shipQuery.findList();
Assert.assertTrue("has rows", shipList.size() > 0);
String generatedSql = shipQuery.getGeneratedSql();
// select ...
// from or_order_ship t0
// left outer join o_order t1 on t1.id = t0.order_id
// left outer join o_customer t3 on t3.id = t1.kcustomer_id
// left outer join o_order_detail t2 on t2.order_id = t1.id
// where t2.id > 0 ; --bind()
Assert.assertTrue(generatedSql.contains("from or_order_ship t0"));
// Relationship from OrderShipment to Order is optional so outer join here
Assert.assertTrue(generatedSql.contains("left outer join o_order t1 on t1.id = t0.order_id"));
Assert.assertTrue(generatedSql.contains("left outer join o_customer t3 on t3.id = t1.kcustomer_id"));
Assert.assertTrue(generatedSql.contains("left outer join o_order_detail t2 on t2.order_id = t1.id"));
// If OrderShipment to Order is not optional you get inner joins up to o_order_detail (which is a many)
// select ...
// from or_order_ship t0
// join o_order t1 on t1.id = t0.order_id
// join o_customer t3 on t3.id = t1.kcustomer_id
// left outer join o_order_detail t2 on t2.order_id = t1.id
// where t2.id > 0 ; --bind()
}
@Test
public void testFetchMandatoryManyToOneThenDownToMany() {
ResetBasicData.reset();
// test that join to order.details is not included
Query<Contact> query = Ebean.find(Contact.class)
.setAutofetch(false)
.fetch("customer")
.fetch("customer.orders");
List<Contact> shipList = query.findList();
Assert.assertTrue("has rows", shipList.size() > 0);
String generatedSql = query.getGeneratedSql();
// select ...
// from contact t0
// join o_customer t1 on t1.id = t0.customer_id
// left outer join o_order t2 on t2.kcustomer_id = t1.id
// left outer join o_customer t3 on t3.id = t2.kcustomer_id
// where t2.order_date is not null ; --bind()
Assert.assertTrue(generatedSql.contains("from contact t0 "));
// Relationship from Contact to Customer is mandatory so inner join here
Assert.assertTrue(generatedSql.contains("join o_customer t1 on t1.id = t0.customer_id"));
// outer join on many relationship 'orders'
Assert.assertTrue(generatedSql.contains("left outer join o_order t2 on t2.kcustomer_id = t1.id"));
}
@Test
public void testFetchMandatoryManyToOneWithPredicate() {
ResetBasicData.reset();
// test that join to order.details is not included
Query<Contact> query = Ebean.find(Contact.class)
.setAutofetch(false)
.fetch("customer")
.where().ilike("customer.name", "Rob%")
.query();
List<Contact> list = query.findList();
Assert.assertTrue("has rows", list.size() > 0);
String generatedSql = query.getGeneratedSql();
// select ...
// from contact t0
// join o_customer t1 on t1.id = t0.customer_id
// where lower(t1.name) like ? ; --bind(rob%)
Assert.assertTrue(generatedSql.contains("from contact t0 "));
Assert.assertTrue(generatedSql.contains("join o_customer t1 on t1.id = t0.customer_id"));
Assert.assertTrue(generatedSql.contains("where lower(t1.name) like ?"));
}
}
@@ -6,7 +6,6 @@ import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.FetchConfig;
import com.avaje.ebean.Query;
import com.avaje.ebean.QueryIterator;
import com.avaje.tests.model.basic.Customer;
@@ -2,8 +2,7 @@ package com.avaje.tests.query;
import java.util.List;
import junit.framework.Assert;
import org.junit.Assert;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
@@ -1,4 +1,4 @@
package com.avaje.tests.query;
package com.avaje.tests.query.joins;
import java.util.List;
@@ -0,0 +1,61 @@
package com.avaje.tests.query.joins;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Query;
import com.avaje.tests.model.basic.UUOne;
import com.avaje.tests.model.basic.UUTwo;
public class TestDisjunctWhereOuterOnMany extends BaseTestCase {
@Test
public void test() {
// setup
UUOne master1 = new UUOne();
master1.setName("testDisjOuter_1_name");
UUTwo detail1 = new UUTwo();
detail1.setMaster(master1);
detail1.setName("testDisjOuter_CHILD_1");
UUOne master2 = new UUOne();
master2.setName("testDisjOuter_2_name");
Ebean.save(master1);
Ebean.save(detail1);
Ebean.save(master2);
// Have outer join so that "testDisjOuter_2_name" is found
Query<UUOne> query = Ebean.find(UUOne.class)
.where().disjunction()
.eq("name", "testDisjOuter_2_name")
.eq("comments.name", "testDisjOuter_CHILD_1")
.endJunction()
.query();
List<UUOne> list = query.findList();
int rowCount = query.findRowCount();
// select distinct t0.id c0, t0.name c1
// from uuone t0
// join uutwo u1 on u1.master_id = t0.id
// left outer join uutwo t1 on t1.master_id = t0.id
// where (t0.name = ? or u1.name = ? ) ;
// --bind(testDisjOuter_2_name,testDisjOuter_CHILD_1)
Assert.assertEquals(2, list.size());
Assert.assertEquals(2, rowCount);
String expectedSql = "select distinct t0.id c0, t0.name c1 from uuone t0 left outer join uutwo u1 on u1.master_id = t0.id where (t0.name = ? or u1.name = ? ) ";
Assert.assertEquals(expectedSql, query.getGeneratedSql());
}
}
@@ -1,4 +1,4 @@
package com.avaje.tests.query;
package com.avaje.tests.query.joins;
import java.util.List;
@@ -30,7 +30,15 @@ public class TestQueryJoinManyNonRoot extends BaseTestCase {
Assert.assertTrue(list.size() > 0);
Assert.assertTrue(sql.contains("join o_customer t1 on t1.id "));
Assert.assertTrue(sql.contains("left outer join contact t2 on"));
// select t0.id c0, t0.status c1, t0.order_date c2, t0.ship_date c3, t1.name c4, t0.cretime c5, t0.updtime c6,
// t1.id c7, t1.status c8, t1.name c9, t1.smallnote c10, t1.anniversary c11, t1.cretime c12, t1.updtime c13, t1.billing_address_id c14, t1.shipping_address_id c15,
// t2.id c16, t2.first_name c17, t2.last_name c18, t2.phone c19, t2.mobile c20, t2.email c21, t2.cretime c22, t2.updtime c23, t2.customer_id c24, t2.group_id c25
// from o_order t0
// join o_customer t1 on t1.id = t0.kcustomer_id
// left outer join contact t2 on t2.customer_id = t1.id
// where t0.id > ? ; --bind(0)
}
@Test
@@ -1,4 +1,4 @@
package com.avaje.tests.query;
package com.avaje.tests.query.joins;
import java.util.List;
@@ -28,7 +28,8 @@ public class TestQueryJoinQueryNonRoot extends BaseTestCase {
ResetBasicData.reset();
List<Order> list = Ebean.find(Order.class).fetch("customer")
List<Order> list = Ebean.find(Order.class)
.fetch("customer")
.fetch("customer.contacts", "firstName", new FetchConfig().query().lazy(10))
.fetch("customer.contacts.group")
.where().lt("id", 3).findList();
@@ -52,4 +53,6 @@ public class TestQueryJoinQueryNonRoot extends BaseTestCase {
// Assert.assertTrue(list2.size() > 0);
}
}
@@ -0,0 +1,94 @@
package com.avaje.tests.query.joins;
import org.junit.Assert;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Query;
import com.avaje.tests.model.basic.Order;
import com.avaje.tests.model.basic.ResetBasicData;
public class TestQueryManyToOneWhereClauseJoin extends BaseTestCase {
/**
* Testing 'where join' created for a ManyToOne relationship.
*/
@Test
public void testJoin() {
ResetBasicData.reset();
Query<Order> query = Ebean.find(Order.class)
.where().ilike("customer.name", "rob%")
.query();
query.findList();
//select t0.id c0, t0.status c1, t0.order_date c2, t0.ship_date c3, t1.name c4, t0.cretime c5, t0.updtime c6, t0.kcustomer_id c7
String expectedSql = "from o_order t0 join o_customer t1 on t1.id = t0.kcustomer_id where lower(t1.name) like ? ";
Assert.assertTrue(query.getGeneratedSql().contains(expectedSql));
// select t0.id c0, t0.status c1, t0.order_date c2, t0.ship_date c3, t1.name c4, t0.cretime c5, t0.updtime c6, t0.kcustomer_id c7
// from o_order t0
// join o_customer t1 on t1.id = t0.kcustomer_id
// where lower(t1.name) like ? ; --bind(rob%)
}
/**
* Although this is a disjunction it is on a ManyToOne so the 'default' join type is still fine.
*/
@Test
public void testDisjunctionOnManyToOneJoin() {
ResetBasicData.reset();
Query<Order> query = Ebean.find(Order.class)
.where().disjunction().ilike("customer.name", "rob%").gt("id", 1).endJunction()
.query();
query.findList();
//select t0.id c0, t0.status c1, t0.order_date c2, t0.ship_date c3, t1.name c4, t0.cretime c5, t0.updtime c6, t0.kcustomer_id c7
String expectedSql = "from o_order t0 join o_customer t1 on t1.id = t0.kcustomer_id where (lower(t1.name) like ? or t0.id > ? ) ";
Assert.assertTrue(query.getGeneratedSql().contains(expectedSql));
// select t0.id c0, t0.status c1, t0.order_date c2, t0.ship_date c3, t1.name c4, t0.cretime c5, t0.updtime c6, t0.kcustomer_id c7
// from o_order t0
// join o_customer t1 on t1.id = t0.kcustomer_id
// where (lower(t1.name) like ? or t0.id > ? ) ; --bind(rob%,1)
}
/**
* Testing ManyToOne relationship with predicate and fetch.
*/
@Test
public void testWhereAndFetch() {
ResetBasicData.reset();
Query<Order> query = Ebean.find(Order.class)
.fetch("customer")
.fetch("customer.contacts")
.where().ilike("customer.name", "rob%")
.query();
query.findList();
String generatedSql = query.getGeneratedSql();
Assert.assertTrue(generatedSql.contains("from o_order t0 join o_customer t1 on t1.id = t0.kcustomer_id"));
Assert.assertTrue(generatedSql.contains("left outer join contact t2 on t2.customer_id = t1.id"));
Assert.assertTrue(generatedSql.contains("where lower(t1.name) like ?"));
// select t0.id c0, t0.status c1, t0.order_date c2, t0.ship_date c3, t1.name c4, t0.cretime c5, t0.updtime c6,
// t1.id c7, t1.status c8, t1.name c9, t1.smallnote c10, t1.anniversary c11, t1.cretime c12, t1.updtime c13, t1.billing_address_id c14, t1.shipping_address_id c15,
// t2.id c16, t2.first_name c17, t2.last_name c18, t2.phone c19, t2.mobile c20, t2.email c21, t2.cretime c22, t2.updtime c23, t2.customer_id c24, t2.group_id c25
// from o_order t0
// join o_customer t1 on t1.id = t0.kcustomer_id
// left outer join contact t2 on t2.customer_id = t1.id
// where lower(t1.name) like ? ; --bind(rob%)
}
}
+1 -1
View File
@@ -118,7 +118,7 @@ datasource.ora.maxConnections=10
datasource.ora.heartbeatsql=select count(*) from dual
datasource.ora.isolationlevel=read_committed
datasource.ora.capturestacktrace=true
datasource.ora.customProperties=oracle.net.CONNECT_TIMEOUT=3000;oracle.jdbc.ReadTimeout=10000
#datasource.ora.customProperties=oracle.net.CONNECT_TIMEOUT=3000;oracle.jdbc.ReadTimeout=10000
datasource.pg.username=test
datasource.pg.password=test