mirror of https://github.com/apache/openjpa.git
OPENJPA-1185: commit subquery overhaul on behalf of Catalina
git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@795934 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
0adf13872e
commit
f74c5d7a69
|
@ -30,7 +30,6 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
import org.apache.openjpa.event.LifecycleEventManager;
|
||||
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
|
||||
import org.apache.openjpa.jdbc.kernel.exps.ExpContext;
|
||||
import org.apache.openjpa.jdbc.kernel.exps.GetColumn;
|
||||
import org.apache.openjpa.jdbc.kernel.exps.JDBCExpressionFactory;
|
||||
|
@ -46,20 +45,20 @@ import org.apache.openjpa.jdbc.meta.ClassMapping;
|
|||
import org.apache.openjpa.jdbc.meta.FieldMapping;
|
||||
import org.apache.openjpa.jdbc.meta.strats.VerticalClassStrategy;
|
||||
import org.apache.openjpa.jdbc.schema.Column;
|
||||
import org.apache.openjpa.jdbc.schema.ForeignKey;
|
||||
import org.apache.openjpa.jdbc.schema.Table;
|
||||
import org.apache.openjpa.jdbc.sql.DBDictionary;
|
||||
import org.apache.openjpa.jdbc.sql.SQLBuffer;
|
||||
import org.apache.openjpa.jdbc.sql.SQLExceptions;
|
||||
import org.apache.openjpa.jdbc.sql.Select;
|
||||
import org.apache.openjpa.jdbc.sql.SelectImpl;
|
||||
import org.apache.openjpa.jdbc.sql.Union;
|
||||
import org.apache.openjpa.kernel.ExpressionStoreQuery;
|
||||
import org.apache.openjpa.kernel.Filters;
|
||||
import org.apache.openjpa.kernel.OpenJPAStateManager;
|
||||
import org.apache.openjpa.kernel.OrderingMergedResultObjectProvider;
|
||||
import org.apache.openjpa.kernel.QueryContext;
|
||||
import org.apache.openjpa.kernel.QueryHints;
|
||||
import org.apache.openjpa.kernel.exps.Constant;
|
||||
import org.apache.openjpa.kernel.exps.Context;
|
||||
import org.apache.openjpa.kernel.exps.ExpressionFactory;
|
||||
import org.apache.openjpa.kernel.exps.ExpressionParser;
|
||||
import org.apache.openjpa.kernel.exps.FilterListener;
|
||||
|
@ -143,11 +142,28 @@ public class JDBCStoreQuery
|
|||
protected ExpressionFactory getExpressionFactory(ClassMetaData meta) {
|
||||
return new JDBCExpressionFactory((ClassMapping) meta);
|
||||
}
|
||||
|
||||
private void resetSelect(Context ctx) {
|
||||
List<Context> subselCtxs = ctx.getSubselContexts();
|
||||
if (subselCtxs != null) {
|
||||
for (Context subselCtx : subselCtxs) {
|
||||
SelectImpl sel = (SelectImpl)subselCtx.getSelect();
|
||||
sel.reset();
|
||||
resetSelect(subselCtx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected ResultObjectProvider executeQuery(Executor ex,
|
||||
ClassMetaData base, ClassMetaData[] metas, boolean subclasses,
|
||||
ExpressionFactory[] facts, QueryExpressions[] exps, Object[] params,
|
||||
Range range) {
|
||||
Context expCtx = exps[0].ctx();
|
||||
if (expCtx != null) {
|
||||
expCtx.resetAliasCount();
|
||||
expCtx.setSelect(null);
|
||||
resetSelect(expCtx);
|
||||
}
|
||||
if (metas.length > 1 && exps[0].isAggregate())
|
||||
throw new UserException(Localizer.forPackage(JDBCStoreQuery.class).
|
||||
get("mult-mapping-aggregate", Arrays.asList(metas)));
|
||||
|
@ -449,6 +465,13 @@ public class JDBCStoreQuery
|
|||
private Number executeBulkOperation(ClassMetaData[] metas,
|
||||
boolean subclasses, ExpressionFactory[] facts, QueryExpressions[] exps,
|
||||
Object[] params, Map updates) {
|
||||
Context expCtx = exps[0].ctx();
|
||||
if (ctx != null) {
|
||||
expCtx.resetAliasCount();
|
||||
expCtx.setSelect(null);
|
||||
resetSelect(expCtx);
|
||||
}
|
||||
|
||||
// we cannot execute a bulk delete statement when have mappings in
|
||||
// multiple tables, so indicate we want to use in-memory with null
|
||||
ClassMapping[] mappings = (ClassMapping[]) metas;
|
||||
|
|
|
@ -114,5 +114,9 @@ abstract class AbstractVal
|
|||
public Path getPath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.apache.openjpa.jdbc.sql.Select;
|
|||
import org.apache.openjpa.kernel.Broker;
|
||||
import org.apache.openjpa.kernel.Filters;
|
||||
import org.apache.openjpa.kernel.OpenJPAStateManager;
|
||||
import org.apache.openjpa.kernel.exps.Context;
|
||||
import org.apache.openjpa.kernel.exps.ExpressionVisitor;
|
||||
import org.apache.openjpa.meta.ClassMetaData;
|
||||
import org.apache.openjpa.meta.FieldMetaData;
|
||||
|
@ -217,4 +218,14 @@ class ConstPath
|
|||
public XMLMetaData getXmlMapping() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setSchemaAlias(String schemaAlias) {
|
||||
}
|
||||
|
||||
public String getSchemaAlias() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setSubqueryContext(Context conext) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,7 +167,8 @@ public class JDBCExpressionFactory
|
|||
}
|
||||
|
||||
public Expression not(Expression exp) {
|
||||
if (HasContainsExpressionVisitor.hasContains(exp))
|
||||
if (!(exp instanceof IsNotEmptyExpression) &&
|
||||
HasContainsExpressionVisitor.hasContains(exp))
|
||||
return new NotContainsExpression((Exp) exp);
|
||||
return new NotExpression((Exp) exp);
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ public class Lit
|
|||
private Object _val;
|
||||
private int _ptype;
|
||||
private boolean _isRaw;
|
||||
private Object _rawVal;
|
||||
|
||||
/**
|
||||
* Constructor. Supply literal value.
|
||||
|
@ -46,6 +47,8 @@ public class Lit
|
|||
}
|
||||
|
||||
public Class getType() {
|
||||
if (_isRaw && _rawVal != null)
|
||||
return Raw.class;
|
||||
return (_val == null) ? Object.class : _val.getClass();
|
||||
}
|
||||
|
||||
|
@ -77,6 +80,10 @@ public class Lit
|
|||
_isRaw = isRaw;
|
||||
}
|
||||
|
||||
public Object getRawValue() {
|
||||
return _rawVal;
|
||||
}
|
||||
|
||||
public ExpState initialize(Select sel, ExpContext ctx, int flags) {
|
||||
return new LitExpState();
|
||||
}
|
||||
|
@ -120,7 +127,7 @@ public class Lit
|
|||
if (!isOrdinal)
|
||||
value.append("'");
|
||||
lstate.sqlValue = new Raw(value.toString());
|
||||
setValue(lstate.sqlValue);
|
||||
_rawVal = lstate.sqlValue;
|
||||
}
|
||||
sql.appendValue(lstate.sqlValue, lstate.getColumn(index));
|
||||
}
|
||||
|
|
|
@ -67,6 +67,10 @@ class NotContainsExpression
|
|||
|
||||
Select sub = ctx.store.getSQLFactory().newSelect();
|
||||
sub.setParent(sel, null);
|
||||
// this subselect has the same context as its parent
|
||||
sub.setContext(sel.ctx());
|
||||
// the context select should still belong to parent
|
||||
sub.ctx().setSelect(sel);
|
||||
ExpState estate = _exp.initialize(sub, ctx, ((NotContainsExpState)
|
||||
state).contains);
|
||||
sub.where(sub.and(null, estate.joins));
|
||||
|
|
|
@ -45,6 +45,7 @@ import org.apache.openjpa.kernel.Filters;
|
|||
import org.apache.openjpa.kernel.OpenJPAStateManager;
|
||||
import org.apache.openjpa.kernel.StoreContext;
|
||||
import org.apache.openjpa.kernel.exps.CandidatePath;
|
||||
import org.apache.openjpa.kernel.exps.Context;
|
||||
import org.apache.openjpa.lib.util.Localizer;
|
||||
import org.apache.openjpa.meta.ClassMetaData;
|
||||
import org.apache.openjpa.meta.FieldMetaData;
|
||||
|
@ -82,6 +83,7 @@ public class PCPath
|
|||
private boolean _cid = false;
|
||||
private FieldMetaData _xmlfield = null;
|
||||
private boolean _keyPath = false;
|
||||
private String _schemaAlias = null;
|
||||
|
||||
/**
|
||||
* Return a path starting with the 'this' ptr.
|
||||
|
@ -99,10 +101,12 @@ public class PCPath
|
|||
|
||||
PCPath other = var.getPCPath();
|
||||
Action action = new Action();
|
||||
action.var = var.getName();
|
||||
if (other == null) {
|
||||
_type = UNBOUND_VAR;
|
||||
action.op = Action.UNBOUND_VAR;
|
||||
action.data = var;
|
||||
_schemaAlias = var.getName();
|
||||
} else {
|
||||
// bound variable; copy path
|
||||
_type = UNACCESSED_VAR;
|
||||
|
@ -111,6 +115,7 @@ public class PCPath
|
|||
|
||||
action.op = Action.VAR;
|
||||
action.data = var.getName();
|
||||
_schemaAlias = other._schemaAlias;
|
||||
}
|
||||
_actions.add(action);
|
||||
_cast = var.getType(); // initial type is var type
|
||||
|
@ -131,6 +136,22 @@ public class PCPath
|
|||
_varName = sub.getCandidateAlias();
|
||||
}
|
||||
|
||||
public void setSchemaAlias(String schemaAlias) {
|
||||
if (_schemaAlias == null)
|
||||
_schemaAlias = schemaAlias;
|
||||
}
|
||||
|
||||
public String getSchemaAlias() {
|
||||
return _schemaAlias;
|
||||
}
|
||||
|
||||
public void setSubqueryContext(Context context) {
|
||||
Action action = lastFieldAction();
|
||||
if (action == null)
|
||||
return;
|
||||
action.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the path as a binding of the given variable.
|
||||
*/
|
||||
|
@ -420,8 +441,7 @@ public class PCPath
|
|||
if (act != null && act.op == Action.GET_XPATH)
|
||||
return ((XMLMetaData) act.data).getType();
|
||||
|
||||
FieldMetaData fld = act == null ? null :
|
||||
(FieldMetaData) act.data;
|
||||
FieldMetaData fld = (act == null) ? null : (FieldMetaData) act.data;
|
||||
boolean key = act != null && act.op == Action.GET_KEY;
|
||||
if (fld != null) {
|
||||
switch (fld.getDeclaredTypeCode()) {
|
||||
|
@ -457,6 +477,8 @@ public class PCPath
|
|||
boolean forceOuter = false;
|
||||
ClassMapping rel = _candidate;
|
||||
|
||||
sel.setSchemaAlias(_schemaAlias);
|
||||
|
||||
// iterate to the final field
|
||||
ClassMapping owner;
|
||||
ClassMapping from, to;
|
||||
|
@ -464,14 +486,27 @@ public class PCPath
|
|||
Variable var;
|
||||
Iterator itr = (_actions == null) ? null : _actions.iterator();
|
||||
FieldMapping field = null;
|
||||
Action prevaction = null;
|
||||
boolean isCorrelatedPath = false;
|
||||
boolean fromParentRootInSubselect = navigateFromParentRootInSubselect(sel);
|
||||
|
||||
while (itr != null && itr.hasNext()) {
|
||||
action = (Action) itr.next();
|
||||
|
||||
// treat subqueries like variables for alias generation purposes
|
||||
if (action.op == Action.VAR)
|
||||
pstate.joins = pstate.joins.setVariable((String) action.data);
|
||||
else if (action.op == Action.SUBQUERY)
|
||||
if (action.op == Action.VAR) {
|
||||
if (sel.getParent() != null && action.var != null &&
|
||||
prevaction != null && prevaction.data != null &&
|
||||
sel.ctx().getVariable(action.var) == null) {
|
||||
//System.out.println("Correlated action var="+action.var);
|
||||
isCorrelatedPath = true;
|
||||
pstate.joins = pstate.joins.setCorrelatedVariable(action.var);
|
||||
} else
|
||||
pstate.joins = pstate.joins.setVariable((String) action.data);
|
||||
}
|
||||
else if (action.op == Action.SUBQUERY) {
|
||||
pstate.joins = pstate.joins.setSubselect((String) action.data);
|
||||
}
|
||||
else if (action.op == Action.UNBOUND_VAR) {
|
||||
// unbound vars are cross-joined to the candidate table
|
||||
var = (Variable) action.data;
|
||||
|
@ -479,8 +514,16 @@ public class PCPath
|
|||
if (rel == null)
|
||||
throw new IllegalArgumentException(_loc.get(
|
||||
"invalid-unbound-var", var.getName()).toString());
|
||||
pstate.joins = pstate.joins.setVariable(var.getName());
|
||||
pstate.joins = pstate.joins.crossJoin(_candidate.getTable(),
|
||||
|
||||
if (sel.getParent() != null && action.var != null &&
|
||||
sel.ctx().getVariable(action.var) == null) {
|
||||
//System.out.println("Correlated action var="+action.var);
|
||||
isCorrelatedPath = true;
|
||||
pstate.joins = pstate.joins.setCorrelatedVariable(var.getName());
|
||||
} else
|
||||
pstate.joins = pstate.joins.setVariable(var.getName());
|
||||
|
||||
pstate.joins = pstate.joins.crossJoin(_candidate.getTable(),
|
||||
rel.getTable());
|
||||
} else {
|
||||
// move past the previous field, if any
|
||||
|
@ -496,6 +539,13 @@ public class PCPath
|
|||
pstate.cmpfield = field;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fromParentRootInSubselect) {
|
||||
isCorrelatedPath = true;
|
||||
pstate.joins = pstate.joins.setCorrelatedVariable(_schemaAlias);
|
||||
pstate.joins.setJoinContext(null);
|
||||
}
|
||||
|
||||
rel = traverseField(pstate, key, forceOuter, false);
|
||||
}
|
||||
|
||||
|
@ -543,6 +593,9 @@ public class PCPath
|
|||
if (action.op == Action.GET_XPATH)
|
||||
break;
|
||||
}
|
||||
prevaction = action;
|
||||
if (prevaction != null && prevaction.context != null)
|
||||
pstate.joins = pstate.joins.setJoinContext(prevaction.context);
|
||||
}
|
||||
if (_varName != null)
|
||||
pstate.joins = pstate.joins.setVariable(_varName);
|
||||
|
@ -557,9 +610,55 @@ public class PCPath
|
|||
if ((flags & JOIN_REL) != 0)
|
||||
joinRelation(pstate, key, forceOuter || (flags & FORCE_OUTER) != 0,
|
||||
false);
|
||||
if (isCorrelatedPath) {
|
||||
// check if there are joins that belong to parent
|
||||
pstate.joins.moveJoinsToParent();
|
||||
}
|
||||
pstate.joins.setJoinContext(null);
|
||||
|
||||
if (_actions == null) {
|
||||
String subqAlias = findSubqAlias(sel);
|
||||
pstate.joins = pstate.joins.setSubselect(subqAlias);
|
||||
pstate.joins.setCorrelatedVariable(_schemaAlias);
|
||||
}
|
||||
|
||||
return pstate;
|
||||
}
|
||||
|
||||
public String findSubqAlias(Select sel) {
|
||||
Select pSel = sel.getParent();
|
||||
if (pSel == null)
|
||||
return null;
|
||||
Context pCtx = pSel.ctx();
|
||||
if (pCtx.subquery == null)
|
||||
return null;
|
||||
if (pCtx.getSchema(_schemaAlias) != null)
|
||||
return ((SubQ)pCtx.subquery).getCandidateAlias();
|
||||
return findSubqAlias(pSel);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a PCPath is in subselect, and it is simply a navigation
|
||||
* from the parent root, the joins involved in this PCPath
|
||||
* must happen in the main select.
|
||||
*/
|
||||
private boolean navigateFromParentRootInSubselect(Select sel) {
|
||||
if (sel.getParent() == null)
|
||||
return false;
|
||||
Iterator itr = (_actions == null) ? null : _actions.iterator();
|
||||
boolean navigateFromRoot = false;
|
||||
boolean hasVar = false;
|
||||
boolean startsWithSubquery = false;
|
||||
while (itr != null && itr.hasNext()) {
|
||||
Action action = (Action) itr.next();
|
||||
if (action.op == Action.VAR)
|
||||
hasVar = true;
|
||||
else if (action.op == Action.SUBQUERY)
|
||||
startsWithSubquery = true;
|
||||
}
|
||||
return !hasVar && !startsWithSubquery && sel.ctx().getSchema(_schemaAlias) == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given source field joins to the given target field.
|
||||
*/
|
||||
|
@ -846,6 +945,8 @@ public class PCPath
|
|||
public void appendTo(Select sel, ExpContext ctx, ExpState state,
|
||||
SQLBuffer sql, int index) {
|
||||
Column col = getColumns(state)[index];
|
||||
if (sel != null)
|
||||
sel.setSchemaAlias(_schemaAlias);
|
||||
|
||||
// if select is null, it means we are not aliasing columns
|
||||
// (e.g., during a bulk update)
|
||||
|
@ -977,6 +1078,8 @@ public class PCPath
|
|||
|
||||
public int op = -1;
|
||||
public Object data = null;
|
||||
public String var = null;
|
||||
public Context context = null;
|
||||
|
||||
public String toString() {
|
||||
return op + "|" + data;
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.apache.openjpa.jdbc.kernel.exps;
|
|||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.openjpa.jdbc.meta.ClassMapping;
|
||||
|
@ -29,8 +30,10 @@ import org.apache.openjpa.jdbc.sql.SQLBuffer;
|
|||
import org.apache.openjpa.jdbc.sql.Select;
|
||||
import org.apache.openjpa.kernel.exps.AbstractExpressionVisitor;
|
||||
import org.apache.openjpa.kernel.exps.Constant;
|
||||
import org.apache.openjpa.kernel.exps.Context;
|
||||
import org.apache.openjpa.kernel.exps.Expression;
|
||||
import org.apache.openjpa.kernel.exps.QueryExpressions;
|
||||
import org.apache.openjpa.kernel.exps.Subquery;
|
||||
import org.apache.openjpa.kernel.exps.Value;
|
||||
|
||||
/**
|
||||
|
@ -43,6 +46,7 @@ public class SelectConstructor
|
|||
implements Serializable {
|
||||
|
||||
private boolean _extent = false;
|
||||
private Select _subselect = null;
|
||||
|
||||
/**
|
||||
* Return true if we know the select to have on criteria; to be an extent.
|
||||
|
@ -53,6 +57,10 @@ public class SelectConstructor
|
|||
return _extent;
|
||||
}
|
||||
|
||||
public void setSubselect(Select subselect) {
|
||||
_subselect = subselect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the expression, returning a new select and filling in any
|
||||
* associated expression state. Use {@link #select} to then select the data.
|
||||
|
@ -114,10 +122,25 @@ public class SelectConstructor
|
|||
*/
|
||||
private Select newSelect(ExpContext ctx, Select parent,
|
||||
String alias, QueryExpressions exps, QueryExpressionsState state) {
|
||||
Select sel = ctx.store.getSQLFactory().newSelect();
|
||||
Select sel = parent != null ? _subselect
|
||||
: ctx.store.getSQLFactory().newSelect();
|
||||
sel.setAutoDistinct((exps.distinct & exps.DISTINCT_AUTO) != 0);
|
||||
sel.setJoinSyntax(ctx.fetch.getJoinSyntax());
|
||||
sel.setParent(parent, alias);
|
||||
|
||||
if (sel.ctx() == null)
|
||||
sel.setContext(exps.ctx());
|
||||
|
||||
if (parent == null && exps.ctx().getSubselContexts() != null) {
|
||||
// this is the case subselect was created before parent got created
|
||||
List<Context> subselCtxs = exps.ctx().getSubselContexts();
|
||||
for (Context subselCtx : subselCtxs) {
|
||||
Select subsel = (Select) subselCtx.getSelect();
|
||||
Subquery subquery = subselCtx.getSubquery();
|
||||
subsel.setParent(sel, subquery.getCandidateAlias());
|
||||
}
|
||||
}
|
||||
|
||||
initialize(sel, ctx, exps, state);
|
||||
|
||||
if (!sel.getAutoDistinct()) {
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.apache.openjpa.jdbc.kernel.exps;
|
|||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
|
||||
import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
|
||||
import org.apache.openjpa.jdbc.meta.ClassMapping;
|
||||
import org.apache.openjpa.jdbc.meta.JavaSQLTypes;
|
||||
|
@ -38,18 +39,19 @@ import org.apache.openjpa.meta.ClassMetaData;
|
|||
*
|
||||
* @author Abe White
|
||||
*/
|
||||
class SubQ
|
||||
public class SubQ
|
||||
extends AbstractVal
|
||||
implements Subquery {
|
||||
|
||||
private final ClassMapping _candidate;
|
||||
private final boolean _subs;
|
||||
private final String _alias;
|
||||
private final String _subqAlias;
|
||||
private final SelectConstructor _cons = new SelectConstructor();
|
||||
|
||||
private Class _type = null;
|
||||
private ClassMetaData _meta = null;
|
||||
private QueryExpressions _exps = null;
|
||||
private Select _select = null;
|
||||
|
||||
/**
|
||||
* Constructor. Supply candidate, whether subclasses are included in
|
||||
|
@ -58,7 +60,14 @@ class SubQ
|
|||
public SubQ(ClassMapping candidate, boolean subs, String alias) {
|
||||
_candidate = candidate;
|
||||
_subs = subs;
|
||||
_alias = alias;
|
||||
_subqAlias = alias;
|
||||
_select = (((JDBCConfiguration) candidate.getMappingRepository().
|
||||
getConfiguration()).getSQLFactoryInstance().newSelect());
|
||||
_cons.setSubselect(_select);
|
||||
}
|
||||
|
||||
public Object getSelect() {
|
||||
return _select;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,6 +76,14 @@ class SubQ
|
|||
public ClassMapping getCandidate() {
|
||||
return _candidate;
|
||||
}
|
||||
|
||||
public boolean getSubs() {
|
||||
return _subs;
|
||||
}
|
||||
|
||||
public String getSubqAlias() {
|
||||
return _subqAlias;
|
||||
}
|
||||
|
||||
public Class getType() {
|
||||
if (_exps != null && _type == null) {
|
||||
|
@ -93,16 +110,19 @@ class SubQ
|
|||
}
|
||||
|
||||
public String getCandidateAlias() {
|
||||
return _alias;
|
||||
return _subqAlias;
|
||||
}
|
||||
|
||||
public void setQueryExpressions(QueryExpressions query) {
|
||||
_exps = query;
|
||||
_select.setContext(query.ctx());
|
||||
}
|
||||
|
||||
public ExpState initialize(Select sel, ExpContext ctx, int flags) {
|
||||
if (_exps.projections.length == 1)
|
||||
return ((Val) _exps.projections[0]).initialize(sel, ctx, flags);
|
||||
_select.setParent(sel, null);
|
||||
if (_exps.projections.length == 1) {
|
||||
return ((Val) _exps.projections[0]).initialize(_select, ctx, flags);
|
||||
}
|
||||
return ExpState.NULL;
|
||||
}
|
||||
|
||||
|
@ -180,7 +200,7 @@ class SubQ
|
|||
private void appendTo(Select sel, ExpContext ctx, ExpState state,
|
||||
SQLBuffer sql, int index, boolean size) {
|
||||
QueryExpressionsState substate = new QueryExpressionsState();
|
||||
Select sub = _cons.evaluate(ctx, sel, _alias, _exps, substate);
|
||||
Select sub = _cons.evaluate(ctx, sel, _subqAlias, _exps, substate);
|
||||
_cons.select(sub, ctx, _candidate, _subs, _exps, substate,
|
||||
JDBCFetchConfiguration.EAGER_NONE);
|
||||
|
||||
|
|
|
@ -583,6 +583,7 @@ public class FieldMapping
|
|||
_unq = _info.getJoinUnique(this, false, adapt);
|
||||
_joinTableUniques = _info.getJoinTableUniques(this, false, adapt);
|
||||
_idx = _info.getJoinIndex(this, adapt);
|
||||
table.setAssociation();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -62,7 +62,8 @@ public class Table
|
|||
private Unique[] _unqs = null;
|
||||
private String _comment = null;
|
||||
private int _lineNum = 0;
|
||||
private int _colNum = 0;
|
||||
private int _colNum = 0;
|
||||
private boolean _isAssociation = false;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
|
@ -84,6 +85,14 @@ public class Table
|
|||
_schema = schema;
|
||||
}
|
||||
|
||||
public void setAssociation() {
|
||||
_isAssociation = true;
|
||||
}
|
||||
|
||||
public boolean isAssociation() {
|
||||
return _isAssociation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the table is removed from its schema. Removes all table
|
||||
* members, and invalidates the table.
|
||||
|
|
|
@ -47,6 +47,7 @@ import org.apache.openjpa.jdbc.meta.JavaSQLTypes;
|
|||
import org.apache.openjpa.jdbc.schema.Column;
|
||||
import org.apache.openjpa.jdbc.schema.ForeignKey;
|
||||
import org.apache.openjpa.jdbc.schema.Table;
|
||||
import org.apache.openjpa.kernel.exps.Context;
|
||||
import org.apache.openjpa.lib.util.Closeable;
|
||||
import org.apache.openjpa.meta.JavaTypes;
|
||||
import org.apache.openjpa.util.UnsupportedException;
|
||||
|
@ -885,7 +886,22 @@ public abstract class AbstractResult
|
|||
return this;
|
||||
}
|
||||
|
||||
public Joins setJoinContext(Context context) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void appendTo(SQLBuffer buf) {
|
||||
}
|
||||
|
||||
public Joins setCorrelatedVariable(String var) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getCorrelatedVariable() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void moveJoinsToParent() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2197,14 +2197,59 @@ public class DBDictionary
|
|||
Iterator itr = sel.getJoinIterator();
|
||||
boolean first = true;
|
||||
while (itr.hasNext()) {
|
||||
fromSQL.append(toSQL92Join((Join) itr.next(), forUpdate,
|
||||
Join join = (Join) itr.next();
|
||||
if (correlatedJoinCondition(join, sel))
|
||||
continue;
|
||||
fromSQL.append(toSQL92Join(sel, join, forUpdate,
|
||||
first));
|
||||
first = false;
|
||||
if (itr.hasNext() && join.isCorrelated()) {
|
||||
fromSQL.append(", ");
|
||||
first = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (Iterator itr2 = aliases.iterator(); itr2.hasNext();) {
|
||||
String tableAlias = itr2.next().toString();
|
||||
if (fromSQL.getSQL().indexOf(tableAlias) == -1) {
|
||||
if (!first)
|
||||
fromSQL.append(", ");
|
||||
fromSQL.append(tableAlias);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return fromSQL;
|
||||
}
|
||||
|
||||
private boolean correlatedJoinCondition(Join join, Select sel) {
|
||||
if (!join.isCorrelated())
|
||||
return false;
|
||||
Iterator itr = sel.getJoinIterator();
|
||||
boolean skip = false;
|
||||
//if table1 in join is in the main query, table2 is in
|
||||
//subquery, and table2 participates in other joins
|
||||
//in subquery, the join condition can only be placed in
|
||||
//the where clause in the subquery
|
||||
while (itr.hasNext()) {
|
||||
Join join1 = (Join) itr.next();
|
||||
if (join == join1)
|
||||
continue;
|
||||
if (join.getIndex2() == join1.getIndex1() ||
|
||||
join.getIndex2() == join1.getIndex2()) {
|
||||
skip = true;
|
||||
if (join.getForeignKey() != null){
|
||||
SQLBuffer where = new SQLBuffer(this);
|
||||
where.append("(").append(toTraditionalJoin(join)).append(")");
|
||||
sel.where(where.getSQL());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return skip;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the FROM clause for a select that selects from a tmp table
|
||||
* created by an inner select.
|
||||
|
@ -2308,9 +2353,11 @@ public class DBDictionary
|
|||
* Use the given join instance to create SQL joining its tables in
|
||||
* the SQL92 style.
|
||||
*/
|
||||
public SQLBuffer toSQL92Join(Join join, boolean forUpdate, boolean first) {
|
||||
public SQLBuffer toSQL92Join(Select sel, Join join, boolean forUpdate,
|
||||
boolean first) {
|
||||
SQLBuffer buf = new SQLBuffer(this);
|
||||
if (first) {
|
||||
boolean corelated = join.isCorrelated();
|
||||
if (first && !corelated) {
|
||||
buf.append(join.getTable1()).append(" ").
|
||||
append(join.getAlias1());
|
||||
if (forUpdate && tableForUpdateClause != null)
|
||||
|
@ -2318,23 +2365,31 @@ public class DBDictionary
|
|||
}
|
||||
|
||||
buf.append(" ");
|
||||
if (join.getType() == Join.TYPE_OUTER)
|
||||
buf.append(outerJoinClause);
|
||||
else if (join.getType() == Join.TYPE_INNER)
|
||||
buf.append(innerJoinClause);
|
||||
else // cross
|
||||
buf.append(crossJoinClause);
|
||||
buf.append(" ");
|
||||
if (!corelated) {
|
||||
if (join.getType() == Join.TYPE_OUTER)
|
||||
buf.append(outerJoinClause);
|
||||
else if (join.getType() == Join.TYPE_INNER)
|
||||
buf.append(innerJoinClause);
|
||||
else // cross
|
||||
buf.append(crossJoinClause);
|
||||
buf.append(" ");
|
||||
}
|
||||
|
||||
buf.append(join.getTable2()).append(" ").append(join.getAlias2());
|
||||
if (forUpdate && tableForUpdateClause != null)
|
||||
buf.append(" ").append(tableForUpdateClause);
|
||||
|
||||
if (join.getForeignKey() != null)
|
||||
buf.append(" ON ").append(toTraditionalJoin(join));
|
||||
else if (requiresConditionForCrossJoin &&
|
||||
join.getType() == Join.TYPE_CROSS)
|
||||
buf.append(" ON (1 = 1)");
|
||||
if (!corelated) {
|
||||
if (join.getForeignKey() != null)
|
||||
buf.append(" ON ").append(toTraditionalJoin(join));
|
||||
else if (requiresConditionForCrossJoin &&
|
||||
join.getType() == Join.TYPE_CROSS)
|
||||
buf.append(" ON (1 = 1)");
|
||||
} else if (join.getForeignKey() != null){
|
||||
SQLBuffer where = new SQLBuffer(this);
|
||||
where.append("(").append(toTraditionalJoin(join)).append(")");
|
||||
sel.where(where.getSQL());
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
|
|
@ -45,6 +45,8 @@ public class Join
|
|||
private int _subs;
|
||||
private Joins _joins;
|
||||
private boolean _inverse;
|
||||
private boolean _correlated = false;
|
||||
private boolean _isNotMyJoin = false;
|
||||
|
||||
/**
|
||||
* Constructor for inner and outer joins.
|
||||
|
@ -189,5 +191,21 @@ public class Join
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCorrelated() {
|
||||
return _correlated;
|
||||
}
|
||||
|
||||
public void setCorrelated() {
|
||||
_correlated = true;
|
||||
}
|
||||
|
||||
public boolean isNotMyJoin() {
|
||||
return _isNotMyJoin;
|
||||
}
|
||||
|
||||
public void setIsNotMyJoin() {
|
||||
_isNotMyJoin = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.apache.openjpa.jdbc.sql;
|
|||
import org.apache.openjpa.jdbc.meta.ClassMapping;
|
||||
import org.apache.openjpa.jdbc.schema.ForeignKey;
|
||||
import org.apache.openjpa.jdbc.schema.Table;
|
||||
import org.apache.openjpa.kernel.exps.Context;
|
||||
|
||||
/**
|
||||
* Tracks joins made when traversing relations in a select.
|
||||
|
@ -77,4 +78,28 @@ public interface Joins {
|
|||
* Set the subquery alias.
|
||||
*/
|
||||
public Joins setSubselect(String alias);
|
||||
|
||||
/**
|
||||
* Set subquery context when traversing into the next join is
|
||||
* in transition from parent context to subquery.
|
||||
* @param context
|
||||
*/
|
||||
public Joins setJoinContext(Context context);
|
||||
|
||||
/**
|
||||
* Set the correlated variable name being traversed into
|
||||
* with the next join.
|
||||
*/
|
||||
public Joins setCorrelatedVariable(String var);
|
||||
|
||||
/**
|
||||
* Return correlated variable name
|
||||
* @return
|
||||
*/
|
||||
public String getCorrelatedVariable();
|
||||
|
||||
/**
|
||||
* Move joins that belong to subquery's parent
|
||||
*/
|
||||
public void moveJoinsToParent();
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
|
|||
import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
|
||||
import org.apache.openjpa.jdbc.kernel.JDBCStore;
|
||||
import org.apache.openjpa.kernel.exps.Value;
|
||||
import org.apache.openjpa.kernel.exps.Context;
|
||||
import org.apache.openjpa.jdbc.meta.ClassMapping;
|
||||
import org.apache.openjpa.jdbc.meta.FieldMapping;
|
||||
import org.apache.openjpa.jdbc.schema.Column;
|
||||
|
@ -880,6 +881,18 @@ public class LogicalUnion
|
|||
boolean force) {
|
||||
sel.setExpectedResultCount(expectedResultCount, force);
|
||||
}
|
||||
|
||||
public void setContext(Context context) {
|
||||
sel.setContext(context);
|
||||
}
|
||||
|
||||
public Context ctx() {
|
||||
return sel.ctx();
|
||||
}
|
||||
|
||||
public void setSchemaAlias(String schemaAlias) {
|
||||
sel.setSchemaAlias(schemaAlias);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.apache.openjpa.jdbc.schema.Column;
|
|||
import org.apache.openjpa.jdbc.schema.ForeignKey;
|
||||
import org.apache.openjpa.jdbc.schema.Table;
|
||||
import org.apache.openjpa.kernel.exps.Value;
|
||||
import org.apache.openjpa.kernel.exps.Context;
|
||||
|
||||
/**
|
||||
* Abstraction of a SQL SELECT statement.
|
||||
|
@ -701,4 +702,21 @@ public interface Select
|
|||
* Return the alias for the given column, without creating new table alias
|
||||
*/
|
||||
public String getColumnAlias(Column col, Object path);
|
||||
|
||||
/**
|
||||
* Set JPQL query context for this select
|
||||
* @param context
|
||||
*/
|
||||
public void setContext(Context context);
|
||||
|
||||
/**
|
||||
* Return the JPQL query context of this select
|
||||
*/
|
||||
public Context ctx();
|
||||
|
||||
/**
|
||||
* Record the initial schemaAlias of a join path
|
||||
* @param schemaAlias
|
||||
*/
|
||||
public void setSchemaAlias(String schemaAlias);
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ import java.sql.Statement;
|
|||
import java.sql.Types;
|
||||
import java.util.AbstractList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -56,6 +55,7 @@ import org.apache.openjpa.jdbc.schema.ForeignKey;
|
|||
import org.apache.openjpa.jdbc.schema.Table;
|
||||
import org.apache.openjpa.kernel.StoreContext;
|
||||
import org.apache.openjpa.kernel.exps.Value;
|
||||
import org.apache.openjpa.kernel.exps.Context;
|
||||
import org.apache.openjpa.lib.log.Log;
|
||||
import org.apache.openjpa.lib.util.Localizer;
|
||||
import org.apache.openjpa.util.ApplicationIds;
|
||||
|
@ -119,9 +119,6 @@ public class SelectImpl
|
|||
// 'parent.address.street' for the purposes of comparisons
|
||||
private Map _aliases = null;
|
||||
|
||||
// to cache table alias using Table as the key
|
||||
private Map _tableAliases = null;
|
||||
|
||||
// map of indexes to table aliases like 'TABLENAME t0'
|
||||
private SortedMap _tables = null;
|
||||
|
||||
|
@ -165,13 +162,12 @@ public class SelectImpl
|
|||
// from select if this select selects from a tmp table created by another
|
||||
private SelectImpl _from = null;
|
||||
protected SelectImpl _outer = null;
|
||||
|
||||
// bitSet indicating if an alias is removed from parent select
|
||||
// bit 0 : correspond to alias 0
|
||||
// bit 1 : correspond to alias 1, etc.
|
||||
// if the bit is set, the corresponding alias has been removed from parent
|
||||
// and recorded under subselect.
|
||||
private BitSet _removedAliasFromParent = new BitSet(16);
|
||||
|
||||
// JPQL Query context this select is associated with
|
||||
private Context _ctx = null;
|
||||
|
||||
// A path navigation is begin with this schema alias
|
||||
private String _schemaAlias = null;
|
||||
|
||||
/**
|
||||
* Helper method to return the proper table alias for the given alias index.
|
||||
|
@ -206,6 +202,21 @@ public class SelectImpl
|
|||
_selects._dict = _dict;
|
||||
}
|
||||
|
||||
public void setContext(Context context) {
|
||||
if (_ctx == null) {
|
||||
_ctx = context;
|
||||
_ctx.setSelect(this);
|
||||
}
|
||||
}
|
||||
|
||||
public Context ctx() {
|
||||
return _ctx;
|
||||
}
|
||||
|
||||
public void setSchemaAlias(String schemaAlias) {
|
||||
_schemaAlias = schemaAlias;
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
// SelectExecutor implementation
|
||||
/////////////////////////////////
|
||||
|
@ -521,7 +532,7 @@ public class SelectImpl
|
|||
|
||||
public void setParent(Select parent, String path) {
|
||||
if (path != null)
|
||||
_subPath = path + ':';
|
||||
_subPath = path;
|
||||
else
|
||||
_subPath = null;
|
||||
|
||||
|
@ -546,62 +557,6 @@ public class SelectImpl
|
|||
else
|
||||
_joinSyntax = _parent._joinSyntax;
|
||||
}
|
||||
|
||||
if (_parent.getAliases() == null || _subPath == null)
|
||||
return;
|
||||
|
||||
if (_parent._aliases.size() <= 1)
|
||||
return;
|
||||
// Do not remove aliases for databases that use SYNTAX_DATABASE (oracle)
|
||||
if(_parent._joinSyntax != JoinSyntaxes.SYNTAX_DATABASE) {
|
||||
// resolve aliases for subselect from parent
|
||||
Set<Map.Entry> entries = _parent.getAliases().entrySet();
|
||||
for (Map.Entry entry : entries) {
|
||||
Object key = entry.getKey();
|
||||
Integer alias = (Integer) entry.getValue();
|
||||
if (key.toString().indexOf(_subPath) != -1 ||
|
||||
_parent.findTableAlias(alias) == false) {
|
||||
if (_aliases == null)
|
||||
_aliases = new HashMap();
|
||||
_aliases.put(key, alias);
|
||||
|
||||
Object tableString = _parent.getTables().get(alias);
|
||||
if (_tables == null)
|
||||
_tables = new TreeMap();
|
||||
_tables.put(alias, tableString);
|
||||
|
||||
_removedAliasFromParent.set(alias.intValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (_aliases != null) {
|
||||
// aliases moved into subselect should be removed from parent
|
||||
entries = _aliases.entrySet();
|
||||
for (Map.Entry entry : entries) {
|
||||
Object key = entry.getKey();
|
||||
Integer alias = (Integer) entry.getValue();
|
||||
if (key.toString().indexOf(_subPath) != -1 ||
|
||||
_parent.findTableAlias(alias) == false) {
|
||||
_parent.removeAlias(key);
|
||||
|
||||
Object tableString = _parent.getTables().get(alias);
|
||||
_parent.removeTable(alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean findTableAlias(Integer alias) {
|
||||
// if alias is defined and referenced, return true.
|
||||
String value = "t" + alias.toString() + ".";
|
||||
if (_tableAliases != null)
|
||||
if (_tableAliases.containsValue(value))
|
||||
return _tables.containsKey(alias);
|
||||
else
|
||||
return _joins != null;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
public Map getAliases() {
|
||||
|
@ -773,17 +728,6 @@ public class SelectImpl
|
|||
* Return the alias for the given column.
|
||||
*/
|
||||
private String getColumnAlias(String col, Table table, PathJoins pj) {
|
||||
String tableAlias = null;
|
||||
if (pj == null || pj.path() == null) {
|
||||
if (_tableAliases == null)
|
||||
_tableAliases = new HashMap();
|
||||
tableAlias = (String) _tableAliases.get(table);
|
||||
if (tableAlias == null) {
|
||||
tableAlias = getTableAlias(table, pj).toString();
|
||||
_tableAliases.put(table, tableAlias);
|
||||
}
|
||||
return new StringBuilder(tableAlias).append(col).toString();
|
||||
}
|
||||
return getTableAlias(table, pj).append(col).toString();
|
||||
}
|
||||
|
||||
|
@ -1663,10 +1607,7 @@ public class SelectImpl
|
|||
if ((_flags & OUTER) != 0)
|
||||
pj = (PathJoins) outer(pj);
|
||||
if (record) {
|
||||
if (!pj.isEmpty())
|
||||
removeParentJoins(pj);
|
||||
if (!pj.isEmpty()) {
|
||||
removeJoinsFromSubselects(pj);
|
||||
if (_joins == null)
|
||||
_joins = new SelectJoins(this);
|
||||
if (_joins.joins() == null)
|
||||
|
@ -1679,43 +1620,6 @@ public class SelectImpl
|
|||
return pj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any joins already in our parent select from the given non-empty
|
||||
* join set.
|
||||
*/
|
||||
private void removeParentJoins(PathJoins pj) {
|
||||
if (_parent == null)
|
||||
return;
|
||||
if (_parent._joins != null && !_parent._joins.isEmpty()) {
|
||||
boolean removed = false;
|
||||
if (!_removedAliasFromParent.isEmpty()) {
|
||||
for (Iterator itr = pj.joins().iterator(); itr.hasNext();) {
|
||||
Join jn = (Join) itr.next();
|
||||
if (_aliases.containsValue(jn.getIndex1()))
|
||||
removed = _parent._joins.joins().remove(jn);
|
||||
}
|
||||
}
|
||||
if (!removed)
|
||||
pj.joins().removeAll(_parent._joins.joins());
|
||||
}
|
||||
if (!pj.isEmpty())
|
||||
_parent.removeParentJoins(pj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given non-empty joins from the joins of our subselects.
|
||||
*/
|
||||
private void removeJoinsFromSubselects(PathJoins pj) {
|
||||
if (_subsels == null)
|
||||
return;
|
||||
SelectImpl sub;
|
||||
for (int i = 0; i < _subsels.size(); i++) {
|
||||
sub = (SelectImpl) _subsels.get(i);
|
||||
if (sub._joins != null && !sub._joins.isEmpty())
|
||||
sub._joins.joins().removeAll(pj.joins());
|
||||
}
|
||||
}
|
||||
|
||||
public SelectExecutor whereClone(int sels) {
|
||||
if (sels < 1)
|
||||
sels = 1;
|
||||
|
@ -1732,6 +1636,7 @@ public class SelectImpl
|
|||
sel._flags &= ~EAGER_TO_MANY;
|
||||
sel._flags &= ~FORCE_COUNT;
|
||||
sel._joinSyntax = _joinSyntax;
|
||||
sel._schemaAlias = _schemaAlias;
|
||||
if (_aliases != null)
|
||||
sel._aliases = new HashMap(_aliases);
|
||||
if (_tables != null)
|
||||
|
@ -1888,6 +1793,8 @@ public class SelectImpl
|
|||
public void append(SQLBuffer buf, Joins joins) {
|
||||
if (joins == null || joins.isEmpty())
|
||||
return;
|
||||
if (_joinSyntax == JoinSyntaxes.SYNTAX_SQL92)
|
||||
return;
|
||||
|
||||
if (!buf.isEmpty())
|
||||
buf.append(" AND ");
|
||||
|
@ -1915,6 +1822,10 @@ public class SelectImpl
|
|||
return and((PathJoins) joins1, (PathJoins) joins2, true);
|
||||
}
|
||||
|
||||
public Select getSelect() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine the given joins.
|
||||
*/
|
||||
|
@ -1925,20 +1836,25 @@ public class SelectImpl
|
|||
|
||||
SelectJoins sj = new SelectJoins(this);
|
||||
if (j1 == null || j1.isEmpty()) {
|
||||
if (nullJoins)
|
||||
sj.setJoins(j2.joins());
|
||||
else
|
||||
sj.setJoins(new JoinSet(j2.joins()));
|
||||
if (j2.getSelect() == this) {
|
||||
if (nullJoins)
|
||||
sj.setJoins(j2.joins());
|
||||
else
|
||||
sj.setJoins(new JoinSet(j2.joins()));
|
||||
}
|
||||
} else {
|
||||
JoinSet set;
|
||||
if (nullJoins)
|
||||
set = j1.joins();
|
||||
else
|
||||
set = new JoinSet(j1.joins());
|
||||
JoinSet set = null;
|
||||
if (j1.getSelect() == this) {
|
||||
if (nullJoins)
|
||||
set = j1.joins();
|
||||
else
|
||||
set = new JoinSet(j1.joins());
|
||||
|
||||
if (j2 != null && !j2.isEmpty())
|
||||
set.addAll(j2.joins());
|
||||
sj.setJoins(set);
|
||||
if (j2 != null && !j2.isEmpty()
|
||||
&& j2.getSelect() == this)
|
||||
set.addAll(j2.joins());
|
||||
sj.setJoins(set);
|
||||
}
|
||||
}
|
||||
|
||||
// null previous joins; all are combined into this one
|
||||
|
@ -2070,72 +1986,111 @@ public class SelectImpl
|
|||
if (_from != null)
|
||||
return -1;
|
||||
|
||||
Integer i = null;
|
||||
Object key = table.getFullName();
|
||||
if (pj != null && pj.path() != null)
|
||||
key = new Key(pj.path().toString(), key);
|
||||
|
||||
if (_ctx != null)
|
||||
i = findAliasForQuery(table, pj, key, create);
|
||||
|
||||
if (i != null)
|
||||
return i.intValue();
|
||||
|
||||
// check out existing aliases
|
||||
Integer i = findAlias(table, key, false, null);
|
||||
i = findAlias(table, key);
|
||||
|
||||
if (i != null)
|
||||
return i.intValue();
|
||||
if (!create)
|
||||
return -1;
|
||||
|
||||
// not found; create alias
|
||||
i = Numbers.valueOf(aliasSize());
|
||||
i = Numbers.valueOf(aliasSize(null));
|
||||
// System.out.println("GetTableIndex\t"+
|
||||
// ((_parent != null) ? "Sub" :"") +
|
||||
// " created alias: "+
|
||||
// i.intValue()+ " "+ key);
|
||||
recordTableAlias(table, key, i);
|
||||
return i.intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to find the alias for the given key.
|
||||
*
|
||||
* @param fromParent whether a parent is checking its subselects
|
||||
* @param fromSub the subselect checking its parent
|
||||
*/
|
||||
private Integer findAlias(Table table, Object key, boolean fromParent,
|
||||
SelectImpl fromSub) {
|
||||
private Integer findAliasForQuery(Table table, PathJoins pj, Object key,
|
||||
boolean create) {
|
||||
Integer i = null;
|
||||
SelectImpl sel = this;
|
||||
String alias = _schemaAlias;
|
||||
if (isPathInThisContext(pj) || table.isAssociation())
|
||||
alias = null;
|
||||
|
||||
// find the context where this alias is defined
|
||||
Context ctx = (alias != null) ?
|
||||
_ctx.findContext(alias) : null;
|
||||
if (ctx != null)
|
||||
sel = (SelectImpl) ctx.getSelect();
|
||||
|
||||
if (!create)
|
||||
i = sel.findAlias(table, key); // find in parent and in myself
|
||||
else
|
||||
i = sel.getAlias(table, key); // find in myself
|
||||
if (i != null)
|
||||
return i;
|
||||
|
||||
if (create) { // create here
|
||||
i = sel.createAlias(table, key);
|
||||
} else if (ctx != null && ctx != ctx()) { // create in other select
|
||||
i = ((SelectImpl)ctx.getSelect()).createAlias(table, key);
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
private boolean isPathInThisContext(PathJoins pj) {
|
||||
// currCtx is set from Action, it is reset to null after the PCPath initialization
|
||||
Context currCtx = pj == null ? null : ((PathJoinsImpl)pj).context;
|
||||
|
||||
// lastCtx is set to currCtx after the SelectJoins.join. pj.lastCtx and pj.path string are
|
||||
// the last snapshot of pj. They will be used together for later table alias resolution in
|
||||
// the getColumnAlias().
|
||||
Context lastCtx = pj == null ? null : ((PathJoinsImpl)pj).lastContext;
|
||||
Context thisCtx = currCtx == null ? lastCtx : currCtx;
|
||||
String corrVar = pj == null ? null : pj.getCorrelatedVariable();
|
||||
|
||||
return (pj != null && pj.path() != null &&
|
||||
(corrVar == null || (thisCtx != null && ctx() == thisCtx)));
|
||||
}
|
||||
|
||||
private Integer getAlias(Table table, Object key) {
|
||||
Integer alias = null;
|
||||
if (_aliases != null)
|
||||
alias = (Integer) _aliases.get(key);
|
||||
return alias;
|
||||
}
|
||||
|
||||
private int createAlias(Table table, Object key) {
|
||||
Integer i = Numbers.valueOf(ctx().nextAlias());
|
||||
// System.out.println("\t"+
|
||||
// ((_parent != null) ? "Sub" :"") +
|
||||
// "Query created alias: "+
|
||||
// i.intValue()+ " "+ key);
|
||||
recordTableAlias(table, key, i);
|
||||
return i.intValue();
|
||||
}
|
||||
|
||||
private Integer findAlias(Table table, Object key) {
|
||||
Integer alias = null;
|
||||
if (_aliases != null) {
|
||||
alias = (Integer) ((fromParent) ? _aliases.remove(key)
|
||||
: _aliases.get(key));
|
||||
alias = (Integer) _aliases.get(key);
|
||||
if (alias != null) {
|
||||
if (fromParent)
|
||||
_tables.remove(alias);
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
if (!fromParent && _parent != null) {
|
||||
boolean removeAliasFromParent = key.toString().indexOf(":") != -1;
|
||||
alias = _parent.findAlias(table, key, removeAliasFromParent, this);
|
||||
if (_parent != null) {
|
||||
alias = _parent.findAlias(table, key);
|
||||
if (alias != null) {
|
||||
if (removeAliasFromParent) {
|
||||
recordTableAlias(table, key, alias);
|
||||
_removedAliasFromParent.set(alias.intValue());
|
||||
}
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
if (_subsels != null) {
|
||||
SelectImpl sub;
|
||||
for (int i = 0; i < _subsels.size(); i++) {
|
||||
sub = (SelectImpl) _subsels.get(i);
|
||||
if (sub == fromSub)
|
||||
continue;
|
||||
if (alias != null) {
|
||||
if (sub._aliases != null)
|
||||
sub._aliases.remove(key);
|
||||
if (sub._tables != null)
|
||||
sub._tables.remove(alias);
|
||||
} else {
|
||||
if (key instanceof String) {
|
||||
alias = sub.findAlias(table, key, true, null);
|
||||
if (!fromParent && alias != null)
|
||||
recordTableAlias(table, key, alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return alias;
|
||||
}
|
||||
|
||||
|
@ -2157,26 +2112,16 @@ public class SelectImpl
|
|||
/**
|
||||
* Calculate total number of aliases.
|
||||
*/
|
||||
private int aliasSize() {
|
||||
return aliasSize(false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate total number of aliases.
|
||||
*
|
||||
* @param fromParent whether a parent is checking its subselects
|
||||
* @param fromSub the subselect checking its parent
|
||||
*/
|
||||
private int aliasSize(boolean fromParent, SelectImpl fromSub) {
|
||||
int aliases = (fromParent || _parent == null) ? 0
|
||||
: _parent.aliasSize(false, this);
|
||||
private int aliasSize(SelectImpl fromSub) {
|
||||
int aliases = (_parent == null) ? 0
|
||||
: _parent.aliasSize(this);
|
||||
aliases += (_aliases == null) ? 0 : _aliases.size();
|
||||
if (_subsels != null) {
|
||||
SelectImpl sub;
|
||||
for (int i = 0; i < _subsels.size(); i++) {
|
||||
sub = (SelectImpl) _subsels.get(i);
|
||||
if (sub != fromSub)
|
||||
aliases += sub.aliasSize(true, null);
|
||||
aliases += sub.aliasSize(null);
|
||||
}
|
||||
}
|
||||
return aliases;
|
||||
|
@ -2268,6 +2213,37 @@ public class SelectImpl
|
|||
*/
|
||||
private static class Placeholder {
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
_aliases = null;
|
||||
_eager = null;
|
||||
_eagerKeys = null;
|
||||
_expectedResultCount = 0;
|
||||
_flags = 0;
|
||||
_from = null;
|
||||
_grouped = null;
|
||||
_grouping = null;
|
||||
_having = null;
|
||||
_joins = null;
|
||||
_joinSyntax = 0;
|
||||
_nullIds = 0;
|
||||
_ordered = null;
|
||||
_ordering = null;
|
||||
_orders = 0;
|
||||
_outer = null;
|
||||
_parent = null;
|
||||
_placeholders = 0;
|
||||
_preJoins = null;
|
||||
_schemaAlias = null;
|
||||
_selects._aliases = null;
|
||||
_selects._ids = null;
|
||||
_subPath = null;
|
||||
_subsels = null;
|
||||
_tables = null;
|
||||
_where = null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Key type used for aliases.
|
||||
|
@ -2599,6 +2575,21 @@ public class SelectImpl
|
|||
return this;
|
||||
return new PathJoinsImpl().setSubselect(alias);
|
||||
}
|
||||
|
||||
public Joins setCorrelatedVariable(String var) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Joins setJoinContext(Context ctx) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getCorrelatedVariable() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void moveJoinsToParent() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2609,6 +2600,13 @@ public class SelectImpl
|
|||
|
||||
protected StringBuffer path = null;
|
||||
protected String var = null;
|
||||
protected String correlatedVar = null;
|
||||
protected Context context = null;
|
||||
protected Context lastContext = null;
|
||||
|
||||
public Select getSelect() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isOuter() {
|
||||
return false;
|
||||
|
@ -2642,9 +2640,25 @@ public class SelectImpl
|
|||
return this;
|
||||
}
|
||||
|
||||
public String getVariable() {
|
||||
return var;
|
||||
}
|
||||
|
||||
public Joins setCorrelatedVariable(String var) {
|
||||
this.correlatedVar = var;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getCorrelatedVariable() {
|
||||
return correlatedVar;
|
||||
}
|
||||
|
||||
public Joins setJoinContext(Context context) {
|
||||
this.context = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Joins setSubselect(String alias) {
|
||||
if (!alias.endsWith(":"))
|
||||
alias += ':';
|
||||
append(alias);
|
||||
return this;
|
||||
}
|
||||
|
@ -2700,6 +2714,9 @@ public class SelectImpl
|
|||
return "PathJoinsImpl<" + hashCode() + ">: "
|
||||
+ String.valueOf(path);
|
||||
}
|
||||
|
||||
public void moveJoinsToParent() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2718,6 +2735,10 @@ public class SelectImpl
|
|||
_sel = sel;
|
||||
}
|
||||
|
||||
public Select getSelect() {
|
||||
return _sel;
|
||||
}
|
||||
|
||||
public boolean isOuter() {
|
||||
return _outer;
|
||||
}
|
||||
|
@ -2778,9 +2799,14 @@ public class SelectImpl
|
|||
// until we get past the local table
|
||||
String var = this.var;
|
||||
this.var = null;
|
||||
Context ctx = context;
|
||||
context = null;
|
||||
|
||||
int alias1 = _sel.getTableIndex(localTable, this, true);
|
||||
this.append(var);
|
||||
this.append(correlatedVar);
|
||||
context = ctx;
|
||||
|
||||
int alias2 = _sel.getTableIndex(foreignTable, this, true);
|
||||
Join j = new Join(localTable, alias1, foreignTable, alias2,
|
||||
null, false);
|
||||
|
@ -2789,7 +2815,10 @@ public class SelectImpl
|
|||
if (_joins == null)
|
||||
_joins = new JoinSet();
|
||||
_joins.add(j);
|
||||
setCorrelated(j);
|
||||
_outer = false;
|
||||
lastContext = context;
|
||||
context = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -2817,6 +2846,8 @@ public class SelectImpl
|
|||
// until we get past the local table
|
||||
String var = this.var;
|
||||
this.var = null;
|
||||
Context ctx = context;
|
||||
context = null;
|
||||
|
||||
// get first table alias before updating path; if there is a from
|
||||
// select then we shouldn't actually create a join object, since
|
||||
|
@ -2825,13 +2856,19 @@ public class SelectImpl
|
|||
Table table1 = null;
|
||||
int alias1 = -1;
|
||||
if (createJoin) {
|
||||
boolean createIndex = true;
|
||||
table1 = (inverse) ? fk.getPrimaryKeyTable() : fk.getTable();
|
||||
alias1 = _sel.getTableIndex(table1, this, true);
|
||||
if (correlatedVar != null)
|
||||
createIndex = false; // not to create here
|
||||
alias1 = _sel.getTableIndex(table1, this, createIndex);
|
||||
}
|
||||
|
||||
// update the path with the relation name before getting pk alias
|
||||
this.append(name);
|
||||
this.append(var);
|
||||
this.append(correlatedVar);
|
||||
context = ctx;
|
||||
|
||||
if (toMany) {
|
||||
_sel._flags |= IMPLICIT_DISTINCT;
|
||||
_sel._flags |= TO_MANY;
|
||||
|
@ -2839,9 +2876,16 @@ public class SelectImpl
|
|||
_outer = outer;
|
||||
|
||||
if (createJoin) {
|
||||
boolean createIndex = true;
|
||||
Table table2 = (inverse) ? fk.getTable()
|
||||
: fk.getPrimaryKeyTable();
|
||||
int alias2 = _sel.getTableIndex(table2, this, true);
|
||||
if (table2.isAssociation())
|
||||
createIndex = true;
|
||||
else if (context == _sel.ctx())
|
||||
createIndex = true;
|
||||
else if (correlatedVar != null)
|
||||
createIndex = false;
|
||||
int alias2 = _sel.getTableIndex(table2, this, createIndex);
|
||||
Join j = new Join(table1, alias1, table2, alias2, fk, inverse);
|
||||
j.setType((outer) ? Join.TYPE_OUTER : Join.TYPE_INNER);
|
||||
|
||||
|
@ -2850,10 +2894,91 @@ public class SelectImpl
|
|||
if (_joins.add(j) && (subs == Select.SUBS_JOINABLE
|
||||
|| subs == Select.SUBS_NONE))
|
||||
j.setRelation(target, subs, clone(_sel));
|
||||
|
||||
setCorrelated(j);
|
||||
}
|
||||
lastContext = context;
|
||||
context = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void setCorrelated(Join j) {
|
||||
if (_sel._parent == null)
|
||||
return;
|
||||
|
||||
if (_sel._aliases == null) {
|
||||
j.setIsNotMyJoin();
|
||||
return;
|
||||
}
|
||||
|
||||
Object aliases[] = _sel._aliases.values().toArray();
|
||||
boolean found1 = false;
|
||||
boolean found2 = false;
|
||||
|
||||
for (int i = 0; i < aliases.length; i++) {
|
||||
int alias = ((Integer)aliases[i]).intValue();
|
||||
if (alias == j.getIndex1())
|
||||
found1 = true;
|
||||
if (alias == j.getIndex2())
|
||||
found2 = true;
|
||||
}
|
||||
|
||||
if (found1 && found2)
|
||||
return;
|
||||
else if (!found1 && !found2) {
|
||||
j.setIsNotMyJoin();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
j.setCorrelated();
|
||||
}
|
||||
}
|
||||
|
||||
public void moveJoinsToParent() {
|
||||
if (_joins == null)
|
||||
return;
|
||||
Join j = null;
|
||||
List<Join> removed = new ArrayList<Join>(5);
|
||||
for (Iterator itr = _joins.iterator(); itr.hasNext();) {
|
||||
j = (Join) itr.next();
|
||||
if (j.isNotMyJoin()) {
|
||||
addJoinsToParent(_sel._parent, j);
|
||||
removed.add(j);
|
||||
}
|
||||
}
|
||||
for (Join join : removed) {
|
||||
_joins.remove(join);
|
||||
}
|
||||
}
|
||||
|
||||
private void addJoinsToParent(SelectImpl parent, Join join) {
|
||||
if (parent._aliases == null)
|
||||
return;
|
||||
Object aliases[] = parent._aliases.values().toArray();
|
||||
boolean found1 = false;
|
||||
boolean found2 = false;
|
||||
|
||||
for (int i = 0; i < aliases.length; i++) {
|
||||
int alias = ((Integer)aliases[i]).intValue();
|
||||
if (alias == join.getIndex1())
|
||||
found1 = true;
|
||||
if (alias == join.getIndex2())
|
||||
found2 = true;
|
||||
}
|
||||
|
||||
if (found1 && found2) {
|
||||
// this is my join, add join
|
||||
if (parent._joins == null)
|
||||
parent._joins = new SelectJoins(parent);
|
||||
SelectJoins p = parent._joins;
|
||||
if (p.joins() == null)
|
||||
p.setJoins(new JoinSet());
|
||||
p.joins().add(join);
|
||||
}
|
||||
else if (parent._parent != null)
|
||||
addJoinsToParent(parent._parent, join);
|
||||
}
|
||||
|
||||
public SelectJoins clone(SelectImpl sel) {
|
||||
SelectJoins sj = new SelectJoins(sel);
|
||||
sj.var = var;
|
||||
|
@ -3067,6 +3192,25 @@ public class SelectImpl
|
|||
_idents = null;
|
||||
}
|
||||
}
|
||||
|
||||
public Joins setCorrelatedVariable(String var) {
|
||||
if (var == null)
|
||||
return this;
|
||||
return new SelectJoins(this).setCorrelatedVariable(var);
|
||||
}
|
||||
|
||||
public Joins setJoinContext(Context ctx) {
|
||||
if (ctx == null)
|
||||
return this;
|
||||
return new SelectJoins(this).setJoinContext(ctx);
|
||||
}
|
||||
|
||||
public String getCorrelatedVariable() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void moveJoinsToParent() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3106,5 +3250,11 @@ interface PathJoins
|
|||
* Null the set of {@link Join} elements.
|
||||
*/
|
||||
public void nullJoins();
|
||||
|
||||
/**
|
||||
* The select owner of this join
|
||||
* @return
|
||||
*/
|
||||
public Select getSelect();
|
||||
}
|
||||
|
||||
|
|
|
@ -169,10 +169,12 @@ public abstract class AbstractExpressionBuilder {
|
|||
type = TYPE_OBJECT;
|
||||
else
|
||||
meta = getMetaData(type, false);
|
||||
if (meta != null)
|
||||
if (meta != null) {
|
||||
addAccessPath(meta);
|
||||
addSchemaToContext(id, meta);
|
||||
}
|
||||
|
||||
Value var;
|
||||
Value var = null;
|
||||
if (bind)
|
||||
var = factory.newBoundVariable(id, type);
|
||||
else
|
||||
|
@ -182,6 +184,8 @@ public abstract class AbstractExpressionBuilder {
|
|||
if (_seenVars == null)
|
||||
_seenVars = new HashMap<String,Value>();
|
||||
_seenVars.put(id, var);
|
||||
|
||||
addVariableToContext(id, var);
|
||||
return var;
|
||||
}
|
||||
|
||||
|
@ -308,7 +312,7 @@ public abstract class AbstractExpressionBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
if (meta != null || !pcOnly)
|
||||
if (meta != null || !pcOnly)
|
||||
path.get(fmd, allowNull);
|
||||
|
||||
return path;
|
||||
|
@ -521,5 +525,28 @@ public abstract class AbstractExpressionBuilder {
|
|||
* Returns the current string being parsed; used for error messages.
|
||||
*/
|
||||
protected abstract String currentQuery ();
|
||||
|
||||
/**
|
||||
* Register the schema alias to the current JPQL query context.
|
||||
* @param alias
|
||||
* @param meta
|
||||
*/
|
||||
protected abstract void addSchemaToContext(String alias,
|
||||
ClassMetaData meta);
|
||||
|
||||
/**
|
||||
* Register the variable associated with the schema alias (id) to
|
||||
* the current JPQL query context.
|
||||
* @param id
|
||||
* @param var
|
||||
*/
|
||||
protected abstract void addVariableToContext(String id, Value var);
|
||||
|
||||
/**
|
||||
* Returns the variable associated with the schema alias (id).
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
protected abstract Value getSeenVariable(String id);
|
||||
}
|
||||
|
||||
|
|
|
@ -196,4 +196,14 @@ public class CandidatePath
|
|||
public XMLMetaData getXmlMapping() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setSchemaAlias(String schemaAlias) {
|
||||
}
|
||||
|
||||
public String getSchemaAlias() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setSubqueryContext(Context conext) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.openjpa.kernel.exps;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.openjpa.kernel.jpql.JPQLExpressionBuilder.ParsedJPQL;
|
||||
import org.apache.openjpa.meta.ClassMetaData;
|
||||
|
||||
/**
|
||||
* JPQL / Criteria Query Context
|
||||
* @since 2.0
|
||||
*
|
||||
*/
|
||||
public class Context implements Serializable {
|
||||
|
||||
public final ParsedJPQL parsed;
|
||||
public ClassMetaData meta;
|
||||
public String schemaAlias;
|
||||
public Subquery subquery;
|
||||
public Expression from = null;
|
||||
private Context parent = null;
|
||||
private List<Context> subsels = null;
|
||||
private Object select = null;
|
||||
private int aliasCount = -1;
|
||||
private Map<String,Value> variables = new HashMap<String,Value>();
|
||||
private Map<String,ClassMetaData> schemas =
|
||||
new HashMap<String,ClassMetaData>();
|
||||
|
||||
public Context(ParsedJPQL parsed, Subquery subquery, Context parent) {
|
||||
this.parsed = parsed;
|
||||
this.subquery = subquery;
|
||||
this.parent = parent;
|
||||
if (subquery != null) {
|
||||
this.select = subquery.getSelect();
|
||||
parent.addSubselContext(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void setSubquery(Subquery subquery) {
|
||||
this.subquery = subquery;
|
||||
this.select = subquery.getSelect();
|
||||
parent.addSubselContext(this);
|
||||
}
|
||||
|
||||
public ClassMetaData meta() {
|
||||
return meta;
|
||||
}
|
||||
|
||||
public String schemaAlias() {
|
||||
return schemaAlias;
|
||||
}
|
||||
|
||||
public Subquery subquery() {
|
||||
return subquery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns next table alias to be created.
|
||||
* @return
|
||||
*/
|
||||
public int nextAlias() {
|
||||
Context p = this;
|
||||
while (p.subquery != null) {
|
||||
p = p.parent;
|
||||
}
|
||||
p.aliasCount++;
|
||||
return p.aliasCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset alias count for prepared query cache
|
||||
*
|
||||
*/
|
||||
public void resetAliasCount() {
|
||||
Context p = this;
|
||||
while (p.subquery != null) {
|
||||
p = p.parent;
|
||||
}
|
||||
p.aliasCount = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the select for this context.
|
||||
* @param select
|
||||
*/
|
||||
public void setSelect(Object select) {
|
||||
this.select = select;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the select associated with this context.
|
||||
* @return
|
||||
*/
|
||||
public Object getSelect() {
|
||||
return select;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the subquery context in this context.
|
||||
* @param sub
|
||||
*/
|
||||
private void addSubselContext(Context sub) {
|
||||
if (subsels == null)
|
||||
subsels = new ArrayList<Context>();
|
||||
subsels.add(sub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the subquery context.
|
||||
* @return
|
||||
*/
|
||||
public List<Context> getSubselContexts() {
|
||||
return subsels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the subquery in this context.
|
||||
* @return
|
||||
*/
|
||||
public Subquery getSubquery() {
|
||||
return subquery;
|
||||
}
|
||||
|
||||
public Context getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void setParent(Context parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public void addVariable(String id, Value var) {
|
||||
variables.put(id.toLowerCase(), var);
|
||||
}
|
||||
|
||||
public void addSchema(String id, ClassMetaData meta) {
|
||||
schemas.put(id.toLowerCase(), meta);
|
||||
}
|
||||
|
||||
public ClassMetaData getSchema(String id) {
|
||||
if (id != null)
|
||||
return schemas.get(id.toLowerCase());
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an alias and return its associated variable.
|
||||
* @param var
|
||||
* @return
|
||||
*/
|
||||
public Value getVariable(String var) {
|
||||
Value variable = var == null ? null
|
||||
: variables.get(var.toLowerCase());
|
||||
return variable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an alias find the context of its associated
|
||||
* variable where it is defined.
|
||||
* @param alias
|
||||
* @return
|
||||
*/
|
||||
public Context findContext(String alias) {
|
||||
Value var = getVariable(alias);
|
||||
if (var != null)
|
||||
return this;
|
||||
for (Context p = parent; p != null; ) {
|
||||
var = p.getVariable(alias);
|
||||
if (var != null)
|
||||
return p;
|
||||
p = p.parent;
|
||||
}
|
||||
if (subsels != null) {
|
||||
for (Context subsel : subsels) {
|
||||
if (subsel != null) {
|
||||
var = subsel.getVariable(alias);
|
||||
if (var != null)
|
||||
return subsel;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an alias find the variable in JPQL contexts.
|
||||
* @param alias
|
||||
* @return
|
||||
*/
|
||||
public Value findVariable(String alias) {
|
||||
Value var = getVariable(alias);
|
||||
if (var != null)
|
||||
return var;
|
||||
for (Context p = parent; p != null; ) {
|
||||
var = p.getVariable(alias);
|
||||
if (var != null)
|
||||
return var;
|
||||
p = p.parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -67,4 +67,15 @@ public interface Path
|
|||
* @return Return xmlmapping
|
||||
*/
|
||||
public XMLMetaData getXmlMapping();
|
||||
|
||||
/**
|
||||
* Set the schema alias (the identification variable)
|
||||
* this path is begin with.
|
||||
* @param schemaAlias
|
||||
*/
|
||||
public void setSchemaAlias(String schemaAlias);
|
||||
|
||||
public String getSchemaAlias();
|
||||
|
||||
public void setSubqueryContext(Context context);
|
||||
}
|
||||
|
|
|
@ -22,10 +22,12 @@ import java.io.Serializable;
|
|||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.apache.commons.collections.map.LinkedMap;
|
||||
import org.apache.openjpa.kernel.QueryOperations;
|
||||
import org.apache.openjpa.kernel.StoreQuery;
|
||||
import org.apache.openjpa.kernel.exps.Context;
|
||||
import org.apache.openjpa.meta.ClassMetaData;
|
||||
import org.apache.openjpa.meta.FieldMetaData;
|
||||
|
||||
|
@ -69,8 +71,25 @@ public class QueryExpressions
|
|||
public String[] fetchInnerPaths = StoreQuery.EMPTY_STRINGS;
|
||||
public Value[] range = EMPTY_VALUES;
|
||||
private Boolean _aggregate = null;
|
||||
private Stack<Context> _contexts = null;
|
||||
public Object state;
|
||||
|
||||
/**
|
||||
* Set reference to the JPQL query contexts.
|
||||
* @param contexts
|
||||
*/
|
||||
public void setContexts(Stack<Context> contexts) {
|
||||
_contexts = contexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current JPQL query context.
|
||||
* @return
|
||||
*/
|
||||
public Context ctx() {
|
||||
return _contexts.peek();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this is an aggregate results.
|
||||
*/
|
||||
|
|
|
@ -41,6 +41,10 @@ class SubQ
|
|||
_alias = alias;
|
||||
}
|
||||
|
||||
public Object getSelect() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getCandidateAlias() {
|
||||
return _alias;
|
||||
}
|
||||
|
|
|
@ -36,4 +36,6 @@ public interface Subquery
|
|||
* Set the parsed subquery.
|
||||
*/
|
||||
public void setQueryExpressions(QueryExpressions query);
|
||||
|
||||
public Object getSelect();
|
||||
}
|
||||
|
|
|
@ -135,4 +135,8 @@ public abstract class Val
|
|||
public Path getPath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,4 +90,6 @@ public interface Value
|
|||
public Value getSelectAs();
|
||||
|
||||
public Path getPath();
|
||||
|
||||
public String getName();
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ import org.apache.openjpa.kernel.exps.QueryExpressions;
|
|||
import org.apache.openjpa.kernel.exps.Resolver;
|
||||
import org.apache.openjpa.kernel.exps.Subquery;
|
||||
import org.apache.openjpa.kernel.exps.Value;
|
||||
import org.apache.openjpa.kernel.exps.Context;
|
||||
import org.apache.openjpa.lib.util.Localizer;
|
||||
import org.apache.openjpa.lib.util.Localizer.Message;
|
||||
import org.apache.openjpa.lib.log.Log;
|
||||
|
@ -99,7 +100,7 @@ public class JPQLExpressionBuilder
|
|||
? (ParsedJPQL) parsedQuery
|
||||
: parsedQuery instanceof String
|
||||
? getParsedQuery((String) parsedQuery)
|
||||
: null, null));
|
||||
: null, null, null));
|
||||
|
||||
if (ctx().parsed == null)
|
||||
throw new InternalException(parsedQuery + "");
|
||||
|
@ -271,11 +272,15 @@ public class JPQLExpressionBuilder
|
|||
|
||||
QueryExpressions getQueryExpressions() {
|
||||
QueryExpressions exps = new QueryExpressions();
|
||||
exps.setContexts(contexts);
|
||||
|
||||
evalQueryOperation(exps);
|
||||
|
||||
Expression filter = null;
|
||||
filter = and(evalFromClause(root().id == JJTSELECT), filter);
|
||||
Expression from = ctx().from;
|
||||
if (from == null)
|
||||
from = evalFromClause(root().id == JJTSELECT);
|
||||
filter = and(from, filter);
|
||||
filter = and(evalWhereClause(), filter);
|
||||
filter = and(evalSelectClause(exps), filter);
|
||||
|
||||
|
@ -413,7 +418,7 @@ public class JPQLExpressionBuilder
|
|||
exps.ascending[i] = node.getChildCount() <= 1 ||
|
||||
lastChild(node).id == JJTASCENDING ? true : false;
|
||||
}
|
||||
// check if order by selec item alias
|
||||
// check if order by select item result alias
|
||||
for (int i = 0; i < ordercount; i++) {
|
||||
if (exps.orderingClauses[i] != null &&
|
||||
!exps.orderingClauses[i].equals(""))
|
||||
|
@ -543,12 +548,15 @@ public class JPQLExpressionBuilder
|
|||
}
|
||||
|
||||
private Expression evalFromClause(boolean needsAlias) {
|
||||
Expression exp = null;
|
||||
|
||||
// build up the alias map in the FROM clause
|
||||
JPQLNode from = root().findChildByID(JJTFROM, false);
|
||||
if (from == null)
|
||||
throw parseException(EX_USER, "no-from-clause", null, null);
|
||||
return evalFromClause(from, needsAlias);
|
||||
}
|
||||
|
||||
private Expression evalFromClause(JPQLNode from, boolean needsAlias) {
|
||||
Expression exp = null;
|
||||
|
||||
for (int i = 0; i < from.children.length; i++) {
|
||||
JPQLNode node = from.children[i];
|
||||
|
@ -573,7 +581,7 @@ public class JPQLExpressionBuilder
|
|||
|
||||
private Expression bindVariableForKeyPath(Path path, String alias,
|
||||
Expression exp) {
|
||||
if (alias != null && !isSeendVariable(alias)) {
|
||||
if (alias != null && ctx().findVariable(alias) == null) {
|
||||
// subquery may have KEY range over a variable
|
||||
// that is not defined.
|
||||
JPQLNode key = root().findChildByID(JJTKEY, true);
|
||||
|
@ -584,6 +592,29 @@ public class JPQLExpressionBuilder
|
|||
}
|
||||
return exp;
|
||||
}
|
||||
|
||||
private Expression getSubquery(String alias, Path path, Expression exp) {
|
||||
FieldMetaData fmd = path.last();
|
||||
ClassMetaData candidate = getFieldType(fmd);
|
||||
if (candidate == null && fmd.isElementCollection())
|
||||
candidate = fmd.getDefiningMetaData();
|
||||
|
||||
setCandidate(candidate, alias);
|
||||
|
||||
Context subContext = ctx();
|
||||
Subquery subquery = ctx().getSubquery();
|
||||
if (subquery == null){
|
||||
subquery = factory.newSubquery(candidate, true, alias);
|
||||
subContext.setSubquery(subquery);
|
||||
}
|
||||
Path subpath = factory.newPath(subquery);
|
||||
subpath.setMetaData(candidate);
|
||||
subquery.setMetaData(candidate);
|
||||
exp = bindVariableForKeyPath(path, alias, exp);
|
||||
exp = and(exp, factory.equal(path, subpath));
|
||||
return exp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a join condition to the given expression.
|
||||
*
|
||||
|
@ -604,14 +635,8 @@ public class JPQLExpressionBuilder
|
|||
JPQLNode alias = node.getChildCount() >= 2 ? right(node) : null;
|
||||
// OPENJPA-15 support subquery's from clause do not start with
|
||||
// identification_variable_declaration()
|
||||
if (inner && ctx().subquery != null && ctx().schemaAlias == null) {
|
||||
setCandidate(getFieldType(path.last()), alias.text);
|
||||
|
||||
Path subpath = factory.newPath(ctx().subquery);
|
||||
subpath.setMetaData(ctx().subquery.getMetaData());
|
||||
exp = bindVariableForKeyPath(path, alias.text, exp);
|
||||
exp = and(exp, factory.equal(path, subpath));
|
||||
return exp;
|
||||
if (inner && ctx().getParent() != null && ctx().schemaAlias == null) {
|
||||
return getSubquery(alias.text, path, exp);
|
||||
}
|
||||
|
||||
return addJoin(path, alias, exp);
|
||||
|
@ -666,23 +691,13 @@ public class JPQLExpressionBuilder
|
|||
} else {
|
||||
alias = right(node).text;
|
||||
JPQLNode left = left(node);
|
||||
addSchemaToContext(alias, cmd);
|
||||
|
||||
// check to see if the we are referring to a path in the from
|
||||
// clause, since we might be in a subquery against a collection
|
||||
if (isPath(left)) {
|
||||
Path path = getPath(left);
|
||||
FieldMetaData fmd = path.last();
|
||||
ClassMetaData candidate = getFieldType(fmd);
|
||||
|
||||
if (candidate == null && fmd.isElementCollection())
|
||||
candidate = fmd.getDefiningMetaData();
|
||||
|
||||
setCandidate(candidate, alias);
|
||||
|
||||
Path subpath = factory.newPath(ctx().subquery);
|
||||
subpath.setMetaData(ctx().subquery.getMetaData());
|
||||
exp = bindVariableForKeyPath(path, alias, exp);
|
||||
return and(exp, factory.equal(path, subpath));
|
||||
return getSubquery(alias, path, exp);
|
||||
} else {
|
||||
// we have an alias: bind it as a variable
|
||||
Value var = getVariable(alias, true);
|
||||
|
@ -764,8 +779,15 @@ public class JPQLExpressionBuilder
|
|||
return super.getVariable(id.toLowerCase(), bind);
|
||||
}
|
||||
|
||||
protected boolean isSeendVariable(String id) {
|
||||
return id != null && super.isSeenVariable(id.toLowerCase());
|
||||
protected Value getDefinedVariable(String id) {
|
||||
return ctx().getVariable(id);
|
||||
}
|
||||
|
||||
protected boolean isSeenVariable(String var) {
|
||||
Context c = ctx().findContext(var);
|
||||
if (c != null)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1181,7 +1203,11 @@ public class JPQLExpressionBuilder
|
|||
return eval(onlyChild(node));
|
||||
|
||||
case JJTCOUNT:
|
||||
return factory.count(getValue(lastChild(node)));
|
||||
JPQLNode c = lastChild(node);
|
||||
if (c.id == JJTIDENTIFIER)
|
||||
// count(e)
|
||||
return factory.count(getPath(node, false, true));
|
||||
return factory.count(getValue(c));
|
||||
|
||||
case JJTMAX:
|
||||
return factory.max(getNumberValue(onlyChild(node)));
|
||||
|
@ -1392,12 +1418,22 @@ public class JPQLExpressionBuilder
|
|||
|
||||
// parse the subquery
|
||||
ParsedJPQL parsed = new ParsedJPQL(node.parser.jpql, node);
|
||||
Context parent = ctx();
|
||||
Context subContext = new Context(parsed, null, ctx());
|
||||
contexts.push(subContext);
|
||||
subContext.setParent(parent);
|
||||
|
||||
ClassMetaData candidate = getCandidateMetaData(node);
|
||||
Subquery subq = factory.newSubquery(candidate, subclasses, alias);
|
||||
Subquery subq = subContext.getSubquery();
|
||||
if (subq == null) {
|
||||
subq = factory.newSubquery(candidate, subclasses, alias);
|
||||
subContext.setSubquery(subq);
|
||||
}
|
||||
subq.setMetaData(candidate);
|
||||
|
||||
contexts.push(new Context(parsed, subq));
|
||||
|
||||
// evaluate from clause for resolving variables defined in subquery
|
||||
JPQLNode from = node.getChild(1);
|
||||
subContext.from = evalFromClause(from, true);
|
||||
|
||||
try {
|
||||
QueryExpressions subexp = getQueryExpressions();
|
||||
|
@ -1506,7 +1542,14 @@ public class JPQLExpressionBuilder
|
|||
if (cmd != null) {
|
||||
// handle the case where the class name is the alias
|
||||
// for the candidate (we don't use variables for this)
|
||||
Value thiz = factory.getThis();
|
||||
Value thiz = null;
|
||||
if (ctx().subquery == null ||
|
||||
ctx().getSchema(name.toLowerCase()) == null) {
|
||||
thiz = factory.getThis();
|
||||
} else {
|
||||
thiz = factory.newPath(ctx().subquery);
|
||||
}
|
||||
((Path)thiz).setSchemaAlias(name);
|
||||
thiz.setMetaData(cmd);
|
||||
return thiz;
|
||||
} else if (val instanceof Path) {
|
||||
|
@ -1734,6 +1777,7 @@ public class JPQLExpressionBuilder
|
|||
if (ctx().subquery != null) {
|
||||
path = factory.newPath(ctx().subquery);
|
||||
path.setMetaData(ctx().subquery.getMetaData());
|
||||
factory.bindVariable(val, path);
|
||||
} else {
|
||||
path = factory.newPath();
|
||||
path.setMetaData(ctx().meta);
|
||||
|
@ -1748,6 +1792,8 @@ public class JPQLExpressionBuilder
|
|||
throw parseException(EX_USER, "path-invalid",
|
||||
new Object[]{ assemble(node), name }, null);
|
||||
|
||||
path.setSchemaAlias(name);
|
||||
|
||||
// walk through the children and assemble the path
|
||||
boolean allowNull = !inner;
|
||||
for (int i = 1; i < node.children.length; i++) {
|
||||
|
@ -1758,7 +1804,10 @@ public class JPQLExpressionBuilder
|
|||
}
|
||||
path = (Path) traversePath(path, node.children[i].text, pcOnly,
|
||||
allowNull);
|
||||
|
||||
if (ctx().getParent() != null && ctx().getVariable(path.getSchemaAlias()) == null) {
|
||||
path.setSubqueryContext(ctx());
|
||||
}
|
||||
|
||||
// all traversals but the first one will always be inner joins
|
||||
allowNull = false;
|
||||
}
|
||||
|
@ -1902,17 +1951,23 @@ public class JPQLExpressionBuilder
|
|||
return null;
|
||||
}
|
||||
|
||||
private class Context {
|
||||
protected void addSchemaToContext(String id, ClassMetaData meta) {
|
||||
ctx().addSchema(id.toLowerCase(), meta);
|
||||
}
|
||||
|
||||
private final ParsedJPQL parsed;
|
||||
private ClassMetaData meta;
|
||||
private String schemaAlias;
|
||||
private Subquery subquery;
|
||||
protected void addVariableToContext(String id, Value var) {
|
||||
ctx().addVariable(id, var);
|
||||
}
|
||||
|
||||
Context(ParsedJPQL parsed, Subquery subquery) {
|
||||
this.parsed = parsed;
|
||||
this.subquery = subquery;
|
||||
}
|
||||
protected Value getSeenVariable(String var) {
|
||||
Context c = ctx();
|
||||
Value v = c.getVariable(var);
|
||||
if (v != null)
|
||||
return v;
|
||||
if (c.getParent() != null)
|
||||
return c.getParent().findVariable(var);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.util.List;
|
|||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
|
||||
import org.apache.openjpa.persistence.query.Customer.CreditRating;
|
||||
import org.apache.openjpa.persistence.test.SingleEMFTestCase;
|
||||
|
||||
/**
|
||||
|
@ -68,7 +69,7 @@ public class TestSubquery
|
|||
" (select o from c.orders o where o.oid = 1) or exists" +
|
||||
" (select o from c.orders o where o.oid = 2)",
|
||||
"select c.name from Customer c, in(c.orders) o where o.amount " +
|
||||
"between" +
|
||||
"between" +
|
||||
" (select max(o.amount) from Order o) and" +
|
||||
" (select avg(o.amount) from Order o) ",
|
||||
"select o.oid from Order o where o.amount >" +
|
||||
|
@ -90,12 +91,12 @@ public class TestSubquery
|
|||
"(SELECT MAX(m3.datePublished) "+
|
||||
"FROM Magazine m3 "+
|
||||
"WHERE m3.idPublisher.id = p.id)) ",
|
||||
// outstanding problem subqueries:
|
||||
// "select o from Order o where o.amount > (select count(o) from Order o)",
|
||||
// "select o from Order o where o.amount > (select count(o2) from
|
||||
// Order o2)",
|
||||
// "select c from Customer c left join c.orders o where not exists"
|
||||
// + " (select o2 from c.orders o2 where o2 = o)",
|
||||
"select o from Order o where o.amount > " +
|
||||
" (select count(o) from Order o)",
|
||||
"select o from Order o where o.amount > " +
|
||||
"(select count(o2) from Order o2)",
|
||||
"select c from Customer c left join c.orders o where not exists"
|
||||
+ " (select o2 from c.orders o2 where o2 = o)",
|
||||
};
|
||||
|
||||
static String[] querys_jpa20 = new String[] {
|
||||
|
@ -206,4 +207,16 @@ public class TestSubquery
|
|||
q.getResultList();
|
||||
em.close();
|
||||
}
|
||||
|
||||
public void testUpdateWithCorrelatedSubquery() {
|
||||
String update = "update Customer c set c.creditRating = ?1 where EXISTS" +
|
||||
" (select o from in(c.orders) o)";
|
||||
EntityManager em = emf.createEntityManager();
|
||||
em.getTransaction().begin();
|
||||
CreditRating creditRating = CreditRating.GOOD;
|
||||
int updateCount = em.createQuery(update).
|
||||
setParameter(1, creditRating).executeUpdate();
|
||||
em.getTransaction().rollback();
|
||||
em.close();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue