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:
Fay Wang 2009-07-20 17:49:36 +00:00
parent 0adf13872e
commit f74c5d7a69
29 changed files with 1151 additions and 279 deletions

View File

@ -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;
@ -144,10 +143,27 @@ public class JDBCStoreQuery
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;

View File

@ -114,5 +114,9 @@ abstract class AbstractVal
public Path getPath() {
return null;
}
public String getName() {
return null;
}
}

View File

@ -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) {
}
}

View File

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

View File

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

View File

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

View File

@ -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)
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)
}
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,7 +514,15 @@ public class PCPath
if (rel == null)
throw new IllegalArgumentException(_loc.get(
"invalid-unbound-var", var.getName()).toString());
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 {
@ -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;

View File

@ -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()) {

View File

@ -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;
}
/**
@ -68,6 +77,14 @@ class SubQ
return _candidate;
}
public boolean getSubs() {
return _subs;
}
public String getSubqAlias() {
return _subqAlias;
}
public Class getType() {
if (_exps != null && _type == null) {
if (_exps.projections.length == 0)
@ -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);

View File

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

View File

@ -63,6 +63,7 @@ public class Table
private String _comment = null;
private int _lineNum = 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.

View File

@ -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() {
}
}
}

View File

@ -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,6 +2365,7 @@ public class DBDictionary
}
buf.append(" ");
if (!corelated) {
if (join.getType() == Join.TYPE_OUTER)
buf.append(outerJoinClause);
else if (join.getType() == Join.TYPE_INNER)
@ -2325,16 +2373,23 @@ public class DBDictionary
else // cross
buf.append(crossJoinClause);
buf.append(" ");
}
buf.append(join.getTable2()).append(" ").append(join.getAlias2());
if (forUpdate && tableForUpdateClause != null)
buf.append(" ").append(tableForUpdateClause);
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;
}

View File

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

View File

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

View File

@ -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);
}
}
/**

View File

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

View File

@ -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;
@ -166,12 +163,11 @@ public class SelectImpl
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,21 +1836,26 @@ public class SelectImpl
SelectJoins sj = new SelectJoins(this);
if (j1 == null || j1.isEmpty()) {
if (j2.getSelect() == this) {
if (nullJoins)
sj.setJoins(j2.joins());
else
sj.setJoins(new JoinSet(j2.joins()));
}
} else {
JoinSet set;
JoinSet set = null;
if (j1.getSelect() == this) {
if (nullJoins)
set = j1.joins();
else
set = new JoinSet(j1.joins());
if (j2 != null && !j2.isEmpty())
if (j2 != null && !j2.isEmpty()
&& j2.getSelect() == this)
set.addAll(j2.joins());
sj.setJoins(set);
}
}
// null previous joins; all are combined into this one
if (nullJoins && j1 != null)
@ -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;
@ -2269,6 +2214,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();
}

View File

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

View File

@ -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) {
}
}

View File

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

View File

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

View File

@ -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.
*/

View File

@ -41,6 +41,10 @@ class SubQ
_alias = alias;
}
public Object getSelect() {
return null;
}
public String getCandidateAlias() {
return _alias;
}

View File

@ -36,4 +36,6 @@ public interface Subquery
* Set the parsed subquery.
*/
public void setQueryExpressions(QueryExpressions query);
public Object getSelect();
}

View File

@ -135,4 +135,8 @@ public abstract class Val
public Path getPath() {
return null;
}
public String getName() {
return null;
}
}

View File

@ -90,4 +90,6 @@ public interface Value
public Value getSelectAs();
public Path getPath();
public String getName();
}

View File

@ -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,6 +1804,9 @@ 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 {
private final ParsedJPQL parsed;
private ClassMetaData meta;
private String schemaAlias;
private Subquery subquery;
Context(ParsedJPQL parsed, Subquery subquery) {
this.parsed = parsed;
this.subquery = subquery;
protected void addSchemaToContext(String id, ClassMetaData meta) {
ctx().addSchema(id.toLowerCase(), meta);
}
protected void addVariableToContext(String id, Value var) {
ctx().addVariable(id, var);
}
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;
}
////////////////////////////

View File

@ -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;
/**
@ -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();
}
}