You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
469 lines
12 KiB
469 lines
12 KiB
7 years ago
|
header
|
||
|
{
|
||
|
/*
|
||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||
|
*
|
||
|
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
|
||
|
* indicated by the @author tags or express copyright attribution
|
||
|
* statements applied by the authors. All third-party contributions are
|
||
|
* distributed under license by Red Hat Middleware LLC.
|
||
|
*
|
||
|
* This copyrighted material is made available to anyone wishing to use, modify,
|
||
|
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||
|
* Lesser General Public License, as published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||
|
* for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU Lesser General Public License
|
||
|
* along with this distribution; if not, write to:
|
||
|
* Free Software Foundation, Inc.
|
||
|
* 51 Franklin Street, Fifth Floor
|
||
|
* Boston, MA 02110-1301 USA
|
||
|
*
|
||
|
*/
|
||
|
package com.fr.third.org.hibernate.sql.ordering.antlr;
|
||
|
}
|
||
|
/**
|
||
|
* Antlr grammar for dealing with <tt>order-by</tt> mapping fragments.
|
||
|
|
||
|
* @author Steve Ebersole
|
||
|
*/
|
||
|
class GeneratedOrderByFragmentParser extends Parser;
|
||
|
|
||
|
options
|
||
|
{
|
||
|
exportVocab=OrderByTemplate;
|
||
|
buildAST=true;
|
||
|
k=3;
|
||
|
}
|
||
|
|
||
|
tokens
|
||
|
{
|
||
|
// synthetic tokens
|
||
|
ORDER_BY;
|
||
|
SORT_SPEC;
|
||
|
ORDER_SPEC;
|
||
|
NULL_ORDER;
|
||
|
SORT_KEY;
|
||
|
EXPR_LIST;
|
||
|
DOT;
|
||
|
IDENT_LIST;
|
||
|
COLUMN_REF;
|
||
|
|
||
|
COLLATE="collate";
|
||
|
ASCENDING="asc";
|
||
|
DESCENDING="desc";
|
||
|
NULLS="nulls";
|
||
|
FIRST;
|
||
|
LAST;
|
||
|
}
|
||
|
|
||
|
|
||
|
{
|
||
|
/**
|
||
|
* Method for logging execution trace information.
|
||
|
*
|
||
|
* @param msg The trace message.
|
||
|
*/
|
||
|
protected void trace(String msg) {
|
||
|
System.out.println( msg );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Extract a node's text.
|
||
|
*
|
||
|
* @param ast The node
|
||
|
*
|
||
|
* @return The text.
|
||
|
*/
|
||
|
protected final String extractText(AST ast) {
|
||
|
// for some reason, within AST creation blocks "[]" I am sometimes unable to refer to the AST.getText() method
|
||
|
// using #var (the #var is not interpreted as the rule's output AST).
|
||
|
return ast.getText();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Process the given node as a quote identifier. These need to be quoted in the dialect-specific way.
|
||
|
*
|
||
|
* @param ident The quoted-identifier node.
|
||
|
*
|
||
|
* @return The processed node.
|
||
|
*
|
||
|
* @see com.fr.third.org.hibernate.dialect.Dialect#quote
|
||
|
*/
|
||
|
protected AST quotedIdentifier(AST ident) {
|
||
|
return ident;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Process the given node as a quote string.
|
||
|
*
|
||
|
* @param ident The quoted string. This is used from within function param recognition, and represents a
|
||
|
* SQL-quoted string.
|
||
|
*
|
||
|
* @return The processed node.
|
||
|
*/
|
||
|
protected AST quotedString(AST ident) {
|
||
|
return ident;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A check to see if the text of the given node represents a known function name.
|
||
|
*
|
||
|
* @param ast The node whose text we want to check.
|
||
|
*
|
||
|
* @return True if the node's text is a known function name, false otherwise.
|
||
|
*
|
||
|
* @see com.fr.third.org.hibernate.dialect.function.SQLFunctionRegistry
|
||
|
*/
|
||
|
protected boolean isFunctionName(AST ast) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Process the given node as a function.
|
||
|
*
|
||
|
* @param The node representing the function invocation (including parameters as subtree components).
|
||
|
*
|
||
|
* @return The processed node.
|
||
|
*/
|
||
|
protected AST resolveFunction(AST ast) {
|
||
|
return ast;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Process the given node as an IDENT. May represent either a column reference or a property reference.
|
||
|
*
|
||
|
* @param ident The node whose text represents either a column or property reference.
|
||
|
*
|
||
|
* @return The processed node.
|
||
|
*/
|
||
|
protected AST resolveIdent(AST ident) {
|
||
|
return ident;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Allow post processing of each <tt>sort specification</tt>
|
||
|
*
|
||
|
* @param The grammar-built sort specification subtree.
|
||
|
*
|
||
|
* @return The processed sort specification subtree.
|
||
|
*/
|
||
|
protected AST postProcessSortSpecification(AST sortSpec) {
|
||
|
return sortSpec;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Main recognition rule for this grammar
|
||
|
*/
|
||
|
orderByFragment { trace("orderByFragment"); }
|
||
|
: sortSpecification ( COMMA! sortSpecification )* {
|
||
|
#orderByFragment = #( [ORDER_BY, "order-by"], #orderByFragment );
|
||
|
}
|
||
|
;
|
||
|
|
||
|
/**
|
||
|
* Recognition rule for what ANSI SQL terms the <tt>sort specification</tt>, which is essentially each thing upon which
|
||
|
* the results should be sorted.
|
||
|
*/
|
||
|
sortSpecification { trace("sortSpecification"); }
|
||
|
: sortKey (collationSpecification)? (orderingSpecification)? (nullOrdering)? {
|
||
|
#sortSpecification = #( [SORT_SPEC, "{sort specification}"], #sortSpecification );
|
||
|
#sortSpecification = postProcessSortSpecification( #sortSpecification );
|
||
|
}
|
||
|
;
|
||
|
|
||
|
/**
|
||
|
* Recognition rule for what ANSI SQL terms the <tt>sort key</tt> which is the expression (column, function, etc) upon
|
||
|
* which to base the sorting.
|
||
|
*/
|
||
|
sortKey! { trace("sortKey"); }
|
||
|
: e:expression {
|
||
|
#sortKey = #( [SORT_KEY, "sort key"], #e );
|
||
|
}
|
||
|
;
|
||
|
|
||
|
/**
|
||
|
* Recognition rule what this grammar recognizes as valid <tt>sort key</tt>.
|
||
|
*/
|
||
|
expression! { trace("expression"); }
|
||
|
: HARD_QUOTE qi:IDENT HARD_QUOTE {
|
||
|
#expression = quotedIdentifier( #qi );
|
||
|
}
|
||
|
| ( IDENT (DOT IDENT)* OPEN_PAREN ) => f:functionCall {
|
||
|
#expression = #f;
|
||
|
}
|
||
|
| p:simplePropertyPath {
|
||
|
#expression = resolveIdent( #p );
|
||
|
}
|
||
|
| i:IDENT {
|
||
|
if ( isFunctionName( #i ) ) {
|
||
|
#expression = resolveFunction( #i );
|
||
|
}
|
||
|
else {
|
||
|
#expression = resolveIdent( #i );
|
||
|
}
|
||
|
}
|
||
|
;
|
||
|
|
||
|
/**
|
||
|
* Intended for use as a syntactic predicate to determine whether an IDENT represents a known SQL function name.
|
||
|
*/
|
||
|
functionCallCheck! { trace("functionCallCheck"); }
|
||
|
: IDENT (DOT IDENT)* OPEN_PAREN { true }?
|
||
|
;
|
||
|
|
||
|
/**
|
||
|
* Recognition rule for a function call
|
||
|
*/
|
||
|
functionCall! { trace("functionCall"); }
|
||
|
: fn:functionName OPEN_PAREN pl:functionParameterList CLOSE_PAREN {
|
||
|
#functionCall = #( [IDENT, extractText( #fn )], #pl );
|
||
|
#functionCall = resolveFunction( #functionCall );
|
||
|
}
|
||
|
;
|
||
|
|
||
|
/**
|
||
|
* A function-name is an IDENT followed by zero or more (DOT IDENT) sequences
|
||
|
*/
|
||
|
functionName {
|
||
|
trace("functionName");
|
||
|
StringBuilder buffer = new StringBuilder();
|
||
|
}
|
||
|
: i:IDENT { buffer.append( i.getText() ); }
|
||
|
( DOT i2:IDENT { buffer.append( '.').append( i2.getText() ); } )* {
|
||
|
#functionName = #( [IDENT,buffer.toString()] );
|
||
|
}
|
||
|
;
|
||
|
|
||
|
/**
|
||
|
* Recognition rule used to "wrap" all function parameters into an EXPR_LIST node
|
||
|
*/
|
||
|
functionParameterList { trace("functionParameterList"); }
|
||
|
: functionParameter ( COMMA! functionParameter )* {
|
||
|
#functionParameterList = #( [EXPR_LIST, "{param list}"], #functionParameterList );
|
||
|
}
|
||
|
;
|
||
|
|
||
|
/**
|
||
|
* Recognized function parameters.
|
||
|
*/
|
||
|
functionParameter { trace("functionParameter"); }
|
||
|
: expression
|
||
|
| NUM_DOUBLE
|
||
|
| NUM_FLOAT
|
||
|
| NUM_INT
|
||
|
| NUM_LONG
|
||
|
| QUOTED_STRING {
|
||
|
#functionParameter = quotedString( #functionParameter );
|
||
|
}
|
||
|
;
|
||
|
|
||
|
/**
|
||
|
* Recognition rule for what ANSI SQL terms the <tt>collation specification</tt> used to allow specifying that sorting for
|
||
|
* the given {@link #sortSpecification} be treated within a specific character-set.
|
||
|
*/
|
||
|
collationSpecification! { trace("collationSpecification"); }
|
||
|
: c:COLLATE cn:collationName {
|
||
|
#collationSpecification = #( [COLLATE, extractText( #cn )] );
|
||
|
}
|
||
|
;
|
||
|
|
||
|
/**
|
||
|
* The collation name wrt {@link #collationSpecification}. Namely, the character-set.
|
||
|
*/
|
||
|
collationName { trace("collationSpecification"); }
|
||
|
: IDENT
|
||
|
;
|
||
|
|
||
|
/**
|
||
|
* Recognition rule for what ANSI SQL terms the <tt>ordering specification</tt>; <tt>ASCENDING</tt> or
|
||
|
* <tt>DESCENDING</tt>.
|
||
|
*/
|
||
|
orderingSpecification! { trace("orderingSpecification"); }
|
||
|
: ( "asc" | "ascending" ) {
|
||
|
#orderingSpecification = #( [ORDER_SPEC, "asc"] );
|
||
|
}
|
||
|
| ( "desc" | "descending") {
|
||
|
#orderingSpecification = #( [ORDER_SPEC, "desc"] );
|
||
|
}
|
||
|
;
|
||
|
|
||
|
/**
|
||
|
* Recognition rule for what SQL-2003 terms the <tt>null ordering</tt>; <tt>NULLS FIRST</tt> or
|
||
|
* <tt>NULLS LAST</tt>.
|
||
|
*/
|
||
|
nullOrdering! { trace("nullOrdering"); }
|
||
|
: NULLS n:nullPrecedence {
|
||
|
#nullOrdering = #( [NULL_ORDER, extractText( #n )] );
|
||
|
}
|
||
|
;
|
||
|
|
||
|
nullPrecedence { trace("nullPrecedence"); }
|
||
|
: IDENT {
|
||
|
if ( "first".equalsIgnoreCase( #nullPrecedence.getText() ) ) {
|
||
|
#nullPrecedence.setType( FIRST );
|
||
|
}
|
||
|
else if ( "last".equalsIgnoreCase( #nullPrecedence.getText() ) ) {
|
||
|
#nullPrecedence.setType( LAST );
|
||
|
}
|
||
|
else {
|
||
|
throw new SemanticException( "Expecting 'first' or 'last', but found '" + #nullPrecedence.getText() + "' as null ordering precedence." );
|
||
|
}
|
||
|
}
|
||
|
;
|
||
|
|
||
|
/**
|
||
|
* A simple-property-path is an IDENT followed by one or more (DOT IDENT) sequences
|
||
|
*/
|
||
|
simplePropertyPath {
|
||
|
trace("simplePropertyPath");
|
||
|
StringBuilder buffer = new StringBuilder();
|
||
|
}
|
||
|
: i:IDENT { buffer.append( i.getText() ); }
|
||
|
( DOT i2:IDENT { buffer.append( '.').append( i2.getText() ); } )+ {
|
||
|
#simplePropertyPath = #( [IDENT,buffer.toString()] );
|
||
|
}
|
||
|
;
|
||
|
|
||
|
|
||
|
// **** LEXER ******************************************************************
|
||
|
|
||
|
/**
|
||
|
* Lexer for the <tt>order-by</tt> fragment parser
|
||
|
|
||
|
* @author Steve Ebersole
|
||
|
* @author Joshua Davis
|
||
|
*/
|
||
|
class GeneratedOrderByLexer extends Lexer;
|
||
|
|
||
|
options {
|
||
|
exportVocab=OrderByTemplate;
|
||
|
testLiterals = false;
|
||
|
k=2;
|
||
|
charVocabulary='\u0000'..'\uFFFE'; // Allow any char but \uFFFF (16 bit -1, ANTLR's EOF character)
|
||
|
caseSensitive = false;
|
||
|
caseSensitiveLiterals = false;
|
||
|
}
|
||
|
|
||
|
// -- Keywords --
|
||
|
|
||
|
OPEN_PAREN: '(';
|
||
|
CLOSE_PAREN: ')';
|
||
|
|
||
|
COMMA: ',';
|
||
|
|
||
|
HARD_QUOTE: '`';
|
||
|
|
||
|
IDENT options { testLiterals=true; }
|
||
|
: ID_START_LETTER ( ID_LETTER )*
|
||
|
;
|
||
|
|
||
|
protected
|
||
|
ID_START_LETTER
|
||
|
: '_'
|
||
|
| '$'
|
||
|
| 'a'..'z'
|
||
|
| '\u0080'..'\ufffe' // HHH-558 : Allow unicode chars in identifiers
|
||
|
;
|
||
|
|
||
|
protected
|
||
|
ID_LETTER
|
||
|
: ID_START_LETTER
|
||
|
| '0'..'9'
|
||
|
;
|
||
|
|
||
|
QUOTED_STRING
|
||
|
: '\'' ( (ESCqs)=> ESCqs | ~'\'' )* '\''
|
||
|
;
|
||
|
|
||
|
protected
|
||
|
ESCqs
|
||
|
:
|
||
|
'\'' '\''
|
||
|
;
|
||
|
|
||
|
//--- From the Java example grammar ---
|
||
|
// a numeric literal
|
||
|
NUM_INT
|
||
|
{boolean isDecimal=false; Token t=null;}
|
||
|
: '.' {_ttype = DOT;}
|
||
|
( ('0'..'9')+ (EXPONENT)? (f1:FLOAT_SUFFIX {t=f1;})?
|
||
|
{
|
||
|
if (t != null && t.getText().toUpperCase().indexOf('F')>=0)
|
||
|
{
|
||
|
_ttype = NUM_FLOAT;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_ttype = NUM_DOUBLE; // assume double
|
||
|
}
|
||
|
}
|
||
|
)?
|
||
|
| ( '0' {isDecimal = true;} // special case for just '0'
|
||
|
( ('x')
|
||
|
( // hex
|
||
|
// the 'e'|'E' and float suffix stuff look
|
||
|
// like hex digits, hence the (...)+ doesn't
|
||
|
// know when to stop: ambig. ANTLR resolves
|
||
|
// it correctly by matching immediately. It
|
||
|
// is therefore ok to hush warning.
|
||
|
options { warnWhenFollowAmbig=false; }
|
||
|
: HEX_DIGIT
|
||
|
)+
|
||
|
| ('0'..'7')+ // octal
|
||
|
)?
|
||
|
| ('1'..'9') ('0'..'9')* {isDecimal=true;} // non-zero decimal
|
||
|
)
|
||
|
( ('l') { _ttype = NUM_LONG; }
|
||
|
|
||
|
// only check to see if it's a float if looks like decimal so far
|
||
|
| {isDecimal}?
|
||
|
( '.' ('0'..'9')* (EXPONENT)? (f2:FLOAT_SUFFIX {t=f2;})?
|
||
|
| EXPONENT (f3:FLOAT_SUFFIX {t=f3;})?
|
||
|
| f4:FLOAT_SUFFIX {t=f4;}
|
||
|
)
|
||
|
{
|
||
|
if (t != null && t.getText().toUpperCase() .indexOf('F') >= 0)
|
||
|
{
|
||
|
_ttype = NUM_FLOAT;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_ttype = NUM_DOUBLE; // assume double
|
||
|
}
|
||
|
}
|
||
|
)?
|
||
|
;
|
||
|
|
||
|
// hexadecimal digit (again, note it's protected!)
|
||
|
protected
|
||
|
HEX_DIGIT
|
||
|
: ('0'..'9'|'a'..'f')
|
||
|
;
|
||
|
|
||
|
// a couple protected methods to assist in matching floating point numbers
|
||
|
protected
|
||
|
EXPONENT
|
||
|
: ('e') ('+'|'-')? ('0'..'9')+
|
||
|
;
|
||
|
|
||
|
protected
|
||
|
FLOAT_SUFFIX
|
||
|
: 'f'|'d'
|
||
|
;
|
||
|
|
||
|
WS : ( ' '
|
||
|
| '\t'
|
||
|
| '\r' '\n' { newline(); }
|
||
|
| '\n' { newline(); }
|
||
|
| '\r' { newline(); }
|
||
|
)
|
||
|
{$setType(Token.SKIP);} //ignore this token
|
||
|
;
|