| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.openjpa.jdbc.sql; |
| |
| import java.sql.Connection; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.sql.Types; |
| import java.util.AbstractList; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.SortedMap; |
| import java.util.Stack; |
| import java.util.TreeMap; |
| |
| import org.apache.openjpa.jdbc.conf.JDBCConfiguration; |
| import org.apache.openjpa.jdbc.kernel.EagerFetchModes; |
| import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration; |
| import org.apache.openjpa.jdbc.kernel.JDBCLockManager; |
| import org.apache.openjpa.jdbc.kernel.JDBCStore; |
| import org.apache.openjpa.jdbc.kernel.JDBCStoreManager; |
| import org.apache.openjpa.jdbc.meta.ClassMapping; |
| import org.apache.openjpa.jdbc.meta.FieldMapping; |
| import org.apache.openjpa.jdbc.meta.Joinable; |
| import org.apache.openjpa.jdbc.meta.ValueMapping; |
| import org.apache.openjpa.jdbc.meta.strats.RelationStrategies; |
| import org.apache.openjpa.jdbc.schema.Column; |
| import org.apache.openjpa.jdbc.schema.ForeignKey; |
| import org.apache.openjpa.jdbc.schema.Table; |
| import org.apache.openjpa.kernel.StoreContext; |
| import org.apache.openjpa.kernel.exps.Context; |
| import org.apache.openjpa.kernel.exps.Value; |
| import org.apache.openjpa.lib.log.Log; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.lib.util.StringUtil; |
| import org.apache.openjpa.meta.ClassMetaData; |
| import org.apache.openjpa.util.ApplicationIds; |
| import org.apache.openjpa.util.Id; |
| import org.apache.openjpa.util.InternalException; |
| |
| import static java.util.Collections.emptyIterator; |
| |
| /** |
| * Standard {@link Select} implementation. Usage note: though this class |
| * implements {@link Joins}, it should not be used for joining directly. |
| * Instead, use the return value of {@link #newJoins}. |
| * |
| * @author Abe White |
| */ |
| public class SelectImpl |
| implements Select, PathJoins { |
| |
| private static final int NONAUTO_DISTINCT = 2 << 0; |
| private static final int DISTINCT = 2 << 1; |
| private static final int NOT_DISTINCT = 2 << 2; |
| private static final int IMPLICIT_DISTINCT = 2 << 3; |
| private static final int TO_MANY = 2 << 4; |
| private static final int AGGREGATE = 2 << 5; |
| private static final int LOB = 2 << 6; |
| private static final int OUTER = 2 << 7; |
| private static final int LRS = 2 << 8; |
| private static final int EAGER_TO_ONE = 2 << 9; |
| private static final int EAGER_TO_MANY = 2 << 10; |
| private static final int RECORD_ORDERED = 2 << 11; |
| private static final int GROUPING = 2 << 12; |
| private static final int FORCE_COUNT = 2 << 13; |
| |
| private static final String[] TABLE_ALIASES = new String[16]; |
| private static final String[] ORDER_ALIASES = new String[16]; |
| private static final Object[] NULL_IDS = new Object[16]; |
| private static final Object[] PLACEHOLDERS = new Object[50]; |
| |
| private static final Localizer _loc = Localizer.forPackage(Select.class); |
| |
| static { |
| for (int i = 0; i < TABLE_ALIASES.length; i++) |
| TABLE_ALIASES[i] = "t" + i; |
| for (int i = 0; i < ORDER_ALIASES.length; i++) |
| ORDER_ALIASES[i] = "o" + i; |
| for (int i = 0; i < NULL_IDS.length; i++) |
| NULL_IDS[i] = new NullId(); |
| for (int i = 0; i < PLACEHOLDERS.length; i++) |
| PLACEHOLDERS[i] = new Placeholder(); |
| } |
| |
| private final JDBCConfiguration _conf; |
| private final DBDictionary _dict; |
| |
| // map of variable + relation path + table keys to the correct alias index: |
| // each relation path/table combination should have a unique alias because |
| // it represents a separate object; for example, if a Person class has a |
| // 'parent' field representing another Person and also has an 'address' |
| // field of type Address: |
| // 'address.street' should map to a different table alias than |
| // 'parent.address.street' for the purposes of comparisons |
| private Map _aliases = null; |
| |
| // map of indexes to table aliases like 'TABLENAME t0' |
| private SortedMap _tables = null; |
| |
| // combined list of selected ids and map of each id to its alias |
| protected final Selects _selects = newSelects(); |
| private List _ordered = null; |
| private List _grouped = null; |
| |
| // flags |
| private int _flags = 0; |
| private int _joinSyntax = 0; |
| private long _startIdx = 0; |
| private long _endIdx = Long.MAX_VALUE; |
| private int _nullIds = 0; |
| private int _orders = 0; |
| private int _placeholders = 0; |
| private int _expectedResultCount = 0; |
| |
| // query clauses |
| private SQLBuffer _ordering = null; |
| private SQLBuffer _where = null; |
| private SQLBuffer _grouping = null; |
| private SQLBuffer _having = null; |
| private SQLBuffer _full = null; |
| |
| // joins to add to the end of our where clause, and joins to prepend to |
| // all selects (see select(classmapping) method) |
| private SelectJoins _joins = null; |
| private Stack _preJoins = null; |
| |
| // map of joins+keys to eager selects and global set of eager keys; the |
| // same key can't be used more than once |
| private Map _eager = null; |
| private Set _eagerKeys = null; |
| |
| // subselect support |
| private List<SelectImpl> _subsels = null; |
| private SelectImpl _parent = null; |
| private String _subPath = null; |
| private boolean _hasSub = false; |
| |
| // from select if this select selects from a tmp table created by another |
| private SelectImpl _from = null; |
| protected SelectImpl _outer = null; |
| |
| // JPQL Query context this select is associated with |
| private Context _ctx = null; |
| |
| // A path navigation is begin with this schema alias |
| private String _schemaAlias = null; |
| private ClassMapping _tpcMeta = null; |
| private List _joinedTables = null; |
| private List _exJoinedTables = null; |
| |
| @Override |
| public ClassMapping getTablePerClassMeta() { |
| return _tpcMeta; |
| } |
| @Override |
| public void setTablePerClassMeta(ClassMapping meta) { |
| _tpcMeta = meta; |
| } |
| |
| @Override |
| public void setJoinedTableClassMeta(List meta) { |
| _joinedTables = meta; |
| } |
| |
| @Override |
| public List getJoinedTableClassMeta() { |
| return _joinedTables; |
| } |
| |
| @Override |
| public void setExcludedJoinedTableClassMeta(List meta) { |
| _exJoinedTables = meta; |
| } |
| |
| @Override |
| public List getExcludedJoinedTableClassMeta() { |
| return _exJoinedTables; |
| } |
| |
| |
| /** |
| * Helper method to return the proper table alias for the given alias index. |
| */ |
| static String toAlias(int index) { |
| if (index == -1) |
| return null; |
| if (index < TABLE_ALIASES.length) |
| return TABLE_ALIASES[index]; |
| return "t" + index; |
| } |
| |
| /** |
| * Helper method to return the proper order alias for the given order |
| * column index. |
| */ |
| public static String toOrderAlias(int index) { |
| if (index == -1) |
| return null; |
| if (index < ORDER_ALIASES.length) |
| return ORDER_ALIASES[index]; |
| return "o" + index; |
| } |
| |
| /** |
| * Constructor. Supply configuration. |
| */ |
| public SelectImpl(JDBCConfiguration conf) { |
| _conf = conf; |
| _dict = _conf.getDBDictionaryInstance(); |
| _joinSyntax = _dict.joinSyntax; |
| _selects._dict = _dict; |
| } |
| |
| @Override |
| public void setContext(Context context) { |
| if (_ctx == null) { |
| _ctx = context; |
| _ctx.setSelect(this); |
| } |
| } |
| |
| @Override |
| public Context ctx() { |
| return _ctx; |
| } |
| |
| @Override |
| public void setSchemaAlias(String schemaAlias) { |
| _schemaAlias = schemaAlias; |
| } |
| |
| ///////////////////////////////// |
| // SelectExecutor implementation |
| ///////////////////////////////// |
| |
| @Override |
| public JDBCConfiguration getConfiguration() { |
| return _conf; |
| } |
| |
| @Override |
| public SQLBuffer toSelect(boolean forUpdate, JDBCFetchConfiguration fetch) { |
| _full = _dict.toSelect(this, forUpdate, fetch); |
| return _full; |
| } |
| |
| @Override |
| public SQLBuffer getSQL() { |
| return _full; |
| } |
| |
| @Override |
| public SQLBuffer toSelectCount() { |
| return _dict.toSelectCount(this); |
| } |
| |
| @Override |
| public boolean getAutoDistinct() { |
| return (_flags & NONAUTO_DISTINCT) == 0; |
| } |
| |
| @Override |
| public void setAutoDistinct(boolean val) { |
| if (val) |
| _flags &= ~NONAUTO_DISTINCT; |
| else |
| _flags |= NONAUTO_DISTINCT; |
| } |
| |
| @Override |
| public boolean isDistinct() { |
| return (_flags & NOT_DISTINCT) == 0 && ((_flags & DISTINCT) != 0 |
| || ((_flags & NONAUTO_DISTINCT) == 0 |
| && (_flags & IMPLICIT_DISTINCT) != 0)); |
| } |
| |
| @Override |
| public void setDistinct(boolean distinct) { |
| // need two flags in case set not_distinct, then a to-many join happens |
| // and distinct flag gets set automatically |
| if (distinct) { |
| _flags |= DISTINCT; |
| _flags &= ~NOT_DISTINCT; |
| } else { |
| _flags |= NOT_DISTINCT; |
| _flags &= ~DISTINCT; |
| } |
| } |
| |
| @Override |
| public boolean isLRS() { |
| return (_flags & LRS) != 0; |
| } |
| |
| @Override |
| public void setLRS(boolean lrs) { |
| if (lrs) |
| _flags |= LRS; |
| else |
| _flags &= ~LRS; |
| } |
| |
| @Override |
| public int getExpectedResultCount() { |
| // if the count isn't forced and we have to-many eager joins that could |
| // throw the count off, don't pay attention to it |
| if ((_flags & FORCE_COUNT) == 0 && hasEagerJoin(true)) |
| return 0; |
| return _expectedResultCount; |
| } |
| |
| @Override |
| public void setExpectedResultCount(int expectedResultCount, boolean force) { |
| _expectedResultCount = expectedResultCount; |
| if (force) |
| _flags |= FORCE_COUNT; |
| else |
| _flags &= ~FORCE_COUNT; |
| } |
| |
| @Override |
| public int getJoinSyntax() { |
| return _joinSyntax; |
| } |
| |
| @Override |
| public void setJoinSyntax(int joinSyntax) { |
| _joinSyntax = joinSyntax; |
| } |
| |
| @Override |
| public boolean supportsRandomAccess(boolean forUpdate) { |
| return _dict.supportsRandomAccessResultSet(this, forUpdate); |
| } |
| |
| @Override |
| public boolean supportsLocking() { |
| return _dict.supportsLocking(this); |
| } |
| |
| @Override |
| public boolean hasMultipleSelects() { |
| if (_eager == null) |
| return false; |
| Map.Entry entry; |
| for (Iterator itr = _eager.entrySet().iterator(); itr.hasNext();) { |
| entry = (Map.Entry) itr.next(); |
| if (entry.getValue() != this) |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public int getCount(JDBCStore store) |
| throws SQLException { |
| Connection conn = null; |
| PreparedStatement stmnt = null; |
| ResultSet rs = null; |
| try { |
| SQLBuffer sql = toSelectCount(); |
| conn = store.getNewConnection(); |
| stmnt = prepareStatement(conn, sql, null, |
| ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY, false); |
| _dict.setQueryTimeout(stmnt, |
| store.getFetchConfiguration().getQueryTimeout()); |
| rs = executeQuery(conn, stmnt, sql, false, store); |
| int count = getCount(rs); |
| |
| return _dict.applyRange(this, count); |
| } finally { |
| if (rs != null) |
| try { rs.close(); } catch (SQLException se) {} |
| if (stmnt != null) |
| try { stmnt.close(); } catch (SQLException se) {} |
| if (conn != null) |
| try { conn.close(); } catch (SQLException se) {} |
| } |
| } |
| |
| @Override |
| public Result execute(JDBCStore store, JDBCFetchConfiguration fetch) |
| throws SQLException { |
| if (fetch == null) |
| fetch = store.getFetchConfiguration(); |
| return execute(store.getContext(), store, fetch, |
| fetch.getReadLockLevel()); |
| } |
| |
| @Override |
| public Result execute(JDBCStore store, JDBCFetchConfiguration fetch, |
| int lockLevel) |
| throws SQLException { |
| if (fetch == null) |
| fetch = store.getFetchConfiguration(); |
| return execute(store.getContext(), store, fetch, lockLevel); |
| } |
| |
| /** |
| * Execute this select in the context of the given store manager. The |
| * context is passed in separately for profiling purposes. |
| */ |
| protected Result execute(StoreContext ctx, JDBCStore store, |
| JDBCFetchConfiguration fetch, int lockLevel) |
| throws SQLException { |
| boolean forUpdate = false; |
| if (!isAggregate() && _grouping == null) { |
| JDBCLockManager lm = store.getLockManager(); |
| if (lm != null) |
| forUpdate = lm.selectForUpdate(this, lockLevel); |
| } |
| |
| logEagerRelations(); |
| SQLBuffer sql = toSelect(forUpdate, fetch); |
| boolean isLRS = isLRS(); |
| int rsType = (isLRS && supportsRandomAccess(forUpdate)) |
| ? -1 : ResultSet.TYPE_FORWARD_ONLY; |
| Connection conn = store.getConnection(); |
| PreparedStatement stmnt = null; |
| ResultSet rs = null; |
| try { |
| if (isLRS) |
| stmnt = prepareStatement(conn, sql, fetch, rsType, -1, true); |
| else |
| stmnt = prepareStatement(conn, sql, null, rsType, -1, false); |
| |
| _dict.setTimeouts(stmnt, fetch, forUpdate); |
| |
| rs = executeQuery(conn, stmnt, sql, isLRS, store); |
| } catch (SQLException se) { |
| // clean up statement |
| if (stmnt != null) |
| try { stmnt.close(); } catch (SQLException se2) {} |
| try { conn.close(); } catch (SQLException se2) {} |
| throw se; |
| } |
| return getEagerResult(conn, stmnt, rs, store, fetch, forUpdate, sql); |
| } |
| |
| /** |
| * Execute our eager selects, adding the results under the same keys |
| * to the given result. |
| */ |
| private static void addEagerResults(SelectResult res, SelectImpl sel, |
| JDBCStore store, JDBCFetchConfiguration fetch) |
| throws SQLException { |
| if (sel._eager == null) |
| return; |
| |
| // execute eager selects |
| Map.Entry entry; |
| Result eres; |
| Map eager; |
| for (Iterator itr = sel._eager.entrySet().iterator(); itr.hasNext();) { |
| entry = (Map.Entry) itr.next(); |
| |
| // simulated batched selects for inner/outer joins; for separate |
| // selects, don't pass on lock level, because they're probably |
| // for relations and therefore should use default level |
| if (entry.getValue() == sel) |
| eres = res; |
| else |
| eres = ((SelectExecutor) entry.getValue()).execute(store, |
| fetch); |
| |
| eager = res.getEagerMap(false); |
| if (eager == null) { |
| eager = new HashMap(); |
| res.setEagerMap(eager); |
| } |
| eager.put(entry.getKey(), eres); |
| } |
| } |
| |
| |
| /** |
| * This method is to provide override for non-JDBC or JDBC-like |
| * implementation of preparing statement. |
| */ |
| protected PreparedStatement prepareStatement(Connection conn, |
| SQLBuffer sql, JDBCFetchConfiguration fetch, int rsType, |
| int rsConcur, boolean isLRS) throws SQLException { |
| if (fetch == null) |
| return sql.prepareStatement(conn, rsType, rsConcur); |
| else |
| return sql.prepareStatement(conn, fetch, rsType, -1); |
| } |
| |
| /** |
| * This method is to provide override for non-JDBC or JDBC-like |
| * implementation of preparing statement. |
| */ |
| public PreparedStatement prepareStatement(Connection conn, |
| String sql) throws SQLException { |
| return conn.prepareStatement(sql); |
| } |
| |
| /** |
| * This method is to provide override for non-JDBC or JDBC-like |
| * implementation of executing query. |
| */ |
| protected ResultSet executeQuery(Connection conn, PreparedStatement stmnt, |
| SQLBuffer sql, boolean isLRS, JDBCStore store) throws SQLException { |
| return stmnt.executeQuery(); |
| } |
| |
| /** |
| * This method is to provide override for non-JDBC or JDBC-like |
| * implementation of executing query. |
| */ |
| public ResultSet executeQuery(Connection conn, PreparedStatement stmnt, |
| String sql, JDBCStore store, Object[] params, Column[] cols) |
| throws SQLException { |
| return stmnt.executeQuery(); |
| } |
| |
| /** |
| * This method is to provide override for non-JDBC or JDBC-like |
| * implementation of getting count from the result set. |
| */ |
| protected int getCount(ResultSet rs) throws SQLException { |
| rs.next(); |
| return rs.getInt(1); |
| } |
| |
| /** |
| * This method is to provide override for non-JDBC or JDBC-like |
| * implementation of executing eager selects. |
| */ |
| public Result getEagerResult(Connection conn, |
| PreparedStatement stmnt, ResultSet rs, JDBCStore store, |
| JDBCFetchConfiguration fetch, boolean forUpdate, SQLBuffer sql) |
| throws SQLException { |
| SelectResult res = new SelectResult(conn, stmnt, rs, _dict); |
| res.setSelect(this); |
| res.setStore(store); |
| res.setLocking(forUpdate); |
| try { |
| addEagerResults(res, this, store, fetch); |
| } catch (SQLException se) { |
| res.close(); |
| throw se; |
| } |
| return res; |
| } |
| |
| ///////////////////////// |
| // Select implementation |
| ///////////////////////// |
| |
| @Override |
| public int indexOf() { |
| return 0; |
| } |
| |
| @Override |
| public List getSubselects() { |
| return (_subsels == null) ? Collections.EMPTY_LIST : _subsels; |
| } |
| |
| @Override |
| public Select getParent() { |
| return _parent; |
| } |
| |
| @Override |
| public String getSubselectPath() { |
| return _subPath; |
| } |
| |
| @Override |
| public void setParent(Select parent, String path) { |
| if (path != null) |
| _subPath = path; |
| else |
| _subPath = null; |
| |
| if (parent == _parent) |
| return; |
| if (_parent != null) |
| _parent._subsels.remove(this); |
| |
| //### right now we can't use sql92 joins with subselects, cause |
| //### I can't figure out what to do when the subselect has a join |
| //### with an alias also present in the outer select... you don't want |
| //### the join to appear in the FROM clause of the subselect cause |
| //### then it re-aliases both tables in the scope of the subselect |
| //### and the correlation with the outer select is lost |
| _parent = (SelectImpl) parent; |
| if (_parent != null) { |
| if (_parent._subsels == null) |
| _parent._subsels = new ArrayList(2); |
| _parent._subsels.add(this); |
| if (_parent._joinSyntax == JoinSyntaxes.SYNTAX_SQL92) |
| _joinSyntax = JoinSyntaxes.SYNTAX_TRADITIONAL; |
| else |
| _joinSyntax = _parent._joinSyntax; |
| } |
| } |
| |
| @Override |
| public void setHasSubselect(boolean hasSub) { |
| _hasSub = hasSub; |
| } |
| |
| @Override |
| public boolean getHasSubselect() { |
| return _hasSub; |
| } |
| |
| public Map getAliases() { |
| return _aliases; |
| } |
| |
| public void removeAlias(Object key) { |
| _aliases.remove(key); |
| } |
| |
| public Map getTables() { |
| return _tables; |
| } |
| |
| public void removeTable(Object key) { |
| _tables.remove(key); |
| } |
| |
| @Override |
| public Select getFromSelect() { |
| return _from; |
| } |
| |
| @Override |
| public void setFromSelect(Select sel) { |
| _from = (SelectImpl) sel; |
| if (_from != null) |
| _from._outer = this; |
| } |
| |
| @Override |
| public boolean hasEagerJoin(boolean toMany) { |
| if (toMany) |
| return (_flags & EAGER_TO_MANY) != 0; |
| return (_flags & EAGER_TO_ONE) != 0; |
| } |
| |
| @Override |
| public boolean hasJoin(boolean toMany) { |
| if (toMany) |
| return (_flags & TO_MANY) != 0; |
| return _tables != null && _tables.size() > 1; |
| } |
| |
| @Override |
| public boolean isSelected(Table table) { |
| PathJoins pj = getJoins(null, false); |
| if (_from != null) |
| return _from.getTableIndex(table, pj, false) != -1; |
| return getTableIndex(table, pj, false) != -1; |
| } |
| |
| @Override |
| public Collection getTableAliases() { |
| return (_tables == null) ? Collections.EMPTY_SET : _tables.values(); |
| } |
| |
| @Override |
| public List getSelects() { |
| return Collections.unmodifiableList(_selects); |
| } |
| |
| @Override |
| public List getSelectAliases() { |
| return _selects.getAliases(false, _outer != null); |
| } |
| |
| @Override |
| public List getIdentifierAliases() { |
| return _selects.getAliases(true, _outer != null); |
| } |
| |
| @Override |
| public SQLBuffer getOrdering() { |
| return _ordering; |
| } |
| |
| @Override |
| public SQLBuffer getGrouping() { |
| return _grouping; |
| } |
| |
| @Override |
| public SQLBuffer getWhere() { |
| return _where; |
| } |
| |
| @Override |
| public SQLBuffer getHaving() { |
| return _having; |
| } |
| |
| @Override |
| public void addJoinClassConditions() { |
| if (_joins == null || _joins.joins() == null) |
| return; |
| |
| // join set iterator allows concurrent modification |
| Join j; |
| for (Iterator itr = _joins.joins().iterator(); itr.hasNext();) { |
| j = (Join) itr.next(); |
| if (j.getRelationTarget() != null) { |
| j.getRelationTarget().getDiscriminator().addClassConditions |
| (this, j.getSubclasses() == SUBS_JOINABLE, |
| j.getRelationJoins()); |
| j.setRelation(null, 0, null); |
| } |
| } |
| } |
| |
| @Override |
| public Joins getJoins() { |
| return _joins; |
| } |
| |
| @Override |
| public Iterator getJoinIterator() { |
| if (_joins == null || _joins.isEmpty()) |
| return emptyIterator(); |
| return _joins.joins().joinIterator(); |
| } |
| |
| @Override |
| public long getStartIndex() { |
| return _startIdx; |
| } |
| |
| @Override |
| public long getEndIndex() { |
| return _endIdx; |
| } |
| |
| @Override |
| public void setRange(long start, long end) { |
| _startIdx = start; |
| _endIdx = end; |
| } |
| |
| @Override |
| public String getColumnAlias(Column col) { |
| return getColumnAlias(col, (Joins) null); |
| } |
| |
| @Override |
| public String getColumnAlias(Column col, Joins joins) { |
| return getColumnAlias(col, getJoins(joins, false)); |
| } |
| |
| /** |
| * Return the alias for the given column. |
| */ |
| private String getColumnAlias(Column col, PathJoins pj) { |
| return getColumnAlias(col.getIdentifier().getName(), col.getTable(), pj); |
| } |
| |
| @Override |
| public String getColumnAlias(String col, Table table) { |
| return getColumnAlias(col, table, (Joins) null); |
| } |
| |
| @Override |
| public String getColumnAlias(String col, Table table, Joins joins) { |
| return getColumnAlias(col, table, getJoins(joins, false)); |
| } |
| |
| /** |
| * Return the alias for the give column |
| */ |
| @Override |
| public String getColumnAlias(Column col, Object path) { |
| Table table = col.getTable(); |
| String tableAlias = null; |
| Iterator itr = getJoinIterator(); |
| while (itr.hasNext()) { |
| Join join = (Join) itr.next(); |
| if (join != null) { |
| if (join.getTable1() == table) |
| tableAlias = join.getAlias1(); |
| else if (join.getTable2() == table) |
| tableAlias = join.getAlias2(); |
| if (tableAlias != null) |
| return new StringBuilder(tableAlias).append("."). |
| append(_dict.getNamingUtil().toDBName(col.getIdentifier())).toString(); |
| } |
| } |
| throw new InternalException("Can not resolve alias for field: " + |
| path.toString() + " mapped to column: " + col.getIdentifier().getName() + |
| " table: "+table.getIdentifier().getName()); |
| } |
| |
| /** |
| * Return the alias for the given column. |
| */ |
| private String getColumnAlias(String col, Table table, PathJoins pj) { |
| return getTableAlias(table, pj).append(_dict.getNamingUtil().toDBName(col)).toString(); |
| } |
| |
| private StringBuilder getTableAlias(Table table, PathJoins pj) { |
| StringBuilder buf = new StringBuilder(); |
| if (_from != null) { |
| String alias = toAlias(_from.getTableIndex(table, pj, true)); |
| if (_dict.requiresAliasForSubselect) |
| return buf.append(FROM_SELECT_ALIAS).append(".").append(alias). |
| append("_"); |
| return buf.append(alias).append("_"); |
| } |
| return buf.append(toAlias(getTableIndex(table, pj, true))).append("."); |
| } |
| |
| @Override |
| public boolean isAggregate() { |
| return (_flags & AGGREGATE) != 0; |
| } |
| |
| @Override |
| public void setAggregate(boolean agg) { |
| if (agg) |
| _flags |= AGGREGATE; |
| else |
| _flags &= ~AGGREGATE; |
| } |
| |
| @Override |
| public boolean isLob() { |
| return (_flags & LOB) != 0; |
| } |
| |
| @Override |
| public void setLob(boolean lob) { |
| if (lob) |
| _flags |= LOB; |
| else |
| _flags &= ~LOB; |
| } |
| |
| @Override |
| public void clearSelects() { |
| _selects.clear(); |
| } |
| |
| @Override |
| public boolean select(SQLBuffer sql, Object id) { |
| return select(sql, id, null); |
| } |
| |
| @Override |
| public boolean select(SQLBuffer sql, Object id, Joins joins) { |
| if (!isGrouping()) |
| return select((Object) sql, id, joins); |
| groupBy(sql, joins); |
| return false; |
| } |
| |
| /** |
| * Record the select of the given SQL buffer or string. |
| */ |
| private boolean select(Object sql, Object id, Joins joins) { |
| getJoins(joins, true); |
| boolean contains; |
| if (id == null) { |
| int idx = _selects.indexOfAlias(sql); |
| contains = idx != -1; |
| if (contains) |
| id = _selects.get(idx); |
| else |
| id = nullId(); |
| } else |
| contains = _selects.contains(id); |
| |
| if (contains) |
| return false; |
| _selects.setAlias(id, sql, false); |
| return true; |
| } |
| |
| /** |
| * Returns a unique id for a SQL string whose given id is null. |
| */ |
| private Object nullId() { |
| if (_nullIds >= NULL_IDS.length) |
| return new NullId(); |
| return NULL_IDS[_nullIds++]; |
| } |
| |
| @Override |
| public boolean select(String sql, Object id) { |
| return select(sql, id, null); |
| } |
| |
| @Override |
| public boolean select(String sql, Object id, Joins joins) { |
| if (!isGrouping()) |
| return select((Object) sql, id, joins); |
| groupBy(sql, joins); |
| return true; |
| } |
| |
| @Override |
| public void selectPlaceholder(String sql) { |
| Object holder = (_placeholders >= PLACEHOLDERS.length) |
| ? new Placeholder() : PLACEHOLDERS[_placeholders++]; |
| select(sql, holder); |
| } |
| |
| /** |
| * Insert a placeholder at the given index; use a negative index |
| * to count from the back of the select list. |
| */ |
| public void insertPlaceholder(String sql, int pos) { |
| Object holder = (_placeholders >= PLACEHOLDERS.length) |
| ? new Placeholder() : PLACEHOLDERS[_placeholders++]; |
| _selects.insertAlias(pos, holder, sql); |
| } |
| |
| /** |
| * Clear selected placeholders, and return removed select indexes. |
| */ |
| public void clearPlaceholderSelects() { |
| _selects.clearPlaceholders(); |
| } |
| |
| @Override |
| public boolean select(Column col) { |
| return select(col, (Joins) null); |
| } |
| |
| @Override |
| public boolean select(Column col, Joins joins) { |
| if (!isGrouping()) |
| return select(col, getJoins(joins, true), false); |
| groupBy(col, joins); |
| return false; |
| } |
| |
| @Override |
| public int select(Column[] cols) { |
| return select(cols, null); |
| } |
| |
| @Override |
| public int select(Column[] cols, Joins joins) { |
| if (cols == null || cols.length == 0) |
| return 0; |
| if (isGrouping()) { |
| groupBy(cols, joins); |
| return 0; |
| } |
| PathJoins pj = getJoins(joins, true); |
| int seld = 0; |
| for (int i = 0; i < cols.length; i++) |
| if (select(cols[i], pj, false)) |
| seld |= 2 << i; |
| return seld; |
| } |
| |
| /** |
| * Select the given column after making the given joins. |
| */ |
| private boolean select(Column col, PathJoins pj, boolean ident) { |
| // we cache on column object if there are no joins so that when |
| // looking up columns in the result we don't have to create a string |
| // buffer for the table + column alias; if there are joins, then |
| // we key on the alias |
| String alias = getColumnAlias(col, pj); |
| Object id; |
| if (pj == null || pj.path() == null) |
| id = col; |
| else |
| id = alias; |
| if (_selects.contains(id)) |
| return false; |
| |
| if (col.getType() == Types.BLOB || col.getType() == Types.CLOB) |
| setLob(true); |
| _selects.setAlias(id, alias, ident); |
| return true; |
| } |
| |
| @Override |
| public void select(ClassMapping mapping, int subclasses, |
| JDBCStore store, JDBCFetchConfiguration fetch, int eager) { |
| select(mapping, subclasses, store, fetch, eager, null); |
| } |
| |
| @Override |
| public void select(ClassMapping mapping, int subclasses, |
| JDBCStore store, JDBCFetchConfiguration fetch, int eager, |
| Joins joins) { |
| select(this, mapping, subclasses, store, fetch, eager, joins, false); |
| } |
| |
| /** |
| * Select the given mapping. |
| */ |
| void select(Select wrapper, ClassMapping mapping, int subclasses, |
| JDBCStore store, JDBCFetchConfiguration fetch, int eager, |
| Joins joins, boolean ident) { |
| // note that this is one case where we don't want to use the result |
| // of getJoins(); just use the given joins, which will either be clean |
| // or the result of previous pre-joins. this way we don't push extra |
| // stack stuff when no actual new joins have been made, and we don't |
| // think the user wants outer joins when actually only the previous |
| // joins were outer. we do invoke getJoins(), though, to add these |
| // joins (if any) to our top-level joins; otherwise it'd be possible |
| // for the user to immediately do another join and select something, |
| // and if we're in outer mode all these joins will get switched to outer |
| // joins. caching them as their original join type prevents that |
| getJoins(joins, true); |
| |
| PathJoins pj = (PathJoins) joins; |
| boolean hasJoins = pj != null && pj.isDirty(); |
| if (hasJoins) { |
| if (_preJoins == null) |
| _preJoins = new Stack(); |
| _preJoins.push(pj); |
| } |
| |
| // if they are selecting this mapping with outer joins, then all joins |
| // from this mapping should also be outer |
| boolean wasOuter = (_flags & OUTER) != 0; |
| if (hasJoins && !wasOuter && pj.isOuter()) |
| _flags |= OUTER; |
| |
| // delegate to store manager to select in same order it loads result |
| ((JDBCStoreManager) store).select(wrapper, mapping, subclasses, null, |
| null, fetch, eager, ident, (_flags & OUTER) != 0); |
| |
| // reset |
| if (hasJoins) |
| _preJoins.pop(); |
| if (!wasOuter && (_flags & OUTER) != 0) |
| _flags &= ~OUTER; |
| } |
| |
| @Override |
| public boolean selectIdentifier(Column col) { |
| return selectIdentifier(col, (Joins) null); |
| } |
| |
| @Override |
| public boolean selectIdentifier(Column col, Joins joins) { |
| if (!isGrouping()) |
| return select(col, getJoins(joins, true), true); |
| groupBy(col, joins); |
| return false; |
| } |
| |
| @Override |
| public int selectIdentifier(Column[] cols) { |
| return selectIdentifier(cols, null); |
| } |
| |
| @Override |
| public int selectIdentifier(Column[] cols, Joins joins) { |
| if (cols == null || cols.length == 0) |
| return 0; |
| if (isGrouping()) { |
| groupBy(cols, joins); |
| return 0; |
| } |
| PathJoins pj = getJoins(joins, true); |
| int seld = 0; |
| for (int i = 0; i < cols.length; i++) |
| if (select(cols[i], pj, true)) |
| seld |= 2 << i; |
| return seld; |
| } |
| |
| @Override |
| public void selectIdentifier(ClassMapping mapping, int subclasses, |
| JDBCStore store, JDBCFetchConfiguration fetch, int eager) { |
| selectIdentifier(mapping, subclasses, store, fetch, eager, null); |
| } |
| |
| @Override |
| public void selectIdentifier(ClassMapping mapping, int subclasses, |
| JDBCStore store, JDBCFetchConfiguration fetch, int eager, |
| Joins joins) { |
| select(this, mapping, subclasses, store, fetch, eager, joins, true); |
| } |
| |
| @Override |
| public int selectPrimaryKey(ClassMapping mapping) { |
| return selectPrimaryKey(mapping, null); |
| } |
| |
| @Override |
| public int selectPrimaryKey(ClassMapping mapping, Joins joins) { |
| return primaryKeyOperation(mapping, true, null, joins, false); |
| } |
| |
| /** |
| * Operate on primary key data. Return a bit mask of selected columns. |
| */ |
| private int primaryKeyOperation(ClassMapping mapping, boolean sel, |
| Boolean asc, Joins joins, boolean aliasOrder) { |
| if (!sel && asc == null) |
| return 0; |
| |
| // if this mapping can't select the full pk values, then join to |
| // super and recurse |
| ClassMapping sup; |
| if (!mapping.isPrimaryKeyObjectId(true)) { |
| sup = mapping.getJoinablePCSuperclassMapping(); |
| if (joins == null) |
| joins = newJoins(); |
| joins = mapping.joinSuperclass(joins, false); |
| return primaryKeyOperation(sup, sel, asc, joins, aliasOrder); |
| } |
| |
| Column[] cols = mapping.getPrimaryKeyColumns(); |
| if (isGrouping()) { |
| groupBy(cols, joins); |
| return 0; |
| } |
| |
| PathJoins pj = getJoins(joins, false); |
| int seld = 0; |
| for (int i = 0; i < cols.length; i++) |
| if (columnOperation(cols[i], sel, asc, pj, aliasOrder)) |
| seld |= 2 << i; |
| |
| // if this mapping has not been used in the select yet (and therefore |
| // is not joined to anything), but has an other-table superclass that |
| // has been used, make sure to join to it |
| boolean joined = false; |
| for (sup = mapping.getJoinablePCSuperclassMapping(); sup != null; |
| mapping = sup, sup = mapping.getJoinablePCSuperclassMapping()) { |
| if (sup.getTable() == mapping.getTable()) |
| continue; |
| |
| if (mapping.getTable() != sup.getTable() |
| && getTableIndex(mapping.getTable(), pj, false) == -1 |
| && getTableIndex(sup.getTable(), pj, false) != -1) { |
| if (pj == null) |
| pj = (PathJoins) newJoins(); |
| pj = (PathJoins) mapping.joinSuperclass(pj, false); |
| joined = true; |
| } else |
| break; |
| } |
| if (joined) |
| where(pj); |
| |
| return seld; |
| } |
| |
| /** |
| * Perform an operation on a column. |
| */ |
| private boolean columnOperation(Column col, boolean sel, Boolean asc, |
| PathJoins pj, boolean aliasOrder) { |
| String as = null; |
| if (asc != null && (aliasOrder || (_flags & RECORD_ORDERED) != 0)) { |
| Object id; |
| if (pj == null || pj.path() == null) |
| id = col; |
| else |
| id = getColumnAlias(col, pj); |
| if ((_flags & RECORD_ORDERED) != 0) { |
| if (_ordered == null) |
| _ordered = new ArrayList(5); |
| _ordered.add(id); |
| } |
| if (aliasOrder) { |
| as = toOrderAlias(_orders++); |
| _selects.setSelectAs(id, as); |
| } |
| } |
| |
| boolean seld = sel && select(col, pj, false); |
| if (asc != null) { |
| String alias = (as != null) ? as : getColumnAlias(col, pj); |
| appendOrdering(alias, asc); |
| } |
| return seld; |
| } |
| |
| /** |
| * Append ordering information to our internal buffer. |
| */ |
| private void appendOrdering(Object orderBy, boolean asc) { |
| if (_ordering == null) |
| _ordering = new SQLBuffer(_dict); |
| else |
| _ordering.append(", "); |
| |
| if (orderBy instanceof SQLBuffer) |
| _ordering.append((SQLBuffer) orderBy); |
| else |
| _ordering.append((String) orderBy); |
| if (asc) |
| _ordering.append(" ASC"); |
| else |
| _ordering.append(" DESC"); |
| } |
| |
| @Override |
| public int orderByPrimaryKey(ClassMapping mapping, boolean asc, |
| boolean sel) { |
| return orderByPrimaryKey(mapping, asc, null, sel); |
| } |
| |
| @Override |
| public int orderByPrimaryKey(ClassMapping mapping, boolean asc, |
| Joins joins, boolean sel) { |
| return orderByPrimaryKey(mapping, asc, joins, sel, false); |
| } |
| |
| /** |
| * Allow unions to set aliases on order columns. |
| */ |
| public int orderByPrimaryKey(ClassMapping mapping, boolean asc, |
| Joins joins, boolean sel, boolean aliasOrder) { |
| return primaryKeyOperation(mapping, sel, |
| (asc) ? Boolean.TRUE : Boolean.FALSE, joins, aliasOrder); |
| } |
| |
| @Override |
| public boolean orderBy(Column col, boolean asc, boolean sel) { |
| return orderBy(col, asc, null, sel); |
| } |
| |
| @Override |
| public boolean orderBy(Column col, boolean asc, Joins joins, boolean sel) { |
| return orderBy(col, asc, joins, sel, false); |
| } |
| |
| /** |
| * Allow unions to set aliases on order columns. |
| */ |
| boolean orderBy(Column col, boolean asc, Joins joins, boolean sel, |
| boolean aliasOrder) { |
| return columnOperation(col, sel, (asc) ? Boolean.TRUE : Boolean.FALSE, |
| getJoins(joins, true), aliasOrder); |
| } |
| |
| @Override |
| public int orderBy(Column[] cols, boolean asc, boolean sel) { |
| return orderBy(cols, asc, null, sel); |
| } |
| |
| @Override |
| public int orderBy(Column[] cols, boolean asc, Joins joins, boolean sel) { |
| return orderBy(cols, asc, joins, sel, false); |
| } |
| |
| /** |
| * Allow unions to set aliases on order columns. |
| */ |
| int orderBy(Column[] cols, boolean asc, Joins joins, boolean sel, |
| boolean aliasOrder) { |
| PathJoins pj = getJoins(joins, true); |
| int seld = 0; |
| for (int i = 0; i < cols.length; i++) |
| if (columnOperation(cols[i], sel, |
| (asc) ? Boolean.TRUE : Boolean.FALSE, pj, aliasOrder)) |
| seld |= 2 << i; |
| return seld; |
| } |
| |
| @Override |
| public boolean orderBy(SQLBuffer sql, boolean asc, boolean sel, Value selAs) |
| { |
| return orderBy(sql, asc, (Joins) null, sel, selAs); |
| } |
| |
| @Override |
| public boolean orderBy(SQLBuffer sql, boolean asc, Joins joins, |
| boolean sel, Value selAs) { |
| return orderBy(sql, asc, joins, sel, false, selAs); |
| } |
| |
| /** |
| * Allow unions to set aliases on order columns. |
| */ |
| boolean orderBy(SQLBuffer sql, boolean asc, Joins joins, boolean sel, |
| boolean aliasOrder, Value selAs) { |
| return orderBy((Object) sql, asc, joins, sel, aliasOrder, selAs); |
| } |
| |
| /** |
| * Order on a SQL buffer or string. |
| */ |
| private boolean orderBy(Object sql, boolean asc, Joins joins, boolean sel, |
| boolean aliasOrder, Value selAs) { |
| Object order = sql; |
| if (aliasOrder) { |
| order = toOrderAlias(_orders++); |
| _selects.setSelectAs(sql, (String) order); |
| } |
| if ((_flags & RECORD_ORDERED) != 0) { |
| if (_ordered == null) |
| _ordered = new ArrayList(5); |
| _ordered.add(selAs == null ? sql : selAs); |
| } |
| |
| getJoins(joins, true); |
| appendOrdering(selAs != null ? selAs.getAlias() : order, asc); |
| if (sel) { |
| int idx = _selects.indexOfAlias(sql); |
| if (idx == -1) { |
| _selects.setAlias(nullId(), sql, false); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean orderBy(String sql, boolean asc, boolean sel) { |
| return orderBy(sql, asc, null, sel); |
| } |
| |
| @Override |
| public boolean orderBy(String sql, boolean asc, Joins joins, boolean sel) { |
| return orderBy(sql, asc, joins, sel, false); |
| } |
| |
| /** |
| * Allow unions to set aliases on order columns. |
| */ |
| boolean orderBy(String sql, boolean asc, Joins joins, boolean sel, |
| boolean aliasOrder) { |
| return orderBy((Object) sql, asc, joins, sel, aliasOrder, null); |
| } |
| |
| @Override |
| public void clearOrdering() { |
| _ordering = null; |
| _orders = 0; |
| } |
| |
| /** |
| * Allow unions to record the select list indexes of items we order by. |
| */ |
| void setRecordOrderedIndexes(boolean record) { |
| if (record) |
| _flags |= RECORD_ORDERED; |
| else { |
| _ordered = null; |
| _flags &= ~RECORD_ORDERED; |
| } |
| } |
| |
| /** |
| * Return the indexes in the select list of all items we're ordering |
| * by, or null if none. For use with unions. |
| */ |
| List getOrderedIndexes() { |
| if (_ordered == null) |
| return null; |
| List idxs = new ArrayList(_ordered.size()); |
| for (int i = 0; i < _ordered.size(); i++) |
| idxs.add(_selects.indexOf(_ordered.get(i))); |
| return idxs; |
| } |
| |
| @Override |
| public void wherePrimaryKey(Object oid, ClassMapping mapping, |
| JDBCStore store) { |
| wherePrimaryKey(oid, mapping, null, store); |
| } |
| |
| /** |
| * Add where conditions setting the mapping's primary key to the given |
| * oid values. If the given mapping does not use oid values for its |
| * primary key, we will recursively join to its superclass until we find |
| * an ancestor that does. |
| */ |
| private void wherePrimaryKey(Object oid, ClassMapping mapping, Joins joins, |
| JDBCStore store) { |
| // if this mapping's identifiers include something other than |
| // the pk values, join to super and recurse |
| if (!mapping.isPrimaryKeyObjectId(false)) { |
| ClassMapping sup = mapping.getJoinablePCSuperclassMapping(); |
| if (joins == null) |
| joins = newJoins(); |
| joins = mapping.joinSuperclass(joins, false); |
| wherePrimaryKey(oid, sup, joins, store); |
| return; |
| } |
| |
| Column[] cols = mapping.getPrimaryKeyColumns(); |
| where(oid, mapping, cols, cols, null, null, getJoins(joins, true), |
| store); |
| } |
| |
| @Override |
| public void whereForeignKey(ForeignKey fk, Object oid, |
| ClassMapping mapping, JDBCStore store) { |
| whereForeignKey(fk, oid, mapping, null, store); |
| } |
| |
| /** |
| * Add where conditions setting the given foreign key to the given |
| * oid values. |
| * |
| * @see #wherePrimaryKey |
| */ |
| private void whereForeignKey(ForeignKey fk, Object oid, |
| ClassMapping mapping, Joins joins, JDBCStore store) { |
| // if this mapping's identifiers include something other than |
| // the pk values, or if this foreign key doesn't link to only |
| // identifiers, join to table and do a getPrimaryKey |
| if (!mapping.isPrimaryKeyObjectId(false) || !containsAll |
| (mapping.getPrimaryKeyColumns(), fk.getPrimaryKeyColumns())) { |
| if (joins == null) |
| joins = newJoins(); |
| // traverse to foreign key target mapping |
| while (mapping.getTable() != fk.getPrimaryKeyTable()) { |
| if (joins == null) |
| joins = newJoins(); |
| joins = mapping.joinSuperclass(joins, false); |
| mapping = mapping.getJoinablePCSuperclassMapping(); |
| if (mapping == null) |
| throw new InternalException(); |
| } |
| joins = joins.join(fk, false, false); |
| wherePrimaryKey(oid, mapping, joins, store); |
| return; |
| } |
| |
| Column[] fromCols = fk.getColumns(); |
| Column[] toCols = fk.getPrimaryKeyColumns(); |
| Column[] constCols = fk.getConstantColumns(); |
| Object[] consts = fk.getConstants(); |
| where(oid, mapping, toCols, fromCols, consts, constCols, |
| getJoins(joins, true), store); |
| } |
| |
| /** |
| * Internal method to flush the oid values as where conditions to the |
| * given columns. |
| */ |
| private void where(Object oid, ClassMapping mapping, Column[] toCols, |
| Column[] fromCols, Object[] vals, Column[] constCols, PathJoins pj, |
| JDBCStore store) { |
| ValueMapping embed = mapping.getEmbeddingMapping(); |
| if (embed != null) { |
| where(oid, embed.getFieldMapping().getDefiningMapping(), |
| toCols, fromCols, vals, constCols, pj, store); |
| return; |
| } |
| |
| // only bother to pack pk values into array if app id |
| Object[] pks = null; |
| boolean relationId = RelationStrategies.isRelationId(fromCols); |
| if (!relationId && mapping.getIdentityType() == ClassMetaData.ID_APPLICATION) |
| pks = ApplicationIds.toPKValues(oid, mapping); |
| |
| SQLBuffer buf = new SQLBuffer(_dict); |
| Joinable join; |
| Object val; |
| int count = 0; |
| for (int i = 0; i < toCols.length; i++, count++) { |
| if (pks == null) { |
| val = (oid == null) ? null : relationId ? oid : ((Id) oid).getId(); |
| } else { |
| // must be app identity; use pk index to get correct pk value |
| join = mapping.assertJoinable(toCols[i]); |
| val = pks[mapping.getField(join.getFieldIndex()). |
| getPrimaryKeyIndex()]; |
| val = join.getJoinValue(val, toCols[i], store); |
| } |
| |
| if (count > 0) |
| buf.append(" AND "); |
| buf.append(getColumnAlias(fromCols[i], pj)); |
| if (val == null) |
| buf.append(" IS "); |
| else |
| buf.append(" = "); |
| buf.appendValue(val, fromCols[i]); |
| } |
| |
| if (constCols != null && constCols.length > 0) { |
| for (int i = 0; i < constCols.length; i++, count++) { |
| if (count > 0) |
| buf.append(" AND "); |
| buf.append(getColumnAlias(constCols[i], pj)); |
| |
| if (vals[i] == null) |
| buf.append(" IS "); |
| else |
| buf.append(" = "); |
| buf.appendValue(vals[i], constCols[i]); |
| } |
| } |
| |
| where(buf, pj); |
| } |
| |
| /** |
| * Test to see if the given set of columns contains all the |
| * columns in the given potential subset. |
| */ |
| private static boolean containsAll(Column[] set, Column[] sub) { |
| if (sub.length > set.length) |
| return false; |
| |
| // this is obviously n^2, but the number of columns should be in |
| // the 1-2 range, so no biggie |
| boolean found = true; |
| for (int i = 0; i < sub.length && found; i++) { |
| found = false; |
| for (int j = 0; j < set.length && !found; j++) |
| found = sub[i] == set[j]; |
| } |
| return found; |
| } |
| |
| @Override |
| public void where(Joins joins) { |
| if (joins != null) |
| where((String) null, joins); |
| } |
| |
| @Override |
| public void where(SQLBuffer sql) { |
| where(sql, (Joins) null); |
| } |
| |
| @Override |
| public void where(SQLBuffer sql, Joins joins) { |
| where(sql, getJoins(joins, true)); |
| } |
| |
| /** |
| * Add the given condition to the WHERE clause. |
| */ |
| private void where(SQLBuffer sql, PathJoins pj) { |
| // no need to use joins... |
| if (sql == null || sql.isEmpty()) |
| return; |
| |
| if (_where == null) |
| _where = new SQLBuffer(_dict); |
| else if (!_where.isEmpty()) |
| _where.append(" AND "); |
| _where.append(sql); |
| } |
| |
| @Override |
| public void where(String sql) { |
| where(sql, (Joins) null); |
| } |
| |
| @Override |
| public void where(String sql, Joins joins) { |
| where(sql, getJoins(joins, true)); |
| } |
| |
| /** |
| * Add the given condition to the WHERE clause. |
| */ |
| private void where(String sql, PathJoins pj) { |
| // no need to use joins... |
| if (StringUtil.isEmpty(sql)) |
| return; |
| |
| if (_where == null) |
| _where = new SQLBuffer(_dict); |
| else if (!_where.isEmpty()) |
| _where.append(" AND "); |
| _where.append(sql); |
| } |
| |
| @Override |
| public void having(SQLBuffer sql) { |
| having(sql, (Joins) null); |
| } |
| |
| @Override |
| public void having(SQLBuffer sql, Joins joins) { |
| having(sql, getJoins(joins, true)); |
| } |
| |
| /** |
| * Add the given condition to the HAVING clause. |
| */ |
| private void having(SQLBuffer sql, PathJoins pj) { |
| // no need to use joins... |
| if (sql == null || sql.isEmpty()) |
| return; |
| |
| if (_having == null) |
| _having = new SQLBuffer(_dict); |
| else if (!_having.isEmpty()) |
| _having.append(" AND "); |
| _having.append(sql); |
| } |
| |
| @Override |
| public void having(String sql) { |
| having(sql, (Joins) null); |
| } |
| |
| @Override |
| public void having(String sql, Joins joins) { |
| having(sql, getJoins(joins, true)); |
| } |
| |
| /** |
| * Add the given condition to the HAVING clause. |
| */ |
| private void having(String sql, PathJoins pj) { |
| // no need to use joins... |
| if (StringUtil.isEmpty(sql)) |
| return; |
| |
| if (_having == null) |
| _having = new SQLBuffer(_dict); |
| else if (!_having.isEmpty()) |
| _having.append(" AND "); |
| _having.append(sql); |
| } |
| |
| @Override |
| public void groupBy(SQLBuffer sql) { |
| groupBy(sql, (Joins) null); |
| } |
| |
| @Override |
| public void groupBy(SQLBuffer sql, Joins joins) { |
| getJoins(joins, true); |
| groupByAppend(sql.getSQL()); |
| } |
| |
| @Override |
| public void groupBy(String sql) { |
| groupBy(sql, (Joins) null); |
| } |
| |
| @Override |
| public void groupBy(String sql, Joins joins) { |
| getJoins(joins, true); |
| groupByAppend(sql); |
| } |
| |
| @Override |
| public void groupBy(Column col) { |
| groupBy(col, null); |
| } |
| |
| @Override |
| public void groupBy(Column col, Joins joins) { |
| PathJoins pj = getJoins(joins, true); |
| groupByAppend(getColumnAlias(col, pj)); |
| } |
| |
| @Override |
| public void groupBy(Column[] cols) { |
| groupBy(cols, null); |
| } |
| |
| @Override |
| public void groupBy(Column[] cols, Joins joins) { |
| PathJoins pj = getJoins(joins, true); |
| for (int i = 0; i < cols.length; i++) { |
| groupByAppend(getColumnAlias(cols[i], pj)); |
| } |
| } |
| |
| private void groupByAppend(String sql) { |
| if (_grouped == null || !_grouped.contains(sql)) { |
| if (_grouping == null) { |
| _grouping = new SQLBuffer(_dict); |
| _grouped = new ArrayList(); |
| } else |
| _grouping.append(", "); |
| |
| _grouping.append(sql); |
| _grouped.add(sql); |
| } |
| } |
| |
| @Override |
| public void groupBy(ClassMapping mapping, int subclasses, JDBCStore store, |
| JDBCFetchConfiguration fetch) { |
| groupBy(mapping, subclasses, store, fetch, null); |
| } |
| |
| @Override |
| public void groupBy(ClassMapping mapping, int subclasses, JDBCStore store, |
| JDBCFetchConfiguration fetch, Joins joins) { |
| // we implement this by putting ourselves into grouping mode, where |
| // all select invocations are re-routed to group-by invocations instead. |
| // this allows us to utilize the same select APIs of the store manager |
| // and all the mapping strategies, rather than having to create |
| // equivalent APIs and duplicate logic for grouping |
| boolean wasGrouping = isGrouping(); |
| _flags |= GROUPING; |
| try { |
| select(mapping, subclasses, store, fetch, |
| EagerFetchModes.EAGER_NONE, joins); |
| } finally { |
| if (!wasGrouping) |
| _flags &= ~GROUPING; |
| } |
| } |
| |
| /** |
| * Whether we're in group mode, where any select is changed to a group-by |
| * call. |
| */ |
| private boolean isGrouping() { |
| return (_flags & GROUPING) != 0; |
| } |
| |
| /** |
| * Return the joins to use for column aliases, etc. |
| * |
| * @param joins joins given by the user |
| * @return the joins to use for aliases, etc |
| */ |
| private PathJoins getJoins(Joins joins, boolean record) { |
| PathJoins pj = (PathJoins) joins; |
| boolean pre = (pj == null || !pj.isDirty()) |
| && _preJoins != null && !_preJoins.isEmpty(); |
| if (pre) |
| pj = (PathJoins) _preJoins.peek(); |
| |
| if (pj == null || !pj.isDirty()) |
| pj = _joins; |
| else if (!pre) { |
| if ((_flags & OUTER) != 0) |
| pj = (PathJoins) outer(pj); |
| if (record) { |
| if (!pj.isEmpty()) { |
| if (_joins == null) |
| _joins = new SelectJoins(this); |
| if (_joins.joins() == null) |
| _joins.setJoins(new JoinSet(pj.joins())); |
| else |
| _joins.joins().addAll(pj.joins()); |
| } |
| } |
| } |
| return pj; |
| } |
| |
| @Override |
| public SelectExecutor whereClone(int sels) { |
| if (sels < 1) |
| sels = 1; |
| |
| Select[] clones = null; |
| SelectImpl sel; |
| for (int i = 0; i < sels; i++) { |
| sel = (SelectImpl) _conf.getSQLFactoryInstance().newSelect(); |
| sel._flags = _flags; |
| sel._flags &= ~AGGREGATE; |
| sel._flags &= ~OUTER; |
| sel._flags &= ~LRS; |
| sel._flags &= ~EAGER_TO_ONE; |
| sel._flags &= ~EAGER_TO_MANY; |
| sel._flags &= ~FORCE_COUNT; |
| sel._joinSyntax = _joinSyntax; |
| sel._schemaAlias = _schemaAlias; |
| if (_aliases != null) |
| sel._aliases = new HashMap(_aliases); |
| if (_tables != null) |
| sel._tables = new TreeMap(_tables); |
| if (_joins != null) |
| sel._joins = _joins.clone(sel); |
| if (_where != null) |
| sel._where = new SQLBuffer(_where); |
| if (_from != null) { |
| sel._from = (SelectImpl) _from.whereClone(1); |
| sel._from._outer = sel; |
| } |
| if (_subsels != null) { |
| sel._subsels = new ArrayList(_subsels.size()); |
| SelectImpl sub, selSub; |
| for (int j = 0; j < _subsels.size(); j++) { |
| sub = (SelectImpl) _subsels.get(j); |
| selSub = (SelectImpl) sub.fullClone(1); |
| selSub._parent = sel; |
| selSub._subPath = sub._subPath; |
| sel._subsels.add(selSub); |
| if (sel._where != null) |
| sel._where.replace(sub, selSub); |
| } |
| } |
| |
| if (sels == 1) |
| return sel; |
| if (clones == null) |
| clones = new Select[sels]; |
| clones[i] = sel; |
| } |
| return _conf.getSQLFactoryInstance().newUnion(clones); |
| } |
| |
| @Override |
| public SelectExecutor fullClone(int sels) { |
| if (sels < 1) |
| sels = 1; |
| |
| Select[] clones = null; |
| SelectImpl sel; |
| for (int i = 0; i < sels; i++) { |
| sel = (SelectImpl) whereClone(1); |
| sel._flags = _flags; |
| sel._expectedResultCount = _expectedResultCount; |
| sel._selects.addAll(_selects); |
| if (_ordering != null) |
| sel._ordering = new SQLBuffer(_ordering); |
| sel._orders = _orders; |
| if (_grouping != null) |
| sel._grouping = new SQLBuffer(_grouping); |
| if (_having != null) |
| sel._having = new SQLBuffer(_having); |
| if (_from != null) { |
| sel._from = (SelectImpl) _from.fullClone(1); |
| sel._from._outer = sel; |
| } |
| |
| if (sels == 1) |
| return sel; |
| if (clones == null) |
| clones = new Select[sels]; |
| clones[i] = sel; |
| } |
| return _conf.getSQLFactoryInstance().newUnion(clones); |
| } |
| |
| @Override |
| public SelectExecutor eagerClone(FieldMapping key, int eagerType, |
| boolean toMany, int sels) { |
| if (eagerType == EAGER_OUTER |
| && _joinSyntax == JoinSyntaxes.SYNTAX_TRADITIONAL) |
| return null; |
| if (_eagerKeys != null && _eagerKeys.contains(key)) |
| return null; |
| |
| // global set of eager keys |
| if (_eagerKeys == null) |
| _eagerKeys = new HashSet(); |
| _eagerKeys.add(key); |
| |
| SelectExecutor sel; |
| if (eagerType != EAGER_PARALLEL) { |
| if (toMany) |
| _flags |= EAGER_TO_MANY; |
| else |
| _flags |= EAGER_TO_ONE; |
| sel = this; |
| } else if (sels < 2) |
| sel = parallelClone(); |
| else { |
| Select[] clones = new Select[sels]; |
| for (int i = 0; i < clones.length; i++) |
| clones[i] = parallelClone(); |
| sel = _conf.getSQLFactoryInstance().newUnion(clones); |
| } |
| |
| if (_eager == null) |
| _eager = new HashMap(); |
| _eager.put(toEagerKey(key, getJoins(null, false)), sel); |
| return sel; |
| } |
| |
| /** |
| * Return a clone of this select for use in eager parallel selects. |
| */ |
| private SelectImpl parallelClone() { |
| SelectImpl sel = (SelectImpl) whereClone(1); |
| sel._flags &= ~NONAUTO_DISTINCT; |
| sel._eagerKeys = _eagerKeys; |
| if (_preJoins != null && !_preJoins.isEmpty()) { |
| sel._preJoins = new Stack(); |
| sel._preJoins.push(((SelectJoins) _preJoins.peek()). |
| clone(sel)); |
| } |
| return sel; |
| } |
| |
| /** |
| * Return view of eager selects. May be null. |
| */ |
| public Map getEagerMap() { |
| return _eager; |
| } |
| |
| @Override |
| public void logEagerRelations() { |
| if (_eagerKeys != null) { |
| _conf.getLog(JDBCConfiguration.LOG_DIAG).trace( |
| "Eager relations: "+_eagerKeys); |
| } |
| } |
| |
| @Override |
| public SelectExecutor getEager(FieldMapping key) { |
| if (_eager == null || !_eagerKeys.contains(key)) |
| return null; |
| return (SelectExecutor) _eager.get(toEagerKey(key, getJoins(null, |
| false))); |
| } |
| |
| /** |
| * Return the eager key to use for the user-given key. |
| */ |
| private static Object toEagerKey(FieldMapping key, PathJoins pj) { |
| if (pj == null || pj.path() == null) |
| return key; |
| return new Key(pj.path().toString(), key); |
| } |
| |
| @Override |
| public Joins newJoins() { |
| if (_preJoins != null && !_preJoins.isEmpty()) { |
| SelectJoins sj = (SelectJoins) _preJoins.peek(); |
| return sj.clone(this); |
| } |
| // return this for efficiency in case no joins end up being made |
| return this; |
| } |
| |
| @Override |
| public Joins newOuterJoins() { |
| return ((PathJoins) newJoins()).setOuter(true); |
| } |
| |
| @Override |
| public void append(SQLBuffer buf, Joins joins) { |
| if (joins == null || joins.isEmpty()) |
| return; |
| if (_joinSyntax == JoinSyntaxes.SYNTAX_SQL92) |
| return; |
| |
| if (!buf.isEmpty()) |
| buf.append(" AND "); |
| Join join = null; |
| for (Iterator itr = ((PathJoins) joins).joins().joinIterator(); |
| itr.hasNext();) { |
| join = (Join) itr.next(); |
| switch (_joinSyntax) { |
| case JoinSyntaxes.SYNTAX_TRADITIONAL: |
| buf.append(_dict.toTraditionalJoin(join)); |
| break; |
| case JoinSyntaxes.SYNTAX_DATABASE: |
| buf.append(_dict.toNativeJoin(join)); |
| break; |
| default: |
| throw new InternalException(); |
| } |
| |
| if (itr.hasNext()) |
| buf.append(" AND "); |
| } |
| } |
| |
| @Override |
| public Joins and(Joins joins1, Joins joins2) { |
| return and((PathJoins) joins1, (PathJoins) joins2, true); |
| } |
| |
| @Override |
| public Select getSelect() { |
| return null; |
| } |
| |
| /** |
| * Combine the given joins. |
| */ |
| private SelectJoins and(PathJoins j1, PathJoins j2, boolean nullJoins) { |
| if ((j1 == null || j1.isEmpty()) |
| && (j2 == null || j2.isEmpty())) |
| return null; |
| |
| SelectJoins sj = new SelectJoins(this); |
| if (j1 == null || j1.isEmpty()) { |
| if (j2.getSelect() == this) { |
| if (nullJoins) |
| sj.setJoins(j2.joins()); |
| else |
| sj.setJoins(new JoinSet(j2.joins())); |
| } |
| } else { |
| JoinSet set = null; |
| if (j1.getSelect() == this) { |
| if (nullJoins) |
| set = j1.joins(); |
| else |
| set = new JoinSet(j1.joins()); |
| |
| if (j2 != null && !j2.isEmpty() |
| && j2.getSelect() == this) |
| set.addAll(j2.joins()); |
| sj.setJoins(set); |
| } |
| } |
| |
| // null previous joins; all are combined into this one |
| if (nullJoins && j1 != null) |
| j1.nullJoins(); |
| if (nullJoins && j2 != null) |
| j2.nullJoins(); |
| |
| return sj; |
| } |
| |
| @Override |
| public Joins or(Joins joins1, Joins joins2) { |
| PathJoins j1 = (PathJoins) joins1; |
| PathJoins j2 = (PathJoins) joins2; |
| |
| // if no common joins, return null; if one side of the or clause has |
| // different joins than the other, then we need to use distinct |
| boolean j1Empty = j1 == null || j1.isEmpty(); |
| boolean j2Empty = j2 == null || j2.isEmpty(); |
| if (j1Empty || j2Empty) { |
| if (j1Empty && !j2Empty) { |
| collectOuterJoins(j2); |
| if (!j2.isEmpty()) |
| _flags |= IMPLICIT_DISTINCT; |
| } else if (j2Empty && !j1Empty) { |
| collectOuterJoins(j1); |
| if (!j1.isEmpty()) |
| _flags |= IMPLICIT_DISTINCT; |
| } |
| return null; |
| } |
| |
| // if all common joins, move all joins to returned instance |
| SelectJoins sj = new SelectJoins(this); |
| if (j1.joins().equals(j2.joins())) { |
| sj.setJoins(j1.joins()); |
| j1.nullJoins(); |
| j2.nullJoins(); |
| } else { |
| JoinSet commonJoins = new JoinSet(j1.joins()); |
| commonJoins.retainAll(j2.joins()); |
| if (!commonJoins.isEmpty()) { |
| // put common joins in returned instance; remove them from |
| // each given instance |
| sj.setJoins(commonJoins); |
| j1.joins().removeAll(commonJoins); |
| j2.joins().removeAll(commonJoins); |
| } |
| collectOuterJoins(j1); |
| collectOuterJoins(j2); |
| |
| // if one side of the or clause has different joins than the other, |
| // then we need to use distinct |
| if (!j1.isEmpty() || !j2.isEmpty()) |
| _flags |= IMPLICIT_DISTINCT; |
| } |
| return sj; |
| } |
| |
| @Override |
| public Joins outer(Joins joins) { |
| if (_joinSyntax == JoinSyntaxes.SYNTAX_TRADITIONAL || joins == null) |
| return joins; |
| |
| // record that this is an outer join set, even if it's empty |
| PathJoins pj = ((PathJoins) joins).setOuter(true); |
| if (pj.isEmpty()) |
| return pj; |
| |
| Join join; |
| Join rec; |
| boolean hasJoins = _joins != null && _joins.joins() != null; |
| for (Iterator itr = pj.joins().iterator(); itr.hasNext();) { |
| join = (Join) itr.next(); |
| if (join.getType() == Join.TYPE_INNER) { |
| if (!hasJoins) |
| join.setType(Join.TYPE_OUTER); |
| else { |
| rec = _joins.joins().getRecordedJoin(join); |
| if (rec == null || rec.getType() == Join.TYPE_OUTER) |
| join.setType(Join.TYPE_OUTER); |
| } |
| } |
| } |
| return joins; |
| } |
| |
| /** |
| * Moves the joins from the given instance into our outer joins set. |
| */ |
| private void collectOuterJoins(PathJoins pj) { |
| if (_joinSyntax == JoinSyntaxes.SYNTAX_TRADITIONAL || pj == null |
| || pj.isEmpty()) |
| return; |
| |
| if (_joins == null) |
| _joins = new SelectJoins(this); |
| |
| boolean add = true; |
| if (_joins.joins() == null) { |
| _joins.setJoins(pj.joins()); |
| add = false; |
| } |
| |
| Join join; |
| for (Iterator itr = pj.joins().iterator(); itr.hasNext();) { |
| join = (Join) itr.next(); |
| if (join.getType() == Join.TYPE_INNER) { |
| if (join.getForeignKey() != null |
| && !_dict.canOuterJoin(_joinSyntax, join.getForeignKey())) { |
| Log log = _conf.getLog(JDBCConfiguration.LOG_JDBC); |
| if (log.isWarnEnabled()) |
| log.warn(_loc.get("cant-outer-fk", |
| join.getForeignKey())); |
| } else |
| join.setType(Join.TYPE_OUTER); |
| } |
| if (add) |
| _joins.joins().add(join); |
| } |
| pj.nullJoins(); |
| } |
| |
| /** |
| * Return the alias for the given table under the given joins. |
| * NOTE: WE RELY ON THESE INDEXES BEING MONOTONICALLY INCREASING FROM 0 |
| */ |
| int getTableIndex(Table table, PathJoins pj, boolean create) { |
| // if we have a from select, then there are no table aliases |
| if (_from != null) |
| return -1; |
| |
| Integer i = null; |
| Object key = table.getFullIdentifier().getName(); |
| if (pj != null && pj.path() != null) |
| key = new Key(pj.getPathStr(), key); |
| |
| if (_ctx != null && (_parent != null || _subsels != null || _hasSub)) { |
| i = findAliasForQuery(table, pj, key, create); |
| } |
| |
| if (i != null) |
| return i; |
| |
| // check out existing aliases |
| i = findAlias(table, key); |
| |
| if (i != null) |
| return i; |
| if (!create) |
| return -1; |
| |
| // not found; create alias |
| i = aliasSize(false, null); |
| // System.out.println("GetTableIndex\t"+ |
| // ((_parent != null) ? "Sub" :"") + |
| // " created alias: "+ |
| // i.intValue()+ " "+ key); |
| recordTableAlias(table, key, i); |
| return i; |
| } |
| |
| private Integer findAliasForQuery(Table table, PathJoins pj, Object key, |
| boolean create) { |
| Integer i = null; |
| SelectImpl sel = this; |
| String alias = _schemaAlias; |
| if (isPathInThisContext(pj) || table.isAssociation()) |
| alias = null; |
| |
| // find the context where this alias is defined |
| Context ctx = (alias != null) ? |
| _ctx.findContext(alias) : null; |
| if (ctx != null) |
| sel = (SelectImpl) ctx.getSelect(); |
| |
| if (!create) |
| i = sel.findAlias(table, key); // find in parent and in myself |
| else |
| i = sel.getAlias(table, key); // find in myself |
| if (i != null) |
| return i; |
| |
| if (create) { // create here |
| i = sel.createAlias(table, key); |
| } else if (ctx != null && ctx != ctx()) { // create in other select |
| i = ((SelectImpl)ctx.getSelect()).createAlias(table, key); |
| } |
| |
| return i; |
| } |
| |
| private boolean isPathInThisContext(PathJoins pj) { |
| // currCtx is set from Action, it is reset to null after the PCPath initialization |
| Context currCtx = pj == null ? null : ((PathJoinsImpl)pj).context; |
| |
| // lastCtx is set to currCtx after the SelectJoins.join. pj.lastCtx and pj.path string are |
| // the last snapshot of pj. They will be used together for later table alias resolution in |
| // the getColumnAlias(). |
| Context lastCtx = pj == null ? null : ((PathJoinsImpl)pj).lastContext; |
| Context thisCtx = currCtx == null ? lastCtx : currCtx; |
| String corrVar = pj == null ? null : pj.getCorrelatedVariable(); |
| |
| return (pj != null && pj.path() != null && |
| (corrVar == null || (thisCtx != null && ctx() == thisCtx))); |
| } |
| |
| private Integer getAlias(Table table, Object key) { |
| Integer alias = null; |
| if (_aliases != null) |
| alias = (Integer) _aliases.get(key); |
| return alias; |
| } |
| |
| private int createAlias(Table table, Object key) { |
| Integer i = ctx().nextAlias(); |
| // System.out.println("\t"+ |
| // ((_parent != null) ? "Sub" :"") + |
| // "Query created alias: "+ |
| // i.intValue()+ " "+ key); |
| recordTableAlias(table, key, i); |
| return i; |
| } |
| |
| private Integer findAlias(Table table, Object key) { |
| Integer alias = null; |
| if (_aliases != null) { |
| alias = (Integer) _aliases.get(key); |
| if (alias != null) { |
| return alias; |
| } |
| } |
| if (_parent != null) { |
| alias = _parent.findAlias(table, key); |
| if (alias != null) { |
| return alias; |
| } |
| } |
| return alias; |
| } |
| |
| /** |
| * Record the mapping of the given key to the given alias. |
| */ |
| private void recordTableAlias(Table table, Object key, Integer alias) { |
| if (_aliases == null) |
| _aliases = new HashMap(); |
| _aliases.put(key, alias); |
| |
| String tableString = _dict.getFullName(table, false) + " " |
| + toAlias(alias); |
| if (_tables == null) |
| _tables = new TreeMap(); |
| _tables.put(alias, tableString); |
| } |
| |
| |
| /** |
| * Calculate total number of aliases. |
| * |
| * From 1.2.x |
| */ |
| private int aliasSize(boolean fromParent, SelectImpl fromSub) { |
| int aliases = (fromParent || _parent == null) ? 0 : _parent.aliasSize(false, this); |
| aliases += (_aliases == null) ? 0 : _aliases.size(); |
| if (_subsels != null) { |
| for (SelectImpl sub : _subsels) { |
| if (sub != fromSub) |
| aliases += sub.aliasSize(true, null); |
| } |
| } |
| return aliases; |
| } |
| |
| @Override |
| public String toString() { |
| return toSelect(false, null).getSQL(); |
| } |
| |
| //////////////////////////// |
| // PathJoins implementation |
| //////////////////////////// |
| |
| @Override |
| public boolean isOuter() { |
| return false; |
| } |
| |
| @Override |
| public PathJoins setOuter(boolean outer) { |
| return new SelectJoins(this).setOuter(true); |
| } |
| |
| @Override |
| public boolean isDirty() { |
| return false; |
| } |
| |
| @Override |
| public StringBuilder path() { |
| return null; |
| } |
| |
| @Override |
| public String getPathStr() { |
| return null; |
| } |
| |
| @Override |
| public JoinSet joins() { |
| return null; |
| } |
| |
| @Override |
| public int joinCount() { |
| return 0; |
| } |
| |
| @Override |
| public void nullJoins() { |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return true; |
| } |
| |
| @Override |
| public Joins crossJoin(Table localTable, Table foreignTable) { |
| return new SelectJoins(this).crossJoin(localTable, foreignTable); |
| } |
| |
| @Override |
| public Joins join(ForeignKey fk, boolean inverse, boolean toMany) { |
| return new SelectJoins(this).join(fk, inverse, toMany); |
| } |
| |
| @Override |
| public Joins outerJoin(ForeignKey fk, boolean inverse, boolean toMany) { |
| return new SelectJoins(this).outerJoin(fk, inverse, toMany); |
| } |
| |
| @Override |
| public Joins joinRelation(String name, ForeignKey fk, ClassMapping target, |
| int subs, boolean inverse, boolean toMany) { |
| return new SelectJoins(this).joinRelation(name, fk, target, subs, |
| inverse, toMany); |
| } |
| |
| @Override |
| public Joins outerJoinRelation(String name, ForeignKey fk, |
| ClassMapping target, int subs, boolean inverse, boolean toMany) { |
| return new SelectJoins(this).outerJoinRelation(name, fk, target, subs, |
| inverse, toMany); |
| } |
| |
| @Override |
| public Joins setVariable(String var) { |
| if (var == null) |
| return this; |
| return new SelectJoins(this).setVariable(var); |
| } |
| |
| @Override |
| public Joins setSubselect(String alias) { |
| if (alias == null) |
| return this; |
| return new SelectJoins(this).setSubselect(alias); |
| } |
| |
| /** |
| * Represents a SQL string selected with null id. |
| */ |
| private static class NullId { |
| } |
| |
| /** |
| * Represents a placeholder SQL string. |
| */ |
| private static class Placeholder { |
| } |
| |
| public SelectImpl clone(Context ctx) { |
| SelectImpl sel = (SelectImpl) _conf.getSQLFactoryInstance().newSelect(); |
| sel._ctx = ctx; |
| if (_parent != null && _parent.ctx() != null) |
| sel._parent = (SelectImpl)_parent.ctx().getSelect(); |
| sel._schemaAlias = _schemaAlias; |
| sel._flags = _flags; |
| return sel; |
| } |
| |
| /** |
| * Key type used for aliases. |
| */ |
| private static class Key { |
| |
| private final String _path; |
| private final Object _key; |
| |
| public Key(String path, Object key) { |
| _path = path; |
| _key = key; |
| } |
| |
| @Override |
| public int hashCode() { |
| return ((_path == null) ? 0 : _path.hashCode()) ^ ((_key == null) ? 0 : _key.hashCode()); |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (other == null) |
| return false; |
| if (other == this) |
| return true; |
| if (other.getClass() != getClass()) |
| return false; |
| Key k = (Key) other; |
| if (k._key == null || k._path == null || _key == null || _path == null) |
| return false; |
| return k._path.equals(_path) && k._key.equals(_key); |
| } |
| |
| @Override |
| public String toString() { |
| return _path + "|" + _key; |
| } |
| |
| Object getKey() { |
| return _key; |
| } |
| } |
| |
| /** |
| * A {@link Result} implementation wrapped around this select. |
| */ |
| public static class SelectResult |
| extends ResultSetResult |
| implements PathJoins { |
| |
| private SelectImpl _sel = null; |
| private Map<CachedColumnAliasKey, Object> cachedColumnAlias_ = null; |
| |
| // position in selected columns list where we expect the next load |
| private int _pos = 0; |
| private Stack _preJoins = null; |
| |
| /** |
| * Constructor. |
| */ |
| public SelectResult(Connection conn, Statement stmnt, ResultSet rs, |
| DBDictionary dict) { |
| super(conn, stmnt, rs, dict); |
| } |
| |
| /** |
| * Select for this result. |
| */ |
| @Override |
| public SelectImpl getSelect() { |
| return _sel; |
| } |
| |
| /** |
| * Select for this result. |
| */ |
| public void setSelect(SelectImpl sel) { |
| _sel = sel; |
| } |
| |
| @Override |
| public Object getEager(FieldMapping key) { |
| // don't bother creating key if we know we don't have any |
| // eager results |
| if (_sel._eager == null || !_sel._eagerKeys.contains(key)) |
| return null; |
| Map map = SelectResult.this.getEagerMap(true); |
| if (map == null) |
| return null; |
| return map.get(SelectImpl.toEagerKey(key, getJoins(null))); |
| } |
| |
| @Override |
| public void putEager(FieldMapping key, Object res) { |
| Map map = SelectResult.this.getEagerMap(true); |
| if (map == null) { |
| map = new HashMap(); |
| setEagerMap(map); |
| } |
| map.put(SelectImpl.toEagerKey(key, getJoins(null)), res); |
| } |
| |
| @Override |
| public Object load(ClassMapping mapping, JDBCStore store, |
| JDBCFetchConfiguration fetch, Joins joins) |
| throws SQLException { |
| boolean hasJoins = joins != null |
| && ((PathJoins) joins).path() != null; |
| if (hasJoins) { |
| if (_preJoins == null) |
| _preJoins = new Stack(); |
| _preJoins.push(joins); |
| } |
| |
| Object obj = super.load(mapping, store, fetch, joins); |
| |
| // reset |
| if (hasJoins) |
| _preJoins.pop(); |
| return obj; |
| } |
| |
| @Override |
| public Joins newJoins() { |
| PathJoins pre = getPreJoins(); |
| if (pre == null || pre.path() == null) |
| return this; |
| |
| PathJoinsImpl pj = new PathJoinsImpl(); |
| pj.path = new StringBuilder(pre.path().toString()); |
| return pj; |
| } |
| |
| @Override |
| protected boolean containsInternal(Object obj, Joins joins) { |
| // we key directly on objs and join-less cols, or on the alias |
| // for cols with joins |
| PathJoins pj = getJoins(joins); |
| if (pj != null && pj.path() != null) { |
| Object columnAlias = getColumnAlias((Column) obj, pj); |
| if (joins == null) { |
| if (cachedColumnAlias_ == null) { |
| cachedColumnAlias_ = new HashMap<>(); |
| } |
| cachedColumnAlias_.put(new CachedColumnAliasKey((Column) obj, pj), columnAlias); |
| } |
| return columnAlias != null && _sel._selects.contains(columnAlias); |
| } |
| return obj != null && _sel._selects.contains(obj); |
| } |
| |
| @Override |
| protected boolean containsAllInternal(Object[] objs, Joins joins) |
| throws SQLException { |
| PathJoins pj = getJoins(joins); |
| Object obj; |
| for (int i = 0; i < objs.length; i++) { |
| if (pj != null && pj.path() != null) |
| obj = getColumnAlias((Column) objs[i], pj); |
| else |
| obj = objs[i]; |
| if (obj == null || !_sel._selects.contains(obj)) |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public void pushBack() |
| throws SQLException { |
| _pos = 0; |
| super.pushBack(); |
| } |
| |
| @Override |
| protected boolean absoluteInternal(int row) |
| throws SQLException { |
| _pos = 0; |
| return super.absoluteInternal(row); |
| } |
| |
| @Override |
| protected boolean nextInternal() |
| throws SQLException { |
| _pos = 0; |
| return super.nextInternal(); |
| } |
| |
| @Override |
| protected int findObject(Object obj, Joins joins) |
| throws SQLException { |
| Object orig = obj; |
| if (_pos == _sel._selects.size()) |
| _pos = 0; |
| |
| // we key directly on objs and join-less cols, or on the alias |
| // for cols with joins |
| PathJoins pj = getJoins(joins); |
| Boolean pk = null; |
| if (pj != null && pj.path() != null) { |
| Column col = (Column) obj; |
| pk = (col.isPrimaryKey()) ? Boolean.TRUE : Boolean.FALSE; |
| if (joins == null && cachedColumnAlias_ != null) { |
| obj = cachedColumnAlias_.get(new CachedColumnAliasKey((Column) obj, pj)); |
| if (obj == null) { |
| obj = getColumnAlias(col, pj); |
| } |
| } else { |
| obj = getColumnAlias(col, pj); |
| } |
| if (obj == null) |
| throw new SQLException(col.getTable() + ": " |
| + pj.path() + " (" + _sel._aliases + ")"); |
| } |
| |
| // we load in the same order we select, more or less... |
| if (_sel._selects.get(_pos).equals(obj)) |
| return ++_pos; |
| |
| // if we're looking for a primary key, try back a couple places, |
| // since pks might be selected in a slightly different order than |
| // they are loaded back; don't change the marker position |
| if (pk == null) |
| pk = (obj instanceof Column && ((Column) obj).isPrimaryKey()) |
| ? Boolean.TRUE : Boolean.FALSE; |
| if (pk) { |
| for (int i = _pos - 1; i >= 0 && i >= _pos - 3; i--) |
| if (_sel._selects.get(i).equals(obj)) |
| return i + 1; |
| } |
| |
| // search forward on the assumption that we might be skipping |
| // selects for sibling classes; advance the position if we find |
| // something forward |
| for (int i = _pos + 1; i < _sel._selects.size(); i++) { |
| if (_sel._selects.get(i).equals(obj)) { |
| _pos = i; |
| return ++_pos; |
| } |
| } |
| |
| // maybe the column was selected by 2 different mappings, so it's |
| // somewhere prior to the current position; in this case leave the |
| // position marker at its current place cause subsequent loads will |
| // still probably start from there |
| for (int i = 0; i < _pos; i++) |
| if (_sel._selects.get(i).equals(obj)) |
| return i + 1; |
| |
| // somethings's wrong... |
| throw new SQLException(obj.toString()); |
| } |
| |
| /** |
| * Return the joins to use to find column data. |
| */ |
| private PathJoins getJoins(Joins joins) { |
| PathJoins pj = (PathJoins) joins; |
| if (pj != null && pj.path() != null) |
| return pj; |
| return getPreJoins(); |
| } |
| |
| /** |
| * Return the pre joins for the result, or null if none. Note that |
| * we have to take the Select's pre joins into account too, since |
| * batched selects can have additional pre joins on the stack even |
| * on execution. |
| */ |
| private PathJoins getPreJoins() { |
| if (_preJoins != null && !_preJoins.isEmpty()) |
| return (PathJoins) _preJoins.peek(); |
| if (_sel._preJoins != null && !_sel._preJoins.isEmpty()) |
| return (PathJoins) _sel._preJoins.peek(); |
| return null; |
| } |
| |
| /** |
| * Return the alias used to key on the column data, considering the |
| * given joins. |
| */ |
| String getColumnAlias(Column col, PathJoins pj) { |
| String alias; |
| if (_sel._from != null) { |
| alias = SelectImpl.toAlias(_sel._from.getTableIndex |
| (col.getTable(), pj, false)); |
| if (alias == null) |
| return null; |
| if (_sel._dict.requiresAliasForSubselect) |
| return FROM_SELECT_ALIAS + "." + alias + "_" + col; |
| return alias + "_" + col; |
| } |
| alias = SelectImpl.toAlias(_sel.getTableIndex(col.getTable(), pj, false)); |
| return (alias == null) ? null : alias + "." + _sel._dict.getNamingUtil().toDBName(col.toString()); |
| } |
| |
| //////////////////////////// |
| // PathJoins implementation |
| //////////////////////////// |
| |
| @Override |
| public boolean isOuter() { |
| return false; |
| } |
| |
| @Override |
| public PathJoins setOuter(boolean outer) { |
| return this; |
| } |
| |
| @Override |
| public boolean isDirty() { |
| return false; |
| } |
| |
| @Override |
| public StringBuilder path() { |
| return null; |
| } |
| |
| @Override |
| public String getPathStr() { |
| return null; |
| } |
| |
| @Override |
| public JoinSet joins() { |
| return null; |
| } |
| |
| @Override |
| public int joinCount() { |
| return 0; |
| } |
| |
| @Override |
| public void nullJoins() { |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return true; |
| } |
| |
| @Override |
| public Joins crossJoin(Table localTable, Table foreignTable) { |
| return this; |
| } |
| |
| @Override |
| public Joins join(ForeignKey fk, boolean inverse, boolean toMany) { |
| return this; |
| } |
| |
| @Override |
| public Joins outerJoin(ForeignKey fk, boolean inverse, boolean toMany) { |
| return this; |
| } |
| |
| @Override |
| public Joins joinRelation(String name, ForeignKey fk, |
| ClassMapping target, int subs, boolean inverse, boolean toMany) { |
| return new PathJoinsImpl().joinRelation(name, fk, target, subs, |
| inverse, toMany); |
| } |
| |
| @Override |
| public Joins outerJoinRelation(String name, ForeignKey fk, |
| ClassMapping target, int subs, boolean inverse, boolean toMany) { |
| return new PathJoinsImpl().outerJoinRelation(name, fk, target, subs, |
| inverse, toMany); |
| } |
| |
| @Override |
| public Joins setVariable(String var) { |
| if (var == null) |
| return this; |
| return new PathJoinsImpl().setVariable(var); |
| } |
| |
| @Override |
| public Joins setSubselect(String alias) { |
| if (alias == null) |
| return this; |
| return new PathJoinsImpl().setSubselect(alias); |
| } |
| |
| @Override |
| public Joins setCorrelatedVariable(String var) { |
| return this; |
| } |
| |
| @Override |
| public Joins setJoinContext(Context ctx) { |
| return this; |
| } |
| |
| @Override |
| public String getCorrelatedVariable() { |
| return null; |
| } |
| |
| @Override |
| public void moveJoinsToParent() { |
| } |
| |
| private static final class CachedColumnAliasKey { |
| private final Column col; |
| private final PathJoins pjs; |
| |
| public CachedColumnAliasKey(Column c, PathJoins p) { |
| col = c; |
| pjs = p; |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + ((col == null) ? 0 : col.hashCode()); |
| result = prime * result + ((pjs == null) ? 0 : pjs.hashCode()); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) |
| return true; |
| if (obj == null) |
| return false; |
| if (getClass() != obj.getClass()) |
| return false; |
| CachedColumnAliasKey other = (CachedColumnAliasKey) obj; |
| if (col == null) { |
| if (other.col != null) |
| return false; |
| } else if (!col.equals(other.col)) |
| return false; |
| if (pjs == null) { |
| if (other.pjs != null) |
| return false; |
| } else if (!pjs.equals(other.pjs)) |
| return false; |
| return true; |
| } |
| |
| } |
| } |
| |
| /** |
| * Base joins implementation. |
| */ |
| private static class PathJoinsImpl |
| implements PathJoins { |
| |
| protected StringBuilder path = null; |
| protected String var = null; |
| protected String correlatedVar = null; |
| protected Context context = null; |
| protected Context lastContext = null; |
| protected String pathStr = null; |
| |
| @Override |
| public Select getSelect() { |
| return null; |
| } |
| |
| @Override |
| public boolean isOuter() { |
| return false; |
| } |
| |
| @Override |
| public PathJoins setOuter(boolean outer) { |
| return this; |
| } |
| |
| @Override |
| public boolean isDirty() { |
| return var != null || path != null; |
| } |
| |
| @Override |
| public StringBuilder path() { |
| return path; |
| } |
| |
| @Override |
| public JoinSet joins() { |
| return null; |
| } |
| |
| @Override |
| public int joinCount() { |
| return 0; |
| } |
| |
| @Override |
| public void nullJoins() { |
| } |
| |
| @Override |
| public Joins setVariable(String var) { |
| this.var = var; |
| return this; |
| } |
| |
| public String getVariable() { |
| return var; |
| } |
| |
| @Override |
| public Joins setCorrelatedVariable(String var) { |
| this.correlatedVar = var; |
| return this; |
| } |
| |
| @Override |
| public String getCorrelatedVariable() { |
| return correlatedVar; |
| } |
| |
| @Override |
| public Joins setJoinContext(Context context) { |
| this.context = context; |
| return this; |
| } |
| |
| @Override |
| public Joins setSubselect(String alias) { |
| append(alias); |
| return this; |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return true; |
| } |
| |
| @Override |
| public Joins crossJoin(Table localTable, Table foreignTable) { |
| append(var); |
| var = null; |
| return this; |
| } |
| |
| @Override |
| public Joins join(ForeignKey fk, boolean inverse, boolean toMany) { |
| append(var); |
| var = null; |
| return this; |
| } |
| |
| @Override |
| public Joins outerJoin(ForeignKey fk, boolean inverse, boolean toMany) { |
| append(var); |
| var = null; |
| return this; |
| } |
| |
| @Override |
| public Joins joinRelation(String name, ForeignKey fk, |
| ClassMapping target, int subs, boolean inverse, boolean toMany) { |
| append(name); |
| append(var); |
| var = null; |
| return this; |
| } |
| |
| @Override |
| public Joins outerJoinRelation(String name, ForeignKey fk, |
| ClassMapping target, int subs, boolean inverse, boolean toMany) { |
| append(name); |
| append(var); |
| var = null; |
| return this; |
| } |
| |
| protected void append(String str) { |
| if (str != null) { |
| if (path == null) |
| path = new StringBuilder(str); |
| else |
| path.append('.').append(str); |
| pathStr = null; |
| } |
| } |
| |
| @Override |
| public String getPathStr() { |
| if (pathStr == null) { |
| pathStr = path.toString(); |
| } |
| return pathStr; |
| } |
| |
| @Override |
| public String toString() { |
| return "PathJoinsImpl<" + hashCode() + ">: " |
| + path; |
| } |
| |
| @Override |
| public void moveJoinsToParent() { |
| } |
| } |
| |
| /** |
| * Joins implementation. |
| */ |
| private static class SelectJoins |
| extends PathJoinsImpl |
| implements Cloneable { |
| |
| private final SelectImpl _sel; |
| private JoinSet _joins = null; |
| private boolean _outer = false; |
| private int _count = 0; |
| |
| public SelectJoins(SelectImpl sel) { |
| _sel = sel; |
| } |
| |
| @Override |
| public Select getSelect() { |
| return _sel; |
| } |
| |
| @Override |
| public boolean isOuter() { |
| return _outer; |
| } |
| |
| @Override |
| public PathJoins setOuter(boolean outer) { |
| _outer = outer; |
| return this; |
| } |
| |
| @Override |
| public boolean isDirty() { |
| return super.isDirty() || !isEmpty(); |
| } |
| |
| @Override |
| public JoinSet joins() { |
| return _joins; |
| } |
| |
| public void setJoins(JoinSet joins) { |
| _joins = joins; |
| _outer = joins != null && joins.last() != null |
| && joins.last().getType() == Join.TYPE_OUTER; |
| } |
| |
| @Override |
| public int joinCount() { |
| if (_joins == null) |
| return _count; |
| return Math.max(_count, _joins.size()); |
| } |
| |
| @Override |
| public void nullJoins() { |
| if (_joins != null) |
| _count = Math.max(_count, _joins.size()); |
| _joins = null; |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return _joins == null || _joins.isEmpty(); |
| } |
| |
| @Override |
| public Joins crossJoin(Table localTable, Table foreignTable) { |
| // cross joins are for unbound variables; unfortunately we have |
| // to always go DISTINCT for unbound vars because there are certain |
| // cases that require it, and we can't differentiate them from the |
| // cases that don't |
| _sel._flags |= IMPLICIT_DISTINCT; |
| |
| if (_sel.getJoinSyntax() != JoinSyntaxes.SYNTAX_SQL92 |
| || _sel._from != null) { |
| // don't make any joins, but update the path if a variable |
| // has been set |
| if (this.var != null) { |
| this.append(this.var); |
| } else if (this.path == null && this.correlatedVar != null && _sel._dict.isImplicitJoin()) { |
| String str = this.var; |
| for(Object o : _sel._parent._aliases.keySet()){ |
| if (o instanceof Key) { |
| Key k = (Key) o; |
| if (this.correlatedVar.equals(k._path)) { |
| str = this.correlatedVar; |
| break; |
| } |
| }else if (o.equals(this.correlatedVar)){ |
| str = this.correlatedVar; |
| break; |
| } |
| } |
| this.append(str); |
| } |
| this.var = null; |
| _outer = false; |
| return this; |
| } |
| |
| // don't let the get alias methods see that a var has been set |
| // until we get past the local table |
| String var = this.var; |
| this.var = null; |
| Context ctx = context; |
| context = null; |
| |
| int alias1 = _sel.getTableIndex(localTable, this, true); |
| this.append(var); |
| this.append(correlatedVar); |
| context = ctx; |
| |
| int alias2 = _sel.getTableIndex(foreignTable, this, true); |
| Join j = new Join(localTable, alias1, foreignTable, alias2, |
| null, false); |
| j.setType(Join.TYPE_CROSS); |
| |
| if (_joins == null) |
| _joins = new JoinSet(); |
| _joins.add(j); |
| setCorrelated(j); |
| _outer = false; |
| lastContext = context; |
| context = null; |
| return this; |
| } |
| |
| @Override |
| public Joins join(ForeignKey fk, boolean inverse, boolean toMany) { |
| return join(null, fk, null, -1, inverse, toMany, false); |
| } |
| |
| @Override |
| public Joins outerJoin(ForeignKey fk, boolean inverse, boolean toMany) { |
| return join(null, fk, null, -1, inverse, toMany, true); |
| } |
| |
| @Override |
| public Joins joinRelation(String name, ForeignKey fk, |
| ClassMapping target, int subs, boolean inverse, boolean toMany) { |
| return join(name, fk, target, subs, inverse, toMany, false); |
| } |
| |
| @Override |
| public Joins outerJoinRelation(String name, ForeignKey fk, |
| ClassMapping target, int subs, boolean inverse, boolean toMany) { |
| return join(name, fk, target, subs, inverse, toMany, true); |
| } |
| |
| private Joins join(String name, ForeignKey fk, ClassMapping target, |
| int subs, boolean inverse, boolean toMany, boolean outer) { |
| // don't let the get alias methods see that a var has been set |
| // until we get past the local table |
| String var = this.var; |
| this.var = null; |
| Context ctx = context; |
| context = null; |
| |
| // get first table alias before updating path; if there is a from |
| // select then we shouldn't actually create a join object, since |
| // the joins will all be done in the from select |
| boolean createJoin = _sel._from == null; |
| Table table1 = null; |
| int alias1 = -1; |
| if (createJoin) { |
| boolean createIndex = true; |
| table1 = (inverse) ? fk.getPrimaryKeyTable() : fk.getTable(); |
| if (correlatedVar != null) |
| createIndex = false; // not to create here |
| alias1 = _sel.getTableIndex(table1, this, createIndex); |
| } |
| |
| // update the path with the relation name before getting pk alias |
| this.append(name); |
| this.append(var); |
| if (var == null) |
| this.append(correlatedVar); |
| context = ctx; |
| |
| if (toMany) { |
| _sel._flags |= IMPLICIT_DISTINCT; |
| _sel._flags |= TO_MANY; |
| } |
| _outer = outer; |
| |
| if (createJoin) { |
| boolean createIndex = true; |
| Table table2 = (inverse) ? fk.getTable() |
| : fk.getPrimaryKeyTable(); |
| boolean created = false; |
| int alias2 = -1; |
| if (table2.isAssociation()) { |
| alias2 = _sel.getTableIndex(table2, this, false); |
| if (alias2 == -1) |
| createIndex = true; |
| else |
| created = true; |
| } |
| else if (context == _sel.ctx()) |
| createIndex = true; |
| else if (correlatedVar != null) |
| createIndex = false; |
| if (!created) |
| alias2 = _sel.getTableIndex(table2, this, createIndex); |
| Join j = new Join(table1, alias1, table2, alias2, fk, inverse); |
| j.setType((outer) ? Join.TYPE_OUTER : Join.TYPE_INNER); |
| |
| if (_joins == null) |
| _joins = new JoinSet(); |
| if (_joins.add(j) && (subs == Select.SUBS_JOINABLE |
| || subs == Select.SUBS_NONE)) |
| j.setRelation(target, subs, clone(_sel)); |
| |
| setCorrelated(j); |
| } |
| lastContext = context; |
| context = null; |
| return this; |
| } |
| |
| private void setCorrelated(Join j) { |
| if (_sel._parent == null) |
| return; |
| |
| if (_sel._aliases == null) { |
| j.setIsNotMyJoin(); |
| return; |
| } |
| |
| Object aliases[] = _sel._aliases.values().toArray(); |
| boolean found1 = false; |
| boolean found2 = false; |
| |
| for (int i = 0; i < aliases.length; i++) { |
| int alias = (Integer) aliases[i]; |
| if (alias == j.getIndex1()) |
| found1 = true; |
| if (alias == j.getIndex2()) |
| found2 = true; |
| } |
| |
| if (found1 && found2) |
| return; |
| else if (!found1 && !found2) { |
| j.setIsNotMyJoin(); |
| return; |
| } |
| else { |
| j.setCorrelated(); |
| } |
| } |
| |
| @Override |
| public void moveJoinsToParent() { |
| if (_joins == null) |
| return; |
| Join j = null; |
| List<Join> removed = new ArrayList<>(5); |
| for (Iterator itr = _joins.iterator(); itr.hasNext();) { |
| j = (Join) itr.next(); |
| if (j.isNotMyJoin()) { |
| addJoinsToParent(_sel._parent, j); |
| removed.add(j); |
| } |
| } |
| for (Join join : removed) { |
| _joins.remove(join); |
| } |
| } |
| |
| private void addJoinsToParent(SelectImpl parent, Join join) { |
| if (parent._aliases == null) |
| return; |
| Object aliases[] = parent._aliases.values().toArray(); |
| boolean found1 = false; |
| boolean found2 = false; |
| |
| for (int i = 0; i < aliases.length; i++) { |
| int alias = (Integer) aliases[i]; |
| if (alias == join.getIndex1()) |
| found1 = true; |
| if (alias == join.getIndex2()) |
| found2 = true; |
| } |
| |
| if (found1 && found2) { |
| // this is my join, add join |
| if (parent._joins == null) |
| parent._joins = new SelectJoins(parent); |
| SelectJoins p = parent._joins; |
| if (p.joins() == null) |
| p.setJoins(new JoinSet()); |
| p.joins().add(join); |
| } |
| else if (parent._parent != null) |
| addJoinsToParent(parent._parent, join); |
| } |
| |
| public SelectJoins clone(SelectImpl sel) { |
| SelectJoins sj = new SelectJoins(sel); |
| sj.var = var; |
| if (path != null) |
| sj.path = new StringBuilder(path.toString()); |
| if (_joins != null && !_joins.isEmpty()) |
| sj._joins = new JoinSet(_joins); |
| sj._outer = _outer; |
| return sj; |
| } |
| |
| @Override |
| public String toString() { |
| return super.toString() + " (" + _outer + "): " + _joins; |
| } |
| } |
| |
| protected Selects newSelects() { |
| return new Selects(); |
| } |
| |
| @Override |
| public DBDictionary getDictionary() { |
| return _dict; |
| } |
| |
| /** |
| * Helper class to track selected columns, with fast contains method. |
| * Acts as a list of select ids, with additional methods to manipulate |
| * the alias of each selected id. |
| */ |
| protected static class Selects |
| extends AbstractList { |
| |
| protected List _ids = null; |
| protected List _idents = null; |
| protected Map _aliases = null; |
| protected Map _selectAs = null; |
| protected DBDictionary _dict = null; |
| |
| /** |
| * Add all aliases from another instance. |
| */ |
| public void addAll(Selects sels) { |
| if (_ids == null && sels._ids != null) |
| _ids = new ArrayList(sels._ids); |
| else if (sels._ids != null) |
| _ids.addAll(sels._ids); |
| |
| if (_idents == null && sels._idents != null) |
| _idents = new ArrayList(sels._idents); |
| else if (sels._idents != null) |
| _idents.addAll(sels._idents); |
| |
| if (_aliases == null && sels._aliases != null) |
| _aliases = new HashMap(sels._aliases); |
| else if (sels._aliases != null) |
| _aliases.putAll(sels._aliases); |
| |
| if (_selectAs == null && sels._selectAs != null) |
| _selectAs = new HashMap(sels._selectAs); |
| else if (sels._selectAs != null) |
| _selectAs.putAll(sels._selectAs); |
| } |
| |
| /** |
| * Returns the alias of a given id. |
| */ |
| public Object getAlias(Object id) { |
| return (_aliases == null) ? null : _aliases.get(id); |
| } |
| |
| /** |
| * Set an alias for a given id. |
| */ |
| public int setAlias(Object id, Object alias, boolean ident) { |
| if (_ids == null) { |
| _ids = new ArrayList(); |
| _aliases = new HashMap(); |
| } |
| |
| int idx; |
| if (_aliases.put(id, alias) != null) |
| idx = _ids.indexOf(id); |
| else { |
| _ids.add(id); |
| idx = _ids.size() - 1; |
| |
| if (ident) { |
| if (_idents == null) |
| _idents = new ArrayList(3); |
| _idents.add(id); |
| } |
| } |
| return idx; |
| } |
| |
| /** |
| * Set an alias for a given index. |
| */ |
| public void setAlias(int idx, Object alias) { |
| Object id = _ids.get(idx); |
| _aliases.put(id, alias); |
| } |
| |
| /** |
| * Insert an alias before the given index, using negative indexes |
| * to count backwards. |
| */ |
| public void insertAlias(int idx, Object id, Object alias) { |
| _aliases.put(id, alias); |
| if (idx >= 0) |
| _ids.add(idx, id); |
| else |
| _ids.add(_ids.size() + idx, id); |
| } |
| |
| /** |
| * Return the index of the given alias. |
| */ |
| public int indexOfAlias(Object alias) { |
| if (_aliases == null) |
| return -1; |
| for (int i = 0; i < _ids.size(); i++) |
| if (alias.equals(_aliases.get(_ids.get(i)))) |
| return i; |
| return -1; |
| } |
| |
| /** |
| * A list representation of the aliases, in select order, with |
| * AS aliases present. |
| */ |
| public List getAliases(final boolean ident, final boolean inner) { |
| if (_ids == null) |
| return Collections.EMPTY_LIST; |
| |
| return new AbstractList() { |
| @Override |
| public int size() { |
| return (ident && _idents != null) ? _idents.size() |
| : _ids.size(); |
| } |
| |
| @Override |
| public Object get(int i) { |
| Object id = (ident && _idents != null) ? _idents.get(i) |
| : _ids.get(i); |
| Object alias = _aliases.get(id); |
| if (id instanceof Column && ((Column) id).isXML()) |
| alias = alias + _dict.getStringVal; |
| |
| String as = null; |
| if (inner) { |
| if (alias instanceof String) |
| as = ((String) alias).replace('.', '_'); |
| } else if (_selectAs != null) |
| as = (String) _selectAs.get(id); |
| else if (id instanceof Value) |
| as = ((Value) id).getAlias(); |
| |
| if (as != null) { |
| if (ident && _idents != null) |
| return as; |
| if (alias instanceof SQLBuffer) |
| alias = new SQLBuffer((SQLBuffer) alias). |
| append(" AS ").append(as); |
| else |
| alias = alias + " AS " + as; |
| } |
| return alias; |
| } |
| }; |
| } |
| |
| /** |
| * Set that a given id's alias has an AS value. |
| */ |
| public void setSelectAs(Object id, String as) { |
| if (_selectAs == null) |
| _selectAs = new HashMap((int) (5 * 1.33 + 1)); |
| _selectAs.put(id, as); |
| } |
| |
| /** |
| * Clear all placeholders and select AS clauses. |
| */ |
| public void clearPlaceholders() { |
| if (_ids == null) |
| return; |
| |
| Object id; |
| for (Iterator itr = _ids.iterator(); itr.hasNext();) { |
| id = itr.next(); |
| if (id instanceof Placeholder) { |
| itr.remove(); |
| _aliases.remove(id); |
| } |
| } |
| } |
| |
| @Override |
| public boolean contains(Object id) { |
| return _aliases != null && _aliases.containsKey(id); |
| } |
| |
| @Override |
| public Object get(int i) { |
| if (_ids == null) |
| throw new ArrayIndexOutOfBoundsException(); |
| return _ids.get(i); |
| } |
| |
| @Override |
| public int size() { |
| return (_ids == null) ? 0 : _ids.size(); |
| } |
| |
| @Override |
| public void clear() { |
| _ids = null; |
| _aliases = null; |
| _selectAs = null; |
| _idents = null; |
| } |
| } |
| |
| @Override |
| public Joins setCorrelatedVariable(String var) { |
| if (var == null) |
| return this; |
| return new SelectJoins(this).setCorrelatedVariable(var); |
| } |
| |
| @Override |
| public Joins setJoinContext(Context ctx) { |
| if (ctx == null) |
| return this; |
| return new SelectJoins(this).setJoinContext(ctx); |
| } |
| |
| @Override |
| public String getCorrelatedVariable() { |
| return null; |
| } |
| |
| @Override |
| public void moveJoinsToParent() { |
| } |
| } |
| |
| /** |
| * Common joins interface used internally. Cannot be made an inner class |
| * because the outer class (Select) has to implement it. |
| */ |
| interface PathJoins |
| extends Joins { |
| |
| /** |
| * Mark this as an outer joins set. |
| */ |
| PathJoins setOuter(boolean outer); |
| |
| /** |
| * Return true if this instance has a path, any joins, or a variable. |
| */ |
| boolean isDirty(); |
| |
| /** |
| * Return the relation path traversed by these joins, or null if none. |
| */ |
| StringBuilder path(); |
| |
| /** |
| * Return the set of {@link Join} elements, or null if none. |
| */ |
| JoinSet joins(); |
| |
| /** |
| * Return the maximum number of joins contained in this instance at any |
| * time. |
| */ |
| int joinCount(); |
| |
| /** |
| * Null the set of {@link Join} elements. |
| */ |
| void nullJoins(); |
| |
| /** |
| * The select owner of this join |
| */ |
| Select getSelect(); |
| |
| String getPathStr(); |
| } |
| |