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;
20
21 import java.sql.Connection;
22 import java.sql.PreparedStatement;
23 import java.sql.SQLException;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.BitSet;
27 import java.util.Collection;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31
32 import org.apache.openjpa.event.LifecycleEventManager;
33 import org.apache.openjpa.jdbc.kernel.exps.ExpContext;
34 import org.apache.openjpa.jdbc.kernel.exps.GetColumn;
35 import org.apache.openjpa.jdbc.kernel.exps.JDBCExpressionFactory;
36 import org.apache.openjpa.jdbc.kernel.exps.JDBCStringContains;
37 import org.apache.openjpa.jdbc.kernel.exps.JDBCWildcardMatch;
38 import org.apache.openjpa.jdbc.kernel.exps.QueryExpressionsState;
39 import org.apache.openjpa.jdbc.kernel.exps.SQLEmbed;
40 import org.apache.openjpa.jdbc.kernel.exps.SQLExpression;
41 import org.apache.openjpa.jdbc.kernel.exps.SQLValue;
42 import org.apache.openjpa.jdbc.meta.ClassMapping;
43 import org.apache.openjpa.jdbc.meta.FieldMapping;
44 import org.apache.openjpa.jdbc.meta.strats.VerticalClassStrategy;
45 import org.apache.openjpa.jdbc.schema.Column;
46 import org.apache.openjpa.jdbc.schema.Table;
47 import org.apache.openjpa.jdbc.sql.DBDictionary;
48 import org.apache.openjpa.jdbc.sql.SQLBuffer;
49 import org.apache.openjpa.jdbc.sql.SQLExceptions;
50 import org.apache.openjpa.jdbc.sql.Select;
51 import org.apache.openjpa.jdbc.sql.Union;
52 import org.apache.openjpa.kernel.ExpressionStoreQuery;
53 import org.apache.openjpa.kernel.OrderingMergedResultObjectProvider;
54 import org.apache.openjpa.kernel.QueryHints;
55 import org.apache.openjpa.kernel.exps.ExpressionFactory;
56 import org.apache.openjpa.kernel.exps.ExpressionParser;
57 import org.apache.openjpa.kernel.exps.FilterListener;
58 import org.apache.openjpa.kernel.exps.QueryExpressions;
59 import org.apache.openjpa.lib.rop.MergedResultObjectProvider;
60 import org.apache.openjpa.lib.rop.RangeResultObjectProvider;
61 import org.apache.openjpa.lib.rop.ResultObjectProvider;
62 import org.apache.openjpa.lib.util.Localizer;
63 import org.apache.openjpa.meta.ClassMetaData;
64 import org.apache.openjpa.meta.ValueMetaData;
65 import org.apache.openjpa.util.UserException;
66 import serp.util.Numbers;
67
68 /**
69 * JDBC query implementation.
70 *
71 * @author Abe White
72 * @nojavadoc
73 */
74 public class JDBCStoreQuery
75 extends ExpressionStoreQuery {
76
77 private static final Table INVALID = new Table();
78
79 // add all standard filter and aggregate listeners to these maps
80 private static final Map _listeners = new HashMap();
81
82 static {
83 // deprecated extensions
84 _listeners.put(JDBCStringContains.TAG, new JDBCStringContains());
85 _listeners.put(JDBCWildcardMatch.TAG, new JDBCWildcardMatch());
86 _listeners.put(SQLExpression.TAG, new SQLExpression());
87 _listeners.put(SQLValue.TAG, new SQLValue());
88
89 // jdbc-specific extensions
90 _listeners.put(GetColumn.TAG, new GetColumn());
91 _listeners.put(SQLEmbed.TAG, new SQLEmbed());
92 }
93
94 private final transient JDBCStore _store;
95
96 /**
97 * Constructor. Supply store manager.
98 */
99 public JDBCStoreQuery(JDBCStore store, ExpressionParser parser) {
100 super(parser);
101 _store = store;
102 }
103
104 /**
105 * Return the store.
106 */
107 public JDBCStore getStore() {
108 return _store;
109 }
110
111 public FilterListener getFilterListener(String tag) {
112 return (FilterListener) _listeners.get(tag);
113 }
114
115 public Object newCompilationKey() {
116 JDBCFetchConfiguration fetch = (JDBCFetchConfiguration) ctx
117 .getFetchConfiguration();
118 return Numbers.valueOf(fetch.getJoinSyntax());
119 }
120
121 public boolean supportsDataStoreExecution() {
122 return true;
123 }
124
125 protected ClassMetaData[] getIndependentExpressionCandidates(
126 ClassMetaData meta, boolean subclasses) {
127 if (!subclasses)
128 return new ClassMapping[] { (ClassMapping) meta };
129 return ((ClassMapping) meta).getIndependentAssignableMappings();
130 }
131
132 protected ExpressionFactory getExpressionFactory(ClassMetaData meta) {
133 return new JDBCExpressionFactory((ClassMapping) meta);
134 }
135
136 protected ResultObjectProvider executeQuery(Executor ex,
137 ClassMetaData base, ClassMetaData[] metas, boolean subclasses,
138 ExpressionFactory[] facts, QueryExpressions[] exps, Object[] params,
139 Range range) {
140 if (metas.length > 1 && exps[0].isAggregate())
141 throw new UserException(Localizer.forPackage(JDBCStoreQuery.class).
142 get("mult-mapping-aggregate", Arrays.asList(metas)));
143
144 ClassMapping[] mappings = (ClassMapping[]) metas;
145 JDBCFetchConfiguration fetch = (JDBCFetchConfiguration)
146 ctx.getFetchConfiguration();
147 if (exps[0].fetchPaths != null) {
148 fetch.addFields(Arrays.asList(exps[0].fetchPaths));
149 fetch.addJoins(Arrays.asList(exps[0].fetchPaths));
150 }
151 if (exps[0].fetchInnerPaths != null)
152 fetch.addFetchInnerJoins(Arrays.asList(exps[0].fetchInnerPaths));
153
154 int eager = calculateEagerMode(exps[0], range.start, range.end);
155 int subclassMode = fetch.getSubclassFetchMode((ClassMapping) base);
156 DBDictionary dict = _store.getDBDictionary();
157 long start = (mappings.length == 1 && dict.supportsSelectStartIndex)
158 ? range.start : 0L;
159 long end = (dict.supportsSelectEndIndex) ? range.end : Long.MAX_VALUE;
160
161 QueryExpressionsState[] states = new QueryExpressionsState[exps.length];
162 for (int i = 0; i < states.length; i++)
163 states[i] = new QueryExpressionsState();
164 ExpContext ctx = new ExpContext(_store, params, fetch);
165
166 // add selects with populate WHERE conditions to list
167 List sels = new ArrayList(mappings.length);
168 List selMappings = new ArrayList(mappings.length);
169 BitSet subclassBits = new BitSet();
170 BitSet nextBits = new BitSet();
171 boolean unionable = createWhereSelects(sels, mappings, selMappings,
172 subclasses, subclassBits, nextBits, facts, exps, states, ctx,
173 subclassMode)
174 && subclassMode == JDBCFetchConfiguration.EAGER_JOIN
175 && start == 0
176 && end == Long.MAX_VALUE;
177
178 // we might want to use lrs settings if we can't use the range
179 if (sels.size() > 1)
180 start = 0L;
181 boolean lrs = range.lrs || (fetch.getFetchBatchSize() >= 0
182 && (start != range.start || end != range.end));
183
184 ResultObjectProvider[] rops = null;
185 ResultObjectProvider rop = null;
186 if (unionable) {
187 Union union = _store.getSQLFactory().newUnion(
188 (Select[]) sels.toArray(new Select[sels.size()]));
189 BitSet[] paged = populateUnion(union, mappings, subclasses, facts,
190 exps, states, ctx, lrs, eager, start, end);
191 union.setLRS(lrs);
192 rop = executeUnion(union, mappings, exps, states, ctx, paged);
193 } else {
194 if (sels.size() > 1)
195 rops = new ResultObjectProvider[sels.size()];
196
197 Select sel;
198 BitSet paged;
199 for (int i = 0, idx = 0; i < sels.size(); i++) {
200 sel = (Select) sels.get(i);
201 paged = populateSelect(sel, (ClassMapping) selMappings.get(i),
202 subclassBits.get(i), (JDBCExpressionFactory) facts[idx],
203 exps[idx], states[idx], ctx, lrs, eager, start, end);
204
205 rop = executeSelect(sel, (ClassMapping) selMappings.get(i),
206 exps[idx], states[idx], ctx, paged, start, end);
207 if (rops != null)
208 rops[i] = rop;
209
210 if (nextBits.get(i))
211 idx++;
212 }
213 }
214
215 if (rops != null) {
216 if (exps[0].ascending.length == 0)
217 rop = new MergedResultObjectProvider(rops);
218 else {
219 rop = new OrderingMergedResultObjectProvider(rops,
220 exps[0].ascending, ex, this, params);
221 }
222 }
223
224 // need to fake result range?
225 if ((rops != null && range.end != Long.MAX_VALUE)
226 || start != range.start || end != range.end)
227 rop = new RangeResultObjectProvider(rop, range.start, range.end);
228 return rop;
229 }
230
231 /**
232 * Select data for the given union, returning paged fields.
233 */
234 private BitSet[] populateUnion(Union union, final ClassMapping[] mappings,
235 final boolean subclasses, final ExpressionFactory[] facts,
236 final QueryExpressions[] exps, final QueryExpressionsState[] states,
237 final ExpContext ctx, final boolean lrs, final int eager,
238 final long start, final long end) {
239 final BitSet[] paged = (exps[0].projections.length > 0) ? null
240 : new BitSet[mappings.length];
241 union.select(new Union.Selector() {
242 public void select(Select sel, int idx) {
243 BitSet bits = populateSelect(sel, mappings[idx], subclasses,
244 (JDBCExpressionFactory) facts[idx], exps[idx], states[idx],
245 ctx, lrs, eager, start, end);
246 if (paged != null)
247 paged[idx] = bits;
248 }
249 });
250 return paged;
251 }
252
253 /**
254 * Select data for the given select, returning paged fields.
255 */
256 private BitSet populateSelect(Select sel, ClassMapping mapping,
257 boolean subclasses, JDBCExpressionFactory fact, QueryExpressions exps,
258 QueryExpressionsState state, ExpContext ctx, boolean lrs, int eager,
259 long start, long end) {
260 sel.setLRS(lrs);
261 sel.setRange(start, end);
262
263 BitSet paged = null;
264 if (exps.projections.length == 0) {
265 paged = PagingResultObjectProvider.getPagedFields(sel, mapping,
266 _store, ctx.fetch, eager, end - start);
267 if (paged != null)
268 eager = JDBCFetchConfiguration.EAGER_JOIN;
269 }
270
271 fact.getSelectConstructor().select(sel, ctx, mapping, subclasses, exps,
272 state, eager);
273 return paged;
274 }
275
276 /**
277 * Execute the given union.
278 */
279 private ResultObjectProvider executeUnion(Union union,
280 ClassMapping[] mappings, QueryExpressions[] exps,
281 QueryExpressionsState[] states, ExpContext ctx, BitSet[] paged) {
282 if (exps[0].projections.length > 0)
283 return new ProjectionResultObjectProvider(union, exps, states, ctx);
284
285 if (paged != null)
286 for (int i = 0; i < paged.length; i++)
287 if (paged[i] != null)
288 return new PagingResultObjectProvider(union, mappings,
289 _store, ctx.fetch, paged, Long.MAX_VALUE);
290
291 return new InstanceResultObjectProvider(union, mappings[0], _store,
292 ctx.fetch);
293 }
294
295 /**
296 * Execute the given select.
297 */
298 private ResultObjectProvider executeSelect(Select sel, ClassMapping mapping,
299 QueryExpressions exps, QueryExpressionsState state, ExpContext ctx,
300 BitSet paged, long start, long end) {
301 if (exps.projections.length > 0)
302 return new ProjectionResultObjectProvider(sel, exps, state, ctx);
303 if (paged != null)
304 return new PagingResultObjectProvider(sel, mapping, _store,
305 ctx.fetch, paged, end - start);
306 return new InstanceResultObjectProvider(sel, mapping, _store,
307 ctx.fetch);
308 }
309
310 /**
311 * Generate the selects with WHERE conditions needed to execute the query
312 * for the given mappings.
313 */
314 private boolean createWhereSelects(List sels, ClassMapping[] mappings,
315 List selMappings, boolean subclasses, BitSet subclassBits,
316 BitSet nextBits, ExpressionFactory[] facts, QueryExpressions[] exps,
317 QueryExpressionsState[] states, ExpContext ctx, int subclassMode) {
318 Number optHint = (Number) ctx.fetch.getHint
319 (QueryHints.HINT_RESULT_COUNT);
320 ClassMapping[] verts;
321 boolean unionable = true;
322 Select sel;
323 for (int i = 0; i < mappings.length; i++) {
324 // determine vertical mappings to select separately
325 verts = getVerticalMappings(mappings[i], subclasses, exps[i],
326 subclassMode);
327 if (verts.length == 1 && subclasses)
328 subclassBits.set(sels.size());
329
330 // create criteria select and clone for each vert mapping
331 sel = ((JDBCExpressionFactory) facts[i]).getSelectConstructor().
332 evaluate(ctx, null, null, exps[i], states[i]);
333 if (optHint != null)
334 sel.setExpectedResultCount(optHint.intValue(), true);
335 else if (this.ctx.isUnique())
336 sel.setExpectedResultCount(1, false);
337 for (int j = 0; j < verts.length; j++) {
338 selMappings.add(verts[j]);
339 if (j == verts.length - 1) {
340 nextBits.set(sels.size());
341 sels.add(sel);
342 } else
343 sels.add(sel.fullClone(1));
344 }
345
346 // turn off unioning if a given independent mapping requires
347 // multiple selects, or if we're using FROM selects
348 if (verts.length > 1 || sel.getFromSelect() != null)
349 unionable = false;
350 }
351 return unionable;
352 }
353
354 /**
355 * Return all the vertical mappings to select separately. Depends on
356 * subclass fetch mode and the type of query.
357 */
358 private ClassMapping[] getVerticalMappings(ClassMapping mapping,
359 boolean subclasses, QueryExpressions exps, int subclassMode) {
360 if (!subclasses || exps.projections.length > 0)
361 return new ClassMapping[] { mapping };
362
363 if (subclassMode != JDBCFetchConfiguration.EAGER_PARALLEL
364 || !hasVerticalSubclasses(mapping))
365 return new ClassMapping[] { mapping };
366
367 List subs = new ArrayList(4);
368 addSubclasses(mapping, subs);
369 return (ClassMapping[]) subs.toArray(new ClassMapping[subs.size()]);
370 }
371
372 /**
373 * Recursive helper to add mappings for subclasses to the given list.
374 */
375 private void addSubclasses(ClassMapping mapping, Collection subs) {
376 // possible future optimizations:
377 // - if no fields in meta or its subclasses (and not in an
378 // already-selected table) are in the current fetch
379 // configuration, stop creating new executors
380 // - allow an executor to select a range of subclasses, rather
381 // than just all subclasses / no subclasses; this would
382 // allow us to do just one query per actual vertically-mapped
383 // subclass, rather than one per mapped subclass, as is happening now
384
385 subs.add(mapping);
386 if (!hasVerticalSubclasses(mapping))
387 return;
388
389 // recurse on immediate subclasses
390 ClassMapping[] subMappings = mapping.getJoinablePCSubclassMappings();
391 for (int i = 0; i < subMappings.length; i++)
392 if (subMappings[i].getJoinablePCSuperclassMapping() == mapping)
393 addSubclasses(subMappings[i], subs);
394 }
395
396 /**
397 * Return whether the given class has any vertical subclasses.
398 */
399 private static boolean hasVerticalSubclasses(ClassMapping mapping) {
400 ClassMapping[] subs = mapping.getJoinablePCSubclassMappings();
401 for (int i = 0; i < subs.length; i++)
402 if (subs[i].getStrategy() instanceof VerticalClassStrategy)
403 return true;
404 return false;
405 }
406
407 /**
408 * The eager mode depends on the unique setting and range. If the range
409 * produces 0 results, use eager setting of none. If it produces 1 result
410 * or the query is unique, use an eager setting of single. Otherwise use
411 * an eager mode of multiple.
412 */
413 private int calculateEagerMode(QueryExpressions exps, long start,
414 long end) {
415 if (exps.projections.length > 0 || start >= end)
416 return EagerFetchModes.EAGER_NONE;
417 if (end - start == 1 || ctx.isUnique())
418 return EagerFetchModes.EAGER_JOIN;
419 return EagerFetchModes.EAGER_PARALLEL;
420 }
421
422 protected Number executeDelete(Executor ex, ClassMetaData base,
423 ClassMetaData[] metas, boolean subclasses, ExpressionFactory[] facts,
424 QueryExpressions[] exps, Object[] params) {
425 return executeBulkOperation(metas, subclasses, facts, exps,
426 params, null);
427 }
428
429 protected Number executeUpdate(Executor ex, ClassMetaData base,
430 ClassMetaData[] metas, boolean subclasses, ExpressionFactory[] facts,
431 QueryExpressions[] exps, Object[] params) {
432 return executeBulkOperation(metas, subclasses, facts, exps,
433 params, exps[0].updates);
434 }
435
436 private Number executeBulkOperation(ClassMetaData[] metas,
437 boolean subclasses, ExpressionFactory[] facts, QueryExpressions[] exps,
438 Object[] params, Map updates) {
439 // we cannot execute a bulk delete statement when have mappings in
440 // multiple tables, so indicate we want to use in-memory with null
441 ClassMapping[] mappings = (ClassMapping[]) metas;
442
443 // specification of the "updates" map indicates that this is
444 // an update query; otherwise, this is a delete statement
445 boolean isUpdate = updates != null && updates.size() > 0;
446
447 for (int i = 0; i < mappings.length; i++) {
448 if (!isSingleTableMapping(mappings[i], subclasses) && !isUpdate)
449 return null;
450
451 if (!isUpdate) {
452 // if there are any delete callbacks, we need to
453 // execute in-memory so the callbacks are invoked
454 LifecycleEventManager mgr = ctx.getStoreContext().getBroker()
455 .getLifecycleEventManager();
456 if (mgr.hasDeleteListeners(null, mappings[i]))
457 return null;
458 }
459 }
460
461 JDBCFetchConfiguration fetch = (JDBCFetchConfiguration)
462 ctx.getFetchConfiguration();
463 ExpContext ctx = new ExpContext(_store, params, fetch);
464 DBDictionary dict = _store.getDBDictionary();
465 QueryExpressionsState[] state = new QueryExpressionsState[exps.length];
466 for (int i = 0; i < state.length; i++)
467 state[i] = new QueryExpressionsState();
468
469 SQLBuffer[] sql = new SQLBuffer[mappings.length];
470 JDBCExpressionFactory jdbcFactory;
471 Select sel;
472 for (int i = 0; i < mappings.length; i++) {
473 jdbcFactory = (JDBCExpressionFactory) facts[i];
474 sel = jdbcFactory.getSelectConstructor().evaluate(ctx, null, null,
475 exps[i], state[i]);
476 jdbcFactory.getSelectConstructor().select(sel, ctx, mappings[i],
477 subclasses, exps[i], state[i],
478 JDBCFetchConfiguration.EAGER_NONE);
479
480 // The bulk operation will return null to indicate that the database
481 // does not support the request bulk delete operation; in
482 // this case, we need to perform the query in-memory and
483 // manually delete the instances
484 if (!isUpdate)
485 sql[i] = dict.toDelete(mappings[i], sel, params);
486 else
487 sql[i] = dict.toUpdate(mappings[i], sel, _store, params,
488 updates);
489
490 if (sql[i] == null)
491 return null;
492 }
493
494 // we need to make sure we have an active store connection
495 _store.getContext().beginStore();
496
497 Connection conn = _store.getConnection();
498 long count = 0;
499 try {
500 PreparedStatement stmnt;
501 for (int i = 0; i < sql.length; i++) {
502 stmnt = null;
503 try {
504 stmnt = prepareStatement(conn, sql[i]);
505 count += executeUpdate(conn, stmnt, sql[i], isUpdate);
506 } catch (SQLException se) {
507 throw SQLExceptions.getStore(se, sql[i].getSQL(),
508 _store.getDBDictionary());
509 } finally {
510 if (stmnt != null)
511 try { stmnt.close(); } catch (SQLException se) {}
512 }
513 }
514 } finally {
515 try { conn.close(); } catch (SQLException se) {}
516 }
517 return Numbers.valueOf(count);
518 }
519
520 /**
521 * Whether the given mapping occupies only one table.
522 */
523 private boolean isSingleTableMapping(ClassMapping mapping,
524 boolean subclasses) {
525 ClassMapping root = mapping;
526 while (root.getJoinablePCSuperclassMapping() != null)
527 root = root.getJoinablePCSuperclassMapping();
528 if (hasVerticalSubclasses(root))
529 return false;
530
531 // we cannot execute a bulk delete if any of the
532 // field mappings for the candidates have columns
533 // in any other table, since bulk deleting just from the
534 // class will leave dangling relations; we might be able
535 // to issue bulk deletes separately for the joins (possibly
536 // using a temporary table to select the primary keys for
537 // all the related tables and then issing a delete against those
538 // keys), but that logic is not currently implemented
539 Table table = getTable(mapping.getFieldMappings(), null);
540 if (table == INVALID)
541 return false;
542
543 if (subclasses) {
544 // if we are including subclasses, we also need to gather
545 // all the mappings for all known subclasses
546 ClassMapping[] subs = mapping.getJoinablePCSubclassMappings();
547 for (int i = 0; subs != null && i < subs.length; i++) {
548 table = getTable(subs[i].getDefinedFieldMappings(), table);
549 if (table == INVALID)
550 return false;
551 }
552 }
553 return true;
554 }
555
556 /**
557 * Return the single table for the given fields, or INVALID if they
558 * use multiple tables.
559 */
560 private Table getTable(FieldMapping[] fields, Table table) {
561 for (int i = 0; i < fields.length; i++) {
562 table = getTable(fields[i], table);
563 if (table == INVALID)
564 break;
565 }
566 return table;
567 }
568
569 /**
570 * Return the table for the field if the given table hasn't been set
571 * yet, or if the tables match. If the field uses a different table,
572 * returns INVALID. Also returns INVALID if field is dependent.
573 */
574 private Table getTable(FieldMapping fm, Table table) {
575 if (fm.getCascadeDelete() != ValueMetaData.CASCADE_NONE)
576 return INVALID;
577
578 Column[] columns = fm.getColumns();
579 for (int i = 0; columns != null && i < columns.length; i++) {
580 if (table == null)
581 table = columns[i].getTable();
582 else if (table != columns[i].getTable())
583 return INVALID;
584 }
585 return table;
586 }
587
588 protected Number executeUpdate(ClassMetaData base, ClassMetaData[] metas,
589 boolean subclasses, ExpressionFactory[] facts,
590 QueryExpressions[] parsed, Object[] params) {
591 return null;
592 }
593
594 protected String[] getDataStoreActions(ClassMetaData base,
595 ClassMetaData[] metas, boolean subclasses, ExpressionFactory[] facts,
596 QueryExpressions[] exps, Object[] params, Range range) {
597 ClassMapping[] mappings = (ClassMapping[]) metas;
598 JDBCFetchConfiguration fetch = (JDBCFetchConfiguration) ctx.
599 getFetchConfiguration();
600 if (exps[0].fetchPaths != null) {
601 fetch.addFields(Arrays.asList(exps[0].fetchPaths));
602 fetch.addJoins(Arrays.asList(exps[0].fetchPaths));
603 }
604 if (exps[0].fetchInnerPaths != null)
605 fetch.addFetchInnerJoins(Arrays.asList(exps[0].fetchInnerPaths));
606
607 int eager = calculateEagerMode(exps[0], range.start, range.end);
608 eager = Math.min(eager, JDBCFetchConfiguration.EAGER_JOIN);
609 int subclassMode = fetch.getSubclassFetchMode((ClassMapping) base);
610 DBDictionary dict = _store.getDBDictionary();
611 long start = (mappings.length == 1 && dict.supportsSelectStartIndex)
612 ? range.start : 0L;
613 long end = (dict.supportsSelectEndIndex) ? range.end : Long.MAX_VALUE;
614
615 QueryExpressionsState[] states = new QueryExpressionsState[exps.length];
616 for (int i = 0; i < states.length; i++)
617 states[i] = new QueryExpressionsState();
618 ExpContext ctx = new ExpContext(_store, params, fetch);
619
620 // add selects with populate WHERE conditions to list
621 List sels = new ArrayList(mappings.length);
622 List selMappings = new ArrayList(mappings.length);
623 BitSet subclassBits = new BitSet();
624 BitSet nextBits = new BitSet();
625 boolean unionable = createWhereSelects(sels, mappings, selMappings,
626 subclasses, subclassBits, nextBits, facts, exps, states, ctx,
627 subclassMode) && subclassMode == JDBCFetchConfiguration.EAGER_JOIN;
628 if (sels.size() > 1)
629 start = 0L;
630
631 if (unionable) {
632 Union union = _store.getSQLFactory().newUnion(
633 (Select[]) sels.toArray(new Select[sels.size()]));
634 populateUnion(union, mappings, subclasses, facts, exps, states, ctx,
635 false, eager, start, end);
636 if (union.isUnion())
637 return new String[] {union.toSelect(false, fetch).getSQL(true)};
638 sels = Arrays.asList(union.getSelects());
639 } else {
640 Select sel;
641 for (int i = 0, idx = 0; i < sels.size(); i++) {
642 sel = (Select) sels.get(i);
643 populateSelect(sel, (ClassMapping) selMappings.get(i),
644 subclassBits.get(i), (JDBCExpressionFactory) facts[idx],
645 exps[idx], states[idx], ctx, false, eager, start, end);
646 if (nextBits.get(i))
647 idx++;
648 }
649 }
650
651 String[] sql = new String[sels.size()];
652 for (int i = 0; i < sels.size(); i++)
653 sql[i] = ((Select) sels.get(i)).toSelect(false, fetch).getSQL(true);
654 return sql;
655 }
656
657 /**
658 * This method is to provide override for non-JDBC or JDBC-like
659 * implementation of executing update.
660 */
661 protected int executeUpdate(Connection conn, PreparedStatement stmnt,
662 SQLBuffer sqlBuf, boolean isUpdate) throws SQLException {
663 return stmnt.executeUpdate();
664 }
665
666 /**
667 * This method is to provide override for non-JDBC or JDBC-like
668 * implementation of preparing statement.
669 */
670 protected PreparedStatement prepareStatement(Connection conn, SQLBuffer sql)
671 throws SQLException {
672 return sql.prepareStatement(conn);
673 }
674 }