001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */
019: package org.apache.openjpa.jdbc.kernel.exps;
020:
021: import java.io.Serializable;
022: import java.util.HashMap;
023: import java.util.Map;
024:
025: import org.apache.openjpa.jdbc.meta.ClassMapping;
026: import org.apache.openjpa.jdbc.sql.DBDictionary;
027: import org.apache.openjpa.jdbc.sql.Joins;
028: import org.apache.openjpa.jdbc.sql.SQLBuffer;
029: import org.apache.openjpa.jdbc.sql.Select;
030: import org.apache.openjpa.kernel.exps.AbstractExpressionVisitor;
031: import org.apache.openjpa.kernel.exps.Constant;
032: import org.apache.openjpa.kernel.exps.Expression;
033: import org.apache.openjpa.kernel.exps.QueryExpressions;
034: import org.apache.openjpa.kernel.exps.Value;
035:
036: /**
037: * Turns parsed queries into selects.
038: *
039: * @author Abe White
040: * @nojavadoc
041: */
042: public class SelectConstructor implements Serializable {
043:
044: private boolean _extent = false;
045:
046: /**
047: * Return true if we know the select to have on criteria; to be an extent.
048: * Note that even if this method returns false, {@link #evaluate} may still
049: * return null if we haven't cached whether the query is an extent yet.
050: */
051: public boolean isExtent() {
052: return _extent;
053: }
054:
055: /**
056: * Evaluate the expression, returning a new select and filling in any
057: * associated expression state. Use {@link #select} to then select the data.
058: *
059: * @param ctx fill with execution context
060: * @param state will be filled with expression state
061: */
062: public Select evaluate(ExpContext ctx, Select parent, String alias,
063: QueryExpressions exps, QueryExpressionsState state) {
064: // already know that this query is equivalent to an extent?
065: Select sel;
066: if (_extent) {
067: sel = ctx.store.getSQLFactory().newSelect();
068: sel
069: .setAutoDistinct((exps.distinct & exps.DISTINCT_AUTO) != 0);
070: return sel;
071: }
072:
073: // create a new select and initialize it with the joins needed for
074: // the criteria of this query
075: sel = newSelect(ctx, parent, alias, exps, state);
076:
077: // create where clause; if there are no where conditions and
078: // no ordering or projections, we return null to signify that this
079: // query should be treated like an extent
080: Select inner = sel.getFromSelect();
081: SQLBuffer where = buildWhere((inner != null) ? inner : sel,
082: ctx, state.filter, exps.filter);
083: if (where == null && exps.projections.length == 0
084: && exps.ordering.length == 0
085: && (sel.getJoins() == null || sel.getJoins().isEmpty())) {
086: _extent = true;
087: sel
088: .setAutoDistinct((exps.distinct & exps.DISTINCT_AUTO) != 0);
089: return sel;
090: }
091:
092: // now set sql criteria; it goes on the inner select if present
093: if (inner != null)
094: inner.where(where);
095: else
096: sel.where(where);
097:
098: // apply grouping and having. this does not select the grouping
099: // columns, just builds the GROUP BY clauses. we don't build the
100: // ORDER BY clauses yet because if we decide to add this select
101: // to a union, the ORDER BY values get aliased differently
102: if (exps.having != null) {
103: Exp havingExp = (Exp) exps.having;
104: SQLBuffer buf = new SQLBuffer(ctx.store.getDBDictionary());
105: havingExp.appendTo(sel, ctx, state.having, buf);
106: sel.having(buf);
107: }
108: for (int i = 0; i < exps.grouping.length; i++)
109: ((Val) exps.grouping[i]).groupBy(sel, ctx,
110: state.grouping[i]);
111: return sel;
112: }
113:
114: /**
115: * Return a new select with expressions initialized.
116: */
117: private Select newSelect(ExpContext ctx, Select parent,
118: String alias, QueryExpressions exps,
119: QueryExpressionsState state) {
120: Select sel = ctx.store.getSQLFactory().newSelect();
121: sel.setAutoDistinct((exps.distinct & exps.DISTINCT_AUTO) != 0);
122: sel.setJoinSyntax(ctx.fetch.getJoinSyntax());
123: sel.setParent(parent, alias);
124: initialize(sel, ctx, exps, state);
125:
126: if (!sel.getAutoDistinct()) {
127: if ((exps.distinct & exps.DISTINCT_TRUE) != 0)
128: sel.setDistinct(true);
129: else if ((exps.distinct & exps.DISTINCT_FALSE) != 0)
130: sel.setDistinct(false);
131: } else if (exps.projections.length > 0) {
132: if (!sel.isDistinct()
133: && (exps.distinct & exps.DISTINCT_TRUE) != 0) {
134: // if the select is not distinct but the query is, force
135: // the select to be distinct
136: sel.setDistinct(true);
137: } else if (sel.isDistinct()) {
138: // when aggregating data or making a non-distinct projection
139: // from a distinct select, we have to select from a tmp
140: // table formed by a distinct subselect in the from clause;
141: // this subselect selects the pks of the candidate (to
142: // get unique candidate values) and needed field values and
143: // applies the where conditions; the outer select applies
144: // ordering, grouping, etc
145: boolean agg = exps.isAggregate();
146: boolean candidate = ProjectionExpressionVisitor
147: .hasCandidateProjections(exps.projections);
148: if (agg
149: || (candidate && (exps.distinct & exps.DISTINCT_TRUE) == 0)) {
150: DBDictionary dict = ctx.store.getDBDictionary();
151: dict.assertSupport(dict.supportsSubselect,
152: "SupportsSubselect");
153:
154: Select inner = sel;
155: sel = ctx.store.getSQLFactory().newSelect();
156: sel.setParent(parent, alias);
157: sel
158: .setDistinct(agg
159: && (exps.distinct & exps.DISTINCT_TRUE) != 0);
160: sel.setFromSelect(inner);
161:
162: // auto-distincting happens to get unique candidate instances
163: // back; don't auto-distinct if the user isn't selecting
164: // candidate data
165: } else if (!candidate
166: && (exps.distinct & exps.DISTINCT_TRUE) == 0)
167: sel.setDistinct(false);
168: }
169: }
170: return sel;
171: }
172:
173: /**
174: * Initialize all expressions.
175: */
176: private void initialize(Select sel, ExpContext ctx,
177: QueryExpressions exps, QueryExpressionsState state) {
178: Map contains = null;
179: if (HasContainsExpressionVisitor.hasContains(exps.filter)
180: || HasContainsExpressionVisitor
181: .hasContains(exps.having))
182: contains = new HashMap(7);
183:
184: // initialize filter and having expressions
185: Exp filterExp = (Exp) exps.filter;
186: state.filter = filterExp.initialize(sel, ctx, contains);
187: Exp havingExp = (Exp) exps.having;
188: if (havingExp != null)
189: state.having = havingExp.initialize(sel, ctx, contains);
190:
191: // get the top-level joins and null the expression's joins
192: // at the same time so they aren't included in the where/having SQL
193: Joins filterJoins = state.filter.joins;
194: Joins havingJoins = (state.having == null) ? null
195: : state.having.joins;
196: Joins joins = sel.and(filterJoins, havingJoins);
197:
198: // initialize result values
199: if (exps.projections.length > 0) {
200: state.projections = new ExpState[exps.projections.length];
201: Val resultVal;
202: for (int i = 0; i < exps.projections.length; i++) {
203: resultVal = (Val) exps.projections[i];
204: // have to join through to related type for pc object
205: // projections; this ensures that we have all our joins cached
206: state.projections[i] = resultVal.initialize(sel, ctx,
207: Val.JOIN_REL | Val.FORCE_OUTER);
208: joins = sel.and(joins, state.projections[i].joins);
209: }
210: }
211:
212: // initialize grouping
213: if (exps.grouping.length > 0) {
214: state.grouping = new ExpState[exps.grouping.length];
215: Val groupVal;
216: for (int i = 0; i < exps.grouping.length; i++) {
217: groupVal = (Val) exps.grouping[i];
218: // have to join through to related type for pc object groupings;
219: // this ensures that we have all our joins cached
220: state.grouping[i] = groupVal.initialize(sel, ctx,
221: Val.JOIN_REL);
222: joins = sel.and(joins, state.grouping[i].joins);
223: }
224: }
225:
226: // initialize ordering
227: if (exps.ordering.length > 0) {
228: state.ordering = new ExpState[exps.ordering.length];
229: Val orderVal;
230: for (int i = 0; i < exps.ordering.length; i++) {
231: orderVal = (Val) exps.ordering[i];
232: state.ordering[i] = orderVal.initialize(sel, ctx, 0);
233: joins = sel.and(joins, state.ordering[i].joins);
234: }
235: }
236: sel.where(joins);
237: }
238:
239: /**
240: * Create the where sql.
241: */
242: private SQLBuffer buildWhere(Select sel, ExpContext ctx,
243: ExpState state, Expression filter) {
244: // create where buffer
245: SQLBuffer where = new SQLBuffer(ctx.store.getDBDictionary());
246: where.append("(");
247: Exp filterExp = (Exp) filter;
248: filterExp.appendTo(sel, ctx, state, where);
249:
250: if (where.sqlEquals("(") || where.sqlEquals("(1 = 1"))
251: return null;
252: return where.append(")");
253: }
254:
255: /**
256: * Select the data for this query.
257: */
258: public void select(Select sel, ExpContext ctx,
259: ClassMapping mapping, boolean subclasses,
260: QueryExpressions exps, QueryExpressionsState state,
261: int eager) {
262: Select inner = sel.getFromSelect();
263: Val val;
264: Joins joins = null;
265: if (sel.getSubselectPath() != null)
266: joins = sel.newJoins().setSubselect(sel.getSubselectPath());
267:
268: // build ordering clauses before select so that any eager join
269: // ordering gets applied after query ordering
270: for (int i = 0; i < exps.ordering.length; i++)
271: ((Val) exps.ordering[i]).orderBy(sel, ctx,
272: state.ordering[i], exps.ascending[i]);
273:
274: // if no result string set, select matching objects like normal
275: if (exps.projections.length == 0 && sel.getParent() == null) {
276: int subs = (subclasses) ? Select.SUBS_JOINABLE
277: : Select.SUBS_NONE;
278: sel.selectIdentifier(mapping, subs, ctx.store, ctx.fetch,
279: eager);
280: } else if (exps.projections.length == 0) {
281: // subselect for objects; we really just need the primary key values
282: sel.select(mapping.getPrimaryKeyColumns(), joins);
283: } else {
284: // if we have an inner select, we need to select the candidate
285: // class' pk columns to guarantee unique instances
286: if (inner != null)
287: inner.select(mapping.getPrimaryKeyColumns(), joins);
288:
289: // select each result value; no need to pass on the eager mode since
290: // under projections we always use EAGER_NONE
291: boolean pks = sel.getParent() != null;
292: for (int i = 0; i < exps.projections.length; i++) {
293: val = (Val) exps.projections[i];
294: if (inner != null)
295: val.selectColumns(inner, ctx, state.projections[i],
296: pks);
297: val.select(sel, ctx, state.projections[i], pks);
298: }
299:
300: // make sure having columns are selected since it is required by
301: // some DBs. put them last so they don't affect result processing
302: if (exps.having != null && inner != null)
303: ((Exp) exps.having).selectColumns(inner, ctx,
304: state.having, true);
305: }
306:
307: // select ordering columns, since it is required by some DBs. put them
308: // last so they don't affect result processing
309: for (int i = 0; i < exps.ordering.length; i++) {
310: val = (Val) exps.ordering[i];
311: if (inner != null)
312: val.selectColumns(inner, ctx, state.ordering[i], true);
313: val.select(sel, ctx, state.ordering[i], true);
314: }
315:
316: // add conditions limiting the projections to the proper classes; if
317: // this isn't a projection or a subq then they will already be added
318: if (exps.projections.length > 0 || sel.getParent() != null) {
319: ctx.store.loadSubclasses(mapping);
320: mapping.getDiscriminator().addClassConditions(
321: (inner != null) ? inner : sel, subclasses, joins);
322: }
323: }
324:
325: /**
326: * Used to check whether a query's result projections are on the candidate.
327: */
328: private static class ProjectionExpressionVisitor extends
329: AbstractExpressionVisitor {
330:
331: private boolean _candidate = false;
332: private int _level = 0;
333:
334: public static boolean hasCandidateProjections(Value[] projs) {
335: ProjectionExpressionVisitor v = new ProjectionExpressionVisitor();
336: for (int i = 0; i < projs.length; i++) {
337: projs[i].acceptVisit(v);
338: if (v._candidate)
339: return true;
340: }
341: return false;
342: }
343:
344: public void enter(Value val) {
345: if (!_candidate) {
346: _candidate = (_level == 0 && val instanceof Constant)
347: || (val instanceof PCPath && !((PCPath) val)
348: .isVariablePath());
349: }
350: _level++;
351: }
352:
353: public void exit(Value val) {
354: _level--;
355: }
356: }
357: }
|