1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.openjpa.jdbc.kernel.exps;
20
21 import java.io.Serializable;
22 import java.util.HashMap;
23 import java.util.Map;
24
25 import org.apache.openjpa.jdbc.meta.ClassMapping;
26 import org.apache.openjpa.jdbc.sql.DBDictionary;
27 import org.apache.openjpa.jdbc.sql.Joins;
28 import org.apache.openjpa.jdbc.sql.SQLBuffer;
29 import org.apache.openjpa.jdbc.sql.Select;
30 import org.apache.openjpa.kernel.exps.AbstractExpressionVisitor;
31 import org.apache.openjpa.kernel.exps.Constant;
32 import org.apache.openjpa.kernel.exps.Expression;
33 import org.apache.openjpa.kernel.exps.QueryExpressions;
34 import org.apache.openjpa.kernel.exps.Value;
35
36 /**
37 * Turns parsed queries into selects.
38 *
39 * @author Abe White
40 * @nojavadoc
41 */
42 public class SelectConstructor
43 implements Serializable {
44
45 private boolean _extent = false;
46
47 /**
48 * Return true if we know the select to have on criteria; to be an extent.
49 * Note that even if this method returns false, {@link #evaluate} may still
50 * return null if we haven't cached whether the query is an extent yet.
51 */
52 public boolean isExtent() {
53 return _extent;
54 }
55
56 /**
57 * Evaluate the expression, returning a new select and filling in any
58 * associated expression state. Use {@link #select} to then select the data.
59 *
60 * @param ctx fill with execution context
61 * @param state will be filled with expression state
62 */
63 public Select evaluate(ExpContext ctx, Select parent, String alias,
64 QueryExpressions exps, QueryExpressionsState state) {
65 // already know that this query is equivalent to an extent?
66 Select sel;
67 if (_extent) {
68 sel = ctx.store.getSQLFactory().newSelect();
69 sel.setAutoDistinct((exps.distinct & exps.DISTINCT_AUTO) != 0);
70 return sel;
71 }
72
73 // create a new select and initialize it with the joins needed for
74 // the criteria of this query
75 sel = newSelect(ctx, parent, alias, exps, state);
76
77 // create where clause; if there are no where conditions and
78 // no ordering or projections, we return null to signify that this
79 // query should be treated like an extent
80 Select inner = sel.getFromSelect();
81 SQLBuffer where = buildWhere((inner != null) ? inner : sel, ctx,
82 state.filter, exps.filter);
83 if (where == null && exps.projections.length == 0
84 && exps.ordering.length == 0
85 && (sel.getJoins() == null || sel.getJoins().isEmpty())) {
86 _extent = true;
87 sel.setAutoDistinct((exps.distinct & exps.DISTINCT_AUTO) != 0);
88 return sel;
89 }
90
91 // now set sql criteria; it goes on the inner select if present
92 if (inner != null)
93 inner.where(where);
94 else
95 sel.where(where);
96
97 // apply grouping and having. this does not select the grouping
98 // columns, just builds the GROUP BY clauses. we don't build the
99 // ORDER BY clauses yet because if we decide to add this select
100 // to a union, the ORDER BY values get aliased differently
101 if (exps.having != null) {
102 Exp havingExp = (Exp) exps.having;
103 SQLBuffer buf = new SQLBuffer(ctx.store.getDBDictionary());
104 havingExp.appendTo(sel, ctx, state.having, buf);
105 sel.having(buf);
106 }
107 for (int i = 0; i < exps.grouping.length; i++)
108 ((Val) exps.grouping[i]).groupBy(sel, ctx, state.grouping[i]);
109 return sel;
110 }
111
112 /**
113 * Return a new select with expressions initialized.
114 */
115 private Select newSelect(ExpContext ctx, Select parent,
116 String alias, QueryExpressions exps, QueryExpressionsState state) {
117 Select sel = ctx.store.getSQLFactory().newSelect();
118 sel.setAutoDistinct((exps.distinct & exps.DISTINCT_AUTO) != 0);
119 sel.setJoinSyntax(ctx.fetch.getJoinSyntax());
120 sel.setParent(parent, alias);
121 initialize(sel, ctx, exps, state);
122
123 if (!sel.getAutoDistinct()) {
124 if ((exps.distinct & exps.DISTINCT_TRUE) != 0)
125 sel.setDistinct(true);
126 else if ((exps.distinct & exps.DISTINCT_FALSE) != 0)
127 sel.setDistinct(false);
128 } else if (exps.projections.length > 0) {
129 if (!sel.isDistinct() && (exps.distinct & exps.DISTINCT_TRUE) != 0){
130 // if the select is not distinct but the query is, force
131 // the select to be distinct
132 sel.setDistinct(true);
133 } else if (sel.isDistinct()) {
134 // when aggregating data or making a non-distinct projection
135 // from a distinct select, we have to select from a tmp
136 // table formed by a distinct subselect in the from clause;
137 // this subselect selects the pks of the candidate (to
138 // get unique candidate values) and needed field values and
139 // applies the where conditions; the outer select applies
140 // ordering, grouping, etc
141 boolean agg = exps.isAggregate();
142 boolean candidate = ProjectionExpressionVisitor.
143 hasCandidateProjections(exps.projections);
144 if (agg || (candidate
145 && (exps.distinct & exps.DISTINCT_TRUE) == 0)) {
146 DBDictionary dict = ctx.store.getDBDictionary();
147 dict.assertSupport(dict.supportsSubselect,
148 "SupportsSubselect");
149
150 Select inner = sel;
151 sel = ctx.store.getSQLFactory().newSelect();
152 sel.setParent(parent, alias);
153 sel.setDistinct(agg
154 && (exps.distinct & exps.DISTINCT_TRUE) != 0);
155 sel.setFromSelect(inner);
156
157 // auto-distincting happens to get unique candidate instances
158 // back; don't auto-distinct if the user isn't selecting
159 // candidate data
160 } else if (!candidate
161 && (exps.distinct & exps.DISTINCT_TRUE) == 0)
162 sel.setDistinct(false);
163 }
164 }
165 return sel;
166 }
167
168 /**
169 * Initialize all expressions.
170 */
171 private void initialize(Select sel, ExpContext ctx, QueryExpressions exps,
172 QueryExpressionsState state) {
173 Map contains = null;
174 if (HasContainsExpressionVisitor.hasContains(exps.filter)
175 || HasContainsExpressionVisitor.hasContains(exps.having))
176 contains = new HashMap(7);
177
178 // initialize filter and having expressions
179 Exp filterExp = (Exp) exps.filter;
180 state.filter = filterExp.initialize(sel, ctx, contains);
181 Exp havingExp = (Exp) exps.having;
182 if (havingExp != null)
183 state.having = havingExp.initialize(sel, ctx, contains);
184
185 // get the top-level joins and null the expression's joins
186 // at the same time so they aren't included in the where/having SQL
187 Joins filterJoins = state.filter.joins;
188 Joins havingJoins = (state.having == null) ? null : state.having.joins;
189 Joins joins = sel.and(filterJoins, havingJoins);
190
191 // initialize result values
192 if (exps.projections.length > 0) {
193 state.projections = new ExpState[exps.projections.length];
194 Val resultVal;
195 for (int i = 0; i < exps.projections.length; i++) {
196 resultVal = (Val) exps.projections[i];
197 // have to join through to related type for pc object
198 // projections; this ensures that we have all our joins cached
199 state.projections[i] = resultVal.initialize(sel, ctx,
200 Val.JOIN_REL | Val.FORCE_OUTER);
201 joins = sel.and(joins, state.projections[i].joins);
202 }
203 }
204
205 // initialize grouping
206 if (exps.grouping.length > 0) {
207 state.grouping = new ExpState[exps.grouping.length];
208 Val groupVal;
209 for (int i = 0; i < exps.grouping.length; i++) {
210 groupVal = (Val) exps.grouping[i];
211 // have to join through to related type for pc object groupings;
212 // this ensures that we have all our joins cached
213 state.grouping[i] = groupVal.initialize(sel, ctx, Val.JOIN_REL);
214 joins = sel.and(joins, state.grouping[i].joins);
215 }
216 }
217
218 // initialize ordering
219 if (exps.ordering.length > 0) {
220 state.ordering = new ExpState[exps.ordering.length];
221 Val orderVal;
222 for (int i = 0; i < exps.ordering.length; i++) {
223 orderVal = (Val) exps.ordering[i];
224 state.ordering[i] = orderVal.initialize(sel, ctx, 0);
225 joins = sel.and(joins, state.ordering[i].joins);
226 }
227 }
228 sel.where(joins);
229 }
230
231 /**
232 * Create the where sql.
233 */
234 private SQLBuffer buildWhere(Select sel, ExpContext ctx, ExpState state,
235 Expression filter) {
236 // create where buffer
237 SQLBuffer where = new SQLBuffer(ctx.store.getDBDictionary());
238 where.append("(");
239 Exp filterExp = (Exp) filter;
240 filterExp.appendTo(sel, ctx, state, where);
241
242 if (where.sqlEquals("(") || where.sqlEquals("(1 = 1"))
243 return null;
244 return where.append(")");
245 }
246
247 /**
248 * Select the data for this query.
249 */
250 public void select(Select sel, ExpContext ctx, ClassMapping mapping,
251 boolean subclasses, QueryExpressions exps, QueryExpressionsState state,
252 int eager) {
253 Select inner = sel.getFromSelect();
254 Val val;
255 Joins joins = null;
256 if (sel.getSubselectPath() != null)
257 joins = sel.newJoins().setSubselect(sel.getSubselectPath());
258
259 // build ordering clauses before select so that any eager join
260 // ordering gets applied after query ordering
261 for (int i = 0; i < exps.ordering.length; i++)
262 ((Val) exps.ordering[i]).orderBy(sel, ctx, state.ordering[i],
263 exps.ascending[i]);
264
265 // if no result string set, select matching objects like normal
266 if (exps.projections.length == 0 && sel.getParent() == null) {
267 int subs = (subclasses) ? Select.SUBS_JOINABLE : Select.SUBS_NONE;
268 sel.selectIdentifier(mapping, subs, ctx.store, ctx.fetch, eager);
269 } else if (exps.projections.length == 0) {
270 // subselect for objects; we really just need the primary key values
271 sel.select(mapping.getPrimaryKeyColumns(), joins);
272 } else {
273 // if we have an inner select, we need to select the candidate
274 // class' pk columns to guarantee unique instances
275 if (inner != null)
276 inner.select(mapping.getPrimaryKeyColumns(), joins);
277
278 // select each result value; no need to pass on the eager mode since
279 // under projections we always use EAGER_NONE
280 boolean pks = sel.getParent() != null;
281 for (int i = 0; i < exps.projections.length; i++) {
282 val = (Val) exps.projections[i];
283 if (inner != null)
284 val.selectColumns(inner, ctx, state.projections[i], pks);
285 val.select(sel, ctx, state.projections[i], pks);
286 }
287
288 // make sure having columns are selected since it is required by
289 // some DBs. put them last so they don't affect result processing
290 if (exps.having != null && inner != null)
291 ((Exp) exps.having).selectColumns(inner, ctx, state.having,
292 true);
293 }
294
295 // select ordering columns, since it is required by some DBs. put them
296 // last so they don't affect result processing
297 for (int i = 0; i < exps.ordering.length; i++) {
298 val = (Val) exps.ordering[i];
299 if (inner != null)
300 val.selectColumns(inner, ctx, state.ordering[i], true);
301 val.select(sel, ctx, state.ordering[i], true);
302 }
303
304 // add conditions limiting the projections to the proper classes; if
305 // this isn't a projection or a subq then they will already be added
306 if (exps.projections.length > 0 || sel.getParent() != null) {
307 ctx.store.loadSubclasses(mapping);
308 mapping.getDiscriminator().addClassConditions((inner != null)
309 ? inner : sel, subclasses, joins);
310 }
311 }
312
313 /**
314 * Used to check whether a query's result projections are on the candidate.
315 */
316 private static class ProjectionExpressionVisitor
317 extends AbstractExpressionVisitor {
318
319 private boolean _candidate = false;
320 private int _level = 0;
321
322 public static boolean hasCandidateProjections(Value[] projs) {
323 ProjectionExpressionVisitor v = new ProjectionExpressionVisitor();
324 for (int i = 0; i < projs.length; i++) {
325 projs[i].acceptVisit(v);
326 if (v._candidate)
327 return true;
328 }
329 return false;
330 }
331
332 public void enter(Value val) {
333 if (!_candidate) {
334 _candidate = (_level == 0 && val instanceof Constant)
335 || (val instanceof PCPath
336 && !((PCPath) val).isVariablePath());
337 }
338 _level++;
339 }
340
341 public void exit(Value val) {
342 _level--;
343 }
344 }
345 }