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
@@ -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;
}
}