/*****************************************************************
* 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.cayenne.access.translator.select;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.access.jdbc.ColumnDescriptor;
import org.apache.cayenne.access.translator.DbAttributeBinding;
import org.apache.cayenne.dba.DbAdapter;
import org.apache.cayenne.dba.QuotingStrategy;
import org.apache.cayenne.dba.TypesMapping;
import org.apache.cayenne.exp.Expression;
import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.exp.Property;
import org.apache.cayenne.exp.TraversalHelper;
import org.apache.cayenne.exp.parser.ASTAggregateFunctionCall;
import org.apache.cayenne.exp.parser.ASTDbPath;
import org.apache.cayenne.map.DataMap;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.map.DbJoin;
import org.apache.cayenne.map.DbRelationship;
import org.apache.cayenne.map.EntityResolver;
import org.apache.cayenne.map.JoinType;
import org.apache.cayenne.map.ObjAttribute;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.map.ObjRelationship;
import org.apache.cayenne.map.PathComponent;
import org.apache.cayenne.query.PrefetchSelectQuery;
import org.apache.cayenne.query.PrefetchTreeNode;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.query.SelectQuery;
import org.apache.cayenne.reflect.ArcProperty;
import org.apache.cayenne.reflect.AttributeProperty;
import org.apache.cayenne.reflect.ClassDescriptor;
import org.apache.cayenne.reflect.PropertyVisitor;
import org.apache.cayenne.reflect.ToManyProperty;
import org.apache.cayenne.reflect.ToOneProperty;
import org.apache.cayenne.util.CayenneMapEntry;
import org.apache.cayenne.util.EqualsBuilder;
import org.apache.cayenne.util.HashCodeBuilder;
import java.sql.Types;
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;
/**
* @since 4.0
*/
public class DefaultSelectTranslator extends QueryAssembler implements SelectTranslator {
protected static final int[] UNSUPPORTED_DISTINCT_TYPES = { Types.BLOB, Types.CLOB, Types.NCLOB,
Types.LONGVARBINARY, Types.LONGVARCHAR, Types.LONGNVARCHAR };
protected static boolean isUnsupportedForDistinct(int type) {
for (int unsupportedDistinctType : UNSUPPORTED_DISTINCT_TYPES) {
if (unsupportedDistinctType == type) {
return true;
}
}
return false;
}
JoinStack joinStack;
List<ColumnDescriptor> resultColumns;
Map<ObjAttribute, ColumnDescriptor> attributeOverrides;
Map<ColumnDescriptor, ObjAttribute> defaultAttributesByColumn;
boolean suppressingDistinct;
/**
* If set to <code>true</code>, indicates that distinct select query is
* required no matter what the original query settings where. This flag can
* be set when joins are created using "to-many" relationships.
*/
boolean forcingDistinct;
/**
* Does this SQL have any aggregate function
*/
boolean haveAggregate;
Map<ColumnDescriptor, List<DbAttributeBinding>> groupByColumns;
/**
* Callback for joins creation
*/
AddJoinListener joinListener;
public DefaultSelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) {
super(query, adapter, entityResolver);
}
protected JoinStack getJoinStack() {
if (joinStack == null) {
joinStack = createJoinStack();
}
return joinStack;
}
protected JoinStack createJoinStack() {
return new JoinStack(getAdapter(), this);
}
@Override
protected void doTranslate() {
DataMap dataMap = queryMetadata.getDataMap();
JoinStack joins = getJoinStack();
QuotingStrategy strategy = getAdapter().getQuotingStrategy();
forcingDistinct = false;
// build column list
this.resultColumns = buildResultColumns();
// build qualifier
QualifierTranslator qualifierTranslator = adapter.getQualifierTranslator(this);
StringBuilder whereQualifierBuffer = qualifierTranslator.appendPart(new StringBuilder());
// build having qualifier
Expression havingQualifier = ((SelectQuery)query).getHavingQualifier();
StringBuilder havingQualifierBuffer = null;
if(havingQualifier != null) {
haveAggregate = true;
QualifierTranslator havingQualifierTranslator = adapter.getQualifierTranslator(this);
havingQualifierTranslator.setQualifier(havingQualifier);
havingQualifierBuffer = havingQualifierTranslator.appendPart(new StringBuilder());
}
if(!haveAggregate && groupByColumns != null) {
// if no expression with aggregation function found
// in select columns and there is no having clause
groupByColumns.clear();
}
// build ORDER BY
OrderingTranslator orderingTranslator = new OrderingTranslator(this);
StringBuilder orderingBuffer = orderingTranslator.appendPart(new StringBuilder());
// assemble
StringBuilder queryBuf = new StringBuilder();
queryBuf.append("SELECT ");
// check if DISTINCT is appropriate
// side effect: "suppressingDistinct" flag may end up being flipped here
if (forcingDistinct || getSelectQuery().isDistinct()) {
suppressingDistinct = queryMetadata.isSuppressingDistinct();
if(!suppressingDistinct) {
for (ColumnDescriptor column : resultColumns) {
if (isUnsupportedForDistinct(column.getJdbcType())) {
suppressingDistinct = true;
break;
}
}
}
if (!suppressingDistinct) {
queryBuf.append(buildDistinctStatement()).append(" ");
}
}
// convert ColumnDescriptors to column names
List<String> selectColumnExpList = new ArrayList<>();
for (ColumnDescriptor column : resultColumns) {
String fullName;
if(column.isExpression()) {
fullName = column.getName();
} else {
fullName = strategy.quotedIdentifier(dataMap, column.getNamePrefix(), column.getName());
}
selectColumnExpList.add(fullName);
}
// append any column expressions used in the order by if this query
// uses the DISTINCT modifier
if (forcingDistinct || getSelectQuery().isDistinct()) {
List<String> orderByColumnList = orderingTranslator.getOrderByColumnList();
for (String orderByColumnExp : orderByColumnList) {
// Convert to ColumnDescriptors??
if (!selectColumnExpList.contains(orderByColumnExp)) {
selectColumnExpList.add(orderByColumnExp);
}
}
}
appendSelectColumns(queryBuf, selectColumnExpList);
// append from clause
queryBuf.append(" FROM ");
// append tables and joins
joins.appendRootWithQuoteSqlIdentifiers(queryBuf, getQueryMetadata().getDbEntity());
joins.appendJoins(queryBuf);
joins.appendQualifier(whereQualifierBuffer, whereQualifierBuffer.length() == 0);
// append qualifier
if (whereQualifierBuffer.length() > 0) {
queryBuf.append(" WHERE ");
queryBuf.append(whereQualifierBuffer);
}
if(groupByColumns != null && !groupByColumns.isEmpty()) {
queryBuf.append(" GROUP BY ");
appendGroupByColumns(queryBuf, groupByColumns);
}
// append HAVING qualifier
if(havingQualifierBuffer != null && havingQualifierBuffer.length() > 0) {
queryBuf.append(" HAVING ");
queryBuf.append(havingQualifierBuffer);
}
// append prebuilt ordering
if (orderingBuffer.length() > 0) {
queryBuf.append(" ORDER BY ").append(orderingBuffer);
}
if (!isSuppressingDistinct()) {
appendLimitAndOffsetClauses(queryBuf);
}
this.sql = queryBuf.toString();
}
/**
* Allows subclasses to insert their own dialect of DISTINCT statement to
* improve performance.
*
* @return string representing the DISTINCT statement
* @since 4.0
*/
protected String buildDistinctStatement() {
return "DISTINCT";
}
/**
* @since 3.1
*/
protected void appendSelectColumns(StringBuilder buffer, List<String> selectColumnExpList) {
// append columns (unroll the loop's first element)
int columnCount = selectColumnExpList.size();
buffer.append(selectColumnExpList.get(0));
// assume there is at least 1 element
for (int i = 1; i < columnCount; i++) {
buffer.append(", ");
buffer.append(selectColumnExpList.get(i));
}
}
/**
* Append columns to GROUP BY clause
* @since 4.0
*/
protected void appendGroupByColumns(StringBuilder buffer, Map<ColumnDescriptor, List<DbAttributeBinding>> groupByColumns) {
Iterator<Map.Entry<ColumnDescriptor, List<DbAttributeBinding>>> it = groupByColumns.entrySet().iterator();
Map.Entry<ColumnDescriptor, List<DbAttributeBinding>> entry = it.next();
appendGroupByColumn(buffer, entry);
while(it.hasNext()) {
entry = it.next();
buffer.append(", ");
appendGroupByColumn(buffer, entry);
}
}
/**
* Append single column to GROUP BY clause
* @since 4.0
*/
protected void appendGroupByColumn(StringBuilder buffer, Map.Entry<ColumnDescriptor, List<DbAttributeBinding>> entry) {
String fullName;
if(entry.getKey().isExpression()) {
fullName = entry.getKey().getDataRowKey();
} else {
QuotingStrategy strategy = getAdapter().getQuotingStrategy();
fullName = strategy.quotedIdentifier(queryMetadata.getDataMap(),
entry.getKey().getNamePrefix(), entry.getKey().getName());
}
buffer.append(fullName);
if(entry.getKey().getDataRowKey().equals(entry.getKey().getName())) {
for (DbAttributeBinding binding : entry.getValue()) {
addToParamList(binding.getAttribute(), binding.getValue());
}
}
}
/**
* Handles appending optional limit and offset clauses. This implementation
* does nothing, deferring to subclasses to define the LIMIT/OFFSET clause
* syntax.
*
* @since 3.0
*/
protected void appendLimitAndOffsetClauses(StringBuilder buffer) {
}
@Override
public String getCurrentAlias() {
return getJoinStack().getCurrentAlias();
}
/**
* Returns a list of ColumnDescriptors for the query columns.
*
* @since 1.2
*/
public ColumnDescriptor[] getResultColumns() {
if (resultColumns == null || resultColumns.isEmpty()) {
return new ColumnDescriptor[0];
}
return resultColumns.toArray(new ColumnDescriptor[resultColumns.size()]);
}
/**
* Returns a map of ColumnDescriptors keyed by ObjAttribute for columns that
* may need to be reprocessed manually due to incompatible mappings along
* the inheritance hierarchy.
*
* @since 1.2
*/
public Map<ObjAttribute, ColumnDescriptor> getAttributeOverrides() {
if (attributeOverrides != null) {
return attributeOverrides;
} else {
return Collections.emptyMap();
}
}
/**
* Returns true if SelectTranslator determined that a query requiring
* DISTINCT can't be run with DISTINCT keyword for internal reasons. If this
* method returns true, DataNode may need to do in-memory distinct
* filtering.
*
* @since 1.1
*/
public boolean isSuppressingDistinct() {
return suppressingDistinct;
}
private SelectQuery<?> getSelectQuery() {
return (SelectQuery<?>) getQuery();
}
protected List<ColumnDescriptor> buildResultColumns() {
this.defaultAttributesByColumn = new HashMap<>();
List<ColumnDescriptor> columns = new ArrayList<>();
SelectQuery<?> query = getSelectQuery();
if(query.getColumns() != null && !query.getColumns().isEmpty()) {
appendOverriddenColumns(columns, query);
} else if (query.getRoot() instanceof DbEntity) {
appendDbEntityColumns(columns, query);
} else if (getQueryMetadata().getPageSize() > 0) {
appendIdColumns(columns, queryMetadata.getClassDescriptor().getEntity());
} else {
appendQueryColumns(columns, query, queryMetadata.getClassDescriptor(), null);
}
return columns;
}
/**
* If query contains explicit column list, use only them
*/
<T> List<ColumnDescriptor> appendOverriddenColumns(List<ColumnDescriptor> columns, SelectQuery<T> query) {
groupByColumns = new HashMap<>();
QualifierTranslator qualifierTranslator = adapter.getQualifierTranslator(this);
AccumulatingBindingListener bindingListener = new AccumulatingBindingListener();
final String[] joinTableAliasForProperty = {null};
joinListener = new AddJoinListener() {
@Override
public void joinAdded() {
// capture last alias for joined table, will use it to resolve object columns
joinTableAliasForProperty[0] = getCurrentAlias();
}
};
setAddBindingListener(bindingListener);
for(Property<?> property : query.getColumns()) {
int expressionType = property.getExpression().getType();
boolean objectProperty = expressionType == Expression.FULL_OBJECT;
// evaluate ObjPath with Persistent type as toOne relations and use it as full object
if(Persistent.class.isAssignableFrom(property.getType())) {
if(expressionType == Expression.OBJ_PATH) {
objectProperty = true;
} else {
// should we warn or throw an error?
}
}
// forbid direct selection of toMany relationships columns
if((expressionType == Expression.OBJ_PATH || expressionType == Expression.DB_PATH) &&
(Collection.class.isAssignableFrom(property.getType()) ||
Map.class.isAssignableFrom(property.getType()))) {
throw new CayenneRuntimeException("Can't directly select toMany relationship columns. " +
"Either select it with aggregate functions like count() or with flat() function to select full related objects.");
}
// Qualifier Translator in case of Object Columns have side effect -
// it will create required joins, that we catch with listener above.
// And we force created join alias for all columns of Object we select.
qualifierTranslator.setQualifier(property.getExpression());
qualifierTranslator.setForceJoinForRelations(objectProperty);
StringBuilder builder = qualifierTranslator.appendPart(new StringBuilder());
// If we want full object, use appendQueryColumns method, to fully process class descriptor
if(objectProperty) {
List<ColumnDescriptor> classColumns = new ArrayList<>();
ObjEntity entity = entityResolver.getObjEntity(property.getType());
if(getQueryMetadata().getPageSize() > 0) {
appendIdColumns(classColumns, entity);
} else {
ClassDescriptor classDescriptor = entityResolver.getClassDescriptor(entity.getName());
appendQueryColumns(classColumns, query, classDescriptor, joinTableAliasForProperty[0]);
}
for(ColumnDescriptor descriptor : classColumns) {
columns.add(descriptor);
groupByColumns.put(descriptor, Collections.<DbAttributeBinding>emptyList());
}
continue;
}
int type = TypesMapping.getSqlTypeByJava(property.getType());
String alias = property.getAlias();
if(alias != null) {
builder.append(" AS ").append(alias);
}
ColumnDescriptor descriptor = new ColumnDescriptor(builder.toString(), type);
descriptor.setDataRowKey(alias);
descriptor.setIsExpression(true);
columns.add(descriptor);
if(isAggregate(property)) {
haveAggregate = true;
} else {
groupByColumns.put(descriptor, bindingListener.getBindings());
}
bindingListener.reset();
}
setAddBindingListener(null);
qualifierTranslator.setForceJoinForRelations(false);
joinListener = null;
return columns;
}
private boolean isAggregate(Property<?> property) {
final boolean[] isAggregate = new boolean[1];
Expression exp = property.getExpression();
exp.traverse(new TraversalHelper() {
@Override
public void startNode(Expression node, Expression parentNode) {
if(node instanceof ASTAggregateFunctionCall) {
isAggregate[0] = true;
}
}
});
return isAggregate[0];
}
<T> List<ColumnDescriptor> appendDbEntityColumns(List<ColumnDescriptor> columns, SelectQuery<T> query) {
Set<ColumnTracker> attributes = new HashSet<>();
DbEntity table = getQueryMetadata().getDbEntity();
for (DbAttribute dba : table.getAttributes()) {
appendColumn(columns, null, dba, attributes, null);
}
return columns;
}
/**
* Appends columns needed for object SelectQuery to the provided columns
* list.
*/
<T> List<ColumnDescriptor> appendQueryColumns(final List<ColumnDescriptor> columns, SelectQuery<T> query, ClassDescriptor descriptor, final String tableAlias) {
final Set<ColumnTracker> attributes = new HashSet<>();
// fetched attributes include attributes that are either:
//
// * class properties
// * PK
// * FK used in relationship
// * joined prefetch PK
ObjEntity oe = descriptor.getEntity();
PropertyVisitor visitor = new PropertyVisitor() {
public boolean visitAttribute(AttributeProperty property) {
ObjAttribute oa = property.getAttribute();
resetJoinStack();
Iterator<CayenneMapEntry> dbPathIterator = oa.getDbPathIterator();
while (dbPathIterator.hasNext()) {
Object pathPart = dbPathIterator.next();
if (pathPart == null) {
throw new CayenneRuntimeException("ObjAttribute has no component: %s", oa.getName());
} else if (pathPart instanceof DbRelationship) {
DbRelationship rel = (DbRelationship) pathPart;
dbRelationshipAdded(rel, JoinType.LEFT_OUTER, null);
} else if (pathPart instanceof DbAttribute) {
DbAttribute dbAttr = (DbAttribute) pathPart;
appendColumn(columns, oa, dbAttr, attributes, null, tableAlias);
}
}
return true;
}
public boolean visitToMany(ToManyProperty property) {
visitRelationship(property);
return true;
}
public boolean visitToOne(ToOneProperty property) {
visitRelationship(property);
return true;
}
private void visitRelationship(ArcProperty property) {
resetJoinStack();
ObjRelationship rel = property.getRelationship();
DbRelationship dbRel = rel.getDbRelationships().get(0);
List<DbJoin> joins = dbRel.getJoins();
for (DbJoin join : joins) {
DbAttribute src = join.getSource();
appendColumn(columns, null, src, attributes, null, tableAlias);
}
}
};
descriptor.visitAllProperties(visitor);
// stack should be reset, because all root table attributes go with "t0"
// table alias
resetJoinStack();
// add remaining needed attrs from DbEntity
DbEntity table = oe.getDbEntity();
for (DbAttribute dba : table.getPrimaryKeys()) {
appendColumn(columns, null, dba, attributes, null, tableAlias);
}
// special handling of a disjoint query...
if (query instanceof PrefetchSelectQuery) {
// for each relationship path add PK of the target entity...
for (String path : ((PrefetchSelectQuery) query).getResultPaths()) {
ASTDbPath pathExp = (ASTDbPath) oe.translateToDbPath(ExpressionFactory.exp(path));
// add joins and find terminating element
resetJoinStack();
PathComponent<DbAttribute, DbRelationship> lastComponent = null;
for (PathComponent<DbAttribute, DbRelationship> component : table
.resolvePath(pathExp, getPathAliases())) {
if (component.getRelationship() != null) {
// do not invoke dbRelationshipAdded(), invoke
// pushJoin() instead. This is to prevent
// 'forcingDistinct' flipping to true, that will result
// in unneeded extra processing and sometimes in invalid
// results (see CAY-1979). Distinctness of each row is
// guaranteed by the prefetch query semantics - we
// include target ID in the result columns
getJoinStack().pushJoin(component.getRelationship(), component.getJoinType(), null);
}
lastComponent = component;
}
// process terminating element
if (lastComponent != null) {
DbRelationship relationship = lastComponent.getRelationship();
if (relationship != null) {
String labelPrefix = pathExp.getPath();
DbEntity targetEntity = relationship.getTargetEntity();
for (DbAttribute pk : targetEntity.getPrimaryKeys()) {
// note that we my select a source attribute, but
// label it as
// target for simplified snapshot processing
appendColumn(columns, null, pk, attributes, labelPrefix + '.' + pk.getName());
}
}
}
}
}
// handle joint prefetches directly attached to this query...
if (query.getPrefetchTree() != null) {
// Set entity name, in case MixedConversionStrategy will be used to select objects from this query
// Note: all prefetch nodes will point to query root, it is not a problem until select query can't
// perform some sort of union or sub-queries.
for(PrefetchTreeNode prefetch : query.getPrefetchTree().getChildren()) {
prefetch.setEntityName(oe.getName());
}
for (PrefetchTreeNode prefetch : query.getPrefetchTree().adjacentJointNodes()) {
// for each prefetch add all joins plus columns from the target
// entity
Expression prefetchExp = ExpressionFactory.exp(prefetch.getPath());
ASTDbPath dbPrefetch = (ASTDbPath) oe.translateToDbPath(prefetchExp);
resetJoinStack();
DbRelationship r = null;
for (PathComponent<DbAttribute, DbRelationship> component : table.resolvePath(dbPrefetch,
getPathAliases())) {
r = component.getRelationship();
dbRelationshipAdded(r, JoinType.LEFT_OUTER, null);
}
if (r == null) {
throw new CayenneRuntimeException("Invalid joint prefetch '%s' for entity: %s"
, prefetch, oe.getName());
}
// add columns from the target entity, including those that are
// matched
// against the FK of the source entity. This is needed to
// determine
// whether optional relationships are null
// go via target OE to make sure that Java types are mapped
// correctly...
ObjRelationship targetRel = (ObjRelationship) prefetchExp.evaluate(oe);
ObjEntity targetEntity = targetRel.getTargetEntity();
String labelPrefix = dbPrefetch.getPath();
for (ObjAttribute oa : targetEntity.getAttributes()) {
Iterator<CayenneMapEntry> dbPathIterator = oa.getDbPathIterator();
while (dbPathIterator.hasNext()) {
Object pathPart = dbPathIterator.next();
if (pathPart == null) {
throw new CayenneRuntimeException("ObjAttribute has no component: %s", oa.getName());
} else if (pathPart instanceof DbRelationship) {
DbRelationship rel = (DbRelationship) pathPart;
dbRelationshipAdded(rel, JoinType.INNER, null);
} else if (pathPart instanceof DbAttribute) {
DbAttribute attribute = (DbAttribute) pathPart;
appendColumn(columns, oa, attribute, attributes, labelPrefix + '.' + attribute.getName());
}
}
}
// append remaining target attributes such as keys
DbEntity targetDbEntity = r.getTargetEntity();
for (DbAttribute attribute : targetDbEntity.getAttributes()) {
appendColumn(columns, null, attribute, attributes, labelPrefix + '.' + attribute.getName());
}
}
}
return columns;
}
<T> List<ColumnDescriptor> appendIdColumns(final List<ColumnDescriptor> columns, ObjEntity objEntity) {
Set<ColumnTracker> skipSet = new HashSet<>();
DbEntity dbEntity = objEntity.getDbEntity();
for (ObjAttribute attribute : objEntity.getPrimaryKeys()) {
// synthetic objattributes can't reliably lookup their DbAttribute,
// so do it manually..
DbAttribute dbAttribute = dbEntity.getAttribute(attribute.getDbAttributeName());
appendColumn(columns, attribute, dbAttribute, skipSet, null);
}
return columns;
}
private void appendColumn(List<ColumnDescriptor> columns, ObjAttribute objAttribute, DbAttribute attribute,
Set<ColumnTracker> skipSet, String label) {
appendColumn(columns, objAttribute, attribute, skipSet, label, null);
}
private void appendColumn(List<ColumnDescriptor> columns, ObjAttribute objAttribute, DbAttribute attribute,
Set<ColumnTracker> skipSet, String label, String alias) {
if(alias == null) {
alias = getCurrentAlias();
}
if (skipSet.add(new ColumnTracker(alias, attribute))) {
ColumnDescriptor column = (objAttribute != null) ? new ColumnDescriptor(objAttribute, attribute, alias)
: new ColumnDescriptor(attribute, alias);
if (label != null) {
column.setDataRowKey(label);
}
columns.add(column);
// TODO: andrus, 5/7/2006 - replace 'columns' collection with this map, as it is redundant
defaultAttributesByColumn.put(column, objAttribute);
} else if (objAttribute != null) {
// record ObjAttribute override
for (ColumnDescriptor column : columns) {
if (attribute.getName().equals(column.getName())) {
if (attributeOverrides == null) {
attributeOverrides = new HashMap<>();
}
// kick out the original attribute
ObjAttribute original = defaultAttributesByColumn.remove(column);
if (original != null) {
attributeOverrides.put(original, column);
}
attributeOverrides.put(objAttribute, column);
column.setJavaClass(Void.TYPE.getName());
break;
}
}
}
}
/**
* @since 3.0
*/
@Override
public void resetJoinStack() {
getJoinStack().resetStack();
}
/**
* @since 3.0
*/
@Override
public void dbRelationshipAdded(DbRelationship relationship, JoinType joinType, String joinSplitAlias) {
if (relationship.isToMany()) {
forcingDistinct = true;
}
getJoinStack().pushJoin(relationship, joinType, joinSplitAlias);
if(joinListener != null) {
joinListener.joinAdded();
}
}
/**
* Always returns true.
*/
@Override
public boolean supportsTableAliases() {
return true;
}
@Override
public String getAliasForExpression(Expression exp) {
Collection<Property<?>> columns = ((SelectQuery<?>)query).getColumns();
if(columns == null) {
return null;
}
for(Property<?> property : columns) {
if(property.getExpression().equals(exp)) {
return property.getAlias();
}
}
return null;
}
/**
* @since 4.0
*/
@Override
public boolean hasJoins() {
return joinStack != null && joinStack.size() > 0;
}
static final class ColumnTracker {
private DbAttribute attribute;
private String alias;
ColumnTracker(String alias, DbAttribute attribute) {
this.attribute = attribute;
this.alias = alias;
}
@Override
public boolean equals(Object object) {
if (!(object instanceof ColumnTracker)) {
return false;
}
ColumnTracker other = (ColumnTracker) object;
return new EqualsBuilder().append(alias, other.alias).append(attribute, other.attribute).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(31, 5).append(alias).append(attribute).toHashCode();
}
}
static final class AccumulatingBindingListener implements AddBindingListener {
private List<DbAttributeBinding> bindings = new ArrayList<>();
@Override
public void onAdd(DbAttributeBinding binding) {
bindings.add(binding);
}
public void reset() {
bindings.clear();
}
public List<DbAttributeBinding> getBindings() {
return new ArrayList<>(bindings);
}
}
interface AddJoinListener {
void joinAdded();
}
}