HHH-13778: `@OrderBy` handling using SQL AST

- initial work - Antlr grammar, parse tree visitation and building OrderByFragment (translation) tree
- fixed bug in HQL parsing - was building an unnecessary HqlLexer instance
This commit is contained in:
Steve Ebersole 2019-12-12 11:57:54 -06:00
parent 8c671d98d0
commit 8600058784
47 changed files with 1064 additions and 590 deletions

View File

@ -289,9 +289,10 @@ test {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Custom Antlr v4 Plugin (to work with Gradle 3)
// Custom Antlr v4 Plugin
// - the Gradle-supplied Antlr plugin attempts to simultaneously support
// multiple versions of Antlr which leads to many difficulties. this custom
// plugin provides dedicated and simplified support for Antlr v4
ext {
baseAntlrInputPath = 'src/main/antlr'
@ -354,6 +355,7 @@ class Antlr4GenerationTask extends DefaultTask {
static final String HQL_PCKG = 'org.hibernate.query.hql.internal'
static final String IMPORT_SQL_PCKG = 'org.hibernate.tool.hbm2ddl.grammar'
static final String GRAPH_PCKG = 'org.hibernate.graph.internal.parse'
static final String ORDER_PCKG = 'org.hibernate.grammars.ordering'
List<GrammarDescriptor> grammarDescriptors = [
new GrammarDescriptor( HQL_PCKG, 'HqlLexer' ),
@ -361,7 +363,9 @@ class Antlr4GenerationTask extends DefaultTask {
new GrammarDescriptor( IMPORT_SQL_PCKG, 'SqlStatementLexer' ),
new GrammarDescriptor( IMPORT_SQL_PCKG, 'SqlStatementParser' ),
new GrammarDescriptor( GRAPH_PCKG, 'GraphLanguageLexer' ),
new GrammarDescriptor( GRAPH_PCKG, 'GraphLanguageParser' )
new GrammarDescriptor( GRAPH_PCKG, 'GraphLanguageParser' ),
new GrammarDescriptor( ORDER_PCKG, 'OrderingLexer' ),
new GrammarDescriptor( ORDER_PCKG, 'OrderingParser' )
]
@InputFiles

View File

@ -1,97 +0,0 @@
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 org.hibernate.sql.ordering.antlr;
}
/**
* Antlr grammar for rendering <tt>ORDER_BY</tt> trees as described by the {@link OrderByFragmentParser}
* @author Steve Ebersole
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
class GeneratedOrderByFragmentRenderer extends TreeParser;
options {
importVocab=OrderByTemplate;
buildAST=false;
}
{
// the buffer to which we write the resulting SQL.
private StringBuilder buffer = new StringBuilder();
protected void out(String text) {
buffer.append( text );
}
protected void out(AST ast) {
buffer.append( ast.getText() );
}
/*package*/ String getRenderedFragment() {
return buffer.toString();
}
/**
* Implementation note: This is just a stub. OrderByFragmentRenderer contains the effective implementation.
*/
protected String renderOrderByElement(String expression, String collation, String order, String nulls) {
throw new UnsupportedOperationException("Concrete ORDER BY renderer should override this method.");
}
}
orderByFragment
: #(
ORDER_BY sortSpecification ( {out(", ");} sortSpecification)*
)
;
sortSpecification { String sortKeySpec = null; String collSpec = null; String ordSpec = null; String nullOrd = null; }
: #(
SORT_SPEC sortKeySpec=sortKeySpecification (collSpec=collationSpecification)? (ordSpec=orderingSpecification)? (nullOrd=nullOrdering)?
{ out( renderOrderByElement( sortKeySpec, collSpec, ordSpec, nullOrd ) ); }
)
;
sortKeySpecification returns [String sortKeyExp = null]
: #(SORT_KEY s:sortKey) { sortKeyExp = #s.getText(); }
;
sortKey
: IDENT
;
collationSpecification returns [String collSpecExp = null]
: c:COLLATE { collSpecExp = "collate " + #c.getText(); }
;
orderingSpecification returns [String ordSpecExp = null]
: o:ORDER_SPEC { ordSpecExp = #o.getText(); }
;
nullOrdering returns [String nullOrdExp = null]
: n:NULL_ORDER { nullOrdExp = #n.getText(); }
;

View File

@ -1,469 +0,0 @@
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 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.
*/
@org.hibernate.internal.build.AllowSysOut
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 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 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
;

View File

@ -0,0 +1,3 @@
# Ignore the `*.tokens` file generated by Antlr when running its grammar generation tool
*.tokens

View File

@ -0,0 +1,133 @@
lexer grammar OrderingLexer;
@header {
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.grammars.ordering;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Whitespace -> ignore
WS : ( ' ' | '\t' | '\f' | EOL ) -> skip;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// fragments and literals
fragment
EOL : [\r\n]+;
INTEGER_LITERAL : INTEGER_NUMBER ;
fragment
INTEGER_NUMBER : ('0' | '1'..'9' '0'..'9'*) ;
LONG_LITERAL : INTEGER_NUMBER ('l'|'L');
BIG_INTEGER_LITERAL : INTEGER_NUMBER ('bi'|'BI') ;
HEX_LITERAL : '0' ('x'|'X') HEX_DIGIT+ ('l'|'L')? ;
fragment
HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ;
OCTAL_LITERAL : '0' ('0'..'7')+ ('l'|'L')? ;
FLOAT_LITERAL : FLOATING_POINT_NUMBER ('f'|'F')? ;
fragment
FLOATING_POINT_NUMBER
: ('0'..'9')+ '.' ('0'..'9')* EXPONENT?
| '.' ('0'..'9')+ EXPONENT?
| ('0'..'9')+ EXPONENT
| ('0'..'9')+
;
DOUBLE_LITERAL : FLOATING_POINT_NUMBER ('d'|'D') ;
BIG_DECIMAL_LITERAL : FLOATING_POINT_NUMBER ('bd'|'BD') ;
fragment
EXPONENT : ('e'|'E') ('+'|'-')? ('0'..'9')+ ;
CHARACTER_LITERAL
: '\'' ( ESCAPE_SEQUENCE | ~('\''|'\\') ) '\'' {setText(getText().substring(1, getText().length()-1));}
;
STRING_LITERAL
: '"' ( ESCAPE_SEQUENCE | ~('\\'|'"') )* '"' {setText(getText().substring(1, getText().length()-1));}
| ('\'' ( ESCAPE_SEQUENCE | ~('\\'|'\'') )* '\'')+ {setText(getText().substring(1, getText().length()-1).replace("''", "'"));}
;
fragment
ESCAPE_SEQUENCE
: '\\' ('b'|'t'|'n'|'f'|'r'|'\\"'|'\''|'\\')
| UNICODE_ESCAPE
| OCTAL_ESCAPE
;
fragment
OCTAL_ESCAPE
: '\\' ('0'..'3') ('0'..'7') ('0'..'7')
| '\\' ('0'..'7') ('0'..'7')
| '\\' ('0'..'7')
;
fragment
UNICODE_ESCAPE
: '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// collation
COLLATE : [cC] [oO] [lL] [lL] [aA] [tT] [eE];
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// NULL precedence
NULLS : 'NULLS' | 'nulls';
FIRST : 'FIRST' | 'first';
LAST : 'LAST' | 'last';
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// direction
ASC : [aA] [sS] [cC] ( [eE] [nN] [dD] [iI] [nN] [gG] )?;
DESC : [dD] [eE] [sS] [cC] ( [eE] [nN] [dD] [iI] [nN] [gG] )?;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Identifiers
IDENTIFIER
: ('a'..'z'|'A'..'Z'|'_'|'$'|'\u0080'..'\ufffe')('a'..'z'|'A'..'Z'|'_'|'$'|'0'..'9'|'\u0080'..'\ufffe')*
;
QUOTED_IDENTIFIER
: '`' ( ESCAPE_SEQUENCE | ~('\\'|'`') )* '`'
;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// general tokens
OPEN_PAREN : '(';
CLOSE_PAREN : ')';
COMMA : ',';
DOT : '.';
PLUS : '+';
MINUS : '-';
MULTIPLY : '*';
DIVIDE : '/';
MODULO : '%';

View File

@ -0,0 +1,82 @@
parser grammar OrderingParser;
options {
tokenVocab=OrderingLexer;
}
@header {
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.grammars.ordering;
/**
* Grammar for parsing order-by fragments.
*
* @implNote While we could re-use the HQL lexer/parser for order fragment parsing, both the HQL lexer and parser
* are way "heavier" than needed here. So we use a simplified lexer and parser that defione just what is needed
* to parse the order fragment
*/
}
// todo (6.0) : add hooks for keyword-as-identifier logging like we do for HQL?
orderByFragment
: sortSpecification (COMMA sortSpecification)*
;
sortSpecification
: expression collationSpecification? direction? nullsPrecedence?
;
expression
: function
| identifier
| dotIdentifier
;
function
: simpleFunction
| packagedFunction
;
simpleFunction
: identifier functionArguments
;
packagedFunction
: dotIdentifier functionArguments
;
functionArguments
: OPEN_PAREN expression* CLOSE_PAREN
;
collationSpecification
: COLLATE identifier
;
direction
: ASC | DESC
;
nullsPrecedence
: NULLS (FIRST | LAST)
;
identifier
: IDENTIFIER
// keyword-as-identifier
| FIRST
| LAST
| ASC
| DESC
| COLLATE
;
dotIdentifier
: IDENTIFIER (DOT IDENTIFIER)+
;

View File

@ -298,14 +298,26 @@ havingClause
// ORDER BY clause
orderByClause
// todo (6.0) : null precedence
: ORDER BY sortSpecification (COMMA sortSpecification)*
;
/**
* Specialized rule for ordered Map and Set `@OrderBy` handling
*/
orderByFragment
: sortSpecification (COMMA sortSpecification)*
;
sortSpecification
// todo (6.0) : null precedence
// : sortExpression collationSpecification? orderingSpecification? nullsPrecedence?
: sortExpression collationSpecification? orderingSpecification?
;
//nullsPrecedence
// : NULLS (FIRST | LAST)
// ;
sortExpression
: identifier
| INTEGER_LITERAL

View File

@ -10,5 +10,4 @@
*
* @deprecated (since 6.0) Use Hibernate's mapping model {@link org.hibernate.metamodel.MappingMetamodel}
*/
@Deprecated
package org.hibernate.metadata;

View File

@ -9,6 +9,7 @@ package org.hibernate.metamodel.mapping;
import java.util.Collections;
import java.util.List;
import org.hibernate.type.BasicType;
import org.hibernate.type.spi.TypeConfiguration;
/**
@ -32,4 +33,7 @@ public interface BasicValuedMapping extends ValueMapping, SqlExpressable {
default List<JdbcMapping> getJdbcMappings(TypeConfiguration typeConfiguration) {
return Collections.singletonList( getJdbcMapping() );
}
BasicType getBasicType();
}

View File

@ -29,4 +29,9 @@ public interface BasicValuedModelPart extends BasicValuedMapping, ModelPart, Fet
* Get the value converter applied to this model part if any
*/
BasicValueConverter getConverter();
@Override
default MappingType getPartMappingType() {
return getBasicType();
}
}

View File

@ -56,8 +56,6 @@ public interface CollectionPart extends ModelPart, Fetchable {
Nature getNature();
MappingType getPartTypeDescriptor();
@Override
default String getPartName() {
return getNature().getName();

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.metamodel.mapping;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
/**
* @author Steve Ebersole
*/
@ -16,4 +18,14 @@ public interface EntityDiscriminatorMapping extends VirtualModelPart, BasicValue
default String getPartName() {
return ROLE_NAME;
}
@Override
default MappingType getPartMappingType() {
return getBasicType();
}
@Override
default JavaTypeDescriptor getJavaTypeDescriptor() {
return null;
}
}

View File

@ -64,6 +64,12 @@ public interface EntityMappingType extends ManagedMappingType, Loadable {
return getMappedJavaTypeDescriptor();
}
@Override
default MappingType getPartMappingType() {
return this;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Inheritance

View File

@ -24,6 +24,11 @@ public interface ManagedMappingType extends MappingType, FetchableContainer {
return getMappedJavaTypeDescriptor();
}
@Override
default MappingType getPartMappingType() {
return this;
}
/**
* Get the number of attributes defined on this class and any supers
*/

View File

@ -0,0 +1,14 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping;
/**
* @author Steve Ebersole
*/
public interface MappingTypedModelPart extends ModelPart {
MappingType getMappingType();
}

View File

@ -29,6 +29,8 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
* @author Steve Ebersole
*/
public interface ModelPart extends MappingModelExpressable {
MappingType getPartMappingType();
JavaTypeDescriptor getJavaTypeDescriptor();
String getPartName();

View File

@ -27,6 +27,11 @@ public abstract class AbstractAttributeMapping implements AttributeMapping {
this.declaringType = declaringType;
}
@Override
public MappingType getPartMappingType() {
return type;
}
@Override
public String getAttributeName() {
return name;

View File

@ -48,6 +48,11 @@ public abstract class AbstractEntityDiscriminatorMapping implements EntityDiscri
this.mappingType = mappingType;
}
@Override
public BasicType getBasicType() {
return mappingType;
}
@Override
public String getContainingTableExpression() {
return tableExpression;

View File

@ -70,7 +70,7 @@ public class BasicValuedCollectionPart implements CollectionPart, BasicValuedMod
}
@Override
public BasicType getPartTypeDescriptor() {
public MappingType getPartMappingType() {
return mapper;
}
@ -197,4 +197,9 @@ public class BasicValuedCollectionPart implements CollectionPart, BasicValuedMod
public List<JdbcMapping> getJdbcMappings(TypeConfiguration typeConfiguration) {
return Collections.singletonList( getJdbcMapping() );
}
@Override
public BasicType getBasicType() {
return mapper;
}
}

View File

@ -67,6 +67,11 @@ public class BasicValuedSingularAttributeMapping extends AbstractSingularAttribu
this.jdbcMapping = jdbcMapping;
}
@Override
public BasicType getBasicType() {
return getMappedTypeDescriptor();
}
@Override
public BasicType getMappedTypeDescriptor() {
return (BasicType) super.getMappedTypeDescriptor();

View File

@ -73,7 +73,7 @@ public class CollectionIdentifierDescriptorImpl implements CollectionIdentifierD
}
@Override
public MappingType getPartTypeDescriptor() {
public MappingType getPartMappingType() {
return type;
}
@ -188,4 +188,9 @@ public class CollectionIdentifierDescriptorImpl implements CollectionIdentifierD
public String toString() {
return getClass().getSimpleName() + "(" + collectionDescriptor.getRole() + ")";
}
@Override
public BasicType getBasicType() {
return type;
}
}

View File

@ -16,6 +16,7 @@ import org.hibernate.engine.FetchTiming;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
@ -84,7 +85,7 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF
}
@Override
public EmbeddableMappingType getPartTypeDescriptor() {
public MappingType getPartMappingType() {
return getEmbeddableTypeDescriptor();
}

View File

@ -77,6 +77,11 @@ public class EmbeddedIdentifierMappingImpl
this.attrColumnNames = attrColumnNames;
}
@Override
public MappingType getPartMappingType() {
return type;
}
@Override
public JavaTypeDescriptor getJavaTypeDescriptor() {
return getMappedTypeDescriptor().getMappedJavaTypeDescriptor();

View File

@ -15,6 +15,7 @@ import org.hibernate.mapping.Value;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.persister.collection.CollectionPersister;
@ -63,7 +64,7 @@ public class EntityCollectionPart implements CollectionPart, EntityAssociationMa
}
@Override
public EntityMappingType getPartTypeDescriptor() {
public MappingType getPartMappingType() {
return getEntityMappingType();
}

View File

@ -45,6 +45,7 @@ import org.hibernate.mapping.Table;
import org.hibernate.mapping.ToOne;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.CollectionClassification;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
@ -63,7 +64,6 @@ import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadata;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.collection.SQLLoadableCollection;
@ -80,12 +80,12 @@ import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.results.graph.basic.BasicFetch;
import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.basic.BasicFetch;
import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.type.AssociationType;
import org.hibernate.type.BasicType;
import org.hibernate.type.CompositeType;
@ -134,9 +134,19 @@ public class MappingModelCreationHelper {
return propertyAccess;
}
@Override
public MappingType getPartMappingType() {
return getBasicType();
}
@Override
public MappingType getMappedTypeDescriptor() {
return ( (BasicType) entityPersister.getIdentifierType() ).getMappedTypeDescriptor();
return getBasicType();
}
@Override
public BasicType getBasicType() {
return (BasicType) entityPersister.getIdentifierType();
}
@Override
@ -337,6 +347,13 @@ public class MappingModelCreationHelper {
return new EntityIdentifierMapping() {
@Override
public MappingType getPartMappingType() {
// non-encapsulated means that the id attributes are directly defined on the entity
// - alternatively we could have the type here be the IdClass descriptor
return entityPersister;
}
@Override
public PropertyAccess getPropertyAccess() {
return propertyAccess;

View File

@ -10,6 +10,7 @@ import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchTiming;
@ -39,6 +40,7 @@ import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.type.BasicType;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.spi.TypeConfiguration;
@ -298,6 +300,11 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa
return Collections.singletonList( jdbcMapping );
}
@Override
public BasicType getBasicType() {
throw new HibernateException( "Unexpected call to SimpleForeignKeyDescriptor#getBasicType" );
}
@Override
public void visitJdbcTypes(
Consumer<JdbcMapping> action,

View File

@ -0,0 +1,13 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.ordering;
/**
* @author Steve Ebersole
*/
public interface AliasResolver {
}

View File

@ -0,0 +1,107 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.ordering;
import java.util.List;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.grammars.ordering.OrderingLexer;
import org.hibernate.grammars.ordering.OrderingParser;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.ordering.ast.ParseTreeVisitor;
import org.hibernate.metamodel.mapping.ordering.ast.SortSpecification;
import org.hibernate.persister.collection.CollectionPersister;
import org.jboss.logging.Logger;
import org.antlr.v4.runtime.BailErrorStrategy;
import org.antlr.v4.runtime.BufferedTokenStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.ConsoleErrorListener;
import org.antlr.v4.runtime.DefaultErrorStrategy;
import org.antlr.v4.runtime.atn.PredictionMode;
import org.antlr.v4.runtime.misc.ParseCancellationException;
/**
* Responsible for performing the translation of the order-by fragment associated
* with an order set or map.
*
* @see javax.persistence.OrderBy
* @see org.hibernate.annotations.OrderBy
*
* @author Steve Ebersole
*/
public class OrderByFragmentTranslator {
private static final Logger LOG = Logger.getLogger( OrderByFragmentTranslator.class.getName() );
/**
* Perform the translation of the user-supplied fragment, returning the translation.
*
* @apiNote The important distinction to this split between (1) translating and (2) resolving aliases is that
* both happen at different times. This is performed at boot-time while building the CollectionPersister
* happens at runtime while loading the described collection
*
* @return The translation.
*/
public static OrderByFragment translate(
String fragment,
PluralAttributeMapping pluralAttributeMapping,
TranslationContext context) {
LOG.tracef( "Beginning parsing of order-by fragment [%s] : %s", pluralAttributeMapping.getCollectionDescriptor().getRole(), fragment );
final OrderingParser.OrderByFragmentContext parseTree = buildParseTree( context, fragment );
final ParseTreeVisitor visitor = new ParseTreeVisitor( pluralAttributeMapping, context );
final List<SortSpecification> interpretation = visitor.visitOrderByFragment( parseTree );
throw new NotYetImplementedFor6Exception( OrderByFragmentTranslator.class );
}
private static OrderingParser.OrderByFragmentContext buildParseTree(TranslationContext context, String fragment) {
final OrderingLexer lexer = new OrderingLexer( CharStreams.fromString( fragment ) );
final OrderingParser parser = new OrderingParser( new BufferedTokenStream( lexer ) );
// try to use SLL(k)-based parsing first - its faster
parser.getInterpreter().setPredictionMode( PredictionMode.SLL );
parser.removeErrorListeners();
parser.setErrorHandler( new BailErrorStrategy() );
try {
return parser.orderByFragment();
}
catch ( ParseCancellationException e) {
// reset the input token stream and parser state
lexer.reset();
parser.reset();
// fall back to LL(k)-based parsing
parser.getInterpreter().setPredictionMode( PredictionMode.LL );
parser.addErrorListener( ConsoleErrorListener.INSTANCE );
parser.setErrorHandler( new DefaultErrorStrategy() );
return parser.orderByFragment();
}
}
/**
* Represents the translation result
*/
public interface OrderByFragment {
/**
* Inject table aliases into the translated fragment to properly qualify column references, using
* the given 'aliasResolver' to determine the the proper table alias to use for each column reference.
*
* @param aliasResolver The strategy to resolver the proper table alias to use per column
*
* @return The fully translated and replaced fragment.
*/
String injectAliases(AliasResolver aliasResolver);
}
}

View File

@ -0,0 +1,15 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.ordering;
/**
* Access to information needed while translating a collection's order-by fragment
*
* @author Steve Ebersole
*/
public interface TranslationContext {
}

View File

@ -0,0 +1,49 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.ordering.ast;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
/**
* @author Steve Ebersole
*/
public class CollectionSubPath implements DomainPath {
private final PluralAttributeMapping pluralAttribute;
private final ModelPart referenceModelPart;
public CollectionSubPath(
PluralAttributeMapping pluralAttribute,
ModelPart referenceModelPart) {
this.pluralAttribute = pluralAttribute;
this.referenceModelPart = referenceModelPart;
}
@Override
public PluralAttributeMapping getPluralAttribute() {
return pluralAttribute;
}
@Override
public DomainPath getLhs() {
return null;
}
@Override
public ModelPart getReferenceModelPart() {
return referenceModelPart;
}
@Override
public SequencePart resolvePathPart(
String name,
boolean isTerminal,
TranslationContext translationContext) {
return null;
}
}

View File

@ -0,0 +1,37 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.ordering.ast;
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
/**
* Represents a column-reference used in an order-by fragment
*
* @apiNote This is Hibernate-specific feature. For {@link javax.persistence.OrderBy} (JPA)
* all path references are expected to be domain paths (attributes).
*
* @author Steve Ebersole
*/
public class ColumnReference implements SortExpression, SequencePart {
private final String columnExpression;
public ColumnReference(String columnExpression) {
this.columnExpression = columnExpression;
}
public String getColumnExpression() {
return columnExpression;
}
@Override
public SequencePart resolvePathPart(
String name,
boolean isTerminal,
TranslationContext translationContext) {
throw new UnsupportedOperationException( "ColumnReference cannot be de-referenced" );
}
}

View File

@ -0,0 +1,25 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.ordering.ast;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
/**
* Represents a domain-path (model part path) used in an order-by fragment
*
* @author Steve Ebersole
*/
public interface DomainPath extends SortExpression, SequencePart {
DomainPath getLhs();
ModelPart getReferenceModelPart();
default PluralAttributeMapping getPluralAttribute() {
return getLhs().getPluralAttribute();
}
}

View File

@ -0,0 +1,40 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.ordering.ast;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Represents a function used in an order-by fragment
*
* @author Steve Ebersole
*/
public class FunctionExpression {
private final String name;
private final List<SortExpression> arguments;
public FunctionExpression(String name, int numberOfArguments) {
this.name = name;
this.arguments = numberOfArguments == 0
? Collections.emptyList()
: new ArrayList<>( numberOfArguments );
}
public String getName() {
return name;
}
public List<SortExpression> getArguments() {
return arguments;
}
public void addArgument(SortExpression argument) {
arguments.add( argument );
}
}

View File

@ -0,0 +1,15 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.ordering.ast;
/**
* Common contract for all nodes in the order-by fragment AST
*
* @author Steve Ebersole
*/
public interface Node {
}

View File

@ -0,0 +1,87 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.ordering.ast;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.SortOrder;
import org.hibernate.grammars.ordering.OrderingParser;
import org.hibernate.grammars.ordering.OrderingParserBaseVisitor;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
import org.hibernate.persister.collection.CollectionPersister;
import org.jboss.logging.Logger;
/**
* @author Steve Ebersole
*/
public class ParseTreeVisitor extends OrderingParserBaseVisitor {
private static final Logger log = Logger.getLogger( ParseTreeVisitor.class );
private final PathConsumer pathConsumer;
private final TranslationContext translationContext;
private List<SortSpecification> specifications;
public ParseTreeVisitor(
PluralAttributeMapping pluralAttributeMapping,
TranslationContext translationContext) {
this.pathConsumer = new PathConsumer( pluralAttributeMapping, translationContext );
this.translationContext = translationContext;
}
@Override
public List<SortSpecification> visitOrderByFragment(OrderingParser.OrderByFragmentContext parsedFragment) {
final List<OrderingParser.SortSpecificationContext> parsedSortSpecifications = parsedFragment.sortSpecification();
Objects.requireNonNull( parsedSortSpecifications );
this.specifications = new ArrayList<>( parsedSortSpecifications.size() );
for ( OrderingParser.SortSpecificationContext parsedSortSpecification : parsedSortSpecifications ) {
visitSortSpecification( parsedSortSpecification );
}
Objects.requireNonNull( specifications );
return specifications;
}
@Override
public SortSpecification visitSortSpecification(OrderingParser.SortSpecificationContext parsedSpec) {
assert parsedSpec != null;
assert parsedSpec.expression() != null;
final SortSpecification result = new SortSpecification( visitExpression( parsedSpec.expression() ) );
if ( parsedSpec.collationSpecification() != null ) {
result.setCollation( parsedSpec.collationSpecification().identifier().getText() );
}
if ( parsedSpec.direction() != null ) {
if ( parsedSpec.direction().ASC() != null ) {
result.setSortOrder( SortOrder.ASCENDING );
}
}
// todo (6.0) : null-precedence (see grammar notes)
return result;
}
@Override
public SortExpression visitExpression(OrderingParser.ExpressionContext ctx) {
throw new NotYetImplementedFor6Exception( getClass() );
}
@Override
public String visitCollationSpecification(OrderingParser.CollationSpecificationContext ctx) {
throw new IllegalStateException( "Unexpected call to #visitCollationSpecification" );
}
}

View File

@ -0,0 +1,71 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.ordering.ast;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
import org.hibernate.query.hql.internal.BasicDotIdentifierConsumer;
import org.jboss.logging.Logger;
/**
* Represents the translation of an individual part of a path in `@OrderBy` translation
*
* Similar in purpose to {@link org.hibernate.query.hql.spi.DotIdentifierConsumer}, but for `@OrderBy` translation
*
* @author Steve Ebersole
*/
public class PathConsumer {
private static final Logger log = Logger.getLogger( BasicDotIdentifierConsumer.class );
private final TranslationContext translationContext;
private final SequencePart rootSequencePart;
private String pathSoFar;
private SequencePart currentPart;
public PathConsumer(
PluralAttributeMapping pluralAttributeMapping, TranslationContext translationContext) {
this.translationContext = translationContext;
this.rootSequencePart = new RootSequencePart( pluralAttributeMapping );
}
public SequencePart getConsumedPart() {
return currentPart;
}
public void consumeIdentifier(String identifier, boolean isBase, boolean isTerminal) {
if ( isBase ) {
// each time we start a new sequence we need to reset our state
reset();
}
if ( pathSoFar == null ) {
pathSoFar = identifier;
}
else {
pathSoFar += ( '.' + identifier );
}
log.tracef(
"BasicDotIdentifierHandler#consumeIdentifier( %s, %s, %s ) - %s",
identifier,
isBase,
isTerminal,
pathSoFar
);
currentPart = currentPart.resolvePathPart( identifier, isTerminal, translationContext );
}
private void reset() {
pathSoFar = null;
currentPart = rootSequencePart;
}
}

View File

@ -0,0 +1,48 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.ordering.ast;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
/**
* PathPart implementation used to translate the root of a path
*
* @author Steve Ebersole
*/
public class RootSequencePart implements SequencePart {
private final PluralAttributeMapping pluralAttributeMapping;
public RootSequencePart(PluralAttributeMapping pluralAttributeMapping) {
this.pluralAttributeMapping = pluralAttributeMapping;
}
@Override
public SequencePart resolvePathPart(
String name,
boolean isTerminal,
TranslationContext translationContext) {
// could be a column-reference (isTerminal would have to be true) or a domain-path
final ModelPart subPart = pluralAttributeMapping.findSubPart( name, null );
if ( subPart != null ) {
return new CollectionSubPath( pluralAttributeMapping, subPart );
}
if ( isTerminal ) {
// assume a column-reference
return new ColumnReference( name );
}
throw new UnexpectedTokenException(
"Could not resolve order-by token : " +
pluralAttributeMapping.getCollectionDescriptor().getRole() + " -> " + name
);
}
}

View File

@ -0,0 +1,21 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.ordering.ast;
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
/**
* Represents an individual identifier in a dot-identifier sequence
*
* @author Steve Ebersole
*/
public interface SequencePart {
SequencePart resolvePathPart(
String name,
boolean isTerminal,
TranslationContext translationContext);
}

View File

@ -0,0 +1,15 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.ordering.ast;
/**
* Contract for anything that can be a sort expression
*
* @author Steve Ebersole
*/
public interface SortExpression extends Node {
}

View File

@ -0,0 +1,55 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.ordering.ast;
import org.hibernate.NullPrecedence;
import org.hibernate.SortOrder;
/**
* An individual sort specification in an order-by fragment
*
* @author Steve Ebersole
*/
public class SortSpecification implements Node {
private final SortExpression sortExpression;
private String collation;
private SortOrder sortOrder;
private NullPrecedence nullPrecedence = NullPrecedence.NONE;
public SortSpecification(SortExpression sortExpression) {
this.sortExpression = sortExpression;
}
public SortExpression getSortExpression() {
return sortExpression;
}
public String getCollation() {
return collation;
}
public void setCollation(String collation) {
this.collation = collation;
}
public SortOrder getSortOrder() {
return sortOrder;
}
public void setSortOrder(SortOrder sortOrder) {
this.sortOrder = sortOrder;
}
public NullPrecedence getNullPrecedence() {
return nullPrecedence;
}
public void setNullPrecedence(NullPrecedence nullPrecedence) {
this.nullPrecedence = nullPrecedence;
}
}

View File

@ -0,0 +1,59 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.ordering.ast;
import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.MappingTypedModelPart;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
/**
* @author Steve Ebersole
*/
public class SubDomainPath implements DomainPath {
private final DomainPath lhs;
private final ModelPart referencedModelPart;
public SubDomainPath(DomainPath lhs, ModelPart referencedModelPart) {
this.lhs = lhs;
this.referencedModelPart = referencedModelPart;
}
@Override
public DomainPath getLhs() {
return lhs;
}
@Override
public ModelPart getReferenceModelPart() {
return referencedModelPart;
}
@Override
public SequencePart resolvePathPart(
String name,
boolean isTerminal,
TranslationContext translationContext) {
if ( referencedModelPart.getPartMappingType() instanceof ManagedMappingType ) {
final ManagedMappingType partMappingType = (ManagedMappingType) referencedModelPart.getPartMappingType();
final ModelPart subPart = partMappingType.findSubPart( name, null );
if ( subPart == null ) {
throw new UnexpectedTokenException(
"Could not resolve path token : " +
referencedModelPart + " -> " + name
);
}
return new SubDomainPath( this, subPart );
}
throw new UnexpectedTokenException(
"Domain path of type `" + referencedModelPart.getPartMappingType() +
"` -> `" + name + "`"
);
}
}

View File

@ -0,0 +1,18 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.ordering.ast;
import org.hibernate.HibernateException;
/**
* @author Steve Ebersole
*/
public class UnexpectedTokenException extends HibernateException {
public UnexpectedTokenException(String message) {
super( message );
}
}

View File

@ -0,0 +1,14 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
/**
* Support for set and map ordering
*
* @see javax.persistence.OrderBy
* @see org.hibernate.annotations.OrderBy
*/
package org.hibernate.metamodel.mapping.ordering;

View File

@ -28,11 +28,12 @@ public class HqlParseTreeBuilder {
*/
public static final HqlParseTreeBuilder INSTANCE = new HqlParseTreeBuilder();
public HqlParser generateHqlParser(String hql) {
// Build the lexer
HqlLexer hqlLexer = new HqlLexer( CharStreams.fromString( hql ) );
public HqlLexer buildHqlLexer(String hql) {
return new HqlLexer( CharStreams.fromString( hql ) );
}
// Build the parser...
public HqlParser buildHqlParser(String hql, HqlLexer hqlLexer) {
// Build the parser
return new HqlParser( new CommonTokenStream( hqlLexer ) ) {
@Override
protected void logUseOfReservedWordAsIdentifier(Token token) {
@ -42,4 +43,9 @@ public class HqlParseTreeBuilder {
}
};
}
public HqlParser buildHqlParser(String hql) {
// Build the lexer
return buildHqlParser( hql, buildHqlLexer( hql ) );
}
}

View File

@ -15,7 +15,6 @@ import org.hibernate.query.hql.spi.SqmCreationOptions;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.antlr.v4.runtime.BailErrorStrategy;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.ConsoleErrorListener;
import org.antlr.v4.runtime.DefaultErrorStrategy;
import org.antlr.v4.runtime.atn.PredictionMode;
@ -64,10 +63,10 @@ public class StandardHqlTranslator implements HqlTranslator {
private HqlParser.StatementContext parseHql(String hql) {
// Build the lexer
final HqlLexer hqlLexer = new HqlLexer( CharStreams.fromString( hql ) );
final HqlLexer hqlLexer = HqlParseTreeBuilder.INSTANCE.buildHqlLexer( hql );
// first, ask Antlr to build the parse tree
final HqlParser hqlParser = HqlParseTreeBuilder.INSTANCE.generateHqlParser( hql );
// Build the parse tree
final HqlParser hqlParser = HqlParseTreeBuilder.INSTANCE.buildHqlParser( hql, hqlLexer );
// try to use SLL(k)-based parsing first - its faster
hqlParser.getInterpreter().setPredictionMode( PredictionMode.SLL );

View File

@ -12,6 +12,7 @@ import org.hibernate.LockMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchTiming;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.results.graph.AssemblerCreationState;
import org.hibernate.sql.results.graph.BiDirectionalFetch;
@ -97,6 +98,11 @@ public class BiDirectionalFetchImpl implements BiDirectionalFetch, Fetchable {
return fetchable.getFetchableName();
}
@Override
public MappingType getPartMappingType() {
return fetchable.getPartMappingType();
}
@Override
public JavaTypeDescriptor getJavaTypeDescriptor() {
return fetchable.getJavaTypeDescriptor();

View File

@ -32,6 +32,11 @@ public interface BasicType<T> extends Type, BasicDomainType<T>, MappingType, Bas
*/
String[] getRegistrationKeys();
@Override
default BasicType getBasicType() {
return this;
}
@Override
default MappingType getMappedTypeDescriptor() {
return this;