Project

General

Profile

SqlGenerator.java

Stanislav Lomany, 07/09/2013 06:13 AM

Download (11.9 KB)

 
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
}