FileDocCategorySizeDatePackage
DotNode.javaAPI DocHibernate 3.2.524122Fri Mar 30 06:23:58 BST 2007org.hibernate.hql.ast.tree

DotNode

public class DotNode extends FromReferenceNode implements SelectExpression, DisplayableNode
Represents a reference to a property or alias expression. This should duplicate the relevant behaviors in PathExpressionParser.
author
Joshua Davis

Fields Summary
public static boolean
useThetaStyleImplicitJoins
public static boolean
REGRESSION_STYLE_JOIN_SUPPRESSION
public static final IllegalCollectionDereferenceExceptionBuilder
DEF_ILLEGAL_COLL_DEREF_EXCP_BUILDER
public static IllegalCollectionDereferenceExceptionBuilder
ILLEGAL_COLL_DEREF_EXCP_BUILDER
private static final Log
log
private static final int
DEREF_UNKNOWN
private static final int
DEREF_ENTITY
private static final int
DEREF_COMPONENT
private static final int
DEREF_COLLECTION
private static final int
DEREF_PRIMITIVE
private static final int
DEREF_IDENTIFIER
private static final int
DEREF_JAVA_CONSTANT
private String
propertyName
The identifier that is the name of the property.
private String
path
The full path, to the root alias of this dot node.
private String
propertyPath
The unresolved property path relative to this dot node.
private String[]
columns
The column names that this resolves to.
private int
joinType
The type of join to create. Default is an inner join.
private boolean
fetch
Fetch join or not.
private int
dereferenceType
The type of dereference that hapened (DEREF_xxx).
private FromElement
impliedJoin
Constructors Summary
Methods Summary
private voidcheckForCorrelatedSubquery(java.lang.String methodName)

		if ( isCorrelatedSubselect() ) {
			if ( log.isDebugEnabled() ) {
				log.debug( methodName + "() : correlated subquery" );
			}
		}
	
private voidcheckLhsIsNotCollection()

		if ( getLhs().getDataType() != null && getLhs().getDataType().isCollectionType() ) {
			throw ILLEGAL_COLL_DEREF_EXCP_BUILDER.buildIllegalCollectionDereferenceException( propertyName, getLhs() );
		}
	
private booleancheckSubclassOrSuperclassPropertyReference(FromReferenceNode lhs, java.lang.String propertyName)

		if ( lhs != null && !( lhs instanceof IndexNode ) ) {
			final FromElement source = lhs.getFromElement();
			if ( source != null ) {
				source.handlePropertyBeingDereferenced( lhs.getDataType(), propertyName );
			}
		}
		return false;
	
private voiddereferenceCollection(org.hibernate.type.CollectionType collectionType, boolean implicitJoin, boolean indexed, java.lang.String classAlias, antlr.collections.AST parent)

		
		dereferenceType = DEREF_COLLECTION;
		String role = collectionType.getRole();
		
		//foo.bars.size (also handles deprecated stuff like foo.bars.maxelement for backwardness)
		boolean isSizeProperty = getNextSibling()!=null && 
			CollectionProperties.isAnyCollectionProperty( getNextSibling().getText() );

		if ( isSizeProperty ) indexed = true; //yuck!

		QueryableCollection queryableCollection = getSessionFactoryHelper().requireQueryableCollection( role );
		String propName = getPath();
		FromClause currentFromClause = getWalker().getCurrentFromClause();

		if ( getWalker().getStatementType() != SqlTokenTypes.SELECT && indexed && classAlias == null ) {
			// should indicate that we are processing an INSERT/UPDATE/DELETE
			// query with a subquery implied via a collection property
			// function. Here, we need to use the table name itself as the
			// qualification alias.
			// TODO : verify this works for all databases...
			// TODO : is this also the case in non-"indexed" scenarios?
			String alias = getLhs().getFromElement().getQueryable().getTableName();
			columns = getFromElement().toColumns( alias, propertyPath, false, true );
		}

		//We do not look for an existing join on the same path, because
		//it makes sense to join twice on the same collection role
		FromElementFactory factory = new FromElementFactory(
		        currentFromClause,
		        getLhs().getFromElement(),
		        propName,
				classAlias,
		        getColumns(),
		        implicitJoin
		);
		FromElement elem = factory.createCollection( queryableCollection, role, joinType, fetch, indexed );
		
		if ( log.isDebugEnabled() ) {
			log.debug( "dereferenceCollection() : Created new FROM element for " + propName + " : " + elem );
		}
		
		setImpliedJoin( elem );
		setFromElement( elem );	// This 'dot' expression now refers to the resulting from element.
		
		if ( isSizeProperty ) {
			elem.setText("");
			elem.setUseWhereFragment(false);
		}
		
		if ( !implicitJoin ) {
			EntityPersister entityPersister = elem.getEntityPersister();
			if ( entityPersister != null ) {
				getWalker().addQuerySpaces( entityPersister.getQuerySpaces() );
			}
		}
		getWalker().addQuerySpaces( queryableCollection.getCollectionSpaces() );	// Always add the collection's query spaces.
	
private voiddereferenceComponent(antlr.collections.AST parent)

		dereferenceType = DEREF_COMPONENT;
		setPropertyNameAndPath( parent );
	
private voiddereferenceEntity(org.hibernate.type.EntityType entityType, boolean implicitJoin, java.lang.String classAlias, boolean generateJoin, antlr.collections.AST parent)

		checkForCorrelatedSubquery( "dereferenceEntity" );
		// three general cases we check here as to whether to render a physical SQL join:
		// 1) is our parent a DotNode as well?  If so, our property reference is
		// 		being further de-referenced...
		// 2) is this a DML statement
		// 3) we were asked to generate any needed joins (generateJoins==true) *OR*
		//		we are currently processing a select or from clause
		// (an additional check is the REGRESSION_STYLE_JOIN_SUPPRESSION check solely intended for the test suite)
		//
		// The REGRESSION_STYLE_JOIN_SUPPRESSION is an additional check
		// intended solely for use within the test suite.  This forces the
		// implicit join resolution to behave more like the classic parser.
		// The underlying issue is that classic translator is simply wrong
		// about its decisions on whether or not to render an implicit join
		// into a physical SQL join in a lot of cases.  The piece it generally
		// tends to miss is that INNER joins effect the results by further
		// restricting the data set!  A particular manifestation of this is
		// the fact that the classic translator will skip the physical join
		// for ToOne implicit joins *if the query is shallow*; the result
		// being that Query.list() and Query.iterate() could return
		// different number of results!
		DotNode parentAsDotNode = null;
		String property = propertyName;
		final boolean joinIsNeeded;

		if ( isDotNode( parent ) ) {
			// our parent is another dot node, meaning we are being further dereferenced.
			// thus we need to generate a join unless the parent refers to the associated
			// entity's PK (because 'our' table would know the FK).
			parentAsDotNode = ( DotNode ) parent;
			property = parentAsDotNode.propertyName;
			joinIsNeeded = generateJoin && !isReferenceToPrimaryKey( parentAsDotNode.propertyName, entityType );
		}
		else if ( ! getWalker().isSelectStatement() ) {
			joinIsNeeded = false;
		}
		else if ( REGRESSION_STYLE_JOIN_SUPPRESSION ) {
			// this is the regression style determination which matches the logic of the classic translator
			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 voiddereferenceEntityIdentifier(java.lang.String propertyName, org.hibernate.hql.ast.tree.DotNode dotParent)

		// special shortcut for id properties, skip the join!
		// this must only occur at the _end_ of a path expression
		if ( log.isDebugEnabled() ) {
			log.debug( "dereferenceShortcut() : property " + 
				propertyName + " in " + getFromElement().getClassName() + 
				" does not require a join." );
		}

		initText();
		setPropertyNameAndPath( dotParent ); // Set the unresolved path in this node and the parent.
		// Set the text for the parent.
		if ( dotParent != null ) {
			dotParent.dereferenceType = DEREF_IDENTIFIER;
			dotParent.setText( getText() );
			dotParent.columns = getColumns();
		}
	
private voiddereferenceEntityJoin(java.lang.String classAlias, org.hibernate.type.EntityType propertyType, boolean impliedJoin, antlr.collections.AST parent)

		dereferenceType = DEREF_ENTITY;
		if ( log.isDebugEnabled() ) {
			log.debug( "dereferenceEntityJoin() : generating join for " + propertyName + " in "
					+ getFromElement().getClassName() + " "
					+ ( ( classAlias == null ) ? "{no alias}" : "(" + classAlias + ")" )
					+ " parent = " + ASTUtil.getDebugString( parent )
			);
		}
		// Create a new FROM node for the referenced class.
		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 );

///////////////////////////////////////////////////////////////////////////////
//
// This is the piece which recognizes the condition where an implicit join path
// resolved earlier in a correlated subquery is now being referenced in the
// outer query.  For 3.0final, we just let this generate a second join (which
// is exactly how the old parser handles this).  Eventually we need to add this
// logic back in and complete the logic in FromClause.promoteJoin; however,
// FromClause.promoteJoin has its own difficulties (see the comments in
// FromClause.promoteJoin).
//
//		if ( elem == null ) {
//			// see if this joinPath has been used in a "child" FromClause, and if so
//			// promote that element to the outer query
//			FromClause currentNodeOwner = getFromElement().getFromClause();
//			FromClause currentJoinOwner = currentNodeOwner.locateChildFromClauseWithJoinByPath( joinPath );
//			if ( currentJoinOwner != null && currentNodeOwner != currentJoinOwner ) {
//				elem = currentJoinOwner.findJoinByPathLocal( joinPath );
//				if ( elem != null ) {
//					currentFromClause.promoteJoin( elem );
//					// EARLY EXIT!!!
//					return;
//				}
//			}
//		}
//
///////////////////////////////////////////////////////////////////////////////

		if ( elem == null ) {
			// If this is an implied join in a from element, then use the impled join type which is part of the
			// tree parser's state (set by the gramamar actions).
			JoinSequence joinSequence = getSessionFactoryHelper()
				.createJoinSequence( impliedJoin, propertyType, tableAlias, joinType, joinColumns );

			FromElementFactory factory = new FromElementFactory(
			        currentFromClause,
					getLhs().getFromElement(),
					joinPath, 
					classAlias, 
					joinColumns, 
					impliedJoin
			);
			elem = factory.createEntityJoin( 
					associatedEntityName, 
					tableAlias, 
					joinSequence, 
					fetch, 
					getWalker().isInFrom(), 
					propertyType
			);
		}
		else {
			currentFromClause.addDuplicateAlias(classAlias, elem);
		}
		setImpliedJoin( elem );
		getWalker().addQuerySpaces( elem.getEntityPersister().getQuerySpaces() );
		setFromElement( elem );	// This 'dot' expression now refers to the resulting from element.
	
private java.lang.String[]getColumns()

		if ( columns == null ) {
			// Use the table fromElement and the property name to get the array of column names.
			String tableAlias = getLhs().getFromElement().getTableAlias();
			columns = getFromElement().toColumns( tableAlias, propertyPath, false );
		}
		return columns;
	
public org.hibernate.type.TypegetDataType()

		if ( super.getDataType() == null ) {
			FromElement fromElement = getLhs().getFromElement();
			if ( fromElement == null ) {
				return null;
			}
			// If the lhs is a collection, use CollectionPropertyMapping
			Type propertyType = fromElement.getPropertyType( propertyName, propertyPath );
			if ( log.isDebugEnabled() ) {
				log.debug( "getDataType() : " + propertyPath + " -> " + propertyType );
			}
			super.setDataType( propertyType );
		}
		return super.getDataType();
	
public java.lang.StringgetDisplayText()

		StringBuffer buf = new StringBuffer();
		FromElement fromElement = getFromElement();
		buf.append( "{propertyName=" ).append( propertyName );
		buf.append( ",dereferenceType=" ).append( ASTPrinter.getConstantName( getClass(), dereferenceType ) );
		buf.append( ",propertyPath=" ).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();
	
public FromElementgetImpliedJoin()

		return impliedJoin;
	
public FromReferenceNodegetLhs()

		FromReferenceNode lhs = ( ( FromReferenceNode ) getFirstChild() );
		if ( lhs == null ) {
			throw new IllegalStateException( "DOT node with no left-hand-side!" );
		}
		return lhs;
	
public java.lang.StringgetPath()
Returns the full path of the node.

return
the full path of the node.

		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 java.lang.StringgetPropertyPath()

		return propertyPath;
	
private voidinitText()

		String[] cols = getColumns();
		String text = StringHelper.join( ", ", cols );
		if ( cols.length > 1 && getWalker().isComparativeExpressionClause() ) {
			text = "(" + text + ")";
		}
		setText( text );
	
private booleanisCorrelatedSubselect()

		return getWalker().isSubQuery() &&
			getFromElement().getFromClause() != getWalker().getCurrentFromClause();
	
private booleanisDotNode(antlr.collections.AST n)

		return n != null && n.getType() == SqlTokenTypes.DOT;
	
private booleanisReferenceToPrimaryKey(java.lang.String propertyName, org.hibernate.type.EntityType owningType)
Is the given property name a reference to the primary key of the associated entity construed by the given entity type?

For example, consider a fragment like order.customer.id (where order is a from-element alias). Here, we'd have: propertyName = "id" AND owningType = ManyToOneType(Customer) and are being asked to determine whether "customer.id" is a reference to customer's PK...

param
propertyName The name of the property to check.
param
owningType The type represeting the entity "owning" the property
return
True if propertyName references the entity's (owningType->associatedEntity) primary key; false otherwise.

		EntityPersister persister = getSessionFactoryHelper()
				.getFactory()
				.getEntityPersister( owningType.getAssociatedEntityName() );
		if ( persister.getEntityMetamodel().hasNonIdentifierPropertyNamedId() ) {
			// only the identifier property field name can be a reference to the associated entity's PK...
			return propertyName.equals( persister.getIdentifierPropertyName() ) && owningType.isReferenceToPrimaryKey();
		}
		else {
			// here, we have two possibilities:
			// 		1) the property-name matches the explicitly identifier property name
			//		2) the property-name matches the implicit 'id' property name
			if ( EntityPersister.ENTITY_ID.equals( propertyName ) ) {
				// the referenced node text is the special 'id'
				return owningType.isReferenceToPrimaryKey();
			}
			else {
				String keyPropertyName = getSessionFactoryHelper().getIdentifierOrUniqueKeyPropertyName( owningType );
				return keyPropertyName != null && keyPropertyName.equals( propertyName ) && owningType.isReferenceToPrimaryKey();
			}
		}
	
private org.hibernate.type.TypeprepareLhs()

		FromReferenceNode lhs = getLhs();
		lhs.prepareForDot( propertyName );
		return getDataType();
	
public voidresolve(boolean generateJoin, boolean implicitJoin, java.lang.String classAlias, antlr.collections.AST parent)

		// If this dot has already been resolved, stop now.
		if ( isResolved() ) {
			return;
		}
		Type propertyType = prepareLhs(); // Prepare the left hand side and get the data type.

		// If there is no data type for this node, and we're at the end of the path (top most dot node), then
		// this might be a Java constant.
		if ( propertyType == null ) {
			if ( parent == null ) {
				getWalker().getLiteralProcessor().lookupConstant( this );
			}
			// If the propertyType is null and there isn't a parent, just 
			// stop now... there was a problem resolving the node anyway.
			return;
		}

		if ( propertyType.isComponentType() ) {
			// The property is a component...
			checkLhsIsNotCollection();
			dereferenceComponent( parent );
			initText();
		}
		else if ( propertyType.isEntityType() ) {
			// The property is another class..
			checkLhsIsNotCollection();
			dereferenceEntity( ( EntityType ) propertyType, implicitJoin, classAlias, generateJoin, parent );
			initText();
		}
		else if ( propertyType.isCollectionType() ) {
			// The property is a collection...
			checkLhsIsNotCollection();
			dereferenceCollection( ( CollectionType ) propertyType, implicitJoin, false, classAlias, parent );
		}
		else {
			// Otherwise, this is a primitive type.
			if ( ! CollectionProperties.isAnyCollectionProperty( propertyName ) ) {
				checkLhsIsNotCollection();
			}
			dereferenceType = DEREF_PRIMITIVE;
			initText();
		}
		setResolved();
	
public voidresolveFirstChild()
Resolves the left hand side of the DOT.

throws
SemanticException

		FromReferenceNode lhs = ( FromReferenceNode ) getFirstChild();
		SqlNode property = ( SqlNode ) lhs.getNextSibling();

		// Set the attributes of the property reference expression.
		String propName = property.getText();
		propertyName = propName;
		// If the uresolved property path isn't set yet, just use the property name.
		if ( propertyPath == null ) {
			propertyPath = propName;
		}
		// Resolve the LHS fully, generate implicit joins.  Pass in the property name so that the resolver can
		// discover foreign key (id) properties.
		lhs.resolve( true, true, null, this );
		setFromElement( lhs.getFromElement() );			// The 'from element' that the property is in.

		checkSubclassOrSuperclassPropertyReference( lhs, propName );
	
public voidresolveInFunctionCall(boolean generateJoin, boolean implicitJoin)

		if ( isResolved() ) {
			return;
		}
		Type propertyType = prepareLhs();			// Prepare the left hand side and get the data type.
		if ( propertyType!=null && propertyType.isCollectionType() ) {
			resolveIndex(null);
		}
		else {
			resolveFirstChild();
			super.resolve(generateJoin, implicitJoin);
		}
	
public voidresolveIndex(antlr.collections.AST parent)

		if ( isResolved() ) {
			return;
		}
		Type propertyType = prepareLhs();			// Prepare the left hand side and get the data type.
		dereferenceCollection( ( CollectionType ) propertyType, true, true, null, parent );
	
public voidresolveSelectExpression()
Special method to resolve expressions in the SELECT list.

throws
SemanticException if this cannot be resolved.

		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 ); // Tell the destination fromElement to 'includeSubclasses'.
				if ( useThetaStyleImplicitJoins ) {
					fromElement.getJoinSequence().setUseThetaStyle( true );	// Use theta style (for regression)
					// Move the node up, after the origin node.
					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 voidsetFetch(boolean fetch)

		this.fetch = fetch;
	
private voidsetImpliedJoin(FromElement elem)

		this.impliedJoin = elem;
		if ( getFirstChild().getType() == SqlTokenTypes.DOT ) {
			DotNode dotLhs = ( DotNode ) getFirstChild();
			if ( dotLhs.getImpliedJoin() != null ) {
				this.impliedJoin = dotLhs.getImpliedJoin();
			}
		}
	
public voidsetJoinType(int joinType)
Sets the join type for this '.' node structure.

param
joinType The type of join to use.
see
JoinFragment


	                   	 
	    
		this.joinType = joinType;
	
private voidsetPropertyNameAndPath(antlr.collections.AST parent)

		if ( isDotNode( parent ) ) {
			DotNode dotNode = ( DotNode ) parent;
			AST lhs = dotNode.getFirstChild();
			AST rhs = lhs.getNextSibling();
			propertyName = rhs.getText();
			propertyPath = propertyPath + "." + propertyName; // Append the new property name onto the unresolved path.
			dotNode.propertyPath = propertyPath;
			if ( log.isDebugEnabled() ) {
				log.debug( "Unresolved property path is now '" + dotNode.propertyPath + "'" );
			}
		}
		else {
			if ( log.isDebugEnabled() ) {
				log.debug( "terminal propertyPath = [" + propertyPath + "]" );
			}
		}
	
public voidsetPropertyPath(java.lang.String propertyPath)

		this.propertyPath = propertyPath;
	
public voidsetResolvedConstant(java.lang.String text)

		path = text;
		dereferenceType = DEREF_JAVA_CONSTANT;
		setResolved(); // Don't resolve the node again.
	
public voidsetScalarColumnText(int i)

		String[] sqlColumns = getColumns();
		ColumnHelper.generateScalarColumns( this, sqlColumns, i );