
org.hibernate.hql.ast.HqlSqlWalker Maven / Gradle / Ivy
The newest version!
// $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" );
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy