package org.hibernate.hql.internal.ast.tree;
import org.hibernate.QueryException;
import org.hibernate.engine.internal.JoinSequence;
import org.hibernate.hql.internal.CollectionProperties;
import org.hibernate.hql.internal.antlr.SqlTokenTypes;
import org.hibernate.hql.internal.ast.util.ASTUtil;
import org.hibernate.hql.internal.ast.util.ColumnHelper;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.sql.JoinFragment;
import org.hibernate.sql.JoinType;
import org.hibernate.type.CollectionType;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;
import antlr.SemanticException;
import antlr.collections.AST;
public class DotNode extends FromReferenceNode implements DisplayableNode, SelectExpression {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( DotNode.class );
public static boolean useThetaStyleImplicitJoins;
public static boolean regressionStyleJoinSuppression;
public static interface IllegalCollectionDereferenceExceptionBuilder {
public QueryException buildIllegalCollectionDereferenceException(
String collectionPropertyName,
FromReferenceNode lhs);
}
public static final IllegalCollectionDereferenceExceptionBuilder DEF_ILLEGAL_COLL_DEREF_EXCP_BUILDER = new IllegalCollectionDereferenceExceptionBuilder() {
public QueryException buildIllegalCollectionDereferenceException(String propertyName, FromReferenceNode lhs) {
String lhsPath = ASTUtil.getPathText( lhs );
return new QueryException( "illegal attempt to dereference collection [" + lhsPath + "] with element property reference [" + propertyName + "]" );
}
};
public static IllegalCollectionDereferenceExceptionBuilder ILLEGAL_COLL_DEREF_EXCP_BUILDER = DEF_ILLEGAL_COLL_DEREF_EXCP_BUILDER;
public static enum DereferenceType {
UNKNOWN,
ENTITY,
COMPONENT,
COLLECTION,
PRIMITIVE,
IDENTIFIER,
JAVA_CONSTANT
}
private String propertyName;
private String path;
private String propertyPath;
private String[] columns;
private JoinType joinType = JoinType.INNER_JOIN;
private boolean fetch;
private DereferenceType dereferenceType = DereferenceType.UNKNOWN;
private FromElement impliedJoin;
public void setJoinType(JoinType joinType) {
this.joinType = joinType;
}
private String[] getColumns() throws QueryException {
if ( columns == null ) {
String tableAlias = getLhs().getFromElement().getTableAlias();
columns = getFromElement().toColumns( tableAlias, propertyPath, false );
}
return columns;
}
@Override
public String getDisplayText() {
StringBuilder buf = new StringBuilder();
FromElement fromElement = getFromElement();
buf.append( "{propertyName=" ).append( propertyName );
buf.append( ",dereferenceType=" ).append( dereferenceType.name() );
buf.append( ",getPropertyPath=" ).append( propertyPath );
buf.append( ",path=" ).append( getPath() );
if ( fromElement != null ) {
buf.append( ",tableAlias=" ).append( fromElement.getTableAlias() );
buf.append( ",className=" ).append( fromElement.getClassName() );
buf.append( ",classAlias=" ).append( fromElement.getClassAlias() );
}
else {
buf.append( ",no from element" );
}
buf.append( '}' );
return buf.toString();
}
@Override
public void resolveFirstChild() throws SemanticException {
FromReferenceNode lhs = (FromReferenceNode) getFirstChild();
SqlNode property = (SqlNode) lhs.getNextSibling();
String propName = property.getText();
propertyName = propName;
if ( propertyPath == null ) {
propertyPath = propName;
}
lhs.resolve( true, true, null, this );
setFromElement( lhs.getFromElement() );
checkSubclassOrSuperclassPropertyReference( lhs, propName );
}
@Override
public void resolveInFunctionCall(boolean generateJoin, boolean implicitJoin) throws SemanticException {
if ( isResolved() ) {
return;
}
Type propertyType = prepareLhs();
if ( propertyType != null && propertyType.isCollectionType() ) {
resolveIndex( null );
}
else {
resolveFirstChild();
super.resolve( generateJoin, implicitJoin );
}
}
public void resolveIndex(AST parent) throws SemanticException {
if ( isResolved() ) {
return;
}
Type propertyType = prepareLhs();
dereferenceCollection( (CollectionType) propertyType, true, true, null, parent );
}
public void resolve(boolean generateJoin, boolean implicitJoin, String classAlias, AST parent)
throws SemanticException {
if ( isResolved() ) {
return;
}
Type propertyType = prepareLhs();
if ( propertyType == null ) {
if ( parent == null ) {
getWalker().getLiteralProcessor().lookupConstant( this );
}
return;
}
if ( propertyType.isComponentType() ) {
checkLhsIsNotCollection();
dereferenceComponent( parent );
initText();
}
else if ( propertyType.isEntityType() ) {
checkLhsIsNotCollection();
dereferenceEntity( (EntityType) propertyType, implicitJoin, classAlias, generateJoin, parent );
initText();
}
else if ( propertyType.isCollectionType() ) {
checkLhsIsNotCollection();
dereferenceCollection( (CollectionType) propertyType, implicitJoin, false, classAlias, parent );
}
else {
if ( !CollectionProperties.isAnyCollectionProperty( propertyName ) ) {
checkLhsIsNotCollection();
}
dereferenceType = DereferenceType.PRIMITIVE;
initText();
}
setResolved();
}
private void initText() {
String[] cols = getColumns();
String text = StringHelper.join( ", ", cols );
boolean countDistinct = getWalker().isInCountDistinct()
&& getWalker().getSessionFactoryHelper().getFactory().getDialect().requiresParensForTupleDistinctCounts();
if ( cols.length > 1 &&
( getWalker().isComparativeExpressionClause() || countDistinct ) ) {
text = "(" + text + ")";
}
setText( text );
}
private Type prepareLhs() throws SemanticException {
FromReferenceNode lhs = getLhs();
lhs.prepareForDot( propertyName );
return getDataType();
}
private void dereferenceCollection(
CollectionType collectionType,
boolean implicitJoin,
boolean indexed,
String classAlias,
AST parent)
throws SemanticException {
dereferenceType = DereferenceType.COLLECTION;
String role = collectionType.getRole();
boolean isSizeProperty = getNextSibling() != null &&
CollectionProperties.isAnyCollectionProperty( getNextSibling().getText() );
if ( isSizeProperty ) {
indexed = true;
}
QueryableCollection queryableCollection = getSessionFactoryHelper().requireQueryableCollection( role );
String propName = getPath();
FromClause currentFromClause = getWalker().getCurrentFromClause();
FromElement lhsFromElement = getLhs().getFromElement();
while ( lhsFromElement != null && ComponentJoin.class.isInstance( lhsFromElement ) ) {
lhsFromElement = lhsFromElement.getOrigin();
}
if ( lhsFromElement == null ) {
throw new QueryException( "Unable to locate appropriate lhs" );
}
if ( getWalker().getStatementType() != SqlTokenTypes.SELECT ) {
if ( isFromElementUpdateOrDeleteRoot( lhsFromElement ) ) {
boolean useAlias = false;
if ( getWalker().getStatementType() != SqlTokenTypes.INSERT ) {
final Queryable persister = lhsFromElement.getQueryable();
if ( persister.isMultiTable() ) {
useAlias = true;
}
}
if ( !useAlias ) {
final String lhsTableName = lhsFromElement.getQueryable().getTableName();
columns = getFromElement().toColumns( lhsTableName, propertyPath, false, true );
}
}
}
FromElementFactory factory = new FromElementFactory(
currentFromClause,
lhsFromElement,
propName,
classAlias,
getColumns(),
implicitJoin
);
FromElement elem = factory.createCollection( queryableCollection, role, joinType, fetch, indexed );
LOG.debugf( "dereferenceCollection() : Created new FROM element for %s : %s", propName, elem );
setImpliedJoin( elem );
setFromElement( elem );
if ( isSizeProperty ) {
elem.setText( "" );
elem.setUseWhereFragment( false );
}
if ( !implicitJoin ) {
EntityPersister entityPersister = elem.getEntityPersister();
if ( entityPersister != null ) {
getWalker().addQuerySpaces( entityPersister.getQuerySpaces() );
}
}
getWalker().addQuerySpaces( queryableCollection.getCollectionSpaces() );
}
private void dereferenceEntity(
EntityType entityType,
boolean implicitJoin,
String classAlias,
boolean generateJoin,
AST parent) throws SemanticException {
checkForCorrelatedSubquery( "dereferenceEntity" );
DotNode parentAsDotNode = null;
String property = propertyName;
final boolean joinIsNeeded;
if ( isDotNode( parent ) ) {
parentAsDotNode = (DotNode) parent;
property = parentAsDotNode.propertyName;
joinIsNeeded = generateJoin && !isReferenceToPrimaryKey( parentAsDotNode.propertyName, entityType );
}
else if ( !getWalker().isSelectStatement() ) {
joinIsNeeded = getWalker().getCurrentStatementType() == SqlTokenTypes.SELECT && getWalker().isInFrom();
}
else if ( regressionStyleJoinSuppression ) {
joinIsNeeded = generateJoin && ( !getWalker().isInSelect() || !getWalker().isShallowQuery() );
}
else {
joinIsNeeded = generateJoin || ( getWalker().isInSelect() || getWalker().isInFrom() );
}
if ( joinIsNeeded ) {
dereferenceEntityJoin( classAlias, entityType, implicitJoin, parent );
}
else {
dereferenceEntityIdentifier( property, parentAsDotNode );
}
}
private boolean isDotNode(AST n) {
return n != null && n.getType() == SqlTokenTypes.DOT;
}
private void dereferenceEntityJoin(String classAlias, EntityType propertyType, boolean impliedJoin, AST parent)
throws SemanticException {
dereferenceType = DereferenceType.ENTITY;
if ( LOG.isDebugEnabled() ) {
LOG.debugf(
"dereferenceEntityJoin() : generating join for %s in %s (%s) parent = %s",
propertyName,
getFromElement().getClassName(),
classAlias == null ? "<no alias>" : classAlias,
ASTUtil.getDebugString( parent )
);
}
String associatedEntityName = propertyType.getAssociatedEntityName();
String tableAlias = getAliasGenerator().createName( associatedEntityName );
String[] joinColumns = getColumns();
String joinPath = getPath();
if ( impliedJoin && getWalker().isInFrom() ) {
joinType = getWalker().getImpliedJoinType();
}
FromClause currentFromClause = getWalker().getCurrentFromClause();
FromElement elem = currentFromClause.findJoinByPath( joinPath );
boolean found = elem != null;
boolean useFoundFromElement = found && canReuse( classAlias, elem );
if ( !useFoundFromElement ) {
JoinSequence joinSequence = getSessionFactoryHelper()
.createJoinSequence( impliedJoin, propertyType, tableAlias, joinType, joinColumns );
FromElement lhsFromElement = getLhs().getFromElement();
while ( lhsFromElement != null && ComponentJoin.class.isInstance( lhsFromElement ) ) {
lhsFromElement = lhsFromElement.getOrigin();
}
if ( lhsFromElement == null ) {
throw new QueryException( "Unable to locate appropriate lhs" );
}
String role = lhsFromElement.getClassName() + "." + propertyName;
FromElementFactory factory = new FromElementFactory(
currentFromClause,
lhsFromElement,
joinPath,
classAlias,
joinColumns,
impliedJoin
);
elem = factory.createEntityJoin(
associatedEntityName,
tableAlias,
joinSequence,
fetch,
getWalker().isInFrom(),
propertyType,
role,
joinPath
);
}
else {
currentFromClause.addDuplicateAlias( classAlias, elem );
}
setImpliedJoin( elem );
getWalker().addQuerySpaces( elem.getEntityPersister().getQuerySpaces() );
setFromElement( elem );
}
private boolean canReuse(String classAlias, FromElement fromElement) {
if ( fromElement.getFromClause() == getWalker().getCurrentFromClause() &&
areSame( classAlias, fromElement.getClassAlias() )) {
return true;
}
return getWalker().getCurrentClauseType() != SqlTokenTypes.FROM;
}
private boolean areSame(String alias1, String alias2) {
return !StringHelper.isEmpty( alias1 ) && !StringHelper.isEmpty( alias2 ) && alias1.equals( alias2 );
}
private void setImpliedJoin(FromElement elem) {
this.impliedJoin = elem;
if ( getFirstChild().getType() == SqlTokenTypes.DOT ) {
DotNode dotLhs = (DotNode) getFirstChild();
if ( dotLhs.getImpliedJoin() != null ) {
this.impliedJoin = dotLhs.getImpliedJoin();
}
}
}
@Override
public FromElement getImpliedJoin() {
return impliedJoin;
}
private boolean isReferenceToPrimaryKey(String propertyName, EntityType owningType) {
EntityPersister persister = getSessionFactoryHelper()
.getFactory()
.getEntityPersister( owningType.getAssociatedEntityName() );
if ( persister.getEntityMetamodel().hasNonIdentifierPropertyNamedId() ) {
return propertyName.equals( persister.getIdentifierPropertyName() ) && owningType.isReferenceToPrimaryKey();
}
if ( EntityPersister.ENTITY_ID.equals( propertyName ) ) {
return owningType.isReferenceToPrimaryKey();
}
String keyPropertyName = getSessionFactoryHelper().getIdentifierOrUniqueKeyPropertyName( owningType );
return keyPropertyName != null && keyPropertyName.equals( propertyName ) && owningType.isReferenceToPrimaryKey();
}
private void checkForCorrelatedSubquery(String methodName) {
if ( isCorrelatedSubselect() ) {
LOG.debugf( "%s() : correlated subquery", methodName );
}
}
private boolean isCorrelatedSubselect() {
return getWalker().isSubQuery() &&
getFromElement().getFromClause() != getWalker().getCurrentFromClause();
}
private void checkLhsIsNotCollection() throws SemanticException {
if ( getLhs().getDataType() != null && getLhs().getDataType().isCollectionType() ) {
throw ILLEGAL_COLL_DEREF_EXCP_BUILDER.buildIllegalCollectionDereferenceException( propertyName, getLhs() );
}
}
private void dereferenceComponent(AST parent) {
dereferenceType = DereferenceType.COMPONENT;
setPropertyNameAndPath( parent );
}
private void dereferenceEntityIdentifier(String propertyName, DotNode dotParent) {
if ( LOG.isDebugEnabled() ) {
LOG.debugf(
"dereferenceShortcut() : property %s in %s does not require a join.",
propertyName,
getFromElement().getClassName()
);
}
initText();
setPropertyNameAndPath( dotParent );
if ( dotParent != null ) {
dotParent.dereferenceType = DereferenceType.IDENTIFIER;
dotParent.setText( getText() );
dotParent.columns = getColumns();
}
}
private void setPropertyNameAndPath(AST parent) {
if ( isDotNode( parent ) ) {
DotNode dotNode = (DotNode) parent;
AST lhs = dotNode.getFirstChild();
AST rhs = lhs.getNextSibling();
propertyName = rhs.getText();
propertyPath = propertyPath + "." + propertyName;
dotNode.propertyPath = propertyPath;
LOG.debugf( "Unresolved property path is now '%s'", dotNode.propertyPath );
}
else {
LOG.debugf( "Terminal getPropertyPath = [%s]", propertyPath );
}
}
@Override
public Type getDataType() {
if ( super.getDataType() == null ) {
FromElement fromElement = getLhs().getFromElement();
if ( fromElement == null ) {
return null;
}
Type propertyType = fromElement.getPropertyType( propertyName, propertyPath );
LOG.debugf( "getDataType() : %s -> %s", propertyPath, propertyType );
super.setDataType( propertyType );
}
return super.getDataType();
}
public void setPropertyPath(String propertyPath) {
this.propertyPath = propertyPath;
}
public String getPropertyPath() {
return propertyPath;
}
public FromReferenceNode getLhs() {
FromReferenceNode lhs = ( (FromReferenceNode) getFirstChild() );
if ( lhs == null ) {
throw new IllegalStateException( "DOT node with no left-hand-side!" );
}
return lhs;
}
@Override
public String getPath() {
if ( path == null ) {
FromReferenceNode lhs = getLhs();
if ( lhs == null ) {
path = getText();
}
else {
SqlNode rhs = (SqlNode) lhs.getNextSibling();
path = lhs.getPath() + "." + rhs.getOriginalText();
}
}
return path;
}
public void setFetch(boolean fetch) {
this.fetch = fetch;
}
public void setScalarColumnText(int i) throws SemanticException {
String[] sqlColumns = getColumns();
ColumnHelper.generateScalarColumns( this, sqlColumns, i );
}
public void resolveSelectExpression() throws SemanticException {
if ( getWalker().isShallowQuery() || getWalker().getCurrentFromClause().isSubQuery() ) {
resolve( false, true );
}
else {
resolve( true, false );
Type type = getDataType();
if ( type.isEntityType() ) {
FromElement fromElement = getFromElement();
fromElement.setIncludeSubclasses( true );
if ( useThetaStyleImplicitJoins ) {
fromElement.getJoinSequence().setUseThetaStyle( true );
FromElement origin = fromElement.getOrigin();
if ( origin != null ) {
ASTUtil.makeSiblingOfParent( origin, fromElement );
}
}
}
}
FromReferenceNode lhs = getLhs();
while ( lhs != null ) {
checkSubclassOrSuperclassPropertyReference( lhs, lhs.getNextSibling().getText() );
lhs = (FromReferenceNode) lhs.getFirstChild();
}
}
public void setResolvedConstant(String text) {
path = text;
dereferenceType = DereferenceType.JAVA_CONSTANT;
setResolved();
}
private boolean checkSubclassOrSuperclassPropertyReference(FromReferenceNode lhs, String propertyName) {
if ( lhs != null && !( lhs instanceof IndexNode ) ) {
final FromElement source = lhs.getFromElement();
if ( source != null ) {
source.handlePropertyBeingDereferenced( lhs.getDataType(), propertyName );
}
}
return false;
}
}