2
* Hibernate, Relational Persistence for Idiomatic Java
4
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
5
* indicated by the @author tags or express copyright attribution
6
* statements applied by the authors. All third-party contributions are
7
* distributed under license by Red Hat Middleware LLC.
9
* This copyrighted material is made available to anyone wishing to use, modify,
10
* copy, or redistribute it subject to the terms and conditions of the GNU
11
* Lesser General Public License, as published by the Free Software Foundation.
13
* This program is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
18
* You should have received a copy of the GNU Lesser General Public License
19
* along with this distribution; if not, write to:
20
* Free Software Foundation, Inc.
21
* 51 Franklin Street, Fifth Floor
22
* Boston, MA 02110-1301 USA
25
package org.hibernate.hql.ast.util;
27
import java.util.ArrayList;
28
import java.util.Iterator;
29
import java.util.ListIterator;
30
import java.util.List;
31
import java.util.StringTokenizer;
32
import java.util.Collection;
34
import org.hibernate.AssertionFailure;
35
import org.hibernate.dialect.Dialect;
36
import org.hibernate.impl.FilterImpl;
37
import org.hibernate.type.Type;
38
import org.hibernate.param.DynamicFilterParameterSpecification;
39
import org.hibernate.engine.JoinSequence;
40
import org.hibernate.engine.QueryParameters;
41
import org.hibernate.hql.antlr.SqlTokenTypes;
42
import org.hibernate.hql.ast.HqlSqlWalker;
43
import org.hibernate.hql.ast.tree.FromClause;
44
import org.hibernate.hql.ast.tree.FromElement;
45
import org.hibernate.hql.ast.tree.QueryNode;
46
import org.hibernate.hql.ast.tree.DotNode;
47
import org.hibernate.hql.ast.tree.ParameterContainer;
48
import org.hibernate.hql.classic.ParserHelper;
49
import org.hibernate.sql.JoinFragment;
50
import org.hibernate.util.StringHelper;
51
import org.hibernate.util.ArrayHelper;
53
import org.slf4j.Logger;
54
import org.slf4j.LoggerFactory;
57
* Performs the post-processing of the join information gathered during semantic analysis.
58
* The join generating classes are complex, this encapsulates some of the JoinSequence-related
61
* @author Joshua Davis
63
public class JoinProcessor implements SqlTokenTypes {
65
private static final Logger log = LoggerFactory.getLogger( JoinProcessor.class );
67
private final HqlSqlWalker walker;
68
private final SyntheticAndFactory syntheticAndFactory;
71
* Constructs a new JoinProcessor.
73
* @param walker The walker to which we are bound, giving us access to needed resources.
75
public JoinProcessor(HqlSqlWalker walker) {
77
this.syntheticAndFactory = new SyntheticAndFactory( walker );
81
* Translates an AST join type (i.e., the token type) into a JoinFragment.XXX join type.
83
* @param astJoinType The AST join type (from HqlSqlTokenTypes or SqlTokenTypes)
84
* @return a JoinFragment.XXX join type.
88
public static int toHibernateJoinType(int astJoinType) {
89
switch ( astJoinType ) {
91
return JoinFragment.LEFT_OUTER_JOIN;
93
return JoinFragment.INNER_JOIN;
95
return JoinFragment.RIGHT_OUTER_JOIN;
97
throw new AssertionFailure( "undefined join type " + astJoinType );
101
public void processJoins(QueryNode query) {
102
final FromClause fromClause = query.getFromClause();
104
final List fromElements;
105
if ( DotNode.useThetaStyleImplicitJoins ) {
106
// for regression testing against output from the old parser...
107
// found it easiest to simply reorder the FromElements here into ascending order
108
// in terms of injecting them into the resulting sql ast in orders relative to those
109
// expected by the old parser; this is definitely another of those "only needed
110
// for regression purposes". The SyntheticAndFactory, then, simply injects them as it
112
fromElements = new ArrayList();
113
ListIterator liter = fromClause.getFromElements().listIterator( fromClause.getFromElements().size() );
114
while ( liter.hasPrevious() ) {
115
fromElements.add( liter.previous() );
119
fromElements = fromClause.getFromElements();
122
// Iterate through the alias,JoinSequence pairs and generate SQL token nodes.
123
Iterator iter = fromElements.iterator();
124
while ( iter.hasNext() ) {
125
final FromElement fromElement = ( FromElement ) iter.next();
126
JoinSequence join = fromElement.getJoinSequence();
128
new JoinSequence.Selector() {
129
public boolean includeSubclasses(String alias) {
130
// The uber-rule here is that we need to include subclass joins if
131
// the FromElement is in any way dereferenced by a property from
132
// the subclass table; otherwise we end up with column references
133
// qualified by a non-existent table reference in the resulting SQL...
134
boolean containsTableAlias = fromClause.containsTableAlias( alias );
135
if ( fromElement.isDereferencedBySubclassProperty() ) {
136
// TODO : or should we return 'containsTableAlias'??
137
log.trace( "forcing inclusion of extra joins [alias=" + alias + ", containsTableAlias=" + containsTableAlias + "]" );
140
boolean shallowQuery = walker.isShallowQuery();
141
boolean includeSubclasses = fromElement.isIncludeSubclasses();
142
boolean subQuery = fromClause.isSubQuery();
143
return includeSubclasses && containsTableAlias && !subQuery && !shallowQuery;
147
addJoinNodes( query, join, fromElement );
152
private void addJoinNodes(QueryNode query, JoinSequence join, FromElement fromElement) {
153
JoinFragment joinFragment = join.toJoinFragment(
154
walker.getEnabledFilters(),
155
fromElement.useFromFragment() || fromElement.isDereferencedBySuperclassOrSubclassProperty(),
156
fromElement.getWithClauseFragment(),
157
fromElement.getWithClauseJoinAlias()
160
String frag = joinFragment.toFromFragmentString();
161
String whereFrag = joinFragment.toWhereFragmentString();
163
// If the from element represents a JOIN_FRAGMENT and it is
164
// a theta-style join, convert its type from JOIN_FRAGMENT
166
if ( fromElement.getType() == JOIN_FRAGMENT &&
167
( join.isThetaStyle() || StringHelper.isNotEmpty( whereFrag ) ) ) {
168
fromElement.setType( FROM_FRAGMENT );
169
fromElement.getJoinSequence().setUseThetaStyle( true ); // this is used during SqlGenerator processing
172
// If there is a FROM fragment and the FROM element is an explicit, then add the from part.
173
if ( fromElement.useFromFragment() /*&& StringHelper.isNotEmpty( frag )*/ ) {
174
String fromFragment = processFromFragment( frag, join ).trim();
175
if ( log.isDebugEnabled() ) {
176
log.debug( "Using FROM fragment [" + fromFragment + "]" );
178
processDynamicFilterParameters(
185
syntheticAndFactory.addWhereFragment(
194
private String processFromFragment(String frag, JoinSequence join) {
195
String fromFragment = frag.trim();
196
// The FROM fragment will probably begin with ', '. Remove this if it is present.
197
if ( fromFragment.startsWith( ", " ) ) {
198
fromFragment = fromFragment.substring( 2 );
203
public static void processDynamicFilterParameters(
204
final String sqlFragment,
205
final ParameterContainer container,
206
final HqlSqlWalker walker) {
207
if ( walker.getEnabledFilters().isEmpty()
208
&& ( ! hasDynamicFilterParam( sqlFragment ) )
209
&& ( ! ( hasCollectionFilterParam( sqlFragment ) ) ) ) {
213
Dialect dialect = walker.getSessionFactoryHelper().getFactory().getDialect();
214
String symbols = new StringBuffer().append( ParserHelper.HQL_SEPARATORS )
215
.append( dialect.openQuote() )
216
.append( dialect.closeQuote() )
218
StringTokenizer tokens = new StringTokenizer( sqlFragment, symbols, true );
219
StringBuffer result = new StringBuffer();
221
while ( tokens.hasMoreTokens() ) {
222
final String token = tokens.nextToken();
223
if ( token.startsWith( ParserHelper.HQL_VARIABLE_PREFIX ) ) {
224
final String filterParameterName = token.substring( 1 );
225
final String[] parts = QueryParameters.parseFilterParameterName( filterParameterName );
226
final FilterImpl filter = ( FilterImpl ) walker.getEnabledFilters().get( parts[0] );
227
final Object value = filter.getParameter( parts[1] );
228
final Type type = filter.getFilterDefinition().getParameterType( parts[1] );
229
final String typeBindFragment = StringHelper.join(
231
ArrayHelper.fillArray( "?", type.getColumnSpan( walker.getSessionFactoryHelper().getFactory() ) )
233
final String bindFragment = ( value != null && Collection.class.isInstance( value ) )
234
? StringHelper.join( ",", ArrayHelper.fillArray( typeBindFragment, ( ( Collection ) value ).size() ) )
236
result.append( bindFragment );
237
container.addEmbeddedParameter( new DynamicFilterParameterSpecification( parts[0], parts[1], type ) );
240
result.append( token );
244
container.setText( result.toString() );
247
private static boolean hasDynamicFilterParam(String sqlFragment) {
248
return sqlFragment.indexOf( ParserHelper.HQL_VARIABLE_PREFIX ) < 0;
251
private static boolean hasCollectionFilterParam(String sqlFragment) {
252
return sqlFragment.indexOf( "?" ) < 0;