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.sql;
20
21 import java.sql.Connection;
22 import java.sql.PreparedStatement;
23 import java.sql.ResultSet;
24 import java.sql.SQLException;
25 import java.sql.Statement;
26 import java.sql.Types;
27 import java.util.AbstractList;
28 import java.util.ArrayList;
29 import java.util.BitSet;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Set;
38 import java.util.SortedMap;
39 import java.util.Stack;
40 import java.util.TreeMap;
41
42 import org.apache.commons.collections.iterators.EmptyIterator;
43 import org.apache.commons.lang.StringUtils;
44 import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
45 import org.apache.openjpa.jdbc.kernel.EagerFetchModes;
46 import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
47 import org.apache.openjpa.jdbc.kernel.JDBCLockManager;
48 import org.apache.openjpa.jdbc.kernel.JDBCStore;
49 import org.apache.openjpa.jdbc.kernel.JDBCStoreManager;
50 import org.apache.openjpa.jdbc.meta.ClassMapping;
51 import org.apache.openjpa.jdbc.meta.FieldMapping;
52 import org.apache.openjpa.jdbc.meta.Joinable;
53 import org.apache.openjpa.jdbc.meta.ValueMapping;
54 import org.apache.openjpa.jdbc.schema.Column;
55 import org.apache.openjpa.jdbc.schema.ForeignKey;
56 import org.apache.openjpa.jdbc.schema.Table;
57 import org.apache.openjpa.kernel.StoreContext;
58 import org.apache.openjpa.lib.log.Log;
59 import org.apache.openjpa.lib.util.Localizer;
60 import org.apache.openjpa.util.ApplicationIds;
61 import org.apache.openjpa.util.Id;
62 import org.apache.openjpa.util.InternalException;
63 import serp.util.Numbers;
64
65 /**
66 * Standard {@link Select} implementation. Usage note: though this class
67 * implements {@link Joins}, it should not be used for joining directly.
68 * Instead, use the return value of {@link #newJoins}.
69 *
70 * @author Abe White
71 * @nojavadoc
72 */
73 public class SelectImpl
74 implements Select, PathJoins {
75
76 private static final int NONAUTO_DISTINCT = 2 << 0;
77 private static final int DISTINCT = 2 << 1;
78 private static final int NOT_DISTINCT = 2 << 2;
79 private static final int IMPLICIT_DISTINCT = 2 << 3;
80 private static final int TO_MANY = 2 << 4;
81 private static final int AGGREGATE = 2 << 5;
82 private static final int LOB = 2 << 6;
83 private static final int OUTER = 2 << 7;
84 private static final int LRS = 2 << 8;
85 private static final int EAGER_TO_ONE = 2 << 9;
86 private static final int EAGER_TO_MANY = 2 << 10;
87 private static final int RECORD_ORDERED = 2 << 11;
88 private static final int GROUPING = 2 << 12;
89 private static final int FORCE_COUNT = 2 << 13;
90
91 private static final String[] TABLE_ALIASES = new String[16];
92 private static final String[] ORDER_ALIASES = new String[16];
93 private static final Object[] NULL_IDS = new Object[16];
94 private static final Object[] PLACEHOLDERS = new Object[50];
95
96 private static final Localizer _loc = Localizer.forPackage(Select.class);
97
98 static {
99 for (int i = 0; i < TABLE_ALIASES.length; i++)
100 TABLE_ALIASES[i] = "t" + i;
101 for (int i = 0; i < ORDER_ALIASES.length; i++)
102 ORDER_ALIASES[i] = "o" + i;
103 for (int i = 0; i < NULL_IDS.length; i++)
104 NULL_IDS[i] = new NullId();
105 for (int i = 0; i < PLACEHOLDERS.length; i++)
106 PLACEHOLDERS[i] = new Placeholder();
107 }
108
109 private final JDBCConfiguration _conf;
110 private final DBDictionary _dict;
111
112 // map of variable + relation path + table keys to the correct alias index:
113 // each relation path/table combination should have a unique alias because
114 // it represents a separate object; for example, if a Person class has a
115 // 'parent' field representing another Person and also has an 'address'
116 // field of type Address:
117 // 'address.street' should map to a different table alias than
118 // 'parent.address.street' for the purposes of comparisons
119 private Map _aliases = null;
120
121 // map of indexes to table aliases like 'TABLENAME t0'
122 private SortedMap _tables = null;
123
124 // combined list of selected ids and map of each id to its alias
125 protected final Selects _selects = newSelects();
126 private List _ordered = null;
127 private List _grouped = null;
128
129 // flags
130 private int _flags = 0;
131 private int _joinSyntax = 0;
132 private long _startIdx = 0;
133 private long _endIdx = Long.MAX_VALUE;
134 private int _nullIds = 0;
135 private int _orders = 0;
136 private int _placeholders = 0;
137 private int _expectedResultCount = 0;
138
139 // query clauses
140 private SQLBuffer _ordering = null;
141 private SQLBuffer _where = null;
142 private SQLBuffer _grouping = null;
143 private SQLBuffer _having = null;
144
145 // joins to add to the end of our where clause, and joins to prepend to
146 // all selects (see select(classmapping) method)
147 private SelectJoins _joins = null;
148 private Stack _preJoins = null;
149
150 // map of joins+keys to eager selects and global set of eager keys; the
151 // same key can't be used more than once
152 private Map _eager = null;
153 private Set _eagerKeys = null;
154
155 // subselect support
156 private List _subsels = null;
157 private SelectImpl _parent = null;
158 private String _subPath = null;
159
160 // from select if this select selects from a tmp table created by another
161 private SelectImpl _from = null;
162 protected SelectImpl _outer = null;
163
164 // bitSet indicating if an alias is removed from parent select
165 // bit 0 : correspond to alias 0
166 // bit 1 : correspond to alias 1, etc.
167 // if the bit is set, the corresponding alias has been removed from parent
168 // and recorded under subselect.
169 private BitSet _removedAliasFromParent = new BitSet(16);
170
171 /**
172 * Helper method to return the proper table alias for the given alias index.
173 */
174 static String toAlias(int index) {
175 if (index == -1)
176 return null;
177 if (index < TABLE_ALIASES.length)
178 return TABLE_ALIASES[index];
179 return "t" + index;
180 }
181
182 /**
183 * Helper method to return the proper order alias for the given order
184 * column index.
185 */
186 public static String toOrderAlias(int index) {
187 if (index == -1)
188 return null;
189 if (index < ORDER_ALIASES.length)
190 return ORDER_ALIASES[index];
191 return "o" + index;
192 }
193
194 /**
195 * Constructor. Supply configuration.
196 */
197 public SelectImpl(JDBCConfiguration conf) {
198 _conf = conf;
199 _dict = _conf.getDBDictionaryInstance();
200 _joinSyntax = _dict.joinSyntax;
201 _selects._dict = _dict;
202 }
203
204 /////////////////////////////////
205 // SelectExecutor implementation
206 /////////////////////////////////
207
208 public JDBCConfiguration getConfiguration() {
209 return _conf;
210 }
211
212 public SQLBuffer toSelect(boolean forUpdate, JDBCFetchConfiguration fetch) {
213 return _dict.toSelect(this, forUpdate, fetch);
214 }
215
216 public SQLBuffer toSelectCount() {
217 return _dict.toSelectCount(this);
218 }
219
220 public boolean getAutoDistinct() {
221 return (_flags & NONAUTO_DISTINCT) == 0;
222 }
223
224 public void setAutoDistinct(boolean val) {
225 if (val)
226 _flags &= ~NONAUTO_DISTINCT;
227 else
228 _flags |= NONAUTO_DISTINCT;
229 }
230
231 public boolean isDistinct() {
232 return (_flags & NOT_DISTINCT) == 0 && ((_flags & DISTINCT) != 0
233 || ((_flags & NONAUTO_DISTINCT) == 0
234 && (_flags & IMPLICIT_DISTINCT) != 0));
235 }
236
237 public void setDistinct(boolean distinct) {
238 // need two flags in case set not_distinct, then a to-many join happens
239 // and distinct flag gets set automatically
240 if (distinct) {
241 _flags |= DISTINCT;
242 _flags &= ~NOT_DISTINCT;
243 } else {
244 _flags |= NOT_DISTINCT;
245 _flags &= ~DISTINCT;
246 }
247 }
248
249 public boolean isLRS() {
250 return (_flags & LRS) != 0;
251 }
252
253 public void setLRS(boolean lrs) {
254 if (lrs)
255 _flags |= LRS;
256 else
257 _flags &= ~LRS;
258 }
259
260 public int getExpectedResultCount() {
261 // if the count isn't forced and we have to-many eager joins that could
262 // throw the count off, don't pay attention to it
263 if ((_flags & FORCE_COUNT) == 0 && hasEagerJoin(true))
264 return 0;
265 return _expectedResultCount;
266 }
267
268 public void setExpectedResultCount(int expectedResultCount, boolean force) {
269 _expectedResultCount = expectedResultCount;
270 if (force)
271 _flags |= FORCE_COUNT;
272 else
273 _flags &= ~FORCE_COUNT;
274 }
275
276 public int getJoinSyntax() {
277 return _joinSyntax;
278 }
279
280 public void setJoinSyntax(int joinSyntax) {
281 _joinSyntax = joinSyntax;
282 }
283
284 public boolean supportsRandomAccess(boolean forUpdate) {
285 return _dict.supportsRandomAccessResultSet(this, forUpdate);
286 }
287
288 public boolean supportsLocking() {
289 return _dict.supportsLocking(this);
290 }
291
292 public int getCount(JDBCStore store)
293 throws SQLException {
294 Connection conn = null;
295 PreparedStatement stmnt = null;
296 ResultSet rs = null;
297 try {
298 SQLBuffer sql = toSelectCount();
299 conn = store.getConnection();
300 stmnt = prepareStatement(conn, sql, null,
301 ResultSet.TYPE_FORWARD_ONLY,
302 ResultSet.CONCUR_READ_ONLY, false);
303 rs = executeQuery(conn, stmnt, sql, false, store);
304 return getCount(rs);
305 } finally {
306 if (rs != null)
307 try { rs.close(); } catch (SQLException se) {}
308 if (stmnt != null)
309 try { stmnt.close(); } catch (SQLException se) {}
310 if (conn != null)
311 try { conn.close(); } catch (SQLException se) {}
312 }
313 }
314
315 public Result execute(JDBCStore store, JDBCFetchConfiguration fetch)
316 throws SQLException {
317 if (fetch == null)
318 fetch = store.getFetchConfiguration();
319 return execute(store.getContext(), store, fetch,
320 fetch.getReadLockLevel());
321 }
322
323 public Result execute(JDBCStore store, JDBCFetchConfiguration fetch,
324 int lockLevel)
325 throws SQLException {
326 if (fetch == null)
327 fetch = store.getFetchConfiguration();
328 return execute(store.getContext(), store, fetch, lockLevel);
329 }
330
331 /**
332 * Execute this select in the context of the given store manager. The
333 * context is passed in separately for profiling purposes.
334 */
335 protected Result execute(StoreContext ctx, JDBCStore store,
336 JDBCFetchConfiguration fetch, int lockLevel)
337 throws SQLException {
338 boolean forUpdate = false;
339 if (!isAggregate() && _grouping == null) {
340 JDBCLockManager lm = store.getLockManager();
341 if (lm != null)
342 forUpdate = lm.selectForUpdate(this, lockLevel);
343 }
344
345 SQLBuffer sql = toSelect(forUpdate, fetch);
346 boolean isLRS = isLRS();
347 int rsType = (isLRS && supportsRandomAccess(forUpdate))
348 ? -1 : ResultSet.TYPE_FORWARD_ONLY;
349 Connection conn = store.getConnection();
350 PreparedStatement stmnt = null;
351 ResultSet rs = null;
352 try {
353 if (isLRS)
354 stmnt = prepareStatement(conn, sql, fetch, rsType, -1, true);
355 else
356 stmnt = prepareStatement(conn, sql, null, rsType, -1, false);
357
358 setTimeout(stmnt, forUpdate, fetch);
359
360 rs = executeQuery(conn, stmnt, sql, isLRS, store);
361 } catch (SQLException se) {
362 // clean up statement
363 if (stmnt != null)
364 try { stmnt.close(); } catch (SQLException se2) {}
365 try { conn.close(); } catch (SQLException se2) {}
366 throw se;
367 }
368
369 return getEagerResult(conn, stmnt, rs, store, fetch, forUpdate,
370 sql.getSQL());
371 }
372
373 /**
374 * Execute our eager selects, adding the results under the same keys
375 * to the given result.
376 */
377 private static void addEagerResults(SelectResult res, SelectImpl sel,
378 JDBCStore store, JDBCFetchConfiguration fetch)
379 throws SQLException {
380 if (sel._eager == null)
381 return;
382
383 // execute eager selects
384 Map.Entry entry;
385 Result eres;
386 Map eager;
387 for (Iterator itr = sel._eager.entrySet().iterator(); itr.hasNext();) {
388 entry = (Map.Entry) itr.next();
389
390 // simulated batched selects for inner/outer joins; for separate
391 // selects, don't pass on lock level, because they're probably
392 // for relations and therefore should use default level
393 if (entry.getValue() == sel)
394 eres = res;
395 else
396 eres = ((SelectExecutor) entry.getValue()).execute(store,
397 fetch);
398
399 eager = res.getEagerMap(false);
400 if (eager == null) {
401 eager = new HashMap();
402 res.setEagerMap(eager);
403 }
404 eager.put(entry.getKey(), eres);
405 }
406 }
407
408
409 /**
410 * This method is to provide override for non-JDBC or JDBC-like
411 * implementation of preparing statement.
412 */
413 protected PreparedStatement prepareStatement(Connection conn,
414 SQLBuffer sql, JDBCFetchConfiguration fetch, int rsType,
415 int rsConcur, boolean isLRS) throws SQLException {
416 if (fetch == null)
417 return sql.prepareStatement(conn, rsType, rsConcur);
418 else
419 return sql.prepareStatement(conn, fetch, rsType, -1);
420 }
421
422 /**
423 * This method is to provide override for non-JDBC or JDBC-like
424 * implementation of setting query timeout.
425 */
426 protected void setTimeout(PreparedStatement stmnt, boolean forUpdate,
427 JDBCFetchConfiguration fetch) throws SQLException {
428 // if this is a locking select and the lock timeout is greater than
429 // the configured query timeout, use the lock timeout
430 if (forUpdate && _dict.supportsQueryTimeout && fetch != null
431 && fetch.getLockTimeout() > stmnt.getQueryTimeout() * 1000) {
432 int timeout = fetch.getLockTimeout();
433 if (timeout < 1000) {
434 timeout = 1000;
435 Log log = _conf.getLog(JDBCConfiguration.LOG_JDBC);
436 if (log.isWarnEnabled())
437 log.warn(_loc.get("millis-query-timeout"));
438 }
439 stmnt.setQueryTimeout(timeout / 1000);
440 }
441 }
442
443 /**
444 * This method is to provide override for non-JDBC or JDBC-like
445 * implementation of executing query.
446 */
447 protected ResultSet executeQuery(Connection conn, PreparedStatement stmnt,
448 SQLBuffer sql, boolean isLRS, JDBCStore store) throws SQLException {
449 return stmnt.executeQuery();
450 }
451
452 /**
453 * This method is to provide override for non-JDBC or JDBC-like
454 * implementation of getting count from the result set.
455 */
456 protected int getCount(ResultSet rs) throws SQLException {
457 rs.next();
458 return rs.getInt(1);
459 }
460
461 /**
462 * This method is to provide override for non-JDBC or JDBC-like
463 * implementation of executing eager selects.
464 */
465 protected Result getEagerResult(Connection conn,
466 PreparedStatement stmnt, ResultSet rs, JDBCStore store,
467 JDBCFetchConfiguration fetch, boolean forUpdate, String sqlStr)
468 throws SQLException {
469 SelectResult res = new SelectResult(conn, stmnt, rs, _dict);
470 res.setSelect(this);
471 res.setStore(store);
472 res.setLocking(forUpdate);
473 try {
474 addEagerResults(res, this, store, fetch);
475 } catch (SQLException se) {
476 res.close();
477 throw se;
478 }
479 return res;
480 }
481
482 /////////////////////////
483 // Select implementation
484 /////////////////////////
485
486 public int indexOf() {
487 return 0;
488 }
489
490 public List getSubselects() {
491 return (_subsels == null) ? Collections.EMPTY_LIST : _subsels;
492 }
493
494 public Select getParent() {
495 return _parent;
496 }
497
498 public String getSubselectPath() {
499 return _subPath;
500 }
501
502 public void setParent(Select parent, String path) {
503 if (path != null)
504 _subPath = path + ':';
505 else
506 _subPath = null;
507
508 if (parent == _parent)
509 return;
510 if (_parent != null)
511 _parent._subsels.remove(this);
512
513 //### right now we can't use sql92 joins with subselects, cause
514 //### I can't figure out what to do when the subselect has a join
515 //### with an alias also present in the outer select... you don't want
516 //### the join to appear in the FROM clause of the subselect cause
517 //### then it re-aliases both tables in the scope of the subselect
518 //### and the correlation with the outer select is lost
519 _parent = (SelectImpl) parent;
520 if (_parent != null) {
521 if (_parent._subsels == null)
522 _parent._subsels = new ArrayList(2);
523 _parent._subsels.add(this);
524 if (_parent._joinSyntax == JoinSyntaxes.SYNTAX_SQL92)
525 _joinSyntax = JoinSyntaxes.SYNTAX_TRADITIONAL;
526 else
527 _joinSyntax = _parent._joinSyntax;
528 }
529 }
530
531 public Select getFromSelect() {
532 return _from;
533 }
534
535 public void setFromSelect(Select sel) {
536 _from = (SelectImpl) sel;
537 if (_from != null)
538 _from._outer = this;
539 }
540
541 public boolean hasEagerJoin(boolean toMany) {
542 if (toMany)
543 return (_flags & EAGER_TO_MANY) != 0;
544 return (_flags & EAGER_TO_ONE) != 0;
545 }
546
547 public boolean hasJoin(boolean toMany) {
548 if (toMany)
549 return (_flags & TO_MANY) != 0;
550 return _tables != null && _tables.size() > 1;
551 }
552
553 public boolean isSelected(Table table) {
554 PathJoins pj = getJoins(null, false);
555 if (_from != null)
556 return _from.getTableIndex(table, pj, false) != -1;
557 return getTableIndex(table, pj, false) != -1;
558 }
559
560 public Collection getTableAliases() {
561 return (_tables == null) ? Collections.EMPTY_SET : _tables.values();
562 }
563
564 public List getSelects() {
565 return Collections.unmodifiableList(_selects);
566 }
567
568 public List getSelectAliases() {
569 return _selects.getAliases(false, _outer != null);
570 }
571
572 public List getIdentifierAliases() {
573 return _selects.getAliases(true, _outer != null);
574 }
575
576 public SQLBuffer getOrdering() {
577 return _ordering;
578 }
579
580 public SQLBuffer getGrouping() {
581 return _grouping;
582 }
583
584 public SQLBuffer getWhere() {
585 return _where;
586 }
587
588 public SQLBuffer getHaving() {
589 return _having;
590 }
591
592 public void addJoinClassConditions() {
593 if (_joins == null || _joins.joins() == null)
594 return;
595
596 // join set iterator allows concurrent modification
597 Join j;
598 for (Iterator itr = _joins.joins().iterator(); itr.hasNext();) {
599 j = (Join) itr.next();
600 if (j.getRelationTarget() != null) {
601 j.getRelationTarget().getDiscriminator().addClassConditions
602 (this, j.getSubclasses() == SUBS_JOINABLE,
603 j.getRelationJoins());
604 j.setRelation(null, 0, null);
605 }
606 }
607 }
608
609 public Joins getJoins() {
610 return _joins;
611 }
612
613 public Iterator getJoinIterator() {
614 if (_joins == null || _joins.isEmpty())
615 return EmptyIterator.INSTANCE;
616 return _joins.joins().joinIterator();
617 }
618
619 public long getStartIndex() {
620 return _startIdx;
621 }
622
623 public long getEndIndex() {
624 return _endIdx;
625 }
626
627 public void setRange(long start, long end) {
628 _startIdx = start;
629 _endIdx = end;
630 }
631
632 public String getColumnAlias(Column col) {
633 return getColumnAlias(col, (Joins) null);
634 }
635
636 public String getColumnAlias(Column col, Joins joins) {
637 return getColumnAlias(col, getJoins(joins, false));
638 }
639
640 /**
641 * Return the alias for the given column.
642 */
643 private String getColumnAlias(Column col, PathJoins pj) {
644 return getColumnAlias(col.getName(), col.getTable(), pj);
645 }
646
647 public String getColumnAlias(String col, Table table) {
648 return getColumnAlias(col, table, (Joins) null);
649 }
650
651 public String getColumnAlias(String col, Table table, Joins joins) {
652 return getColumnAlias(col, table, getJoins(joins, false));
653 }
654
655 /**
656 * Return the alias for the given column.
657 */
658 private String getColumnAlias(String col, Table table, PathJoins pj) {
659 if (_from != null) {
660 String alias = toAlias(_from.getTableIndex(table, pj, true));
661 if (_dict.requiresAliasForSubselect)
662 return FROM_SELECT_ALIAS + "." + alias + "_" + col;
663 return alias + "_" + col;
664 }
665 return toAlias(getTableIndex(table, pj, true)) + "." + col;
666 }
667
668 public boolean isAggregate() {
669 return (_flags & AGGREGATE) != 0;
670 }
671
672 public void setAggregate(boolean agg) {
673 if (agg)
674 _flags |= AGGREGATE;
675 else
676 _flags &= ~AGGREGATE;
677 }
678
679 public boolean isLob() {
680 return (_flags & LOB) != 0;
681 }
682
683 public void setLob(boolean lob) {
684 if (lob)
685 _flags |= LOB;
686 else
687 _flags &= ~LOB;
688 }
689
690 public void clearSelects() {
691 _selects.clear();
692 }
693
694 public boolean select(SQLBuffer sql, Object id) {
695 return select(sql, id, null);
696 }
697
698 public boolean select(SQLBuffer sql, Object id, Joins joins) {
699 if (!isGrouping())
700 return select((Object) sql, id, joins);
701 groupBy(sql, joins);
702 return false;
703 }
704
705 /**
706 * Record the select of the given SQL buffer or string.
707 */
708 private boolean select(Object sql, Object id, Joins joins) {
709 getJoins(joins, true);
710 boolean contains;
711 if (id == null) {
712 int idx = _selects.indexOfAlias(sql);
713 contains = idx != -1;
714 if (contains)
715 id = _selects.get(idx);
716 else
717 id = nullId();
718 } else
719 contains = _selects.contains(id);
720
721 if (contains)
722 return false;
723 _selects.setAlias(id, sql, false);
724 return true;
725 }
726
727 /**
728 * Returns a unique id for a SQL string whose given id is null.
729 */
730 private Object nullId() {
731 if (_nullIds >= NULL_IDS.length)
732 return new NullId();
733 return NULL_IDS[_nullIds++];
734 }
735
736 public boolean select(String sql, Object id) {
737 return select(sql, id, null);
738 }
739
740 public boolean select(String sql, Object id, Joins joins) {
741 if (!isGrouping())
742 return select((Object) sql, id, joins);
743 groupBy(sql, joins);
744 return true;
745 }
746
747 public void selectPlaceholder(String sql) {
748 Object holder = (_placeholders >= PLACEHOLDERS.length)
749 ? new Placeholder() : PLACEHOLDERS[_placeholders++];
750 select(sql, holder);
751 }
752
753 /**
754 * Insert a placeholder at the given index; use a negative index
755 * to count from the back of the select list.
756 */
757 public void insertPlaceholder(String sql, int pos) {
758 Object holder = (_placeholders >= PLACEHOLDERS.length)
759 ? new Placeholder() : PLACEHOLDERS[_placeholders++];
760 _selects.insertAlias(pos, holder, sql);
761 }
762
763 /**
764 * Clear selected placeholders, and return removed select indexes.
765 */
766 public void clearPlaceholderSelects() {
767 _selects.clearPlaceholders();
768 }
769
770 public boolean select(Column col) {
771 return select(col, (Joins) null);
772 }
773
774 public boolean select(Column col, Joins joins) {
775 if (!isGrouping())
776 return select(col, getJoins(joins, true), false);
777 groupBy(col, joins);
778 return false;
779 }
780
781 public int select(Column[] cols) {
782 return select(cols, null);
783 }
784
785 public int select(Column[] cols, Joins joins) {
786 if (cols == null || cols.length == 0)
787 return 0;
788 if (isGrouping()) {
789 groupBy(cols, joins);
790 return 0;
791 }
792 PathJoins pj = getJoins(joins, true);
793 int seld = 0;
794 for (int i = 0; i < cols.length; i++)
795 if (select(cols[i], pj, false))
796 seld |= 2 << i;
797 return seld;
798 }
799
800 /**
801 * Select the given column after making the given joins.
802 */
803 private boolean select(Column col, PathJoins pj, boolean ident) {
804 // we cache on column object if there are no joins so that when
805 // looking up columns in the result we don't have to create a string
806 // buffer for the table + column alias; if there are joins, then
807 // we key on the alias
808 String alias = getColumnAlias(col, pj);
809 Object id;
810 if (pj == null || pj.path() == null)
811 id = col;
812 else
813 id = alias;
814 if (_selects.contains(id))
815 return false;
816
817 if (col.getType() == Types.BLOB || col.getType() == Types.CLOB)
818 setLob(true);
819 _selects.setAlias(id, alias, ident);
820 return true;
821 }
822
823 public void select(ClassMapping mapping, int subclasses,
824 JDBCStore store, JDBCFetchConfiguration fetch, int eager) {
825 select(mapping, subclasses, store, fetch, eager, null);
826 }
827
828 public void select(ClassMapping mapping, int subclasses,
829 JDBCStore store, JDBCFetchConfiguration fetch, int eager,
830 Joins joins) {
831 select(this, mapping, subclasses, store, fetch, eager, joins, false);
832 }
833
834 /**
835 * Select the given mapping.
836 */
837 void select(Select wrapper, ClassMapping mapping, int subclasses,
838 JDBCStore store, JDBCFetchConfiguration fetch, int eager,
839 Joins joins, boolean ident) {
840 // note that this is one case where we don't want to use the result
841 // of getJoins(); just use the given joins, which will either be clean
842 // or the result of previous pre-joins. this way we don't push extra
843 // stack stuff when no actual new joins have been made, and we don't
844 // think the user wants outer joins when actually only the previous
845 // joins were outer. we do invoke getJoins(), though, to add these
846 // joins (if any) to our top-level joins; otherwise it'd be possible
847 // for the user to immediately do another join and select something,
848 // and if we're in outer mode all these joins will get switched to outer
849 // joins. caching them as their original join type prevents that
850 getJoins(joins, true);
851
852 PathJoins pj = (PathJoins) joins;
853 boolean hasJoins = pj != null && pj.isDirty();
854 if (hasJoins) {
855 if (_preJoins == null)
856 _preJoins = new Stack();
857 _preJoins.push(pj);
858 }
859
860 // if they are selecting this mapping with outer joins, then all joins
861 // from this mapping should also be outer
862 boolean wasOuter = (_flags & OUTER) != 0;
863 if (hasJoins && !wasOuter && pj.isOuter())
864 _flags |= OUTER;
865
866 // delegate to store manager to select in same order it loads result
867 ((JDBCStoreManager) store).select(wrapper, mapping, subclasses, null,
868 null, fetch, eager, ident, (_flags & OUTER) != 0);
869
870 // reset
871 if (hasJoins)
872 _preJoins.pop();
873 if (!wasOuter && (_flags & OUTER) != 0)
874 _flags &= ~OUTER;
875 }
876
877 public boolean selectIdentifier(Column col) {
878 return selectIdentifier(col, (Joins) null);
879 }
880
881 public boolean selectIdentifier(Column col, Joins joins) {
882 if (!isGrouping())
883 return select(col, getJoins(joins, true), true);
884 groupBy(col, joins);
885 return false;
886 }
887
888 public int selectIdentifier(Column[] cols) {
889 return selectIdentifier(cols, null);
890 }
891
892 public int selectIdentifier(Column[] cols, Joins joins) {
893 if (cols == null || cols.length == 0)
894 return 0;
895 if (isGrouping()) {
896 groupBy(cols, joins);
897 return 0;
898 }
899 PathJoins pj = getJoins(joins, true);
900 int seld = 0;
901 for (int i = 0; i < cols.length; i++)
902 if (select(cols[i], pj, true))
903 seld |= 2 << i;
904 return seld;
905 }
906
907 public void selectIdentifier(ClassMapping mapping, int subclasses,
908 JDBCStore store, JDBCFetchConfiguration fetch, int eager) {
909 selectIdentifier(mapping, subclasses, store, fetch, eager, null);
910 }
911
912 public void selectIdentifier(ClassMapping mapping, int subclasses,
913 JDBCStore store, JDBCFetchConfiguration fetch, int eager,
914 Joins joins) {
915 select(this, mapping, subclasses, store, fetch, eager, joins, true);
916 }
917
918 public int selectPrimaryKey(ClassMapping mapping) {
919 return selectPrimaryKey(mapping, null);
920 }
921
922 public int selectPrimaryKey(ClassMapping mapping, Joins joins) {
923 return primaryKeyOperation(mapping, true, null, joins, false);
924 }
925
926 /**
927 * Operate on primary key data. Return a bit mask of selected columns.
928 */
929 private int primaryKeyOperation(ClassMapping mapping, boolean sel,
930 Boolean asc, Joins joins, boolean aliasOrder) {
931 if (!sel && asc == null)
932 return 0;
933
934 // if this mapping can't select the full pk values, then join to
935 // super and recurse
936 ClassMapping sup;
937 if (!mapping.isPrimaryKeyObjectId(true)) {
938 sup = mapping.getJoinablePCSuperclassMapping();
939 if (joins == null)
940 joins = newJoins();
941 joins = mapping.joinSuperclass(joins, false);
942 return primaryKeyOperation(sup, sel, asc, joins, aliasOrder);
943 }
944
945 Column[] cols = mapping.getPrimaryKeyColumns();
946 if (isGrouping()) {
947 groupBy(cols, joins);
948 return 0;
949 }
950
951 PathJoins pj = getJoins(joins, false);
952 int seld = 0;
953 for (int i = 0; i < cols.length; i++)
954 if (columnOperation(cols[i], sel, asc, pj, aliasOrder))
955 seld |= 2 << i;
956
957 // if this mapping has not been used in the select yet (and therefore
958 // is not joined to anything), but has an other-table superclass that
959 // has been used, make sure to join to it
960 boolean joined = false;
961 for (sup = mapping.getJoinablePCSuperclassMapping(); sup != null;
962 mapping = sup, sup = mapping.getJoinablePCSuperclassMapping()) {
963 if (sup.getTable() == mapping.getTable())
964 continue;
965
966 if (mapping.getTable() != sup.getTable()
967 && getTableIndex(mapping.getTable(), pj, false) == -1
968 && getTableIndex(sup.getTable(), pj, false) != -1) {
969 if (pj == null)
970 pj = (PathJoins) newJoins();
971 pj = (PathJoins) mapping.joinSuperclass(pj, false);
972 joined = true;
973 } else
974 break;
975 }
976 if (joined)
977 where(pj);
978
979 return seld;
980 }
981
982 /**
983 * Perform an operation on a column.
984 */
985 private boolean columnOperation(Column col, boolean sel, Boolean asc,
986 PathJoins pj, boolean aliasOrder) {
987 String as = null;
988 if (asc != null && (aliasOrder || (_flags & RECORD_ORDERED) != 0)) {
989 Object id;
990 if (pj == null || pj.path() == null)
991 id = col;
992 else
993 id = getColumnAlias(col, pj);
994 if ((_flags & RECORD_ORDERED) != 0) {
995 if (_ordered == null)
996 _ordered = new ArrayList(5);
997 _ordered.add(id);
998 }
999 if (aliasOrder) {
1000 as = toOrderAlias(_orders++);
1001 _selects.setSelectAs(id, as);
1002 }
1003 }
1004
1005 boolean seld = sel && select(col, pj, false);
1006 if (asc != null) {
1007 String alias = (as != null) ? as : getColumnAlias(col, pj);
1008 appendOrdering(alias, asc.booleanValue());
1009 }
1010 return seld;
1011 }
1012
1013 /**
1014 * Append ordering information to our internal buffer.
1015 */
1016 private void appendOrdering(Object orderBy, boolean asc) {
1017 if (_ordering == null)
1018 _ordering = new SQLBuffer(_dict);
1019 else
1020 _ordering.append(", ");
1021
1022 if (orderBy instanceof SQLBuffer)
1023 _ordering.append((SQLBuffer) orderBy);
1024 else
1025 _ordering.append((String) orderBy);
1026 if (asc)
1027 _ordering.append(" ASC");
1028 else
1029 _ordering.append(" DESC");
1030 }
1031
1032 public int orderByPrimaryKey(ClassMapping mapping, boolean asc,
1033 boolean sel) {
1034 return orderByPrimaryKey(mapping, asc, null, sel);
1035 }
1036
1037 public int orderByPrimaryKey(ClassMapping mapping, boolean asc,
1038 Joins joins, boolean sel) {
1039 return orderByPrimaryKey(mapping, asc, joins, sel, false);
1040 }
1041
1042 /**
1043 * Allow unions to set aliases on order columns.
1044 */
1045 public int orderByPrimaryKey(ClassMapping mapping, boolean asc,
1046 Joins joins, boolean sel, boolean aliasOrder) {
1047 return primaryKeyOperation(mapping, sel,
1048 (asc) ? Boolean.TRUE : Boolean.FALSE, joins, aliasOrder);
1049 }
1050
1051 public boolean orderBy(Column col, boolean asc, boolean sel) {
1052 return orderBy(col, asc, null, sel);
1053 }
1054
1055 public boolean orderBy(Column col, boolean asc, Joins joins, boolean sel) {
1056 return orderBy(col, asc, joins, sel, false);
1057 }
1058
1059 /**
1060 * Allow unions to set aliases on order columns.
1061 */
1062 boolean orderBy(Column col, boolean asc, Joins joins, boolean sel,
1063 boolean aliasOrder) {
1064 return columnOperation(col, sel, (asc) ? Boolean.TRUE : Boolean.FALSE,
1065 getJoins(joins, true), aliasOrder);
1066 }
1067
1068 public int orderBy(Column[] cols, boolean asc, boolean sel) {
1069 return orderBy(cols, asc, null, sel);
1070 }
1071
1072 public int orderBy(Column[] cols, boolean asc, Joins joins, boolean sel) {
1073 return orderBy(cols, asc, joins, sel, false);
1074 }
1075
1076 /**
1077 * Allow unions to set aliases on order columns.
1078 */
1079 int orderBy(Column[] cols, boolean asc, Joins joins, boolean sel,
1080 boolean aliasOrder) {
1081 PathJoins pj = getJoins(joins, true);
1082 int seld = 0;
1083 for (int i = 0; i < cols.length; i++)
1084 if (columnOperation(cols[i], sel,
1085 (asc) ? Boolean.TRUE : Boolean.FALSE, pj, aliasOrder))
1086 seld |= 2 << i;
1087 return seld;
1088 }
1089
1090 public boolean orderBy(SQLBuffer sql, boolean asc, boolean sel) {
1091 return orderBy(sql, asc, (Joins) null, sel);
1092 }
1093
1094 public boolean orderBy(SQLBuffer sql, boolean asc, Joins joins,
1095 boolean sel) {
1096 return orderBy(sql, asc, joins, sel, false);
1097 }
1098
1099 /**
1100 * Allow unions to set aliases on order columns.
1101 */
1102 boolean orderBy(SQLBuffer sql, boolean asc, Joins joins, boolean sel,
1103 boolean aliasOrder) {
1104 return orderBy((Object) sql, asc, joins, sel, aliasOrder);
1105 }
1106
1107 /**
1108 * Order on a SQL buffer or string.
1109 */
1110 private boolean orderBy(Object sql, boolean asc, Joins joins, boolean sel,
1111 boolean aliasOrder) {
1112 Object order = sql;
1113 if (aliasOrder) {
1114 order = toOrderAlias(_orders++);
1115 _selects.setSelectAs(sql, (String) order);
1116 }
1117 if ((_flags & RECORD_ORDERED) != 0) {
1118 if (_ordered == null)
1119 _ordered = new ArrayList(5);
1120 _ordered.add(sql);
1121 }
1122
1123 getJoins(joins, true);
1124 appendOrdering(order, asc);
1125 if (sel) {
1126 int idx = _selects.indexOfAlias(sql);
1127 if (idx == -1) {
1128 _selects.setAlias(nullId(), sql, false);
1129 return true;
1130 }
1131 }
1132 return false;
1133 }
1134
1135 public boolean orderBy(String sql, boolean asc, boolean sel) {
1136 return orderBy(sql, asc, null, sel);
1137 }
1138
1139 public boolean orderBy(String sql, boolean asc, Joins joins, boolean sel) {
1140 return orderBy(sql, asc, joins, sel, false);
1141 }
1142
1143 /**
1144 * Allow unions to set aliases on order columns.
1145 */
1146 boolean orderBy(String sql, boolean asc, Joins joins, boolean sel,
1147 boolean aliasOrder) {
1148 return orderBy((Object) sql, asc, joins, sel, aliasOrder);
1149 }
1150
1151 public void clearOrdering() {
1152 _ordering = null;
1153 _orders = 0;
1154 }
1155
1156 /**
1157 * Allow unions to record the select list indexes of items we order by.
1158 */
1159 void setRecordOrderedIndexes(boolean record) {
1160 if (record)
1161 _flags |= RECORD_ORDERED;
1162 else {
1163 _ordered = null;
1164 _flags &= ~RECORD_ORDERED;
1165 }
1166 }
1167
1168 /**
1169 * Return the indexes in the select list of all items we're ordering
1170 * by, or null if none. For use with unions.
1171 */
1172 List getOrderedIndexes() {
1173 if (_ordered == null)
1174 return null;
1175 List idxs = new ArrayList(_ordered.size());
1176 for (int i = 0; i < _ordered.size(); i++)
1177 idxs.add(Numbers.valueOf(_selects.indexOf(_ordered.get(i))));
1178 return idxs;
1179 }
1180
1181 public void wherePrimaryKey(Object oid, ClassMapping mapping,
1182 JDBCStore store) {
1183 wherePrimaryKey(oid, mapping, null, store);
1184 }
1185
1186 /**
1187 * Add where conditions setting the mapping's primary key to the given
1188 * oid values. If the given mapping does not use oid values for its
1189 * primary key, we will recursively join to its superclass until we find
1190 * an ancestor that does.
1191 */
1192 private void wherePrimaryKey(Object oid, ClassMapping mapping, Joins joins,
1193 JDBCStore store) {
1194 // if this mapping's identifiers include something other than
1195 // the pk values, join to super and recurse
1196 if (!mapping.isPrimaryKeyObjectId(false)) {
1197 ClassMapping sup = mapping.getJoinablePCSuperclassMapping();
1198 if (joins == null)
1199 joins = newJoins();
1200 joins = mapping.joinSuperclass(joins, false);
1201 wherePrimaryKey(oid, sup, joins, store);
1202 return;
1203 }
1204
1205 Column[] cols = mapping.getPrimaryKeyColumns();
1206 where(oid, mapping, cols, cols, null, null, getJoins(joins, true),
1207 store);
1208 }
1209
1210 public void whereForeignKey(ForeignKey fk, Object oid,
1211 ClassMapping mapping, JDBCStore store) {
1212 whereForeignKey(fk, oid, mapping, null, store);
1213 }
1214
1215 /**
1216 * Add where conditions setting the given foreign key to the given
1217 * oid values.
1218 *
1219 * @see #wherePrimaryKey
1220 */
1221 private void whereForeignKey(ForeignKey fk, Object oid,
1222 ClassMapping mapping, Joins joins, JDBCStore store) {
1223 // if this mapping's identifiers include something other than
1224 // the pk values, or if this foreign key doesn't link to only
1225 // identifiers, join to table and do a getPrimaryKey
1226 if (!mapping.isPrimaryKeyObjectId(false) || !containsAll
1227 (mapping.getPrimaryKeyColumns(), fk.getPrimaryKeyColumns())) {
1228 if (joins == null)
1229 joins = newJoins();
1230 // traverse to foreign key target mapping
1231 while (mapping.getTable() != fk.getPrimaryKeyTable()) {
1232 if (joins == null)
1233 joins = newJoins();
1234 joins = mapping.joinSuperclass(joins, false);
1235 mapping = mapping.getJoinablePCSuperclassMapping();
1236 if (mapping == null)
1237 throw new InternalException();
1238 }
1239 joins = joins.join(fk, false, false);
1240 wherePrimaryKey(oid, mapping, joins, store);
1241 return;
1242 }
1243
1244 Column[] fromCols = fk.getColumns();
1245 Column[] toCols = fk.getPrimaryKeyColumns();
1246 Column[] constCols = fk.getConstantColumns();
1247 Object[] consts = fk.getConstants();
1248 where(oid, mapping, toCols, fromCols, consts, constCols,
1249 getJoins(joins, true), store);
1250 }
1251
1252 /**
1253 * Internal method to flush the oid values as where conditions to the
1254 * given columns.
1255 */
1256 private void where(Object oid, ClassMapping mapping, Column[] toCols,
1257 Column[] fromCols, Object[] vals, Column[] constCols, PathJoins pj,
1258 JDBCStore store) {
1259 ValueMapping embed = mapping.getEmbeddingMapping();
1260 if (embed != null) {
1261 where(oid, embed.getFieldMapping().getDefiningMapping(),
1262 toCols, fromCols, vals, constCols, pj, store);
1263 return;
1264 }
1265
1266 // only bother to pack pk values into array if app id
1267 Object[] pks = null;
1268 if (mapping.getIdentityType() == ClassMapping.ID_APPLICATION)
1269 pks = ApplicationIds.toPKValues(oid, mapping);
1270
1271 SQLBuffer buf = new SQLBuffer(_dict);
1272 Joinable join;
1273 Object val;
1274 int count = 0;
1275 for (int i = 0; i < toCols.length; i++, count++) {
1276 if (pks == null)
1277 val = (oid == null) ? null : Numbers.valueOf(((Id) oid).getId());
1278 else {
1279 // must be app identity; use pk index to get correct pk value
1280 join = mapping.assertJoinable(toCols[i]);
1281 val = pks[mapping.getField(join.getFieldIndex()).
1282 getPrimaryKeyIndex()];
1283 val = join.getJoinValue(val, toCols[i], store);
1284 }
1285
1286 if (count > 0)
1287 buf.append(" AND ");
1288 buf.append(getColumnAlias(fromCols[i], pj));
1289 if (val == null)
1290 buf.append(" IS ");
1291 else
1292 buf.append(" = ");
1293 buf.appendValue(val, fromCols[i]);
1294 }
1295
1296 if (constCols != null && constCols.length > 0) {
1297 for (int i = 0; i < constCols.length; i++, count++) {
1298 if (count > 0)
1299 buf.append(" AND ");
1300 buf.append(getColumnAlias(constCols[i], pj));
1301
1302 if (vals[i] == null)
1303 buf.append(" IS ");
1304 else
1305 buf.append(" = ");
1306 buf.appendValue(vals[i], constCols[i]);
1307 }
1308 }
1309
1310 where(buf, pj);
1311 }
1312
1313 /**
1314 * Test to see if the given set of columns contains all the
1315 * columns in the given potential subset.
1316 */
1317 private static boolean containsAll(Column[] set, Column[] sub) {
1318 if (sub.length > set.length)
1319 return false;
1320
1321 // this is obviously n^2, but the number of columns should be in
1322 // the 1-2 range, so no biggie
1323 boolean found = true;
1324 for (int i = 0; i < sub.length && found; i++) {
1325 found = false;
1326 for (int j = 0; j < set.length && !found; j++)
1327 found = sub[i] == set[j];
1328 }
1329 return found;
1330 }
1331
1332 public void where(Joins joins) {
1333 if (joins != null)
1334 where((String) null, joins);
1335 }
1336
1337 public void where(SQLBuffer sql) {
1338 where(sql, (Joins) null);
1339 }
1340
1341 public void where(SQLBuffer sql, Joins joins) {
1342 where(sql, getJoins(joins, true));
1343 }
1344
1345 /**
1346 * Add the given condition to the WHERE clause.
1347 */
1348 private void where(SQLBuffer sql, PathJoins pj) {
1349 // no need to use joins...
1350 if (sql == null || sql.isEmpty())
1351 return;
1352
1353 if (_where == null)
1354 _where = new SQLBuffer(_dict);
1355 else if (!_where.isEmpty())
1356 _where.append(" AND ");
1357 _where.append(sql);
1358 }
1359
1360 public void where(String sql) {
1361 where(sql, (Joins) null);
1362 }
1363
1364 public void where(String sql, Joins joins) {
1365 where(sql, getJoins(joins, true));
1366 }
1367
1368 /**
1369 * Add the given condition to the WHERE clause.
1370 */
1371 private void where(String sql, PathJoins pj) {
1372 // no need to use joins...
1373 if (StringUtils.isEmpty(sql))
1374 return;
1375
1376 if (_where == null)
1377 _where = new SQLBuffer(_dict);
1378 else if (!_where.isEmpty())
1379 _where.append(" AND ");
1380 _where.append(sql);
1381 }
1382
1383 public void having(SQLBuffer sql) {
1384 having(sql, (Joins) null);
1385 }
1386
1387 public void having(SQLBuffer sql, Joins joins) {
1388 having(sql, getJoins(joins, true));
1389 }
1390
1391 /**
1392 * Add the given condition to the HAVING clause.
1393 */
1394 private void having(SQLBuffer sql, PathJoins pj) {
1395 // no need to use joins...
1396 if (sql == null || sql.isEmpty())
1397 return;
1398
1399 if (_having == null)
1400 _having = new SQLBuffer(_dict);
1401 else if (!_having.isEmpty())
1402 _having.append(" AND ");
1403 _having.append(sql);
1404 }
1405
1406 public void having(String sql) {
1407 having(sql, (Joins) null);
1408 }
1409
1410 public void having(String sql, Joins joins) {
1411 having(sql, getJoins(joins, true));
1412 }
1413
1414 /**
1415 * Add the given condition to the HAVING clause.
1416 */
1417 private void having(String sql, PathJoins pj) {
1418 // no need to use joins...
1419 if (StringUtils.isEmpty(sql))
1420 return;
1421
1422 if (_having == null)
1423 _having = new SQLBuffer(_dict);
1424 else if (!_having.isEmpty())
1425 _having.append(" AND ");
1426 _having.append(sql);
1427 }
1428
1429 public void groupBy(SQLBuffer sql) {
1430 groupBy(sql, (Joins) null);
1431 }
1432
1433 public void groupBy(SQLBuffer sql, Joins joins) {
1434 getJoins(joins, true);
1435 groupByAppend(sql.getSQL());
1436 }
1437
1438 public void groupBy(String sql) {
1439 groupBy(sql, (Joins) null);
1440 }
1441
1442 public void groupBy(String sql, Joins joins) {
1443 getJoins(joins, true);
1444 groupByAppend(sql);
1445 }
1446
1447 public void groupBy(Column col) {
1448 groupBy(col, null);
1449 }
1450
1451 public void groupBy(Column col, Joins joins) {
1452 PathJoins pj = getJoins(joins, true);
1453 groupByAppend(getColumnAlias(col, pj));
1454 }
1455
1456 public void groupBy(Column[] cols) {
1457 groupBy(cols, null);
1458 }
1459
1460 public void groupBy(Column[] cols, Joins joins) {
1461 PathJoins pj = getJoins(joins, true);
1462 for (int i = 0; i < cols.length; i++) {
1463 groupByAppend(getColumnAlias(cols[i], pj));
1464 }
1465 }
1466
1467 private void groupByAppend(String sql) {
1468 if (_grouped == null || !_grouped.contains(sql)) {
1469 if (_grouping == null) {
1470 _grouping = new SQLBuffer(_dict);
1471 _grouped = new ArrayList();
1472 } else
1473 _grouping.append(", ");
1474
1475 _grouping.append(sql);
1476 _grouped.add(sql);
1477 }
1478 }
1479
1480 public void groupBy(ClassMapping mapping, int subclasses, JDBCStore store,
1481 JDBCFetchConfiguration fetch) {
1482 groupBy(mapping, subclasses, store, fetch, null);
1483 }
1484
1485 public void groupBy(ClassMapping mapping, int subclasses, JDBCStore store,
1486 JDBCFetchConfiguration fetch, Joins joins) {
1487 // we implement this by putting ourselves into grouping mode, where
1488 // all select invocations are re-routed to group-by invocations instead.
1489 // this allows us to utilize the same select APIs of the store manager
1490 // and all the mapping strategies, rather than having to create
1491 // equivalent APIs and duplicate logic for grouping
1492 boolean wasGrouping = isGrouping();
1493 _flags |= GROUPING;
1494 try {
1495 select(mapping, subclasses, store, fetch,
1496 EagerFetchModes.EAGER_NONE, joins);
1497 } finally {
1498 if (!wasGrouping)
1499 _flags &= ~GROUPING;
1500 }
1501 }
1502
1503 /**
1504 * Whether we're in group mode, where any select is changed to a group-by
1505 * call.
1506 */
1507 private boolean isGrouping() {
1508 return (_flags & GROUPING) != 0;
1509 }
1510
1511 /**
1512 * Return the joins to use for column aliases, etc.
1513 *
1514 * @param joins joins given by the user
1515 * @return the joins to use for aliases, etc
1516 */
1517 private PathJoins getJoins(Joins joins, boolean record) {
1518 PathJoins pj = (PathJoins) joins;
1519 boolean pre = (pj == null || !pj.isDirty())
1520 && _preJoins != null && !_preJoins.isEmpty();
1521 if (pre)
1522 pj = (PathJoins) _preJoins.peek();
1523
1524 if (pj == null || !pj.isDirty())
1525 pj = _joins;
1526 else if (!pre) {
1527 if ((_flags & OUTER) != 0)
1528 pj = (PathJoins) outer(pj);
1529 if (record) {
1530 if (!pj.isEmpty())
1531 removeParentJoins(pj);
1532 if (!pj.isEmpty()) {
1533 removeJoinsFromSubselects(pj);
1534 if (_joins == null)
1535 _joins = new SelectJoins(this);
1536 if (_joins.joins() == null)
1537 _joins.setJoins(new JoinSet(pj.joins()));
1538 else
1539 _joins.joins().addAll(pj.joins());
1540 }
1541 }
1542 }
1543 return pj;
1544 }
1545
1546 /**
1547 * Remove any joins already in our parent select from the given non-empty
1548 * join set.
1549 */
1550 private void removeParentJoins(PathJoins pj) {
1551 if (_parent == null)
1552 return;
1553 if (_parent._joins != null && !_parent._joins.isEmpty()) {
1554 boolean removed = false;
1555 if (!_removedAliasFromParent.isEmpty())
1556 removed = _parent._joins.joins().removeAll(pj.joins());
1557 if (!removed)
1558 pj.joins().removeAll(_parent._joins.joins());
1559 }
1560 if (!pj.isEmpty())
1561 _parent.removeParentJoins(pj);
1562 }
1563
1564 /**
1565 * Remove the given non-empty joins from the joins of our subselects.
1566 */
1567 private void removeJoinsFromSubselects(PathJoins pj) {
1568 if (_subsels == null)
1569 return;
1570 SelectImpl sub;
1571 for (int i = 0; i < _subsels.size(); i++) {
1572 sub = (SelectImpl) _subsels.get(i);
1573 if (sub._joins != null && !sub._joins.isEmpty())
1574 sub._joins.joins().removeAll(pj.joins());
1575 }
1576 }
1577
1578 public SelectExecutor whereClone(int sels) {
1579 if (sels < 1)
1580 sels = 1;
1581
1582 Select[] clones = null;
1583 SelectImpl sel;
1584 for (int i = 0; i < sels; i++) {
1585 sel = (SelectImpl) _conf.getSQLFactoryInstance().newSelect();
1586 sel._flags = _flags;
1587 sel._flags &= ~AGGREGATE;
1588 sel._flags &= ~OUTER;
1589 sel._flags &= ~LRS;
1590 sel._flags &= ~EAGER_TO_ONE;
1591 sel._flags &= ~EAGER_TO_MANY;
1592 sel._flags &= ~FORCE_COUNT;
1593 sel._joinSyntax = _joinSyntax;
1594 if (_aliases != null)
1595 sel._aliases = new HashMap(_aliases);
1596 if (_tables != null)
1597 sel._tables = new TreeMap(_tables);
1598 if (_joins != null)
1599 sel._joins = _joins.clone(sel);
1600 if (_where != null)
1601 sel._where = new SQLBuffer(_where);
1602 if (_from != null) {
1603 sel._from = (SelectImpl) _from.whereClone(1);
1604 sel._from._outer = sel;
1605 }
1606 if (_subsels != null) {
1607 sel._subsels = new ArrayList(_subsels.size());
1608 SelectImpl sub, selSub;
1609 for (int j = 0; j < _subsels.size(); j++) {
1610 sub = (SelectImpl) _subsels.get(j);
1611 selSub = (SelectImpl) sub.fullClone(1);
1612 selSub._parent = sel;
1613 selSub._subPath = sub._subPath;
1614 sel._subsels.add(selSub);
1615 if (sel._where != null)
1616 sel._where.replace(sub, selSub);
1617 }
1618 }
1619
1620 if (sels == 1)
1621 return sel;
1622 if (clones == null)
1623 clones = new Select[sels];
1624 clones[i] = sel;
1625 }
1626 return _conf.getSQLFactoryInstance().newUnion(clones);
1627 }
1628
1629 public SelectExecutor fullClone(int sels) {
1630 if (sels < 1)
1631 sels = 1;
1632
1633 Select[] clones = null;
1634 SelectImpl sel;
1635 for (int i = 0; i < sels; i++) {
1636 sel = (SelectImpl) whereClone(1);
1637 sel._flags = _flags;
1638 sel._expectedResultCount = _expectedResultCount;
1639 sel._selects.addAll(_selects);
1640 if (_ordering != null)
1641 sel._ordering = new SQLBuffer(_ordering);
1642 sel._orders = _orders;
1643 if (_grouping != null)
1644 sel._grouping = new SQLBuffer(_grouping);
1645 if (_having != null)
1646 sel._having = new SQLBuffer(_having);
1647 if (_from != null) {
1648 sel._from = (SelectImpl) _from.fullClone(1);
1649 sel._from._outer = sel;
1650 }
1651
1652 if (sels == 1)
1653 return sel;
1654 if (clones == null)
1655 clones = new Select[sels];
1656 clones[i] = sel;
1657 }
1658 return _conf.getSQLFactoryInstance().newUnion(clones);
1659 }
1660
1661 public SelectExecutor eagerClone(FieldMapping key, int eagerType,
1662 boolean toMany, int sels) {
1663 if (eagerType == EAGER_OUTER
1664 && _joinSyntax == JoinSyntaxes.SYNTAX_TRADITIONAL)
1665 return null;
1666 if (_eagerKeys != null && _eagerKeys.contains(key))
1667 return null;
1668
1669 // global set of eager keys
1670 if (_eagerKeys == null)
1671 _eagerKeys = new HashSet();
1672 _eagerKeys.add(key);
1673
1674 SelectExecutor sel;
1675 if (eagerType != EAGER_PARALLEL) {
1676 if (toMany)
1677 _flags |= EAGER_TO_MANY;
1678 else
1679 _flags |= EAGER_TO_ONE;
1680 sel = this;
1681 } else if (sels < 2)
1682 sel = parallelClone();
1683 else {
1684 Select[] clones = new Select[sels];
1685 for (int i = 0; i < clones.length; i++)
1686 clones[i] = parallelClone();
1687 sel = _conf.getSQLFactoryInstance().newUnion(clones);
1688 }
1689
1690 if (_eager == null)
1691 _eager = new HashMap();
1692 _eager.put(toEagerKey(key, getJoins(null, false)), sel);
1693 return sel;
1694 }
1695
1696 /**
1697 * Return a clone of this select for use in eager parallel selects.
1698 */
1699 private SelectImpl parallelClone() {
1700 SelectImpl sel = (SelectImpl) whereClone(1);
1701 sel._flags &= ~NONAUTO_DISTINCT;
1702 sel._eagerKeys = _eagerKeys;
1703 if (_preJoins != null && !_preJoins.isEmpty()) {
1704 sel._preJoins = new Stack();
1705 sel._preJoins.push(((SelectJoins) _preJoins.peek()).
1706 clone(sel));
1707 }
1708 return sel;
1709 }
1710
1711 /**
1712 * Return view of eager selects. May be null.
1713 */
1714 public Map getEagerMap() {
1715 return _eager;
1716 }
1717
1718 public SelectExecutor getEager(FieldMapping key) {
1719 if (_eager == null || !_eagerKeys.contains(key))
1720 return null;
1721 return (SelectExecutor) _eager.get(toEagerKey(key, getJoins(null,
1722 false)));
1723 }
1724
1725 /**
1726 * Return the eager key to use for the user-given key.
1727 */
1728 private static Object toEagerKey(FieldMapping key, PathJoins pj) {
1729 if (pj == null || pj.path() == null)
1730 return key;
1731 return new Key(pj.path().toString(), key);
1732 }
1733
1734 public Joins newJoins() {
1735 if (_preJoins != null && !_preJoins.isEmpty()) {
1736 SelectJoins sj = (SelectJoins) _preJoins.peek();
1737 return sj.clone(this);
1738 }
1739 // return this for efficiency in case no joins end up being made
1740 return this;
1741 }
1742
1743 public Joins newOuterJoins() {
1744 return ((PathJoins) newJoins()).setOuter(true);
1745 }
1746
1747 public void append(SQLBuffer buf, Joins joins) {
1748 if (joins == null || joins.isEmpty())
1749 return;
1750
1751 if (!buf.isEmpty())
1752 buf.append(" AND ");
1753 Join join = null;
1754 for (Iterator itr = ((PathJoins) joins).joins().joinIterator();
1755 itr.hasNext();) {
1756 join = (Join) itr.next();
1757 switch (_joinSyntax) {
1758 case JoinSyntaxes.SYNTAX_TRADITIONAL:
1759 buf.append(_dict.toTraditionalJoin(join));
1760 break;
1761 case JoinSyntaxes.SYNTAX_DATABASE:
1762 buf.append(_dict.toNativeJoin(join));
1763 break;
1764 default:
1765 throw new InternalException();
1766 }
1767
1768 if (itr.hasNext())
1769 buf.append(" AND ");
1770 }
1771 }
1772
1773 public Joins and(Joins joins1, Joins joins2) {
1774 return and((PathJoins) joins1, (PathJoins) joins2, true);
1775 }
1776
1777 /**
1778 * Combine the given joins.
1779 */
1780 private SelectJoins and(PathJoins j1, PathJoins j2, boolean nullJoins) {
1781 if ((j1 == null || j1.isEmpty())
1782 && (j2 == null || j2.isEmpty()))
1783 return null;
1784
1785 SelectJoins sj = new SelectJoins(this);
1786 if (j1 == null || j1.isEmpty()) {
1787 if (nullJoins)
1788 sj.setJoins(j2.joins());
1789 else
1790 sj.setJoins(new JoinSet(j2.joins()));
1791 } else {
1792 JoinSet set;
1793 if (nullJoins)
1794 set = j1.joins();
1795 else
1796 set = new JoinSet(j1.joins());
1797
1798 if (j2 != null && !j2.isEmpty())
1799 set.addAll(j2.joins());
1800 sj.setJoins(set);
1801 }
1802
1803 // null previous joins; all are combined into this one
1804 if (nullJoins && j1 != null)
1805 j1.nullJoins();
1806 if (nullJoins && j2 != null)
1807 j2.nullJoins();
1808
1809 return sj;
1810 }
1811
1812 public Joins or(Joins joins1, Joins joins2) {
1813 PathJoins j1 = (PathJoins) joins1;
1814 PathJoins j2 = (PathJoins) joins2;
1815
1816 // if no common joins, return null; if one side of the or clause has
1817 // different joins than the other, then we need to use distinct
1818 boolean j1Empty = j1 == null || j1.isEmpty();
1819 boolean j2Empty = j2 == null || j2.isEmpty();
1820 if (j1Empty || j2Empty) {
1821 if (j1Empty && !j2Empty) {
1822 collectOuterJoins(j2);
1823 if (!j2.isEmpty())
1824 _flags |= IMPLICIT_DISTINCT;
1825 } else if (j2Empty && !j1Empty) {
1826 collectOuterJoins(j1);
1827 if (!j1.isEmpty())
1828 _flags |= IMPLICIT_DISTINCT;
1829 }
1830 return null;
1831 }
1832
1833 // if all common joins, move all joins to returned instance
1834 SelectJoins sj = new SelectJoins(this);
1835 if (j1.joins().equals(j2.joins())) {
1836 sj.setJoins(j1.joins());
1837 j1.nullJoins();
1838 j2.nullJoins();
1839 } else {
1840 JoinSet commonJoins = new JoinSet(j1.joins());
1841 commonJoins.retainAll(j2.joins());
1842 if (!commonJoins.isEmpty()) {
1843 // put common joins in returned instance; remove them from
1844 // each given instance
1845 sj.setJoins(commonJoins);
1846 j1.joins().removeAll(commonJoins);
1847 j2.joins().removeAll(commonJoins);
1848 }
1849 collectOuterJoins(j1);
1850 collectOuterJoins(j2);
1851
1852 // if one side of the or clause has different joins than the other,
1853 // then we need to use distinct
1854 if (!j1.isEmpty() || !j2.isEmpty())
1855 _flags |= IMPLICIT_DISTINCT;
1856 }
1857 return sj;
1858 }
1859
1860 public Joins outer(Joins joins) {
1861 if (_joinSyntax == JoinSyntaxes.SYNTAX_TRADITIONAL || joins == null)
1862 return joins;
1863
1864 // record that this is an outer join set, even if it's empty
1865 PathJoins pj = ((PathJoins) joins).setOuter(true);
1866 if (pj.isEmpty())
1867 return pj;
1868
1869 Join join;
1870 Join rec;
1871 boolean hasJoins = _joins != null && _joins.joins() != null;
1872 for (Iterator itr = pj.joins().iterator(); itr.hasNext();) {
1873 join = (Join) itr.next();
1874 if (join.getType() == Join.TYPE_INNER) {
1875 if (!hasJoins)
1876 join.setType(Join.TYPE_OUTER);
1877 else {
1878 rec = _joins.joins().getRecordedJoin(join);
1879 if (rec == null || rec.getType() == Join.TYPE_OUTER)
1880 join.setType(Join.TYPE_OUTER);
1881 }
1882 }
1883 }
1884 return joins;
1885 }
1886
1887 /**
1888 * Moves the joins from the given instance into our outer joins set.
1889 */
1890 private void collectOuterJoins(PathJoins pj) {
1891 if (_joinSyntax == JoinSyntaxes.SYNTAX_TRADITIONAL || pj == null
1892 || pj.isEmpty())
1893 return;
1894
1895 if (_joins == null)
1896 _joins = new SelectJoins(this);
1897
1898 boolean add = true;
1899 if (_joins.joins() == null) {
1900 _joins.setJoins(pj.joins());
1901 add = false;
1902 }
1903
1904 Join join;
1905 for (Iterator itr = pj.joins().iterator(); itr.hasNext();) {
1906 join = (Join) itr.next();
1907 if (join.getType() == Join.TYPE_INNER) {
1908 if (join.getForeignKey() != null
1909 && !_dict.canOuterJoin(_joinSyntax, join.getForeignKey())) {
1910 Log log = _conf.getLog(JDBCConfiguration.LOG_JDBC);
1911 if (log.isWarnEnabled())
1912 log.warn(_loc.get("cant-outer-fk",
1913 join.getForeignKey()));
1914 } else
1915 join.setType(Join.TYPE_OUTER);
1916 }
1917 if (add)
1918 _joins.joins().add(join);
1919 }
1920 pj.nullJoins();
1921 }
1922
1923 /**
1924 * Return the alias for the given table under the given joins.
1925 * NOTE: WE RELY ON THESE INDEXES BEING MONOTONICALLY INCREASING FROM 0
1926 */
1927 private int getTableIndex(Table table, PathJoins pj, boolean create) {
1928 // if we have a from select, then there are no table aliases
1929 if (_from != null)
1930 return -1;
1931
1932 Object key = table.getFullName();
1933 if (pj != null && pj.path() != null)
1934 key = new Key(pj.path().toString(), key);
1935
1936 // check out existing aliases
1937 Integer i = findAlias(table, key, false, null);
1938 if (i != null)
1939 return i.intValue();
1940 if (!create)
1941 return -1;
1942
1943 // not found; create alias
1944 i = Numbers.valueOf(aliasSize());
1945 recordTableAlias(table, key, i);
1946 return i.intValue();
1947 }
1948
1949 /**
1950 * Attempt to find the alias for the given key.
1951 *
1952 * @param fromParent whether a parent is checking its subselects
1953 * @param fromSub the subselect checking its parent
1954 */
1955 private Integer findAlias(Table table, Object key, boolean fromParent,
1956 SelectImpl fromSub) {
1957 Integer alias = null;
1958 if (_aliases != null) {
1959 alias = (Integer) ((fromParent) ? _aliases.remove(key)
1960 : _aliases.get(key));
1961 if (alias != null) {
1962 if (fromParent)
1963 _tables.remove(alias);
1964 return alias;
1965 }
1966 }
1967 if (!fromParent && _parent != null) {
1968 boolean removeAliasFromParent = key.toString().indexOf(":") != -1;
1969 alias = _parent.findAlias(table, key, removeAliasFromParent, this);
1970 if (alias != null) {
1971 if (removeAliasFromParent) {
1972 recordTableAlias(table, key, alias);
1973 _removedAliasFromParent.set(alias.intValue());
1974 }
1975 return alias;
1976 }
1977 }
1978 if (_subsels != null) {
1979 SelectImpl sub;
1980 for (int i = 0; i < _subsels.size(); i++) {
1981 sub = (SelectImpl) _subsels.get(i);
1982 if (sub == fromSub)
1983 continue;
1984 if (alias != null) {
1985 if (sub._aliases != null)
1986 sub._aliases.remove(key);
1987 if (sub._tables != null)
1988 sub._tables.remove(alias);
1989 } else {
1990 if (key instanceof String) {
1991 alias = sub.findAlias(table, key, true, null);
1992 if (!fromParent && alias != null)
1993 recordTableAlias(table, key, alias);
1994 }
1995 }
1996 }
1997 }
1998 return alias;
1999 }
2000
2001 /**
2002 * Record the mapping of the given key to the given alias.
2003 */
2004 private void recordTableAlias(Table table, Object key, Integer alias) {
2005 if (_aliases == null)
2006 _aliases = new HashMap();
2007 _aliases.put(key, alias);
2008
2009 String tableString = _dict.getFullName(table, false) + " "
2010 + toAlias(alias.intValue());
2011 if (_tables == null)
2012 _tables = new TreeMap();
2013 _tables.put(alias, tableString);
2014 }
2015
2016 /**
2017 * Calculate total number of aliases.
2018 */
2019 private int aliasSize() {
2020 return aliasSize(false, null);
2021 }
2022
2023 /**
2024 * Calculate total number of aliases.
2025 *
2026 * @param fromParent whether a parent is checking its subselects
2027 * @param fromSub the subselect checking its parent
2028 */
2029 private int aliasSize(boolean fromParent, SelectImpl fromSub) {
2030 int aliases = (fromParent || _parent == null) ? 0
2031 : _parent.aliasSize(false, this);
2032 aliases += (_aliases == null) ? 0 : _aliases.size();
2033 if (_subsels != null) {
2034 SelectImpl sub;
2035 for (int i = 0; i < _subsels.size(); i++) {
2036 sub = (SelectImpl) _subsels.get(i);
2037 if (sub != fromSub)
2038 aliases += sub.aliasSize(true, null);
2039 }
2040 }
2041 return aliases;
2042 }
2043
2044 public String toString() {
2045 return toSelect(false, null).getSQL();
2046 }
2047
2048 ////////////////////////////
2049 // PathJoins implementation
2050 ////////////////////////////
2051
2052 public boolean isOuter() {
2053 return false;
2054 }
2055
2056 public PathJoins setOuter(boolean outer) {
2057 return new SelectJoins(this).setOuter(true);
2058 }
2059
2060 public boolean isDirty() {
2061 return false;
2062 }
2063
2064 public StringBuffer path() {
2065 return null;
2066 }
2067
2068 public JoinSet joins() {
2069 return null;
2070 }
2071
2072 public int joinCount() {
2073 return 0;
2074 }
2075
2076 public void nullJoins() {
2077 }
2078
2079 public boolean isEmpty() {
2080 return true;
2081 }
2082
2083 public Joins crossJoin(Table localTable, Table foreignTable) {
2084 return new SelectJoins(this).crossJoin(localTable, foreignTable);
2085 }
2086
2087 public Joins join(ForeignKey fk, boolean inverse, boolean toMany) {
2088 return new SelectJoins(this).join(fk, inverse, toMany);
2089 }
2090
2091 public Joins outerJoin(ForeignKey fk, boolean inverse, boolean toMany) {
2092 return new SelectJoins(this).outerJoin(fk, inverse, toMany);
2093 }
2094
2095 public Joins joinRelation(String name, ForeignKey fk, ClassMapping target,
2096 int subs, boolean inverse, boolean toMany) {
2097 return new SelectJoins(this).joinRelation(name, fk, target, subs,
2098 inverse, toMany);
2099 }
2100
2101 public Joins outerJoinRelation(String name, ForeignKey fk,
2102 ClassMapping target, int subs, boolean inverse, boolean toMany) {
2103 return new SelectJoins(this).outerJoinRelation(name, fk, target, subs,
2104 inverse, toMany);
2105 }
2106
2107 public Joins setVariable(String var) {
2108 if (var == null)
2109 return this;
2110 return new SelectJoins(this).setVariable(var);
2111 }
2112
2113 public Joins setSubselect(String alias) {
2114 if (alias == null)
2115 return this;
2116 return new SelectJoins(this).setSubselect(alias);
2117 }
2118
2119 /**
2120 * Represents a SQL string selected with null id.
2121 */
2122 private static class NullId {
2123 }
2124
2125 /**
2126 * Represents a placeholder SQL string.
2127 */
2128 private static class Placeholder {
2129 }
2130
2131 /**
2132 * Key type used for aliases.
2133 */
2134 private static class Key {
2135
2136 private final String _path;
2137 private final Object _key;
2138
2139 public Key(String path, Object key) {
2140 _path = path;
2141 _key = key;
2142 }
2143
2144 public int hashCode() {
2145 return _path.hashCode() ^ _key.hashCode();
2146 }
2147
2148 public boolean equals(Object other) {
2149 if (other == this)
2150 return true;
2151 if (other.getClass() != getClass())
2152 return false;
2153 Key k = (Key) other;
2154 return k._path.equals(_path) && k._key.equals(_key);
2155 }
2156
2157 public String toString() {
2158 return _path + "|" + _key;
2159 }
2160 }
2161
2162 /**
2163 * A {@link Result} implementation wrapped around this select.
2164 */
2165 public static class SelectResult
2166 extends ResultSetResult
2167 implements PathJoins {
2168
2169 private SelectImpl _sel = null;
2170
2171 // position in selected columns list where we expect the next load
2172 private int _pos = 0;
2173 private Stack _preJoins = null;
2174
2175 /**
2176 * Constructor.
2177 */
2178 public SelectResult(Connection conn, Statement stmnt, ResultSet rs,
2179 DBDictionary dict) {
2180 super(conn, stmnt, rs, dict);
2181 }
2182
2183 /**
2184 * Select for this result.
2185 */
2186 public SelectImpl getSelect() {
2187 return _sel;
2188 }
2189
2190 /**
2191 * Select for this result.
2192 */
2193 public void setSelect(SelectImpl sel) {
2194 _sel = sel;
2195 }
2196
2197 public Object getEager(FieldMapping key) {
2198 // don't bother creating key if we know we don't have any
2199 // eager results
2200 if (_sel._eager == null || !_sel._eagerKeys.contains(key))
2201 return null;
2202 Map map = SelectResult.this.getEagerMap(true);
2203 if (map == null)
2204 return null;
2205 return map.get(_sel.toEagerKey(key, getJoins(null)));
2206 }
2207
2208 public void putEager(FieldMapping key, Object res) {
2209 Map map = SelectResult.this.getEagerMap(true);
2210 if (map == null) {
2211 map = new HashMap();
2212 setEagerMap(map);
2213 }
2214 map.put(_sel.toEagerKey(key, getJoins(null)), res);
2215 }
2216
2217 public Object load(ClassMapping mapping, JDBCStore store,
2218 JDBCFetchConfiguration fetch, Joins joins)
2219 throws SQLException {
2220 boolean hasJoins = joins != null
2221 && ((PathJoins) joins).path() != null;
2222 if (hasJoins) {
2223 if (_preJoins == null)
2224 _preJoins = new Stack();
2225 _preJoins.push(joins);
2226 }
2227
2228 Object obj = super.load(mapping, store, fetch, joins);
2229
2230 // reset
2231 if (hasJoins)
2232 _preJoins.pop();
2233 return obj;
2234 }
2235
2236 public Joins newJoins() {
2237 PathJoins pre = getPreJoins();
2238 if (pre == null || pre.path() == null)
2239 return this;
2240
2241 PathJoinsImpl pj = new PathJoinsImpl();
2242 pj.path = new StringBuffer(pre.path().toString());
2243 return pj;
2244 }
2245
2246 protected boolean containsInternal(Object obj, Joins joins) {
2247 // we key directly on objs and join-less cols, or on the alias
2248 // for cols with joins
2249 PathJoins pj = getJoins(joins);
2250 if (pj != null && pj.path() != null)
2251 obj = getColumnAlias((Column) obj, pj);
2252 return obj != null && _sel._selects.contains(obj);
2253 }
2254
2255 protected boolean containsAllInternal(Object[] objs, Joins joins)
2256 throws SQLException {
2257 PathJoins pj = getJoins(joins);
2258 Object obj;
2259 for (int i = 0; i < objs.length; i++) {
2260 if (pj != null && pj.path() != null)
2261 obj = getColumnAlias((Column) objs[i], pj);
2262 else
2263 obj = objs[i];
2264 if (obj == null || !_sel._selects.contains(obj))
2265 return false;
2266 }
2267 return true;
2268 }
2269
2270 public void pushBack()
2271 throws SQLException {
2272 _pos = 0;
2273 super.pushBack();
2274 }
2275
2276 protected boolean absoluteInternal(int row)
2277 throws SQLException {
2278 _pos = 0;
2279 return super.absoluteInternal(row);
2280 }
2281
2282 protected boolean nextInternal()
2283 throws SQLException {
2284 _pos = 0;
2285 return super.nextInternal();
2286 }
2287
2288 protected int findObject(Object obj, Joins joins)
2289 throws SQLException {
2290 if (_pos == _sel._selects.size())
2291 _pos = 0;
2292
2293 // we key directly on objs and join-less cols, or on the alias
2294 // for cols with joins
2295 PathJoins pj = getJoins(joins);
2296 Boolean pk = null;
2297 if (pj != null && pj.path() != null) {
2298 Column col = (Column) obj;
2299 pk = (col.isPrimaryKey()) ? Boolean.TRUE : Boolean.FALSE;
2300 obj = getColumnAlias(col, pj);
2301 if (obj == null)
2302 throw new SQLException(col.getTable() + ": "
2303 + pj.path() + " (" + _sel._aliases + ")");
2304 }
2305
2306 // we load in the same order we select, more or less...
2307 if (_sel._selects.get(_pos).equals(obj))
2308 return ++_pos;
2309
2310 // if we're looking for a primary key, try back a couple places,
2311 // since pks might be selected in a slightly different order than
2312 // they are loaded back; don't change the marker position
2313 if (pk == null)
2314 pk = (obj instanceof Column && ((Column) obj).isPrimaryKey())
2315 ? Boolean.TRUE : Boolean.FALSE;
2316 if (pk.booleanValue()) {
2317 for (int i = _pos - 1; i >= 0 && i >= _pos - 3; i--)
2318 if (_sel._selects.get(i).equals(obj))
2319 return i + 1;
2320 }
2321
2322 // search forward on the assumption that we might be skipping
2323 // selects for sibling classes; advance the position if we find
2324 // something forward
2325 for (int i = _pos + 1; i < _sel._selects.size(); i++) {
2326 if (_sel._selects.get(i).equals(obj)) {
2327 _pos = i;
2328 return ++_pos;
2329 }
2330 }
2331
2332 // maybe the column was selected by 2 different mappings, so it's
2333 // somewhere prior to the current position; in this case leave the
2334 // position marker at its current place cause subsequent loads will
2335 // still probably start from there
2336 for (int i = 0; i < _pos; i++)
2337 if (_sel._selects.get(i).equals(obj))
2338 return i + 1;
2339
2340 // somethings's wrong...
2341 throw new SQLException(obj.toString());
2342 }
2343
2344 /**
2345 * Return the joins to use to find column data.
2346 */
2347 private PathJoins getJoins(Joins joins) {
2348 PathJoins pj = (PathJoins) joins;
2349 if (pj != null && pj.path() != null)
2350 return pj;
2351 return getPreJoins();
2352 }
2353
2354 /**
2355 * Return the pre joins for the result, or null if none. Note that
2356 * we have to take the Select's pre joins into account too, since
2357 * batched selects can have additional pre joins on the stack even
2358 * on execution.
2359 */
2360 private PathJoins getPreJoins() {
2361 if (_preJoins != null && !_preJoins.isEmpty())
2362 return (PathJoins) _preJoins.peek();
2363 if (_sel._preJoins != null && !_sel._preJoins.isEmpty())
2364 return (PathJoins) _sel._preJoins.peek();
2365 return null;
2366 }
2367
2368 /**
2369 * Return the alias used to key on the column data, considering the
2370 * given joins.
2371 */
2372 private String getColumnAlias(Column col, PathJoins pj) {
2373 String alias;
2374 if (_sel._from != null) {
2375 alias = _sel.toAlias(_sel._from.getTableIndex
2376 (col.getTable(), pj, false));
2377 if (alias == null)
2378 return null;
2379 if (_sel._dict.requiresAliasForSubselect)
2380 return FROM_SELECT_ALIAS + "." + alias + "_" + col;
2381 return alias + "_" + col;
2382 }
2383 alias = _sel.toAlias(_sel.getTableIndex(col.getTable(), pj, false));
2384 return (alias == null) ? null : alias + "." + col;
2385 }
2386
2387 ////////////////////////////
2388 // PathJoins implementation
2389 ////////////////////////////
2390
2391 public boolean isOuter() {
2392 return false;
2393 }
2394
2395 public PathJoins setOuter(boolean outer) {
2396 return this;
2397 }
2398
2399 public boolean isDirty() {
2400 return false;
2401 }
2402
2403 public StringBuffer path() {
2404 return null;
2405 }
2406
2407 public JoinSet joins() {
2408 return null;
2409 }
2410
2411 public int joinCount() {
2412 return 0;
2413 }
2414
2415 public void nullJoins() {
2416 }
2417
2418 public boolean isEmpty() {
2419 return true;
2420 }
2421
2422 public Joins crossJoin(Table localTable, Table foreignTable) {
2423 return this;
2424 }
2425
2426 public Joins join(ForeignKey fk, boolean inverse, boolean toMany) {
2427 return this;
2428 }
2429
2430 public Joins outerJoin(ForeignKey fk, boolean inverse, boolean toMany) {
2431 return this;
2432 }
2433
2434 public Joins joinRelation(String name, ForeignKey fk,
2435 ClassMapping target, int subs, boolean inverse, boolean toMany) {
2436 return new PathJoinsImpl().joinRelation(name, fk, target, subs,
2437 inverse, toMany);
2438 }
2439
2440 public Joins outerJoinRelation(String name, ForeignKey fk,
2441 ClassMapping target, int subs, boolean inverse, boolean toMany) {
2442 return new PathJoinsImpl().outerJoinRelation(name, fk, target, subs,
2443 inverse, toMany);
2444 }
2445
2446 public Joins setVariable(String var) {
2447 if (var == null)
2448 return this;
2449 return new PathJoinsImpl().setVariable(var);
2450 }
2451
2452 public Joins setSubselect(String alias) {
2453 if (alias == null)
2454 return this;
2455 return new PathJoinsImpl().setSubselect(alias);
2456 }
2457 }
2458
2459 /**
2460 * Base joins implementation.
2461 */
2462 private static class PathJoinsImpl
2463 implements PathJoins {
2464
2465 protected StringBuffer path = null;
2466 protected String var = null;
2467
2468 public boolean isOuter() {
2469 return false;
2470 }
2471
2472 public PathJoins setOuter(boolean outer) {
2473 return this;
2474 }
2475
2476 public boolean isDirty() {
2477 return var != null || path != null;
2478 }
2479
2480 public StringBuffer path() {
2481 return path;
2482 }
2483
2484 public JoinSet joins() {
2485 return null;
2486 }
2487
2488 public int joinCount() {
2489 return 0;
2490 }
2491
2492 public void nullJoins() {
2493 }
2494
2495 public Joins setVariable(String var) {
2496 this.var = var;
2497 return this;
2498 }
2499
2500 public Joins setSubselect(String alias) {
2501 if (!alias.endsWith(":"))
2502 alias += ':';
2503 append(alias);
2504 return this;
2505 }
2506
2507 public boolean isEmpty() {
2508 return true;
2509 }
2510
2511 public Joins crossJoin(Table localTable, Table foreignTable) {
2512 append(var);
2513 var = null;
2514 return this;
2515 }
2516
2517 public Joins join(ForeignKey fk, boolean inverse, boolean toMany) {
2518 append(var);
2519 var = null;
2520 return this;
2521 }
2522
2523 public Joins outerJoin(ForeignKey fk, boolean inverse, boolean toMany) {
2524 append(var);
2525 var = null;
2526 return this;
2527 }
2528
2529 public Joins joinRelation(String name, ForeignKey fk,
2530 ClassMapping target, int subs, boolean inverse, boolean toMany) {
2531 append(name);
2532 append(var);
2533 var = null;
2534 return this;
2535 }
2536
2537 public Joins outerJoinRelation(String name, ForeignKey fk,
2538 ClassMapping target, int subs, boolean inverse, boolean toMany) {
2539 append(name);
2540 append(var);
2541 var = null;
2542 return this;
2543 }
2544
2545 protected void append(String str) {
2546 if (str != null) {
2547 if (path == null)
2548 path = new StringBuffer(str);
2549 else
2550 path.append('.').append(str);
2551 }
2552 }
2553
2554 public String toString() {
2555 return "PathJoinsImpl<" + hashCode() + ">: "
2556 + String.valueOf(path);
2557 }
2558 }
2559
2560 /**
2561 * Joins implementation.
2562 */
2563 private static class SelectJoins
2564 extends PathJoinsImpl
2565 implements Cloneable {
2566
2567 private final SelectImpl _sel;
2568 private JoinSet _joins = null;
2569 private boolean _outer = false;
2570 private int _count = 0;
2571
2572 public SelectJoins(SelectImpl sel) {
2573 _sel = sel;
2574 }
2575
2576 public boolean isOuter() {
2577 return _outer;
2578 }
2579
2580 public PathJoins setOuter(boolean outer) {
2581 _outer = outer;
2582 return this;
2583 }
2584
2585 public boolean isDirty() {
2586 return super.isDirty() || !isEmpty();
2587 }
2588
2589 public JoinSet joins() {
2590 return _joins;
2591 }
2592
2593 public void setJoins(JoinSet joins) {
2594 _joins = joins;
2595 _outer = joins != null && joins.last() != null
2596 && joins.last().getType() == Join.TYPE_OUTER;
2597 }
2598
2599 public int joinCount() {
2600 if (_joins == null)
2601 return _count;
2602 return Math.max(_count, _joins.size());
2603 }
2604
2605 public void nullJoins() {
2606 if (_joins != null)
2607 _count = Math.max(_count, _joins.size());
2608 _joins = null;
2609 }
2610
2611 public boolean isEmpty() {
2612 return _joins == null || _joins.isEmpty();
2613 }
2614
2615 public Joins crossJoin(Table localTable, Table foreignTable) {
2616 // cross joins are for unbound variables; unfortunately we have
2617 // to always go DISTINCT for unbound vars because there are certain
2618 // cases that require it, and we can't differentiate them from the
2619 // cases that don't
2620 _sel._flags |= IMPLICIT_DISTINCT;
2621
2622 if (_sel.getJoinSyntax() != JoinSyntaxes.SYNTAX_SQL92
2623 || _sel._from != null) {
2624 // don't make any joins, but update the path if a variable
2625 // has been set
2626 this.append(this.var);
2627 this.var = null;
2628 _outer = false;
2629 return this;
2630 }
2631
2632 // don't let the get alias methods see that a var has been set
2633 // until we get past the local table
2634 String var = this.var;
2635 this.var = null;
2636
2637 int alias1 = _sel.getTableIndex(localTable, this, true);
2638 this.append(var);
2639 int alias2 = _sel.getTableIndex(foreignTable, this, true);
2640 Join j = new Join(localTable, alias1, foreignTable, alias2,
2641 null, false);
2642 j.setType(Join.TYPE_CROSS);
2643
2644 if (_joins == null)
2645 _joins = new JoinSet();
2646 _joins.add(j);
2647 _outer = false;
2648 return this;
2649 }
2650
2651 public Joins join(ForeignKey fk, boolean inverse, boolean toMany) {
2652 return join(null, fk, null, -1, inverse, toMany, false);
2653 }
2654
2655 public Joins outerJoin(ForeignKey fk, boolean inverse, boolean toMany) {
2656 return join(null, fk, null, -1, inverse, toMany, true);
2657 }
2658
2659 public Joins joinRelation(String name, ForeignKey fk,
2660 ClassMapping target, int subs, boolean inverse, boolean toMany) {
2661 return join(name, fk, target, subs, inverse, toMany, false);
2662 }
2663
2664 public Joins outerJoinRelation(String name, ForeignKey fk,
2665 ClassMapping target, int subs, boolean inverse, boolean toMany) {
2666 return join(name, fk, target, subs, inverse, toMany, true);
2667 }
2668
2669 private Joins join(String name, ForeignKey fk, ClassMapping target,
2670 int subs, boolean inverse, boolean toMany, boolean outer) {
2671 // don't let the get alias methods see that a var has been set
2672 // until we get past the local table
2673 String var = this.var;
2674 this.var = null;
2675
2676 // get first table alias before updating path; if there is a from
2677 // select then we shouldn't actually create a join object, since
2678 // the joins will all be done in the from select
2679 boolean createJoin = _sel._from == null;
2680 Table table1 = null;
2681 int alias1 = -1;
2682 if (createJoin) {
2683 table1 = (inverse) ? fk.getPrimaryKeyTable() : fk.getTable();
2684 alias1 = _sel.getTableIndex(table1, this, true);
2685 }
2686
2687 // update the path with the relation name before getting pk alias
2688 this.append(name);
2689 this.append(var);
2690 if (toMany) {
2691 _sel._flags |= IMPLICIT_DISTINCT;
2692 _sel._flags |= TO_MANY;
2693 }
2694 _outer = outer;
2695
2696 if (createJoin) {
2697 Table table2 = (inverse) ? fk.getTable()
2698 : fk.getPrimaryKeyTable();
2699 int alias2 = _sel.getTableIndex(table2, this, true);
2700 Join j = new Join(table1, alias1, table2, alias2, fk, inverse);
2701 j.setType((outer) ? Join.TYPE_OUTER : Join.TYPE_INNER);
2702
2703 if (_joins == null)
2704 _joins = new JoinSet();
2705 if (_joins.add(j) && (subs == Select.SUBS_JOINABLE
2706 || subs == Select.SUBS_NONE))
2707 j.setRelation(target, subs, clone(_sel));
2708 }
2709 return this;
2710 }
2711
2712 public SelectJoins clone(SelectImpl sel) {
2713 SelectJoins sj = new SelectJoins(sel);
2714 sj.var = var;
2715 if (path != null)
2716 sj.path = new StringBuffer(path.toString());
2717 if (_joins != null && !_joins.isEmpty())
2718 sj._joins = new JoinSet(_joins);
2719 sj._outer = _outer;
2720 return sj;
2721 }
2722
2723 public String toString() {
2724 return super.toString() + " (" + _outer + "): " + _joins;
2725 }
2726 }
2727
2728 protected Selects newSelects() {
2729 return new Selects();
2730 }
2731
2732 /**
2733 * Helper class to track selected columns, with fast contains method.
2734 * Acts as a list of select ids, with additional methods to manipulate
2735 * the alias of each selected id.
2736 */
2737 protected static class Selects
2738 extends AbstractList {
2739
2740 protected List _ids = null;
2741 protected List _idents = null;
2742 protected Map _aliases = null;
2743 protected Map _selectAs = null;
2744 protected DBDictionary _dict = null;
2745
2746 /**
2747 * Add all aliases from another instance.
2748 */
2749 public void addAll(Selects sels) {
2750 if (_ids == null && sels._ids != null)
2751 _ids = new ArrayList(sels._ids);
2752 else if (sels._ids != null)
2753 _ids.addAll(sels._ids);
2754
2755 if (_idents == null && sels._idents != null)
2756 _idents = new ArrayList(sels._idents);
2757 else if (sels._idents != null)
2758 _idents.addAll(sels._idents);
2759
2760 if (_aliases == null && sels._aliases != null)
2761 _aliases = new HashMap(sels._aliases);
2762 else if (sels._aliases != null)
2763 _aliases.putAll(sels._aliases);
2764
2765 if (_selectAs == null && sels._selectAs != null)
2766 _selectAs = new HashMap(sels._selectAs);
2767 else if (sels._selectAs != null)
2768 _selectAs.putAll(sels._selectAs);
2769 }
2770
2771 /**
2772 * Returns the alias of a given id.
2773 */
2774 public Object getAlias(Object id) {
2775 return (_aliases == null) ? null : _aliases.get(id);
2776 }
2777
2778 /**
2779 * Set an alias for a given id.
2780 */
2781 public int setAlias(Object id, Object alias, boolean ident) {
2782 if (_ids == null) {
2783 _ids = new ArrayList();
2784 _aliases = new HashMap();
2785 }
2786
2787 int idx;
2788 if (_aliases.put(id, alias) != null)
2789 idx = _ids.indexOf(id);
2790 else {
2791 _ids.add(id);
2792 idx = _ids.size() - 1;
2793
2794 if (ident) {
2795 if (_idents == null)
2796 _idents = new ArrayList(3);
2797 _idents.add(id);
2798 }
2799 }
2800 return idx;
2801 }
2802
2803 /**
2804 * Set an alias for a given index.
2805 */
2806 public void setAlias(int idx, Object alias) {
2807 Object id = _ids.get(idx);
2808 _aliases.put(id, alias);
2809 }
2810
2811 /**
2812 * Insert an alias before the given index, using negative indexes
2813 * to count backwards.
2814 */
2815 public void insertAlias(int idx, Object id, Object alias) {
2816 _aliases.put(id, alias);
2817 if (idx >= 0)
2818 _ids.add(idx, id);
2819 else
2820 _ids.add(_ids.size() + idx, id);
2821 }
2822
2823 /**
2824 * Return the index of the given alias.
2825 */
2826 public int indexOfAlias(Object alias) {
2827 if (_aliases == null)
2828 return -1;
2829 for (int i = 0; i < _ids.size(); i++)
2830 if (alias.equals(_aliases.get(_ids.get(i))))
2831 return i;
2832 return -1;
2833 }
2834
2835 /**
2836 * A list representation of the aliases, in select order, with
2837 * AS aliases present.
2838 */
2839 public List getAliases(final boolean ident, final boolean inner) {
2840 if (_ids == null)
2841 return Collections.EMPTY_LIST;
2842
2843 return new AbstractList() {
2844 public int size() {
2845 return (ident && _idents != null) ? _idents.size()
2846 : _ids.size();
2847 }
2848
2849 public Object get(int i) {
2850 Object id = (ident && _idents != null) ? _idents.get(i)
2851 : _ids.get(i);
2852 Object alias = _aliases.get(id);
2853 if (id instanceof Column && ((Column) id).isXML())
2854 alias = alias + _dict.getStringVal;
2855
2856 String as = null;
2857 if (inner)
2858 as = ((String) alias).replace('.', '_');
2859 else if (_selectAs != null)
2860 as = (String) _selectAs.get(id);
2861
2862 if (as != null) {
2863 if (ident && _idents != null)
2864 return as;
2865 if (alias instanceof SQLBuffer)
2866 alias = new SQLBuffer((SQLBuffer) alias).
2867 append(" AS ").append(as);
2868 else
2869 alias = alias + " AS " + as;
2870 }
2871 return alias;
2872 }
2873 };
2874 }
2875
2876 /**
2877 * Set that a given id's alias has an AS value.
2878 */
2879 public void setSelectAs(Object id, String as) {
2880 if (_selectAs == null)
2881 _selectAs = new HashMap((int) (5 * 1.33 + 1));
2882 _selectAs.put(id, as);
2883 }
2884
2885 /**
2886 * Clear all placeholders and select AS clauses.
2887 */
2888 public void clearPlaceholders() {
2889 if (_ids == null)
2890 return;
2891
2892 Object id;
2893 for (Iterator itr = _ids.iterator(); itr.hasNext();) {
2894 id = itr.next();
2895 if (id instanceof Placeholder) {
2896 itr.remove();
2897 _aliases.remove(id);
2898 }
2899 }
2900 }
2901
2902 public boolean contains(Object id) {
2903 return _aliases != null && _aliases.containsKey(id);
2904 }
2905
2906 public Object get(int i) {
2907 if (_ids == null)
2908 throw new ArrayIndexOutOfBoundsException();
2909 return _ids.get(i);
2910 }
2911
2912 public int size() {
2913 return (_ids == null) ? 0 : _ids.size();
2914 }
2915
2916 public void clear() {
2917 _ids = null;
2918 _aliases = null;
2919 _selectAs = null;
2920 _idents = null;
2921 }
2922 }
2923 }
2924
2925 /**
2926 * Common joins interface used internally. Cannot be made an inner class
2927 * because the outer class (Select) has to implement it.
2928 */
2929 interface PathJoins
2930 extends Joins {
2931
2932 /**
2933 * Mark this as an outer joins set.
2934 */
2935 public PathJoins setOuter(boolean outer);
2936
2937 /**
2938 * Return true if this instance has a path, any joins, or a variable.
2939 */
2940 public boolean isDirty();
2941
2942 /**
2943 * Return the relation path traversed by these joins, or null if none.
2944 */
2945 public StringBuffer path();
2946
2947 /**
2948 * Return the set of {@link Join} elements, or null if none.
2949 */
2950 public JoinSet joins();
2951
2952 /**
2953 * Return the maximum number of joins contained in this instance at any
2954 * time.
2955 */
2956 public int joinCount();
2957
2958 /**
2959 * Null the set of {@link Join} elements.
2960 */
2961 public void nullJoins();
2962 }
2963