mirror of https://github.com/apache/openjpa.git
OPENJPA-547 INNER JOIN FETCH query incorrectly generates LEFT join SQL
git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@646455 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
5196ae0bc2
commit
50b81e6d77
|
@ -260,4 +260,38 @@ public class DelegatingJDBCFetchConfiguration
|
||||||
throw translate(re);
|
throw translate(re);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set getFetchInnerJoins() {
|
||||||
|
try {
|
||||||
|
return getJDBCDelegate().getFetchInnerJoins();
|
||||||
|
} catch (RuntimeException re) {
|
||||||
|
throw translate(re);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasFetchInnerJoin(String field) {
|
||||||
|
try {
|
||||||
|
return getJDBCDelegate().hasFetchInnerJoin(field);
|
||||||
|
} catch (RuntimeException re) {
|
||||||
|
throw translate(re);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JDBCFetchConfiguration addFetchInnerJoin(String field) {
|
||||||
|
try {
|
||||||
|
getJDBCDelegate().addFetchInnerJoin(field);
|
||||||
|
return this;
|
||||||
|
} catch (RuntimeException re) {
|
||||||
|
throw translate(re);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JDBCFetchConfiguration addFetchInnerJoins(Collection fields) {
|
||||||
|
try {
|
||||||
|
getJDBCDelegate().addFetchInnerJoins(fields);
|
||||||
|
return this;
|
||||||
|
} catch (RuntimeException re) {
|
||||||
|
throw translate(re);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -207,4 +207,38 @@ public interface JDBCFetchConfiguration
|
||||||
* Convenience method to cast traversal to store-specific type.
|
* Convenience method to cast traversal to store-specific type.
|
||||||
*/
|
*/
|
||||||
public JDBCFetchConfiguration traverseJDBC(FieldMetaData fm);
|
public JDBCFetchConfiguration traverseJDBC(FieldMetaData fm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the names of the inner fetch joins that this component will use
|
||||||
|
* when loading objects. Defaults to the empty set. This set is not
|
||||||
|
* thread safe.
|
||||||
|
*
|
||||||
|
* @since 1.0.3
|
||||||
|
*/
|
||||||
|
public Set getFetchInnerJoins();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the given fully-qualified inner fetch join has been added.
|
||||||
|
*
|
||||||
|
* @since 1.0.3
|
||||||
|
*/
|
||||||
|
public boolean hasFetchInnerJoin(String field);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds <code>field</code> to the set of fully-qualified field names to
|
||||||
|
* eagerly join when loading objects. Each class can have at most
|
||||||
|
* one to-many eagerly joined fields.
|
||||||
|
*
|
||||||
|
* @since 1.0.3
|
||||||
|
*/
|
||||||
|
public JDBCFetchConfiguration addFetchInnerJoin(String field);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds <code>fields</code> to the set of fully-qualified field names to
|
||||||
|
* eagerly join when loading objects. Each class can have at most
|
||||||
|
* one to-many eagerly joined fields.
|
||||||
|
*
|
||||||
|
* @since 1.0.3
|
||||||
|
*/
|
||||||
|
public JDBCFetchConfiguration addFetchInnerJoins(Collection fields);
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,7 @@ public class JDBCFetchConfigurationImpl
|
||||||
public int size = 0;
|
public int size = 0;
|
||||||
public int syntax = 0;
|
public int syntax = 0;
|
||||||
public Set joins = null;
|
public Set joins = null;
|
||||||
|
public Set fetchInnerJoins = null;
|
||||||
public int isolationLevel = -1;
|
public int isolationLevel = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,4 +346,37 @@ public class JDBCFetchConfigurationImpl
|
||||||
return null;
|
return null;
|
||||||
return (JDBCConfiguration) conf;
|
return (JDBCConfiguration) conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set getFetchInnerJoins() {
|
||||||
|
return (_state.fetchInnerJoins == null) ? Collections.EMPTY_SET
|
||||||
|
: _state.fetchInnerJoins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasFetchInnerJoin(String field) {
|
||||||
|
return _state.fetchInnerJoins != null &&
|
||||||
|
_state.fetchInnerJoins.contains(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JDBCFetchConfiguration addFetchInnerJoin(String join) {
|
||||||
|
if (StringUtils.isEmpty(join))
|
||||||
|
throw new UserException(_loc.get("null-join"));
|
||||||
|
|
||||||
|
lock();
|
||||||
|
try {
|
||||||
|
if (_state.fetchInnerJoins == null)
|
||||||
|
_state.fetchInnerJoins = new HashSet();
|
||||||
|
_state.fetchInnerJoins.add(join);
|
||||||
|
} finally {
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JDBCFetchConfiguration addFetchInnerJoins(Collection joins) {
|
||||||
|
if (joins == null || joins.isEmpty())
|
||||||
|
return this;
|
||||||
|
for (Iterator itr = joins.iterator(); itr.hasNext();)
|
||||||
|
addFetchInnerJoin((String) itr.next());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,6 +148,8 @@ public class JDBCStoreQuery
|
||||||
fetch.addFields(Arrays.asList(exps[0].fetchPaths));
|
fetch.addFields(Arrays.asList(exps[0].fetchPaths));
|
||||||
fetch.addJoins(Arrays.asList(exps[0].fetchPaths));
|
fetch.addJoins(Arrays.asList(exps[0].fetchPaths));
|
||||||
}
|
}
|
||||||
|
if (exps[0].fetchInnerPaths != null)
|
||||||
|
fetch.addFetchInnerJoins(Arrays.asList(exps[0].fetchInnerPaths));
|
||||||
|
|
||||||
int eager = calculateEagerMode(exps[0], range.start, range.end);
|
int eager = calculateEagerMode(exps[0], range.start, range.end);
|
||||||
int subclassMode = fetch.getSubclassFetchMode((ClassMapping) base);
|
int subclassMode = fetch.getSubclassFetchMode((ClassMapping) base);
|
||||||
|
@ -599,6 +601,8 @@ public class JDBCStoreQuery
|
||||||
fetch.addFields(Arrays.asList(exps[0].fetchPaths));
|
fetch.addFields(Arrays.asList(exps[0].fetchPaths));
|
||||||
fetch.addJoins(Arrays.asList(exps[0].fetchPaths));
|
fetch.addJoins(Arrays.asList(exps[0].fetchPaths));
|
||||||
}
|
}
|
||||||
|
if (exps[0].fetchInnerPaths != null)
|
||||||
|
fetch.addFetchInnerJoins(Arrays.asList(exps[0].fetchInnerPaths));
|
||||||
|
|
||||||
int eager = calculateEagerMode(exps[0], range.start, range.end);
|
int eager = calculateEagerMode(exps[0], range.start, range.end);
|
||||||
eager = Math.min(eager, JDBCFetchConfiguration.EAGER_JOIN);
|
eager = Math.min(eager, JDBCFetchConfiguration.EAGER_JOIN);
|
||||||
|
|
|
@ -409,9 +409,11 @@ public class RelationFieldStrategy
|
||||||
// clone it for a to-many eager select can result in a clone that
|
// clone it for a to-many eager select can result in a clone that
|
||||||
// produces invalid SQL
|
// produces invalid SQL
|
||||||
ClassMapping cls = field.getIndependentTypeMappings()[0];
|
ClassMapping cls = field.getIndependentTypeMappings()[0];
|
||||||
|
boolean forceInner = fetch.hasFetchInnerJoin(field.getFullName(false)) ?
|
||||||
|
true : false;
|
||||||
sel.select(cls, field.getSelectSubclasses(), store, fetch,
|
sel.select(cls, field.getSelectSubclasses(), store, fetch,
|
||||||
JDBCFetchConfiguration.EAGER_JOIN,
|
JDBCFetchConfiguration.EAGER_JOIN,
|
||||||
eagerJoin(sel.newJoins(), cls, false));
|
eagerJoin(sel.newJoins(), cls, forceInner));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -24,7 +24,6 @@ import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.openjpa.conf.OpenJPAConfiguration;
|
|
||||||
import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
|
import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
|
||||||
import org.apache.openjpa.jdbc.kernel.JDBCStore;
|
import org.apache.openjpa.jdbc.kernel.JDBCStore;
|
||||||
import org.apache.openjpa.jdbc.meta.ClassMapping;
|
import org.apache.openjpa.jdbc.meta.ClassMapping;
|
||||||
|
@ -168,10 +167,13 @@ public abstract class StoreCollectionFieldStrategy
|
||||||
// we limit further eager fetches to joins, because after this point
|
// we limit further eager fetches to joins, because after this point
|
||||||
// the select has been modified such that parallel clones may produce
|
// the select has been modified such that parallel clones may produce
|
||||||
// invalid sql
|
// invalid sql
|
||||||
|
boolean outer = field.getNullValue() != FieldMapping.NULL_EXCEPTION;
|
||||||
|
// force inner join for inner join fetch
|
||||||
|
if (fetch.hasFetchInnerJoin(field.getFullName(false)))
|
||||||
|
outer = false;
|
||||||
selectEager(sel, getDefaultElementMapping(true), sm, store, fetch,
|
selectEager(sel, getDefaultElementMapping(true), sm, store, fetch,
|
||||||
JDBCFetchConfiguration.EAGER_JOIN, false,
|
JDBCFetchConfiguration.EAGER_JOIN, false,
|
||||||
field.getNullValue()
|
outer);
|
||||||
!= FieldMapping.NULL_EXCEPTION);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEagerSelectToMany() {
|
public boolean isEagerSelectToMany() {
|
||||||
|
|
|
@ -65,6 +65,7 @@ public class QueryExpressions
|
||||||
public int operation = QueryOperations.OP_SELECT;
|
public int operation = QueryOperations.OP_SELECT;
|
||||||
public ClassMetaData[] accessPath = StoreQuery.EMPTY_METAS;
|
public ClassMetaData[] accessPath = StoreQuery.EMPTY_METAS;
|
||||||
public String[] fetchPaths = StoreQuery.EMPTY_STRINGS;
|
public String[] fetchPaths = StoreQuery.EMPTY_STRINGS;
|
||||||
|
public String[] fetchInnerPaths = StoreQuery.EMPTY_STRINGS;
|
||||||
public Value[] range = EMPTY_VALUES;
|
public Value[] range = EMPTY_VALUES;
|
||||||
private Boolean _aggregate = null;
|
private Boolean _aggregate = null;
|
||||||
|
|
||||||
|
|
|
@ -452,6 +452,7 @@ public class JPQLExpressionBuilder
|
||||||
|
|
||||||
// handle JOIN FETCH
|
// handle JOIN FETCH
|
||||||
Set joins = null;
|
Set joins = null;
|
||||||
|
Set innerJoins = null;
|
||||||
|
|
||||||
JPQLNode[] outers = root().findChildrenByID(JJTOUTERFETCHJOIN);
|
JPQLNode[] outers = root().findChildrenByID(JJTOUTERFETCHJOIN);
|
||||||
for (int i = 0; outers != null && i < outers.length; i++)
|
for (int i = 0; outers != null && i < outers.length; i++)
|
||||||
|
@ -459,13 +460,20 @@ public class JPQLExpressionBuilder
|
||||||
add(getPath(onlyChild(outers[i])).last().getFullName(false));
|
add(getPath(onlyChild(outers[i])).last().getFullName(false));
|
||||||
|
|
||||||
JPQLNode[] inners = root().findChildrenByID(JJTINNERFETCHJOIN);
|
JPQLNode[] inners = root().findChildrenByID(JJTINNERFETCHJOIN);
|
||||||
for (int i = 0; inners != null && i < inners.length; i++)
|
for (int i = 0; inners != null && i < inners.length; i++) {
|
||||||
(joins == null ? joins = new TreeSet() : joins).
|
String path = getPath(onlyChild(inners[i])).last()
|
||||||
add(getPath(onlyChild(inners[i])).last().getFullName(false));
|
.getFullName(false);
|
||||||
|
(joins == null ? joins = new TreeSet() : joins).add(path);
|
||||||
|
(innerJoins == null ? innerJoins = new TreeSet() : innerJoins).
|
||||||
|
add(path);
|
||||||
|
}
|
||||||
|
|
||||||
if (joins != null)
|
if (joins != null)
|
||||||
exps.fetchPaths = (String[]) joins.
|
exps.fetchPaths = (String[]) joins.
|
||||||
toArray(new String[joins.size()]);
|
toArray(new String[joins.size()]);
|
||||||
|
if (innerJoins != null)
|
||||||
|
exps.fetchInnerPaths = (String[]) innerJoins.
|
||||||
|
toArray(new String[innerJoins.size()]);
|
||||||
|
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
@ -506,7 +514,7 @@ public class JPQLExpressionBuilder
|
||||||
else if (node.id == JJTINNERJOIN)
|
else if (node.id == JJTINNERJOIN)
|
||||||
exp = addJoin(node, true, exp);
|
exp = addJoin(node, true, exp);
|
||||||
else if (node.id == JJTINNERFETCHJOIN)
|
else if (node.id == JJTINNERFETCHJOIN)
|
||||||
exp = addJoin(node, true, exp);
|
; // we handle inner fetch joins in the evalFetchJoins() method
|
||||||
else if (node.id == JJTOUTERFETCHJOIN)
|
else if (node.id == JJTOUTERFETCHJOIN)
|
||||||
; // we handle outer fetch joins in the evalFetchJoins() method
|
; // we handle outer fetch joins in the evalFetchJoins() method
|
||||||
else
|
else
|
||||||
|
|
Loading…
Reference in New Issue