mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Fix for #37 - disjunction expression should not produce inner join - left outer join instead
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user