From f74c5d7a69fb3dda67fb26c1c0250d48e29fe359 Mon Sep 17 00:00:00 2001 From: Fay Wang Date: Mon, 20 Jul 2009 17:49:36 +0000 Subject: [PATCH] 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 --- .../openjpa/jdbc/kernel/JDBCStoreQuery.java | 29 +- .../openjpa/jdbc/kernel/exps/AbstractVal.java | 4 + .../openjpa/jdbc/kernel/exps/ConstPath.java | 11 + .../kernel/exps/JDBCExpressionFactory.java | 3 +- .../apache/openjpa/jdbc/kernel/exps/Lit.java | 9 +- .../kernel/exps/NotContainsExpression.java | 4 + .../openjpa/jdbc/kernel/exps/PCPath.java | 117 +++- .../jdbc/kernel/exps/SelectConstructor.java | 25 +- .../apache/openjpa/jdbc/kernel/exps/SubQ.java | 34 +- .../openjpa/jdbc/meta/FieldMapping.java | 1 + .../org/apache/openjpa/jdbc/schema/Table.java | 11 +- .../openjpa/jdbc/sql/AbstractResult.java | 16 + .../apache/openjpa/jdbc/sql/DBDictionary.java | 85 ++- .../org/apache/openjpa/jdbc/sql/Join.java | 18 + .../org/apache/openjpa/jdbc/sql/Joins.java | 25 + .../apache/openjpa/jdbc/sql/LogicalUnion.java | 13 + .../org/apache/openjpa/jdbc/sql/Select.java | 18 + .../apache/openjpa/jdbc/sql/SelectImpl.java | 530 +++++++++++------- .../exps/AbstractExpressionBuilder.java | 33 +- .../openjpa/kernel/exps/CandidatePath.java | 10 + .../apache/openjpa/kernel/exps/Context.java | 224 ++++++++ .../org/apache/openjpa/kernel/exps/Path.java | 11 + .../openjpa/kernel/exps/QueryExpressions.java | 19 + .../org/apache/openjpa/kernel/exps/SubQ.java | 4 + .../apache/openjpa/kernel/exps/Subquery.java | 2 + .../org/apache/openjpa/kernel/exps/Val.java | 4 + .../org/apache/openjpa/kernel/exps/Value.java | 2 + .../kernel/jpql/JPQLExpressionBuilder.java | 141 +++-- .../persistence/query/TestSubquery.java | 27 +- 29 files changed, 1151 insertions(+), 279 deletions(-) create mode 100644 openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Context.java diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java index 8213c9cc1..22608e27d 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java @@ -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 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; diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/AbstractVal.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/AbstractVal.java index 6e79b54f4..fcbc2f3f0 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/AbstractVal.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/AbstractVal.java @@ -114,5 +114,9 @@ abstract class AbstractVal public Path getPath() { return null; } + + public String getName() { + return null; + } } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/ConstPath.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/ConstPath.java index 96049edd0..c6bf4b779 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/ConstPath.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/ConstPath.java @@ -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) { + } } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java index 76f9dace3..954d0f1c8 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java @@ -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); } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Lit.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Lit.java index e2dc8c620..c68ec854f 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Lit.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Lit.java @@ -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)); } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/NotContainsExpression.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/NotContainsExpression.java index 4592103b0..5c2c97a78 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/NotContainsExpression.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/NotContainsExpression.java @@ -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)); diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/PCPath.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/PCPath.java index 06a1f6eab..1d0cc5325 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/PCPath.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/PCPath.java @@ -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; diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/SelectConstructor.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/SelectConstructor.java index 200f40421..fc5594abb 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/SelectConstructor.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/SelectConstructor.java @@ -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 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()) { diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/SubQ.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/SubQ.java index 6afba5a8b..c9b3d7454 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/SubQ.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/SubQ.java @@ -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); diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/FieldMapping.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/FieldMapping.java index f88f0259d..e7cfa8abd 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/FieldMapping.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/FieldMapping.java @@ -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(); } } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/Table.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/Table.java index 3068639a1..7dd895aef 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/Table.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/Table.java @@ -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. diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/AbstractResult.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/AbstractResult.java index ad2ec2e9c..8e462238f 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/AbstractResult.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/AbstractResult.java @@ -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() { + } } } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java index 127fb514f..594ce2083 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java @@ -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; } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Join.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Join.java index 2ace5d99d..d3b3f0919 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Join.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Join.java @@ -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; + } } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Joins.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Joins.java index 75ee71702..31b98e59b 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Joins.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Joins.java @@ -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(); } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/LogicalUnion.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/LogicalUnion.java index 078eb94e4..2d92d9e1b 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/LogicalUnion.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/LogicalUnion.java @@ -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); + } } /** diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Select.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Select.java index 5a310dd40..d7c70fd70 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Select.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Select.java @@ -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); } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SelectImpl.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SelectImpl.java index fcf9aa1fb..17391102a 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SelectImpl.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SelectImpl.java @@ -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 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 removed = new ArrayList(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(); } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/AbstractExpressionBuilder.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/AbstractExpressionBuilder.java index 0c5bc772f..c1df80fcc 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/AbstractExpressionBuilder.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/AbstractExpressionBuilder.java @@ -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(); _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); } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/CandidatePath.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/CandidatePath.java index 87a2b0364..16bccfe88 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/CandidatePath.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/CandidatePath.java @@ -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) { + } } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Context.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Context.java new file mode 100644 index 000000000..ae110e20b --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Context.java @@ -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 subsels = null; + private Object select = null; + private int aliasCount = -1; + private Map variables = new HashMap(); + private Map schemas = + new HashMap(); + + 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(); + subsels.add(sub); + } + + /** + * Returns the subquery context. + * @return + */ + public List 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; + } +} + diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Path.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Path.java index 71b41a2c6..face10d97 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Path.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Path.java @@ -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); } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/QueryExpressions.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/QueryExpressions.java index 99fd2c59c..7f44cb940 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/QueryExpressions.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/QueryExpressions.java @@ -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 _contexts = null; public Object state; + /** + * Set reference to the JPQL query contexts. + * @param contexts + */ + public void setContexts(Stack contexts) { + _contexts = contexts; + } + + /** + * Returns the current JPQL query context. + * @return + */ + public Context ctx() { + return _contexts.peek(); + } + /** * Whether this is an aggregate results. */ diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/SubQ.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/SubQ.java index 55c995e94..1271cea90 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/SubQ.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/SubQ.java @@ -41,6 +41,10 @@ class SubQ _alias = alias; } + public Object getSelect() { + return null; + } + public String getCandidateAlias() { return _alias; } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Subquery.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Subquery.java index d4f654dae..abe16fe86 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Subquery.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Subquery.java @@ -36,4 +36,6 @@ public interface Subquery * Set the parsed subquery. */ public void setQueryExpressions(QueryExpressions query); + + public Object getSelect(); } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Val.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Val.java index 01e1bf18a..8051062e7 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Val.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Val.java @@ -135,4 +135,8 @@ public abstract class Val public Path getPath() { return null; } + + public String getName() { + return null; + } } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Value.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Value.java index 4b312a8ba..77e707678 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Value.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Value.java @@ -90,4 +90,6 @@ public interface Value public Value getSelectAs(); public Path getPath(); + + public String getName(); } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java index 1180537e6..d72a00b93 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java @@ -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; } //////////////////////////// diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/TestSubquery.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/TestSubquery.java index a248cef46..aa769afaf 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/TestSubquery.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/TestSubquery.java @@ -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(); + } }