SqlGenerator.java
1 |
/*
|
---|---|
2 |
* Hibernate, Relational Persistence for Idiomatic Java
|
3 |
*
|
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.
|
8 |
*
|
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.
|
12 |
*
|
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
|
16 |
* for more details.
|
17 |
*
|
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
|
23 |
*
|
24 |
*/
|
25 |
package org.hibernate.hql.internal.ast; |
26 |
|
27 |
import java.util.ArrayList; |
28 |
import java.util.Arrays; |
29 |
import java.util.LinkedList; |
30 |
import java.util.List; |
31 |
import java.util.Stack; |
32 |
|
33 |
import antlr.RecognitionException; |
34 |
import antlr.collections.AST; |
35 |
import org.jboss.logging.Logger; |
36 |
|
37 |
import org.hibernate.QueryException; |
38 |
import org.hibernate.dialect.function.SQLFunction; |
39 |
import org.hibernate.engine.spi.SessionFactoryImplementor; |
40 |
import org.hibernate.hql.internal.antlr.SqlGeneratorBase; |
41 |
import org.hibernate.hql.internal.antlr.SqlTokenTypes; |
42 |
import org.hibernate.hql.internal.ast.tree.FromElement; |
43 |
import org.hibernate.hql.internal.ast.tree.FunctionNode; |
44 |
import org.hibernate.hql.internal.ast.tree.Node; |
45 |
import org.hibernate.hql.internal.ast.tree.ParameterContainer; |
46 |
import org.hibernate.hql.internal.ast.tree.ParameterNode; |
47 |
import org.hibernate.hql.internal.ast.util.ASTPrinter; |
48 |
import org.hibernate.internal.CoreMessageLogger; |
49 |
import org.hibernate.internal.util.StringHelper; |
50 |
import org.hibernate.param.ParameterSpecification; |
51 |
import org.hibernate.type.Type; |
52 |
|
53 |
/**
|
54 |
* Generates SQL by overriding callback methods in the base class, which does
|
55 |
* the actual SQL AST walking.
|
56 |
*
|
57 |
* @author Joshua Davis
|
58 |
* @author Steve Ebersole
|
59 |
*/
|
60 |
public class SqlGenerator extends SqlGeneratorBase implements ErrorReporter { |
61 |
|
62 |
private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, SqlGenerator.class.getName()); |
63 |
|
64 |
public static boolean REGRESSION_STYLE_CROSS_JOINS = false; |
65 |
|
66 |
/**
|
67 |
* all append invocations on the buf should go through this Output instance variable.
|
68 |
* The value of this variable may be temporarily substituted by sql function processing code
|
69 |
* to catch generated arguments.
|
70 |
* This is because sql function templates need arguments as separate string chunks
|
71 |
* that will be assembled into the target dialect-specific function call.
|
72 |
*/
|
73 |
private SqlWriter writer = new DefaultWriter(); |
74 |
|
75 |
private ParseErrorHandler parseErrorHandler;
|
76 |
private SessionFactoryImplementor sessionFactory;
|
77 |
private Stack<SqlWriter> outputStack = new Stack<SqlWriter>(); |
78 |
private final ASTPrinter printer = new ASTPrinter( SqlTokenTypes.class ); |
79 |
private List collectedParameters = new ArrayList(); |
80 |
|
81 |
|
82 |
// handle trace logging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
83 |
|
84 |
private int traceDepth = 0; |
85 |
|
86 |
@Override
|
87 |
public void traceIn(String ruleName, AST tree) { |
88 |
if ( !LOG.isTraceEnabled() ) return; |
89 |
if ( inputState.guessing > 0 ) return; |
90 |
String prefix = StringHelper.repeat( '-', ( traceDepth++ * 2 ) ) + "-> "; |
91 |
String traceText = ruleName + " (" + buildTraceNodeName( tree ) + ")"; |
92 |
LOG.trace( prefix + traceText ); |
93 |
} |
94 |
|
95 |
private String buildTraceNodeName(AST tree) { |
96 |
return tree == null |
97 |
? "???"
|
98 |
: tree.getText() + " [" + printer.getTokenTypeName( tree.getType() ) + "]"; |
99 |
} |
100 |
|
101 |
@Override
|
102 |
public void traceOut(String ruleName, AST tree) { |
103 |
if ( !LOG.isTraceEnabled() ) return; |
104 |
if ( inputState.guessing > 0 ) return; |
105 |
String prefix = "<-" + StringHelper.repeat( '-', ( --traceDepth * 2 ) ) + " "; |
106 |
LOG.trace( prefix + ruleName ); |
107 |
} |
108 |
|
109 |
public List getCollectedParameters() { |
110 |
return collectedParameters;
|
111 |
} |
112 |
|
113 |
@Override
|
114 |
protected void out(String s) { |
115 |
writer.clause( s ); |
116 |
} |
117 |
|
118 |
@Override
|
119 |
protected void out(AST n) { |
120 |
if ( n instanceof Node ) { |
121 |
out( ( ( Node ) n ).getRenderText( sessionFactory ) ); |
122 |
} |
123 |
else {
|
124 |
super.out( n );
|
125 |
} |
126 |
|
127 |
if ( n instanceof ParameterNode ) { |
128 |
collectedParameters.add( ( ( ParameterNode ) n ).getHqlParameterSpecification() ); |
129 |
} |
130 |
else if ( n instanceof ParameterContainer ) { |
131 |
if ( ( ( ParameterContainer ) n ).hasEmbeddedParameters() ) {
|
132 |
ParameterSpecification[] specifications = ( ( ParameterContainer ) n ).getEmbeddedParameters();
|
133 |
if ( specifications != null ) { |
134 |
collectedParameters.addAll( Arrays.asList( specifications ) );
|
135 |
} |
136 |
} |
137 |
} |
138 |
} |
139 |
|
140 |
@Override
|
141 |
protected void commaBetweenParameters(String comma) { |
142 |
writer.commaBetweenParameters( comma ); |
143 |
} |
144 |
|
145 |
@Override
|
146 |
public void reportError(RecognitionException e) { |
147 |
parseErrorHandler.reportError( e ); // Use the delegate.
|
148 |
} |
149 |
|
150 |
@Override
|
151 |
public void reportError(String s) { |
152 |
parseErrorHandler.reportError( s ); // Use the delegate.
|
153 |
} |
154 |
|
155 |
@Override
|
156 |
public void reportWarning(String s) { |
157 |
parseErrorHandler.reportWarning( s ); |
158 |
} |
159 |
|
160 |
public ParseErrorHandler getParseErrorHandler() {
|
161 |
return parseErrorHandler;
|
162 |
} |
163 |
|
164 |
public SqlGenerator(SessionFactoryImplementor sfi) {
|
165 |
super();
|
166 |
parseErrorHandler = new ErrorCounter();
|
167 |
sessionFactory = sfi; |
168 |
} |
169 |
|
170 |
public String getSQL() { |
171 |
return getStringBuilder().toString();
|
172 |
} |
173 |
|
174 |
@Override
|
175 |
protected void optionalSpace() { |
176 |
int c = getLastChar();
|
177 |
switch ( c ) {
|
178 |
case -1: |
179 |
return;
|
180 |
case ' ': |
181 |
return;
|
182 |
case ')': |
183 |
return;
|
184 |
case '(': |
185 |
return;
|
186 |
default:
|
187 |
out( " " );
|
188 |
} |
189 |
} |
190 |
|
191 |
@Override
|
192 |
protected void beginFunctionTemplate(AST node, AST nameNode) { |
193 |
// NOTE for AGGREGATE both nodes are the same; for METHOD the first is the METHOD, the second is the
|
194 |
// METHOD_NAME
|
195 |
FunctionNode functionNode = ( FunctionNode ) node; |
196 |
SQLFunction sqlFunction = functionNode.getSQLFunction(); |
197 |
// save off the previous writer
|
198 |
outputStack.push( writer ); |
199 |
if ( sqlFunction == null ) { |
200 |
writer = new StandardFunctionWriter();
|
201 |
// if SQLFunction is null we just write the function out as it appears in the hql statement
|
202 |
super.beginFunctionTemplate( node, nameNode );
|
203 |
} |
204 |
else {
|
205 |
// this function has a registered SQLFunction -> redirect output and catch the arguments
|
206 |
FunctionWriter func = new FunctionArguments();
|
207 |
func.init(sqlFunction); |
208 |
writer = func; |
209 |
} |
210 |
} |
211 |
|
212 |
@Override
|
213 |
protected void endFunctionTemplate(AST node) { |
214 |
FunctionNode functionNode = ( FunctionNode ) node; |
215 |
SQLFunction sqlFunction = functionNode.getSQLFunction(); |
216 |
Type functionType = null; |
217 |
if ( sqlFunction == null ) { |
218 |
super.endFunctionTemplate( node );
|
219 |
} |
220 |
else {
|
221 |
functionType = functionNode.getFirstArgumentType(); |
222 |
} |
223 |
// save the current writer
|
224 |
FunctionWriter func = (FunctionWriter) writer; |
225 |
// restore the original writer
|
226 |
writer = (SqlWriter) outputStack.pop(); |
227 |
// generate the correct output to the original writer
|
228 |
out( func.render(functionType) ); |
229 |
} |
230 |
|
231 |
// --- Inner classes (moved here from sql-gen.g) ---
|
232 |
|
233 |
/**
|
234 |
* API to ease writing of SQL function calls.
|
235 |
*/
|
236 |
interface FunctionWriter extends SqlWriter { |
237 |
void init(SQLFunction template);
|
238 |
|
239 |
String render(Type functionType); |
240 |
} |
241 |
|
242 |
/**
|
243 |
* The generic function writer. This is needed to allow function calls
|
244 |
* to be nested properly.
|
245 |
*/
|
246 |
class StandardFunctionWriter implements FunctionWriter { |
247 |
private StringBuffer sb = new StringBuffer(); |
248 |
|
249 |
public void clause(String clause) { |
250 |
sb.append( clause ); |
251 |
} |
252 |
|
253 |
public void commaBetweenParameters(String comma) { |
254 |
sb.append( comma ); |
255 |
} |
256 |
|
257 |
public void init(SQLFunction template) { |
258 |
} |
259 |
|
260 |
public String render(Type functionType) { |
261 |
return sb.toString();
|
262 |
} |
263 |
} |
264 |
|
265 |
/**
|
266 |
* Writes SQL fragments.
|
267 |
*/
|
268 |
interface SqlWriter { |
269 |
void clause(String clause); |
270 |
|
271 |
/**
|
272 |
* todo remove this hack
|
273 |
* The parameter is either ", " or " , ". This is needed to pass sql generating tests as the old
|
274 |
* sql generator uses " , " in the WHERE and ", " in SELECT.
|
275 |
*
|
276 |
* @param comma either " , " or ", "
|
277 |
*/
|
278 |
void commaBetweenParameters(String comma); |
279 |
} |
280 |
|
281 |
/**
|
282 |
* SQL function processing code redirects generated SQL output to an instance of this class
|
283 |
* which catches function arguments.
|
284 |
*/
|
285 |
class FunctionArguments extends StandardFunctionWriter { |
286 |
private int argInd; |
287 |
private final List<String> args = new ArrayList<String>(3); |
288 |
private SQLFunction template = null; |
289 |
|
290 |
public void clause(String clause) { |
291 |
if ( argInd == args.size() ) {
|
292 |
args.add( clause ); |
293 |
} |
294 |
else {
|
295 |
args.set( argInd, args.get( argInd ) + clause ); |
296 |
} |
297 |
} |
298 |
|
299 |
public void commaBetweenParameters(String comma) { |
300 |
++argInd; |
301 |
} |
302 |
|
303 |
public void init(SQLFunction template) { |
304 |
this.template = template;
|
305 |
} |
306 |
|
307 |
public String render(Type functionType) { |
308 |
return template.render(functionType, args, sessionFactory);
|
309 |
} |
310 |
} |
311 |
|
312 |
/**
|
313 |
* The default SQL writer.
|
314 |
*/
|
315 |
class DefaultWriter implements SqlWriter { |
316 |
public void clause(String clause) { |
317 |
getStringBuilder().append( clause ); |
318 |
} |
319 |
|
320 |
public void commaBetweenParameters(String comma) { |
321 |
getStringBuilder().append( comma ); |
322 |
} |
323 |
} |
324 |
|
325 |
public static void panic() { |
326 |
throw new QueryException( "TreeWalker: panic" ); |
327 |
} |
328 |
|
329 |
@Override
|
330 |
protected void fromFragmentSeparator(AST a) { |
331 |
// check two "adjecent" nodes at the top of the from-clause tree
|
332 |
AST next = a.getNextSibling(); |
333 |
if ( next == null || !hasText( a ) ) { |
334 |
return;
|
335 |
} |
336 |
|
337 |
FromElement left = ( FromElement ) a; |
338 |
FromElement right = ( FromElement ) next; |
339 |
|
340 |
///////////////////////////////////////////////////////////////////////
|
341 |
// HACK ALERT !!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
342 |
// Attempt to work around "ghost" ImpliedFromElements that occasionally
|
343 |
// show up between the actual things being joined. This consistently
|
344 |
// occurs from index nodes (at least against many-to-many). Not sure
|
345 |
// if there are other conditions
|
346 |
//
|
347 |
// Essentially, look-ahead to the next FromElement that actually
|
348 |
// writes something to the SQL
|
349 |
while ( right != null && !hasText( right ) ) { |
350 |
right = ( FromElement ) right.getNextSibling(); |
351 |
} |
352 |
if ( right == null ) { |
353 |
return;
|
354 |
} |
355 |
///////////////////////////////////////////////////////////////////////
|
356 |
|
357 |
if ( !hasText( right ) ) {
|
358 |
return;
|
359 |
} |
360 |
|
361 |
if ( right.getRealOrigin() == left ||
|
362 |
( right.getRealOrigin() != null && right.getRealOrigin() == left.getRealOrigin() ) ) {
|
363 |
// right represents a joins originating from left; or
|
364 |
// both right and left reprersent joins originating from the same FromElement
|
365 |
if ( right.getJoinSequence() != null && right.getJoinSequence().isThetaStyle() ) { |
366 |
writeCrossJoinSeparator(); |
367 |
} |
368 |
else {
|
369 |
out( " " );
|
370 |
} |
371 |
} |
372 |
else {
|
373 |
// these are just two unrelated table references
|
374 |
writeCrossJoinSeparator(); |
375 |
} |
376 |
} |
377 |
|
378 |
private void writeCrossJoinSeparator() { |
379 |
if ( REGRESSION_STYLE_CROSS_JOINS ) {
|
380 |
out( ", " );
|
381 |
} |
382 |
else {
|
383 |
out( sessionFactory.getDialect().getCrossJoinSeparator() ); |
384 |
} |
385 |
} |
386 |
|
387 |
@Override
|
388 |
protected void nestedFromFragment(AST d, AST parent) { |
389 |
// check a set of parent/child nodes in the from-clause tree
|
390 |
// to determine if a comma is required between them
|
391 |
if ( d != null && hasText( d ) ) { |
392 |
if ( parent != null && hasText( parent ) ) { |
393 |
// again, both should be FromElements
|
394 |
FromElement left = ( FromElement ) parent; |
395 |
FromElement right = ( FromElement ) d; |
396 |
if ( right.getRealOrigin() == left ) {
|
397 |
// right represents a joins originating from left...
|
398 |
if ( right.getJoinSequence() != null && right.getJoinSequence().isThetaStyle() ) { |
399 |
out( ", " );
|
400 |
} |
401 |
else {
|
402 |
out( " " );
|
403 |
} |
404 |
} |
405 |
else {
|
406 |
// not so sure this is even valid subtree. but if it was, it'd
|
407 |
// represent two unrelated table references...
|
408 |
out( ", " );
|
409 |
} |
410 |
} |
411 |
out( d ); |
412 |
} |
413 |
} |
414 |
} |