org.hibernate.hql.ast.HqlSqlWalker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate Show documentation
Show all versions of hibernate Show documentation
Relational Persistence for Java
// $Id: HqlSqlWalker.java 10946 2006-12-07 14:50:59Z [email protected] $
package org.hibernate.hql.ast;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.QueryException;
import org.hibernate.HibernateException;
import org.hibernate.engine.JoinSequence;
import org.hibernate.engine.ParameterBinder;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.hql.QueryTranslator;
import org.hibernate.hql.antlr.HqlSqlBaseWalker;
import org.hibernate.hql.antlr.HqlSqlTokenTypes;
import org.hibernate.hql.antlr.HqlTokenTypes;
import org.hibernate.hql.antlr.SqlTokenTypes;
import org.hibernate.hql.ast.tree.AssignmentSpecification;
import org.hibernate.hql.ast.tree.CollectionFunction;
import org.hibernate.hql.ast.tree.ConstructorNode;
import org.hibernate.hql.ast.tree.DeleteStatement;
import org.hibernate.hql.ast.tree.DotNode;
import org.hibernate.hql.ast.tree.FromClause;
import org.hibernate.hql.ast.tree.FromElement;
import org.hibernate.hql.ast.tree.FromReferenceNode;
import org.hibernate.hql.ast.tree.IdentNode;
import org.hibernate.hql.ast.tree.IndexNode;
import org.hibernate.hql.ast.tree.InsertStatement;
import org.hibernate.hql.ast.tree.IntoClause;
import org.hibernate.hql.ast.tree.MethodNode;
import org.hibernate.hql.ast.tree.ParameterNode;
import org.hibernate.hql.ast.tree.QueryNode;
import org.hibernate.hql.ast.tree.ResolvableNode;
import org.hibernate.hql.ast.tree.RestrictableStatement;
import org.hibernate.hql.ast.tree.SelectClause;
import org.hibernate.hql.ast.tree.SelectExpression;
import org.hibernate.hql.ast.tree.UpdateStatement;
import org.hibernate.hql.ast.tree.Node;
import org.hibernate.hql.ast.tree.OperatorNode;
import org.hibernate.hql.ast.util.ASTPrinter;
import org.hibernate.hql.ast.util.ASTUtil;
import org.hibernate.hql.ast.util.AliasGenerator;
import org.hibernate.hql.ast.util.JoinProcessor;
import org.hibernate.hql.ast.util.LiteralProcessor;
import org.hibernate.hql.ast.util.SessionFactoryHelper;
import org.hibernate.hql.ast.util.SyntheticAndFactory;
import org.hibernate.hql.ast.util.NodeTraverser;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.PostInsertIdentifierGenerator;
import org.hibernate.id.SequenceGenerator;
import org.hibernate.param.NamedParameterSpecification;
import org.hibernate.param.ParameterSpecification;
import org.hibernate.param.PositionalParameterSpecification;
import org.hibernate.param.VersionTypeSeedParameterSpecification;
import org.hibernate.param.CollectionFilterKeyParameterSpecification;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.sql.JoinFragment;
import org.hibernate.type.AssociationType;
import org.hibernate.type.Type;
import org.hibernate.type.VersionType;
import org.hibernate.type.DbTimestampType;
import org.hibernate.usertype.UserVersionType;
import org.hibernate.util.ArrayHelper;
import antlr.ASTFactory;
import antlr.RecognitionException;
import antlr.SemanticException;
import antlr.collections.AST;
/**
* Implements methods used by the HQL->SQL tree transform grammar (a.k.a. the second phase).
*
* - Isolates the Hibernate API-specific code from the ANTLR generated code.
* - Handles the SQL framgents generated by the persisters in order to create the SELECT and FROM clauses,
* taking into account the joins and projections that are implied by the mappings (persister/queryable).
* - Uses SqlASTFactory to create customized AST nodes.
*
*
* @see SqlASTFactory
*/
public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, ParameterBinder.NamedParameterSource {
private static final Log log = LogFactory.getLog( HqlSqlWalker.class );
private final QueryTranslatorImpl queryTranslatorImpl;
private final HqlParser hqlParser;
private final SessionFactoryHelper sessionFactoryHelper;
private final Map tokenReplacements;
private final AliasGenerator aliasGenerator = new AliasGenerator();
private final LiteralProcessor literalProcessor;
private final ParseErrorHandler parseErrorHandler;
private final ASTPrinter printer;
private final String collectionFilterRole;
private FromClause currentFromClause = null;
private SelectClause selectClause;
private Set querySpaces = new HashSet();
private int parameterCount;
private Map namedParameters = new HashMap();
private ArrayList parameters = new ArrayList();
private int numberOfParametersInSetClause;
private int positionalParameterCount;
private ArrayList assignmentSpecifications = new ArrayList();
private int impliedJoinType;
/**
* Create a new tree transformer.
*
* @param qti Back pointer to the query translator implementation that is using this tree transform.
* @param sfi The session factory implementor where the Hibernate mappings can be found.
* @param parser A reference to the phase-1 parser
* @param tokenReplacements Registers the token replacement map with the walker. This map will
* be used to substitute function names and constants.
* @param collectionRole The collection role name of the collection used as the basis for the
* filter, NULL if this is not a collection filter compilation.
*/
public HqlSqlWalker(
QueryTranslatorImpl qti,
SessionFactoryImplementor sfi,
HqlParser parser,
Map tokenReplacements,
String collectionRole) {
setASTFactory( new SqlASTFactory( this ) );
this.parseErrorHandler = new ErrorCounter();
this.queryTranslatorImpl = qti;
this.sessionFactoryHelper = new SessionFactoryHelper( sfi );
this.literalProcessor = new LiteralProcessor( this );
this.tokenReplacements = tokenReplacements;
this.hqlParser = parser;
this.printer = new ASTPrinter( SqlTokenTypes.class );
this.collectionFilterRole = collectionRole;
}
protected void prepareFromClauseInputTree(AST fromClauseInput) {
if ( !isSubQuery() ) {
// // inject param specifications to account for dynamic filter param values
// if ( ! getEnabledFilters().isEmpty() ) {
// Iterator filterItr = getEnabledFilters().values().iterator();
// while ( filterItr.hasNext() ) {
// FilterImpl filter = ( FilterImpl ) filterItr.next();
// if ( ! filter.getFilterDefinition().getParameterNames().isEmpty() ) {
// Iterator paramItr = filter.getFilterDefinition().getParameterNames().iterator();
// while ( paramItr.hasNext() ) {
// String parameterName = ( String ) paramItr.next();
// // currently param filters *only* work with single-column parameter types;
// // if that limitation is ever lifted, this logic will need to change to account for that
// ParameterNode collectionFilterKeyParameter = ( ParameterNode ) astFactory.create( PARAM, "?" );
// DynamicFilterParameterSpecification paramSpec = new DynamicFilterParameterSpecification(
// filter.getName(),
// parameterName,
// filter.getFilterDefinition().getParameterType( parameterName ),
// positionalParameterCount++
// );
// collectionFilterKeyParameter.setHqlParameterSpecification( paramSpec );
// parameters.add( paramSpec );
// }
// }
// }
// }
if ( isFilter() ) {
// Handle collection-fiter compilation.
// IMPORTANT NOTE: This is modifying the INPUT (HQL) tree, not the output tree!
QueryableCollection persister = sessionFactoryHelper.getCollectionPersister( collectionFilterRole );
Type collectionElementType = persister.getElementType();
if ( !collectionElementType.isEntityType() ) {
throw new QueryException( "collection of values in filter: this" );
}
String collectionElementEntityName = persister.getElementPersister().getEntityName();
ASTFactory inputAstFactory = hqlParser.getASTFactory();
AST fromElement = ASTUtil.create( inputAstFactory, HqlTokenTypes.FILTER_ENTITY, collectionElementEntityName );
ASTUtil.createSibling( inputAstFactory, HqlTokenTypes.ALIAS, "this", fromElement );
fromClauseInput.addChild( fromElement );
// Show the modified AST.
if ( log.isDebugEnabled() ) {
log.debug( "prepareFromClauseInputTree() : Filter - Added 'this' as a from element..." );
}
queryTranslatorImpl.showHqlAst( hqlParser.getAST() );
// Create a parameter specification for the collection filter...
Type collectionFilterKeyType = sessionFactoryHelper.requireQueryableCollection( collectionFilterRole ).getKeyType();
ParameterNode collectionFilterKeyParameter = ( ParameterNode ) astFactory.create( PARAM, "?" );
CollectionFilterKeyParameterSpecification collectionFilterKeyParameterSpec = new CollectionFilterKeyParameterSpecification(
collectionFilterRole, collectionFilterKeyType, positionalParameterCount++
);
collectionFilterKeyParameter.setHqlParameterSpecification( collectionFilterKeyParameterSpec );
parameters.add( collectionFilterKeyParameterSpec );
}
}
}
public boolean isFilter() {
return collectionFilterRole != null;
}
public SessionFactoryHelper getSessionFactoryHelper() {
return sessionFactoryHelper;
}
public Map getTokenReplacements() {
return tokenReplacements;
}
public AliasGenerator getAliasGenerator() {
return aliasGenerator;
}
public FromClause getCurrentFromClause() {
return currentFromClause;
}
public ParseErrorHandler getParseErrorHandler() {
return parseErrorHandler;
}
public void reportError(RecognitionException e) {
parseErrorHandler.reportError( e ); // Use the delegate.
}
public void reportError(String s) {
parseErrorHandler.reportError( s ); // Use the delegate.
}
public void reportWarning(String s) {
parseErrorHandler.reportWarning( s );
}
/**
* Returns the set of unique query spaces (a.k.a.
* table names) that occurred in the query.
*
* @return A set of table names (Strings).
*/
public Set getQuerySpaces() {
return querySpaces;
}
protected AST createFromElement(String path, AST alias, AST propertyFetch) throws SemanticException {
FromElement fromElement = currentFromClause.addFromElement( path, alias );
fromElement.setAllPropertyFetch(propertyFetch!=null);
return fromElement;
}
protected AST createFromFilterElement(AST filterEntity, AST alias) throws SemanticException {
FromElement fromElement = currentFromClause.addFromElement( filterEntity.getText(), alias );
FromClause fromClause = fromElement.getFromClause();
QueryableCollection persister = sessionFactoryHelper.getCollectionPersister( collectionFilterRole );
// Get the names of the columns used to link between the collection
// owner and the collection elements.
String[] keyColumnNames = persister.getKeyColumnNames();
String fkTableAlias = persister.isOneToMany()
? fromElement.getTableAlias()
: fromClause.getAliasGenerator().createName( collectionFilterRole );
JoinSequence join = sessionFactoryHelper.createJoinSequence();
join.setRoot( persister, fkTableAlias );
if ( !persister.isOneToMany() ) {
join.addJoin( ( AssociationType ) persister.getElementType(),
fromElement.getTableAlias(),
JoinFragment.INNER_JOIN,
persister.getElementColumnNames( fkTableAlias ) );
}
join.addCondition( fkTableAlias, keyColumnNames, " = ?" );
fromElement.setJoinSequence( join );
fromElement.setFilter( true );
if ( log.isDebugEnabled() ) {
log.debug( "createFromFilterElement() : processed filter FROM element." );
}
return fromElement;
}
protected void createFromJoinElement(
AST path,
AST alias,
int joinType,
AST fetchNode,
AST propertyFetch,
AST with) throws SemanticException {
boolean fetch = fetchNode != null;
if ( fetch && isSubQuery() ) {
throw new QueryException( "fetch not allowed in subquery from-elements" );
}
// The path AST should be a DotNode, and it should have been evaluated already.
if ( path.getType() != SqlTokenTypes.DOT ) {
throw new SemanticException( "Path expected for join!" );
}
DotNode dot = ( DotNode ) path;
int hibernateJoinType = JoinProcessor.toHibernateJoinType( joinType );
dot.setJoinType( hibernateJoinType ); // Tell the dot node about the join type.
dot.setFetch( fetch );
// Generate an explicit join for the root dot node. The implied joins will be collected and passed up
// to the root dot node.
dot.resolve( true, false, alias == null ? null : alias.getText() );
FromElement fromElement = dot.getImpliedJoin();
fromElement.setAllPropertyFetch(propertyFetch!=null);
if ( with != null ) {
if ( fetch ) {
throw new SemanticException( "with-clause not allowed on fetched associations; use filters" );
}
handleWithFragment( fromElement, with );
}
if ( log.isDebugEnabled() ) {
log.debug( "createFromJoinElement() : " + getASTPrinter().showAsString( fromElement, "-- join tree --" ) );
}
}
private void handleWithFragment(FromElement fromElement, AST hqlWithNode) throws SemanticException
{
try {
withClause( hqlWithNode );
AST hqlSqlWithNode = returnAST;
if ( log.isDebugEnabled() ) {
log.debug( "handleWithFragment() : " + getASTPrinter().showAsString( hqlSqlWithNode, "-- with clause --" ) );
}
WithClauseVisitor visitor = new WithClauseVisitor();
NodeTraverser traverser = new NodeTraverser( visitor );
traverser.traverseDepthFirst( hqlSqlWithNode );
FromElement referencedFromElement = visitor.getReferencedFromElement();
if ( referencedFromElement != fromElement ) {
throw new InvalidWithClauseException( "with-clause expressions did not reference from-clause element to which the with-clause was associated" );
}
SqlGenerator sql = new SqlGenerator( getSessionFactoryHelper().getFactory() );
sql.whereExpr( hqlSqlWithNode.getFirstChild() );
fromElement.setWithClauseFragment( visitor.getJoinAlias(), "(" + sql.getSQL() + ")" );
}
catch( SemanticException e ) {
throw e;
}
catch( InvalidWithClauseException e ) {
throw e;
}
catch ( Exception e) {
throw new SemanticException( e.getMessage() );
}
}
private static class WithClauseVisitor implements NodeTraverser.VisitationStrategy {
private FromElement referencedFromElement;
private String joinAlias;
public void visit(AST node) {
// todo : currently expects that the individual with expressions apply to the same sql table join.
// This may not be the case for joined-subclass where the property values
// might be coming from different tables in the joined hierarchy. At some
// point we should expand this to support that capability. However, that has
// some difficulties:
// 1) the biggest is how to handle ORs when the individual comparisons are
// linked to different sql joins.
// 2) here we would need to track each comparison individually, along with
// the join alias to which it applies and then pass that information
// back to the FromElement so it can pass it along to the JoinSequence
if ( node instanceof DotNode ) {
DotNode dotNode = ( DotNode ) node;
FromElement fromElement = dotNode.getFromElement();
if ( referencedFromElement != null ) {
if ( fromElement != referencedFromElement ) {
throw new HibernateException( "with-clause referenced two different from-clause elements" );
}
}
else {
referencedFromElement = fromElement;
joinAlias = extractAppliedAlias( dotNode );
// todo : temporary
// needed because currently persister is the one that
// creates and renders the join fragments for inheritence
// hierarchies...
if ( !joinAlias.equals( referencedFromElement.getTableAlias() ) ) {
throw new InvalidWithClauseException( "with clause can only reference columns in the driving table" );
}
}
}
}
private String extractAppliedAlias(DotNode dotNode) {
return dotNode.getText().substring( 0, dotNode.getText().indexOf( '.' ) );
}
public FromElement getReferencedFromElement() {
return referencedFromElement;
}
public String getJoinAlias() {
return joinAlias;
}
}
/**
* Sets the current 'FROM' context.
*
* @param fromNode The new 'FROM' context.
* @param inputFromNode The from node from the input AST.
*/
protected void pushFromClause(AST fromNode, AST inputFromNode) {
FromClause newFromClause = ( FromClause ) fromNode;
newFromClause.setParentFromClause( currentFromClause );
currentFromClause = newFromClause;
}
/**
* Returns to the previous 'FROM' context.
*/
private void popFromClause() {
currentFromClause = currentFromClause.getParentFromClause();
}
protected void lookupAlias(AST aliasRef)
throws SemanticException {
FromElement alias = currentFromClause.getFromElement( aliasRef.getText() );
FromReferenceNode aliasRefNode = ( FromReferenceNode ) aliasRef;
aliasRefNode.setFromElement( alias );
}
protected void setImpliedJoinType(int joinType) {
impliedJoinType = JoinProcessor.toHibernateJoinType( joinType );
}
public int getImpliedJoinType() {
return impliedJoinType;
}
protected AST lookupProperty(AST dot, boolean root, boolean inSelect) throws SemanticException {
DotNode dotNode = ( DotNode ) dot;
FromReferenceNode lhs = dotNode.getLhs();
AST rhs = lhs.getNextSibling();
switch ( rhs.getType() ) {
case SqlTokenTypes.ELEMENTS:
case SqlTokenTypes.INDICES:
if ( log.isDebugEnabled() ) {
log.debug( "lookupProperty() " + dotNode.getPath() + " => " + rhs.getText() + "(" + lhs.getPath() + ")" );
}
CollectionFunction f = ( CollectionFunction ) rhs;
// Re-arrange the tree so that the collection function is the root and the lhs is the path.
f.setFirstChild( lhs );
lhs.setNextSibling( null );
dotNode.setFirstChild( f );
resolve( lhs ); // Don't forget to resolve the argument!
f.resolve( inSelect ); // Resolve the collection function now.
return f;
default:
// Resolve everything up to this dot, but don't resolve the placeholders yet.
dotNode.resolveFirstChild();
return dotNode;
}
}
protected boolean isNonQualifiedPropertyRef(AST ident) {
final String identText = ident.getText();
if ( currentFromClause.isFromElementAlias( identText ) ) {
return false;
}
List fromElements = currentFromClause.getExplicitFromElements();
if ( fromElements.size() == 1 ) {
final FromElement fromElement = ( FromElement ) fromElements.get( 0 );
try {
log.trace( "attempting to resolve property [" + identText + "] as a non-qualified ref" );
return fromElement.getPropertyMapping( identText ).toType( identText ) != null;
}
catch( QueryException e ) {
// Should mean that no such property was found
}
}
return false;
}
protected AST lookupNonQualifiedProperty(AST property) throws SemanticException {
final FromElement fromElement = ( FromElement ) currentFromClause.getExplicitFromElements().get( 0 );
AST syntheticDotNode = generateSyntheticDotNodeForNonQualifiedPropertyRef( property, fromElement );
return lookupProperty( syntheticDotNode, false, getCurrentClauseType() == HqlSqlTokenTypes.SELECT );
}
private AST generateSyntheticDotNodeForNonQualifiedPropertyRef(AST property, FromElement fromElement) {
AST dot = getASTFactory().create( DOT, "{non-qualified-property-ref}" );
// TODO : better way?!?
( ( DotNode ) dot ).setPropertyPath( ( ( FromReferenceNode ) property ).getPath() );
IdentNode syntheticAlias = ( IdentNode ) getASTFactory().create( IDENT, "{synthetic-alias}" );
syntheticAlias.setFromElement( fromElement );
syntheticAlias.setResolved();
dot.setFirstChild( syntheticAlias );
dot.addChild( property );
return dot;
}
protected void processQuery(AST select, AST query) throws SemanticException {
if ( log.isDebugEnabled() ) {
log.debug( "processQuery() : " + query.toStringTree() );
}
try {
QueryNode qn = ( QueryNode ) query;
// Was there an explicit select expression?
boolean explicitSelect = select != null && select.getNumberOfChildren() > 0;
if ( !explicitSelect ) {
// No explicit select expression; render the id and properties
// projection lists for every persister in the from clause into
// a single 'token node'.
//TODO: the only reason we need this stuff now is collection filters,
// we should get rid of derived select clause completely!
createSelectClauseFromFromClause( qn );
}
else {
// Use the explicitly declared select expression; determine the
// return types indicated by each select token
useSelectClause( select );
}
// After that, process the JOINs.
// Invoke a delegate to do the work, as this is farily complex.
JoinProcessor joinProcessor = new JoinProcessor( astFactory, queryTranslatorImpl );
joinProcessor.processJoins( qn, isSubQuery() );
// Attach any mapping-defined "ORDER BY" fragments
Iterator itr = qn.getFromClause().getProjectionList().iterator();
while ( itr.hasNext() ) {
final FromElement fromElement = ( FromElement ) itr.next();
// if ( fromElement.isFetch() && fromElement.isCollectionJoin() ) {
if ( fromElement.isFetch() && fromElement.getQueryableCollection() != null ) {
// Does the collection referenced by this FromElement
// specify an order-by attribute? If so, attach it to
// the query's order-by
if ( fromElement.getQueryableCollection().hasOrdering() ) {
String orderByFragment = fromElement
.getQueryableCollection()
.getSQLOrderByString( fromElement.getCollectionTableAlias() );
qn.getOrderByClause().addOrderFragment( orderByFragment );
}
if ( fromElement.getQueryableCollection().hasManyToManyOrdering() ) {
String orderByFragment = fromElement.getQueryableCollection()
.getManyToManyOrderByString( fromElement.getTableAlias() );
qn.getOrderByClause().addOrderFragment( orderByFragment );
}
}
}
}
finally {
popFromClause();
}
}
protected void postProcessDML(RestrictableStatement statement) throws SemanticException {
statement.getFromClause().resolve();
FromElement fromElement = ( FromElement ) statement.getFromClause().getFromElements().get( 0 );
Queryable persister = fromElement.getQueryable();
// Make #@%$^#^ sure no alias is applied to the table name
fromElement.setText( persister.getTableName() );
// append any filter fragments; the EMPTY_MAP is used under the assumption that
// currently enabled filters should not affect this process
if ( persister.getDiscriminatorType() != null ) {
new SyntheticAndFactory( getASTFactory() ).addDiscriminatorWhereFragment(
statement,
persister,
java.util.Collections.EMPTY_MAP,
fromElement.getTableAlias()
);
}
}
protected void postProcessUpdate(AST update) throws SemanticException {
UpdateStatement updateStatement = ( UpdateStatement ) update;
postProcessDML( updateStatement );
}
protected void postProcessDelete(AST delete) throws SemanticException {
postProcessDML( ( DeleteStatement ) delete );
}
public static boolean supportsIdGenWithBulkInsertion(IdentifierGenerator generator) {
return SequenceGenerator.class.isAssignableFrom( generator.getClass() )
|| PostInsertIdentifierGenerator.class.isAssignableFrom( generator.getClass() );
}
protected void postProcessInsert(AST insert) throws SemanticException, QueryException {
InsertStatement insertStatement = ( InsertStatement ) insert;
insertStatement.validate();
SelectClause selectClause = insertStatement.getSelectClause();
Queryable persister = insertStatement.getIntoClause().getQueryable();
if ( !insertStatement.getIntoClause().isExplicitIdInsertion() ) {
// We need to generate ids as part of this bulk insert.
//
// Note that this is only supported for sequence-style generators and
// post-insert-style generators; basically, only in-db generators
IdentifierGenerator generator = persister.getIdentifierGenerator();
if ( !supportsIdGenWithBulkInsertion( generator ) ) {
throw new QueryException( "can only generate ids as part of bulk insert with either sequence or post-insert style generators" );
}
AST idSelectExprNode = null;
if ( SequenceGenerator.class.isAssignableFrom( generator.getClass() ) ) {
String seqName = ( String ) ( ( SequenceGenerator ) generator ).generatorKey();
String nextval = sessionFactoryHelper.getFactory().getDialect().getSelectSequenceNextValString( seqName );
idSelectExprNode = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, nextval );
}
else {
//Don't need this, because we should never ever be selecting no columns in an insert ... select...
//and because it causes a bug on DB2
/*String idInsertString = sessionFactoryHelper.getFactory().getDialect().getIdentityInsertString();
if ( idInsertString != null ) {
idSelectExprNode = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, idInsertString );
}*/
}
if ( idSelectExprNode != null ) {
AST currentFirstSelectExprNode = selectClause.getFirstChild();
selectClause.setFirstChild( idSelectExprNode );
idSelectExprNode.setNextSibling( currentFirstSelectExprNode );
insertStatement.getIntoClause().prependIdColumnSpec();
}
}
final boolean includeVersionProperty = persister.isVersioned() &&
!insertStatement.getIntoClause().isExplicitVersionInsertion() &&
persister.isVersionPropertyInsertable();
if ( includeVersionProperty ) {
// We need to seed the version value as part of this bulk insert
VersionType versionType = persister.getVersionType();
AST versionValueNode = null;
if ( sessionFactoryHelper.getFactory().getDialect().supportsParametersInInsertSelect() ) {
versionValueNode = getASTFactory().create( HqlSqlTokenTypes.PARAM, "?" );
ParameterSpecification paramSpec = new VersionTypeSeedParameterSpecification( versionType );
( ( ParameterNode ) versionValueNode ).setHqlParameterSpecification( paramSpec );
parameters.add( 0, paramSpec );
}
else {
if ( isIntegral( versionType ) ) {
try {
Object seedValue = versionType.seed( null );
versionValueNode = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, seedValue.toString() );
}
catch( Throwable t ) {
throw new QueryException( "could not determine seed value for version on bulk insert [" + versionType + "]" );
}
}
else if ( isDatabaseGeneratedTimestamp( versionType ) ) {
String functionName = sessionFactoryHelper.getFactory().getDialect().getCurrentTimestampSQLFunctionName();
versionValueNode = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, functionName );
}
else {
throw new QueryException( "cannot handle version type [" + versionType + "] on bulk inserts with dialects not supporting parameters in insert-select statements" );
}
}
AST currentFirstSelectExprNode = selectClause.getFirstChild();
selectClause.setFirstChild( versionValueNode );
versionValueNode.setNextSibling( currentFirstSelectExprNode );
insertStatement.getIntoClause().prependVersionColumnSpec();
}
if ( insertStatement.getIntoClause().isDiscriminated() ) {
String sqlValue = insertStatement.getIntoClause().getQueryable().getDiscriminatorSQLValue();
AST discrimValue = getASTFactory().create( HqlSqlTokenTypes.SQL_TOKEN, sqlValue );
insertStatement.getSelectClause().addChild( discrimValue );
}
}
private boolean isDatabaseGeneratedTimestamp(Type type) {
// currently only the Hibernate-supplied DbTimestampType is supported here
return DbTimestampType.class.isAssignableFrom( type.getClass() );
}
private boolean isIntegral(Type type) {
return Long.class.isAssignableFrom( type.getReturnedClass() )
|| Integer.class.isAssignableFrom( type.getReturnedClass() )
|| long.class.isAssignableFrom( type.getReturnedClass() )
|| int.class.isAssignableFrom( type.getReturnedClass() );
}
private void useSelectClause(AST select) throws SemanticException {
selectClause = ( SelectClause ) select;
selectClause.initializeExplicitSelectClause( currentFromClause );
}
private void createSelectClauseFromFromClause(QueryNode qn) throws SemanticException {
AST select = astFactory.create( SELECT_CLAUSE, "{derived select clause}" );
AST sibling = qn.getFromClause();
qn.setFirstChild( select );
select.setNextSibling( sibling );
selectClause = ( SelectClause ) select;
selectClause.initializeDerivedSelectClause( currentFromClause );
if ( log.isDebugEnabled() ) {
log.debug( "Derived SELECT clause created." );
}
}
protected void resolve(AST node) throws SemanticException {
if ( node != null ) {
// This is called when it's time to fully resolve a path expression.
ResolvableNode r = ( ResolvableNode ) node;
if ( isInFunctionCall() ) {
r.resolveInFunctionCall( false, true );
}
else {
r.resolve( false, true ); // Generate implicit joins, only if necessary.
}
}
}
protected void resolveSelectExpression(AST node) throws SemanticException {
// This is called when it's time to fully resolve a path expression.
int type = node.getType();
switch ( type ) {
case DOT:
DotNode dot = ( DotNode ) node;
dot.resolveSelectExpression();
break;
case ALIAS_REF:
// Notify the FROM element that it is being referenced by the select.
FromReferenceNode aliasRefNode = ( FromReferenceNode ) node;
//aliasRefNode.resolve( false, false, aliasRefNode.getText() ); //TODO: is it kosher to do it here?
aliasRefNode.resolve( false, false ); //TODO: is it kosher to do it here?
FromElement fromElement = aliasRefNode.getFromElement();
if ( fromElement != null ) {
fromElement.setIncludeSubclasses( true );
}
default:
break;
}
}
protected void beforeSelectClause() throws SemanticException {
// Turn off includeSubclasses on all FromElements.
FromClause from = getCurrentFromClause();
List fromElements = from.getFromElements();
for ( Iterator iterator = fromElements.iterator(); iterator.hasNext(); ) {
FromElement fromElement = ( FromElement ) iterator.next();
fromElement.setIncludeSubclasses( false );
}
}
protected AST generatePositionalParameter(AST inputNode) throws SemanticException {
if ( namedParameters.size() > 0 ) {
throw new SemanticException( "cannot define positional parameter after any named parameters have been defined" );
}
ParameterNode parameter = ( ParameterNode ) astFactory.create( PARAM, "?" );
PositionalParameterSpecification paramSpec = new PositionalParameterSpecification(
( ( Node ) inputNode ).getLine(),
( ( Node ) inputNode ).getColumn(),
positionalParameterCount++
);
parameter.setHqlParameterSpecification( paramSpec );
parameters.add( paramSpec );
return parameter;
}
protected AST generateNamedParameter(AST delimiterNode, AST nameNode) throws SemanticException {
String name = nameNode.getText();
trackNamedParameterPositions( name );
// create the node initially with the param name so that it shows
// appropriately in the "original text" attribute
ParameterNode parameter = ( ParameterNode ) astFactory.create( NAMED_PARAM, name );
parameter.setText( "?" );
NamedParameterSpecification paramSpec = new NamedParameterSpecification(
( ( Node ) delimiterNode ).getLine(),
( ( Node ) delimiterNode ).getColumn(),
name
);
parameter.setHqlParameterSpecification( paramSpec );
parameters.add( paramSpec );
return parameter;
}
private void trackNamedParameterPositions(String name) {
Integer loc = new Integer( parameterCount++ );
Object o = namedParameters.get( name );
if ( o == null ) {
namedParameters.put( name, loc );
}
else if ( o instanceof Integer ) {
ArrayList list = new ArrayList( 4 );
list.add( o );
list.add( loc );
namedParameters.put( name, list );
}
else {
( ( ArrayList ) o ).add( loc );
}
}
protected void processConstant(AST constant) throws SemanticException {
literalProcessor.processConstant( constant, true ); // Use the delegate, resolve identifiers as FROM element aliases.
}
protected void processBoolean(AST constant) throws SemanticException {
literalProcessor.processBoolean( constant ); // Use the delegate.
}
protected void processNumericLiteral(AST literal) {
literalProcessor.processNumeric( literal );
}
protected void processIndex(AST indexOp) throws SemanticException {
IndexNode indexNode = ( IndexNode ) indexOp;
indexNode.resolve( true, true );
}
protected void processFunction(AST functionCall, boolean inSelect) throws SemanticException {
MethodNode methodNode = ( MethodNode ) functionCall;
methodNode.resolve( inSelect );
}
protected void processConstructor(AST constructor) throws SemanticException {
ConstructorNode constructorNode = ( ConstructorNode ) constructor;
constructorNode.prepare();
}
protected void setAlias(AST selectExpr, AST ident) {
((SelectExpression) selectExpr).setAlias(ident.getText());
}
/**
* Returns the locations of all occurrences of the named parameter.
*/
public int[] getNamedParameterLocations(String name) throws QueryException {
Object o = namedParameters.get( name );
if ( o == null ) {
QueryException qe = new QueryException( QueryTranslator.ERROR_NAMED_PARAMETER_DOES_NOT_APPEAR + name );
qe.setQueryString( queryTranslatorImpl.getQueryString() );
throw qe;
}
if ( o instanceof Integer ) {
return new int[]{( ( Integer ) o ).intValue()};
}
else {
return ArrayHelper.toIntArray( ( ArrayList ) o );
}
}
public void addQuerySpaces(Serializable[] spaces) {
for ( int i = 0; i < spaces.length; i++ ) {
querySpaces.add( spaces[i] );
}
}
public Type[] getReturnTypes() {
return selectClause.getQueryReturnTypes();
}
public String[] getReturnAliases() {
return selectClause.getQueryReturnAliases();
}
public SelectClause getSelectClause() {
return selectClause;
}
public FromClause getFinalFromClause() {
FromClause top = currentFromClause;
while ( top.getParentFromClause() != null ) {
top = top.getParentFromClause();
}
return top;
}
public boolean isShallowQuery() {
// select clauses for insert statements should alwasy be treated as shallow
return getStatementType() == INSERT || queryTranslatorImpl.isShallowQuery();
}
public Map getEnabledFilters() {
return queryTranslatorImpl.getEnabledFilters();
}
public LiteralProcessor getLiteralProcessor() {
return literalProcessor;
}
public ASTPrinter getASTPrinter() {
return printer;
}
public ArrayList getParameters() {
return parameters;
}
public int getNumberOfParametersInSetClause() {
return numberOfParametersInSetClause;
}
protected void evaluateAssignment(AST eq) throws SemanticException {
prepareLogicOperator( eq );
Queryable persister = getCurrentFromClause().getFromElement().getQueryable();
evaluateAssignment( eq, persister, -1 );
}
private void evaluateAssignment(AST eq, Queryable persister, int targetIndex) {
if ( persister.isMultiTable() ) {
// no need to even collect this information if the persister is considered multi-table
AssignmentSpecification specification = new AssignmentSpecification( eq, persister );
if ( targetIndex >= 0 ) {
assignmentSpecifications.add( targetIndex, specification );
}
else {
assignmentSpecifications.add( specification );
}
numberOfParametersInSetClause += specification.getParameters().length;
}
}
public ArrayList getAssignmentSpecifications() {
return assignmentSpecifications;
}
protected AST createIntoClause(String path, AST propertySpec) throws SemanticException {
Queryable persister = ( Queryable ) getSessionFactoryHelper().requireClassPersister( path );
IntoClause intoClause = ( IntoClause ) getASTFactory().create( INTO, persister.getEntityName() );
intoClause.setFirstChild( propertySpec );
intoClause.initialize( persister );
addQuerySpaces( persister.getQuerySpaces() );
return intoClause;
}
protected void prepareVersioned(AST updateNode, AST versioned) throws SemanticException {
UpdateStatement updateStatement = ( UpdateStatement ) updateNode;
FromClause fromClause = updateStatement.getFromClause();
if ( versioned != null ) {
// Make sure that the persister is versioned
Queryable persister = fromClause.getFromElement().getQueryable();
if ( !persister.isVersioned() ) {
throw new SemanticException( "increment option specified for update of non-versioned entity" );
}
VersionType versionType = persister.getVersionType();
if ( versionType instanceof UserVersionType ) {
throw new SemanticException( "user-defined version types not supported for increment option" );
}
AST eq = getASTFactory().create( HqlSqlTokenTypes.EQ, "=" );
AST versionPropertyNode = generateVersionPropertyNode( persister );
eq.setFirstChild( versionPropertyNode );
AST versionIncrementNode = null;
if ( Date.class.isAssignableFrom( versionType.getReturnedClass() ) ) {
versionIncrementNode = getASTFactory().create( HqlSqlTokenTypes.PARAM, "?" );
ParameterSpecification paramSpec = new VersionTypeSeedParameterSpecification( versionType );
( ( ParameterNode ) versionIncrementNode ).setHqlParameterSpecification( paramSpec );
parameters.add( 0, paramSpec );
}
else {
// Not possible to simply re-use the versionPropertyNode here as it causes
// OOM errors due to circularity :(
versionIncrementNode = getASTFactory().create( HqlSqlTokenTypes.PLUS, "+" );
versionIncrementNode.setFirstChild( generateVersionPropertyNode( persister ) );
versionIncrementNode.addChild( getASTFactory().create( HqlSqlTokenTypes.IDENT, "1" ) );
}
eq.addChild( versionIncrementNode );
evaluateAssignment( eq, persister, 0 );
AST setClause = updateStatement.getSetClause();
AST currentFirstSetElement = setClause.getFirstChild();
setClause.setFirstChild( eq );
eq.setNextSibling( currentFirstSetElement );
}
}
private AST generateVersionPropertyNode(Queryable persister) throws SemanticException {
String versionPropertyName = persister.getPropertyNames()[ persister.getVersionProperty() ];
AST versionPropertyRef = getASTFactory().create( HqlSqlTokenTypes.IDENT, versionPropertyName );
AST versionPropertyNode = lookupNonQualifiedProperty( versionPropertyRef );
resolve( versionPropertyNode );
return versionPropertyNode;
}
protected void prepareLogicOperator(AST operator) throws SemanticException {
( ( OperatorNode ) operator ).initialize();
}
protected void prepareArithmeticOperator(AST operator) throws SemanticException {
( ( OperatorNode ) operator ).initialize();
}
public static void panic() {
throw new QueryException( "TreeWalker: panic" );
}
}