From 538982e8e6e96c3da87916c7b068a0ce43f9b2ca Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Mon, 20 Oct 2008 17:57:44 +0000 Subject: [PATCH] HHH-2802 : order-by mapping -> support for property names git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@15359 1b8cb986-b30d-0410-93ca-fae66ebed9b2 --- core/pom.xml | 2 +- core/src/main/antlr/order-by-render.g | 92 ++++ core/src/main/antlr/order-by.g | 440 ++++++++++++++++++ .../AbstractCollectionPersister.java | 72 ++- .../main/java/org/hibernate/sql/Template.java | 160 +++---- .../antlr/CollationSpecification.java | 34 ++ .../sql/ordering/antlr/ColumnMapper.java | 46 ++ .../hibernate/sql/ordering/antlr/Factory.java | 54 +++ .../hibernate/sql/ordering/antlr/Node.java | 53 +++ .../sql/ordering/antlr/NodeSupport.java | 48 ++ .../sql/ordering/antlr/OrderByFragment.java | 33 ++ .../ordering/antlr/OrderByFragmentParser.java | 194 ++++++++ .../antlr/OrderByFragmentRenderer.java | 38 ++ .../antlr/OrderByFragmentTranslator.java | 87 ++++ .../ordering/antlr/OrderingSpecification.java | 70 +++ .../hibernate/sql/ordering/antlr/SortKey.java | 34 ++ .../sql/ordering/antlr/SortSpecification.java | 80 ++++ .../ordering/antlr/TranslationContext.java | 64 +++ .../java/org/hibernate/sql/TemplateTest.java | 184 ++++++++ core/src/test/resources/log4j.properties | 2 +- 20 files changed, 1677 insertions(+), 110 deletions(-) create mode 100644 core/src/main/antlr/order-by-render.g create mode 100644 core/src/main/antlr/order-by.g create mode 100644 core/src/main/java/org/hibernate/sql/ordering/antlr/CollationSpecification.java create mode 100644 core/src/main/java/org/hibernate/sql/ordering/antlr/ColumnMapper.java create mode 100644 core/src/main/java/org/hibernate/sql/ordering/antlr/Factory.java create mode 100644 core/src/main/java/org/hibernate/sql/ordering/antlr/Node.java create mode 100644 core/src/main/java/org/hibernate/sql/ordering/antlr/NodeSupport.java create mode 100644 core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragment.java create mode 100644 core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentParser.java create mode 100644 core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentRenderer.java create mode 100644 core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentTranslator.java create mode 100644 core/src/main/java/org/hibernate/sql/ordering/antlr/OrderingSpecification.java create mode 100644 core/src/main/java/org/hibernate/sql/ordering/antlr/SortKey.java create mode 100644 core/src/main/java/org/hibernate/sql/ordering/antlr/SortSpecification.java create mode 100644 core/src/main/java/org/hibernate/sql/ordering/antlr/TranslationContext.java create mode 100644 core/src/test/java/org/hibernate/sql/TemplateTest.java diff --git a/core/pom.xml b/core/pom.xml index 3255511cb2..5cea86da94 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -77,7 +77,7 @@ antlr-maven-plugin ${antlrPluginVersion} - hql.g,hql-sql.g,sql-gen.g + hql.g,hql-sql.g,sql-gen.g,order-by.g,order-by-render.g diff --git a/core/src/main/antlr/order-by-render.g b/core/src/main/antlr/order-by-render.g new file mode 100644 index 0000000000..bba7bddcf6 --- /dev/null +++ b/core/src/main/antlr/order-by-render.g @@ -0,0 +1,92 @@ +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 ORDER_BY trees as described by the {@link OrderByFragmentParser} + + * @author Steve Ebersole + */ +class GeneratedOrderByFragmentRenderer extends TreeParser; + +options { + importVocab=OrderByTemplate; + buildAST=false; +} + +{ + // the buffer to which we write the resulting SQL. + private StringBuffer buffer = new StringBuffer(); + + protected void out(String text) { + buffer.append( text ); + } + + protected void out(AST ast) { + buffer.append( ast.getText() ); + } + + /*package*/ String getRenderedFragment() { + return buffer.toString(); + } +} + +orderByFragment + : #( + ORDER_BY sortSpecification ( {out(", ");} sortSpecification)* + ) + ; + +sortSpecification + : #( + SORT_SPEC sortKeySpecification (collationSpecification)? (orderingSpecification)? + ) + ; + +sortKeySpecification + : #(SORT_KEY sortKey) + ; + +sortKey + : i:IDENT { + out( #i ); + } + ; + +collationSpecification + : c:COLLATE { + out( " collate " ); + out( c ); + } + ; + +orderingSpecification + : o:ORDER_SPEC { + out( " " ); + out( #o ); + } + ; \ No newline at end of file diff --git a/core/src/main/antlr/order-by.g b/core/src/main/antlr/order-by.g new file mode 100644 index 0000000000..48d3017a36 --- /dev/null +++ b/core/src/main/antlr/order-by.g @@ -0,0 +1,440 @@ +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 order-by 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; + SORT_KEY; + EXPR_LIST; + DOT; + IDENT_LIST; + COLUMN_REF; + + COLLATE="collate"; + ASCENDING="asc"; + DESCENDING="desc"; +} + + +{ + /** + * 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 somtimes 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 sort specification + * + * @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 ); + } + ; + +/** + * Reconition rule for what ANSI SQL terms the sort specification, which is essentially each thing upon which + * the results should be sorted. + */ +sortSpecification { trace("sortSpecification"); } + : sortKey (collationSpecification)? (orderingSpecification)? { + #sortSpecification = #( [SORT_SPEC, "{sort specification}"], #sortSpecification ); + #sortSpecification = postProcessSortSpecification( #sortSpecification ); + } + ; + +/** + * Reconition rule for what ANSI SQL terms the sort key which is the expression (column, function, etc) upon + * which to base the sorting. + */ +sortKey! { trace("sortKey"); } + : e:expression { + #sortKey = #( [SORT_KEY, "sort key"], #e ); + } + ; + +/** + * Reconition rule what this grammar recognizes as valid sort key. + */ +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"); + StringBuffer buffer = new StringBuffer(); + } + : 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 ); + } + ; + +/** + * Reconition rule for what ANSI SQL terms the collation specification 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 + ; + +/** + * Reconition rule for what ANSI SQL terms the ordering specification; ASCENDING or + * DESCENDING. + */ +orderingSpecification! { trace("orderingSpecification"); } + : ( "asc" | "ascending" ) { + #orderingSpecification = #( [ORDER_SPEC, "asc"] ); + } + | ( "desc" | "descending") { + #orderingSpecification = #( [ORDER_SPEC, "desc"] ); + } + ; + +/** + * A simple-property-path is an IDENT followed by one or more (DOT IDENT) sequences + */ +simplePropertyPath { + trace("simplePropertyPath"); + StringBuffer buffer = new StringBuffer(); + } + : i:IDENT { buffer.append( i.getText() ); } + ( DOT i2:IDENT { buffer.append( '.').append( i2.getText() ); } )+ { + #simplePropertyPath = #( [IDENT,buffer.toString()] ); + } + ; + + +// **** LEXER ****************************************************************** + +/** + * Lexer for the order-by 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 + ; diff --git a/core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java b/core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java index 880e21d116..482c79af83 100644 --- a/core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java +++ b/core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java @@ -80,12 +80,12 @@ import org.hibernate.sql.Alias; import org.hibernate.sql.SelectFragment; import org.hibernate.sql.SimpleSelect; import org.hibernate.sql.Template; +import org.hibernate.sql.ordering.antlr.ColumnMapper; import org.hibernate.type.AbstractComponentType; import org.hibernate.type.CollectionType; import org.hibernate.type.EntityType; import org.hibernate.type.Type; import org.hibernate.util.ArrayHelper; -import org.hibernate.util.CollectionHelper; import org.hibernate.util.FilterHelper; import org.hibernate.util.StringHelper; @@ -113,7 +113,6 @@ public abstract class AbstractCollectionPersister private final String sqlDetectRowByIndexString; private final String sqlDetectRowByElementString; - private final String sqlOrderByString; protected final String sqlWhereString; private final String sqlOrderByStringTemplate; private final String sqlWhereStringTemplate; @@ -197,7 +196,7 @@ public abstract class AbstractCollectionPersister private final String manyToManyWhereString; private final String manyToManyWhereTemplate; - private final String manyToManyOrderByString; + private final boolean hasManyToManyOrder; private final String manyToManyOrderByTemplate; // custom sql @@ -266,12 +265,7 @@ public abstract class AbstractCollectionPersister for ( int i = 1; i < spacesSize; i++ ) { spaces[i] = (String) iter.next(); } - - sqlOrderByString = collection.getOrderBy(); - hasOrder = sqlOrderByString != null; - sqlOrderByStringTemplate = hasOrder ? - Template.renderOrderByStringTemplate(sqlOrderByString, dialect, factory.getSqlFunctionRegistry()) : - null; + sqlWhereString = StringHelper.isNotEmpty( collection.getWhere() ) ? "( " + collection.getWhere() + ") " : null; hasWhere = sqlWhereString != null; sqlWhereStringTemplate = hasWhere ? @@ -540,7 +534,26 @@ public abstract class AbstractCollectionPersister ); } } - + + hasOrder = collection.getOrderBy() != null; + if ( hasOrder ) { + ColumnMapper mapper = new ColumnMapper() { + public String[] map(String reference) { + return elementPropertyMapping.toColumns( reference ); + } + }; + sqlOrderByStringTemplate = Template.renderOrderByStringTemplate( + collection.getOrderBy(), + mapper, + factory, + dialect, + factory.getSqlFunctionRegistry() + ); + } + else { + sqlOrderByStringTemplate = null; + } + // Handle any filters applied to this collection filterHelper = new FilterHelper( collection.getFilterMap(), dialect, factory.getSqlFunctionRegistry() ); @@ -552,10 +565,25 @@ public abstract class AbstractCollectionPersister manyToManyWhereTemplate = manyToManyWhereString == null ? null : Template.renderWhereStringTemplate( manyToManyWhereString, factory.getDialect(), factory.getSqlFunctionRegistry() ); - manyToManyOrderByString = collection.getManyToManyOrdering(); - manyToManyOrderByTemplate = manyToManyOrderByString == null - ? null - : Template.renderOrderByStringTemplate( manyToManyOrderByString, factory.getDialect(), factory.getSqlFunctionRegistry() ); + + hasManyToManyOrder = collection.getManyToManyOrdering() != null; + if ( hasManyToManyOrder ) { + ColumnMapper mapper = new ColumnMapper() { + public String[] map(String reference) { + return elementPropertyMapping.toColumns( reference ); + } + }; + manyToManyOrderByTemplate = Template.renderOrderByStringTemplate( + collection.getManyToManyOrdering(), + mapper, + factory, + dialect, + factory.getSqlFunctionRegistry() + ); + } + else { + manyToManyOrderByTemplate = null; + } initCollectionPropertyMap(); } @@ -658,17 +686,15 @@ public abstract class AbstractCollectionPersister } public String getSQLOrderByString(String alias) { - return hasOrdering() ? - StringHelper.replace( sqlOrderByStringTemplate, Template.TEMPLATE, alias ) : ""; + return hasOrdering() + ? StringHelper.replace( sqlOrderByStringTemplate, Template.TEMPLATE, alias ) + : ""; } public String getManyToManyOrderByString(String alias) { - if ( isManyToMany() && manyToManyOrderByString != null ) { - return StringHelper.replace( manyToManyOrderByTemplate, Template.TEMPLATE, alias ); - } - else { - return ""; - } + return hasManyToManyOrdering() + ? StringHelper.replace( manyToManyOrderByTemplate, Template.TEMPLATE, alias ) + : ""; } public FetchMode getFetchMode() { return fetchMode; @@ -679,7 +705,7 @@ public abstract class AbstractCollectionPersister } public boolean hasManyToManyOrdering() { - return isManyToMany() && manyToManyOrderByTemplate != null; + return isManyToMany() && hasManyToManyOrder; } public boolean hasWhere() { diff --git a/core/src/main/java/org/hibernate/sql/Template.java b/core/src/main/java/org/hibernate/sql/Template.java index bed3c7d5d6..68cb61e7ea 100644 --- a/core/src/main/java/org/hibernate/sql/Template.java +++ b/core/src/main/java/org/hibernate/sql/Template.java @@ -30,6 +30,10 @@ import java.util.StringTokenizer; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.function.SQLFunctionRegistry; import org.hibernate.util.StringHelper; +import org.hibernate.sql.ordering.antlr.ColumnMapper; +import org.hibernate.sql.ordering.antlr.TranslationContext; +import org.hibernate.sql.ordering.antlr.OrderByFragmentTranslator; +import org.hibernate.engine.SessionFactoryImplementor; /** * Parses SQL fragments specified in mapping documents @@ -233,93 +237,79 @@ public final class Template { return result.toString(); } - /** - * Takes order by clause provided in the mapping attribute and interpolates the alias. - * Handles asc, desc, SQL functions, quoted identifiers. - */ - public static String renderOrderByStringTemplate(String sqlOrderByString, Dialect dialect, SQLFunctionRegistry functionRegistry) { - //TODO: make this a bit nicer - String symbols = new StringBuffer() - .append("=>ORDER BY template + * has all column references "qualified" with a placeholder identified by {@link Template#TEMPLATE} + * + * @param orderByFragment The order-by fragment to render. + * @param dialect The SQL dialect being used. + * @param functionRegistry The SQL function registry + * + * @return The rendered ORDER BY template. + * + * @see #renderOrderByStringTemplate(String,ColumnMapper,SessionFactoryImplementor,Dialect,SQLFunctionRegistry) + */ + public static String renderOrderByStringTemplate( + String orderByFragment, + Dialect dialect, + SQLFunctionRegistry functionRegistry) { + return renderOrderByStringTemplate( + orderByFragment, + NoOpColumnMapper.INSTANCE, + null, + dialect, + functionRegistry + ); + } + + /** + * Performs order-by template rendering allowing {@link ColumnMapper column mapping}. An ORDER BY template + * has all column references "qualified" with a placeholder identified by {@link Template#TEMPLATE} which can later + * be used to easily inject the SQL alias. + * + * @param orderByFragment The order-by fragment to render. + * @param columnMapper The column mapping strategy to use. + * @param sessionFactory The session factory. + * @param dialect The SQL dialect being used. + * @param functionRegistry The SQL function registry + * + * @return The rendered ORDER BY template. + */ + public static String renderOrderByStringTemplate( + String orderByFragment, + final ColumnMapper columnMapper, + final SessionFactoryImplementor sessionFactory, + final Dialect dialect, + final SQLFunctionRegistry functionRegistry) { + TranslationContext context = new TranslationContext() { + public SessionFactoryImplementor getSessionFactory() { + return sessionFactory; + } + + public Dialect getDialect() { + return dialect; + } + + public SQLFunctionRegistry getSqlFunctionRegistry() { + return functionRegistry; + } + + public ColumnMapper getColumnMapper() { + return columnMapper; + } + }; + + OrderByFragmentTranslator translator = new OrderByFragmentTranslator( context ); + return translator.render( orderByFragment ); + } + private static boolean isNamedParameter(String token) { return token.startsWith(":"); } diff --git a/core/src/main/java/org/hibernate/sql/ordering/antlr/CollationSpecification.java b/core/src/main/java/org/hibernate/sql/ordering/antlr/CollationSpecification.java new file mode 100644 index 0000000000..b119d99097 --- /dev/null +++ b/core/src/main/java/org/hibernate/sql/ordering/antlr/CollationSpecification.java @@ -0,0 +1,34 @@ +/* + * 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; + +/** + * Models a collation specification (COLLATE using a specific character-set) within a + * {@link SortSpecification}. + * + * @author Steve Ebersole + */ +public class CollationSpecification extends NodeSupport { +} diff --git a/core/src/main/java/org/hibernate/sql/ordering/antlr/ColumnMapper.java b/core/src/main/java/org/hibernate/sql/ordering/antlr/ColumnMapper.java new file mode 100644 index 0000000000..af3d4848c9 --- /dev/null +++ b/core/src/main/java/org/hibernate/sql/ordering/antlr/ColumnMapper.java @@ -0,0 +1,46 @@ +/* + * 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; + +import org.hibernate.HibernateException; + +/** + * Contract for mapping a (an assumed) property reference to its columns. + * + * @author Steve Ebersole + */ +public interface ColumnMapper { + /** + * Resolve the property reference to its underlying columns. + * + * @param reference The property reference name. + * + * @return The underlying columns, or null if the property reference is unknown. + * + * @throws HibernateException Generally indicates that the property reference is unkown; interpretation is the + * same as a null return. + */ + public String[] map(String reference) throws HibernateException; +} diff --git a/core/src/main/java/org/hibernate/sql/ordering/antlr/Factory.java b/core/src/main/java/org/hibernate/sql/ordering/antlr/Factory.java new file mode 100644 index 0000000000..c6343436ed --- /dev/null +++ b/core/src/main/java/org/hibernate/sql/ordering/antlr/Factory.java @@ -0,0 +1,54 @@ +/* + * 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; + +import antlr.ASTFactory; + +/** + * Acts as a {@link ASTFactory} for injecting our specific AST node classes into the Antlr generated trees. + * + * @author Steve Ebersole + */ +public class Factory extends ASTFactory implements OrderByTemplateTokenTypes { + /** + * {@inheritDoc} + */ + public Class getASTNodeType(int i) { + switch ( i ) { + case ORDER_BY: + return OrderByFragment.class; + case SORT_SPEC: + return SortSpecification.class; + case ORDER_SPEC: + return OrderingSpecification.class; + case COLLATE: + return CollationSpecification.class; + case SORT_KEY: + return SortKey.class; + default: + return NodeSupport.class; + } + } +} diff --git a/core/src/main/java/org/hibernate/sql/ordering/antlr/Node.java b/core/src/main/java/org/hibernate/sql/ordering/antlr/Node.java new file mode 100644 index 0000000000..4f8a6adfd7 --- /dev/null +++ b/core/src/main/java/org/hibernate/sql/ordering/antlr/Node.java @@ -0,0 +1,53 @@ +/* + * 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; + +/** + * General contract for AST nodes. + * + * @author Steve Ebersole + */ +public interface Node { + /** + * Get the intrinsic text of this node. + * + * @return The node's text. + */ + public String getText(); + + /** + * Get a string representation of this node usable for debug logging or similar. + * + * @return The node's debugging text. + */ + public String getDebugText(); + + /** + * Build the node's representation for use in the resulting rendering. + * + * @return The renderable text. + */ + public String getRenderableText(); +} diff --git a/core/src/main/java/org/hibernate/sql/ordering/antlr/NodeSupport.java b/core/src/main/java/org/hibernate/sql/ordering/antlr/NodeSupport.java new file mode 100644 index 0000000000..0abe56e67c --- /dev/null +++ b/core/src/main/java/org/hibernate/sql/ordering/antlr/NodeSupport.java @@ -0,0 +1,48 @@ +/* + * 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; + +import antlr.CommonAST; + +/** + * Basic implementation of a {@link Node}. + * + * @author Steve Ebersole + */ +public class NodeSupport extends CommonAST implements Node { + /** + * {@inheritDoc} + */ + public String getDebugText() { + return getText(); + } + + /** + * {@inheritDoc} + */ + public String getRenderableText() { + return getText(); + } +} diff --git a/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragment.java b/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragment.java new file mode 100644 index 0000000000..00d86a1a46 --- /dev/null +++ b/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragment.java @@ -0,0 +1,33 @@ +/* + * 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; + +/** + * Represents a parsed order-by mapping fragment. This holds the tree of all {@link SortSpecification}s. + * + * @author Steve Ebersole + */ +public class OrderByFragment extends NodeSupport { +} diff --git a/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentParser.java b/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentParser.java new file mode 100644 index 0000000000..059bb91c30 --- /dev/null +++ b/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentParser.java @@ -0,0 +1,194 @@ +/* + * 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; + +import java.util.ArrayList; + +import antlr.TokenStream; +import antlr.CommonAST; +import antlr.collections.AST; + +import org.hibernate.sql.Template; +import org.hibernate.dialect.function.SQLFunction; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Extension of the Antlr-generated parser for the purpose of adding our custom parsing behavior. + * + * @author Steve Ebersole + */ +public class OrderByFragmentParser extends GeneratedOrderByFragmentParser { + private static final Logger log = LoggerFactory.getLogger( OrderByFragmentParser.class ); + + private final TranslationContext context; + + public OrderByFragmentParser(TokenStream lexer, TranslationContext context) { + super( lexer ); + super.setASTFactory( new Factory() ); + this.context = context; + } + + /** + * {@inheritDoc} + */ + protected void trace(String msg) { + log.trace( msg ); + } + + /** + * {@inheritDoc} + */ + protected AST quotedIdentifier(AST ident) { + return getASTFactory().create( + OrderByTemplateTokenTypes.IDENT, + Template.TEMPLATE + "." + context.getDialect().quote( '`' + ident.getText() + '`' ) + ); + } + + /** + * {@inheritDoc} + */ + protected AST quotedString(AST ident) { + return getASTFactory().create( OrderByTemplateTokenTypes.IDENT, context.getDialect().quote( ident.getText() ) ); + } + + /** + * {@inheritDoc} + */ + protected boolean isFunctionName(AST ast) { + return context.getSqlFunctionRegistry().hasFunction( ast.getText() ); + } + + /** + * {@inheritDoc} + */ + protected AST resolveFunction(AST ast) { + AST child = ast.getFirstChild(); + assert "{param list}".equals( child.getText() ); + child = child.getFirstChild(); + + final String functionName = ast.getText(); + final SQLFunction function = context.getSqlFunctionRegistry().findSQLFunction( functionName ); + if ( function == null ) { + String text = functionName; + if ( child != null ) { + text += '('; + while ( child != null ) { + text += child.getText(); + child = child.getNextSibling(); + if ( child != null ) { + text += ", "; + } + } + text += ')'; + } + return getASTFactory().create( OrderByTemplateTokenTypes.IDENT, text ); + } + else { + ArrayList expressions = new ArrayList(); + while ( child != null ) { + expressions.add( child.getText() ); + child = child.getNextSibling(); + } + final String text = function.render( expressions, context.getSessionFactory() ); + return getASTFactory().create( OrderByTemplateTokenTypes.IDENT, text ); + } + } + + /** + * {@inheritDoc} + */ + protected AST resolveIdent(AST ident) { + String text = ident.getText(); + String[] replacements; + try { + replacements = context.getColumnMapper().map( text ); + } + catch( Throwable t ) { + replacements = null; + } + + if ( replacements == null || replacements.length == 0 ) { + return getASTFactory().create( OrderByTemplateTokenTypes.IDENT, Template.TEMPLATE + "." + ident ); + } + else if ( replacements.length == 1 ) { + return getASTFactory().create( OrderByTemplateTokenTypes.IDENT, Template.TEMPLATE + "." + replacements[0] ); + } + else { + final AST root = getASTFactory().create( OrderByTemplateTokenTypes.IDENT_LIST, "{ident list}" ); + for ( int i = 0; i < replacements.length; i++ ) { + final String identText = Template.TEMPLATE + '.' + replacements[i]; + root.addChild( getASTFactory().create( OrderByTemplateTokenTypes.IDENT, identText ) ); + } + return root; + } + } + + /** + * {@inheritDoc} + */ + protected AST postProcessSortSpecification(AST sortSpec) { + assert SORT_SPEC == sortSpec.getType(); + SortSpecification sortSpecification = ( SortSpecification ) sortSpec; + AST sortKey = sortSpecification.getSortKey(); + if ( IDENT_LIST == sortKey.getFirstChild().getType() ) { + AST identList = sortKey.getFirstChild(); + AST ident = identList.getFirstChild(); + AST holder = new CommonAST(); + do { + holder.addChild( + createSortSpecification( + ident, + sortSpecification.getCollation(), + sortSpecification.getOrdering() + ) + ); + ident = ident.getNextSibling(); + } while ( ident != null ); + sortSpec = holder.getFirstChild(); + } + return sortSpec; + } + + private SortSpecification createSortSpecification( + AST ident, + CollationSpecification collationSpecification, + OrderingSpecification orderingSpecification) { + AST sortSpecification = getASTFactory().create( SORT_SPEC, "{{sort specification}}" ); + AST sortKey = getASTFactory().create( SORT_KEY, "{{sort key}}" ); + AST newIdent = getASTFactory().create( ident.getType(), ident.getText() ); + sortKey.setFirstChild( newIdent ); + sortSpecification.setFirstChild( sortKey ); + if ( collationSpecification != null ) { + sortSpecification.addChild( collationSpecification ); + } + if ( orderingSpecification != null ) { + sortSpecification.addChild( orderingSpecification ); + } + return ( SortSpecification ) sortSpecification; + } +} diff --git a/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentRenderer.java b/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentRenderer.java new file mode 100644 index 0000000000..741ef77055 --- /dev/null +++ b/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentRenderer.java @@ -0,0 +1,38 @@ +/* + * 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; + +import antlr.collections.AST; + +/** + * TODO : javadoc + * + * @author Steve Ebersole + */ +public class OrderByFragmentRenderer extends GeneratedOrderByFragmentRenderer { + protected void out(AST ast) { + out( ( ( Node ) ast ).getRenderableText() ); + } +} diff --git a/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentTranslator.java b/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentTranslator.java new file mode 100644 index 0000000000..e85b703bdd --- /dev/null +++ b/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentTranslator.java @@ -0,0 +1,87 @@ +/* + * 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; + +import java.io.StringReader; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.hibernate.HibernateException; +import org.hibernate.hql.ast.util.ASTPrinter; + +/** + * A translator which coordinates translation of an order-by mapping. + * + * @author Steve Ebersole + */ +public class OrderByFragmentTranslator { + private static final Logger log = LoggerFactory.getLogger( OrderByFragmentTranslator.class ); + + public final TranslationContext context; + + public OrderByFragmentTranslator(TranslationContext context) { + this.context = context; + } + + /** + * The main contract, performing the transaction. + * + * @param fragment The order-by mapping fragment to be translated. + * + * @return The translated fragment. + */ + public String render(String fragment) { + GeneratedOrderByLexer lexer = new GeneratedOrderByLexer( new StringReader( fragment ) ); + OrderByFragmentParser parser = new OrderByFragmentParser( lexer, context ); + try { + parser.orderByFragment(); + } + catch ( HibernateException e ) { + throw e; + } + catch ( Throwable t ) { + throw new HibernateException( "Unable to parse order-by fragment", t ); + } + + if ( log.isTraceEnabled() ) { + ASTPrinter printer = new ASTPrinter( OrderByTemplateTokenTypes.class ); + log.trace( printer.showAsString( parser.getAST(), "--- {order-by fragment} ---" ) ); + } + + GeneratedOrderByFragmentRenderer renderer = new GeneratedOrderByFragmentRenderer(); + try { + renderer.orderByFragment( parser.getAST() ); + } + catch ( HibernateException e ) { + throw e; + } + catch ( Throwable t ) { + throw new HibernateException( "Unable to render parsed order-by fragment", t ); + } + + return renderer.getRenderedFragment(); + } +} diff --git a/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderingSpecification.java b/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderingSpecification.java new file mode 100644 index 0000000000..4eaf4017ba --- /dev/null +++ b/core/src/main/java/org/hibernate/sql/ordering/antlr/OrderingSpecification.java @@ -0,0 +1,70 @@ +/* + * 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; + +/** + * Models an ordering specification (ASCENDING or DESCENDING) within a {@link SortSpecification}. + * + * @author Steve Ebersole + */ +public class OrderingSpecification extends NodeSupport { + public static class Ordering { + public static final Ordering ASCENDING = new Ordering( "asc" ); + public static final Ordering DESCENDING = new Ordering( "desc" ); + + private final String name; + + private Ordering(String name) { + this.name = name; + } + } + + private boolean resolved; + private Ordering ordering; + + public Ordering getOrdering() { + if ( !resolved ) { + ordering = resolve( getText() ); + resolved = true; + } + return ordering; + } + + private static Ordering resolve(String text) { + if ( Ordering.ASCENDING.name.equals( text ) ) { + return Ordering.ASCENDING; + } + else if ( Ordering.DESCENDING.name.equals( text ) ) { + return Ordering.DESCENDING; + } + else { + throw new IllegalStateException( "Unknown ordering [" + text + "]" ); + } + } + + public String getRenderableText() { + return getOrdering().name; + } +} diff --git a/core/src/main/java/org/hibernate/sql/ordering/antlr/SortKey.java b/core/src/main/java/org/hibernate/sql/ordering/antlr/SortKey.java new file mode 100644 index 0000000000..aee743ee48 --- /dev/null +++ b/core/src/main/java/org/hibernate/sql/ordering/antlr/SortKey.java @@ -0,0 +1,34 @@ +/* + * 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; + +/** + * Models the container node for the sort key, which is the term given by the ANSI SQL specification to the + * expression upon which to sort for each {@link SortSpecification} + * + * @author Steve Ebersole + */ +public class SortKey extends NodeSupport { +} diff --git a/core/src/main/java/org/hibernate/sql/ordering/antlr/SortSpecification.java b/core/src/main/java/org/hibernate/sql/ordering/antlr/SortSpecification.java new file mode 100644 index 0000000000..29ef97b40d --- /dev/null +++ b/core/src/main/java/org/hibernate/sql/ordering/antlr/SortSpecification.java @@ -0,0 +1,80 @@ +/* + * 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; + +import antlr.collections.AST; + +/** + * Models each sorting exprersion. + * + * @author Steve Ebersole + */ +public class SortSpecification extends NodeSupport { + /** + * Locate the specified {@link SortKey}. + * + * @return The sort key. + */ + public SortKey getSortKey() { + return ( SortKey ) getFirstChild(); + } + + /** + * Locate the specified collation specification, if one. + * + * @return The collation specification, or null if none was specified. + */ + public CollationSpecification getCollation() { + AST possible = getSortKey().getNextSibling(); + return possible != null && OrderByTemplateTokenTypes.COLLATE == possible.getType() + ? ( CollationSpecification ) possible + : null; + } + + /** + * Locate the specified ordering specification, if one. + * + * @return The ordering specification, or null if none was specified. + */ + public OrderingSpecification getOrdering() { + // IMPL NOTE : the ordering-spec would be either the 2nd or 3rd child (of the overall sort-spec), if it existed, + // depending on whether a collation-spec was specified. + + AST possible = getSortKey().getNextSibling(); + if ( possible == null ) { + // There was no sort-spec parts specified other then the sort-key so there can be no ordering-spec... + return null; + } + + if ( OrderByTemplateTokenTypes.COLLATE == possible.getType() ) { + // the 2nd child was a collation-spec, so we need to check the 3rd child instead. + possible = possible.getNextSibling(); + } + + return possible != null && OrderByTemplateTokenTypes.ORDER_SPEC == possible.getType() + ? ( OrderingSpecification ) possible + : null; + } +} diff --git a/core/src/main/java/org/hibernate/sql/ordering/antlr/TranslationContext.java b/core/src/main/java/org/hibernate/sql/ordering/antlr/TranslationContext.java new file mode 100644 index 0000000000..01348440de --- /dev/null +++ b/core/src/main/java/org/hibernate/sql/ordering/antlr/TranslationContext.java @@ -0,0 +1,64 @@ +/* + * 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; + +import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.function.SQLFunctionRegistry; + +/** + * Contract for contextual information required to perform translation. +* +* @author Steve Ebersole +*/ +public interface TranslationContext { + /** + * Retrieves the session factory for this context. + * + * @return The session factory + */ + public SessionFactoryImplementor getSessionFactory(); + + /** + * Retrieves the dialect for this context. + * + * @return The dialect + */ + public Dialect getDialect(); + + /** + * Retrieves the SQL function registry/tt> for this context. + * + * @return The SQL function registry. + */ + public SQLFunctionRegistry getSqlFunctionRegistry(); + + /** + * Retrieves the column mapper for this context. + * + * @return The column mapper + */ + public ColumnMapper getColumnMapper(); +} diff --git a/core/src/test/java/org/hibernate/sql/TemplateTest.java b/core/src/test/java/org/hibernate/sql/TemplateTest.java new file mode 100644 index 0000000000..464b195690 --- /dev/null +++ b/core/src/test/java/org/hibernate/sql/TemplateTest.java @@ -0,0 +1,184 @@ +/* + * 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; + +import java.util.Collections; + +import junit.framework.TestCase; + +import org.hibernate.persister.entity.PropertyMapping; +import org.hibernate.type.Type; +import org.hibernate.QueryException; +import org.hibernate.sql.ordering.antlr.ColumnMapper; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.HSQLDialect; +import org.hibernate.dialect.function.SQLFunctionRegistry; + +/** + * TODO : javadoc + * + * @author Steve Ebersole + */ +public class TemplateTest extends TestCase { + private static final PropertyMapping PROPERTY_MAPPING = new PropertyMapping() { + public String[] toColumns(String propertyName) throws QueryException, UnsupportedOperationException { + if ( "sql".equals( propertyName ) ) { + return new String[] { "sql" }; + } + else if ( "component".equals( propertyName ) ) { + return new String[] { "comp_1", "comp_2" }; + } + else if ( "component.prop1".equals( propertyName ) ) { + return new String[] { "comp_1" }; + } + else if ( "component.prop2".equals( propertyName ) ) { + return new String[] { "comp_2" }; + } + else if ( "property".equals( propertyName ) ) { + return new String[] { "prop" }; + } + throw new QueryException( "could not resolve property: " + propertyName ); + } + + public Type toType(String propertyName) throws QueryException { + return null; + } + + public String[] toColumns(String alias, String propertyName) throws QueryException { + return new String[0]; + } + + public Type getType() { + return null; + } + }; + + private static final ColumnMapper MAPPER = new ColumnMapper() { + public String[] map(String reference) { + return PROPERTY_MAPPING.toColumns( reference ); + } + }; + + private static final Dialect DIALECT = new HSQLDialect(); + + private static final SQLFunctionRegistry FUNCTION_REGISTRY = new SQLFunctionRegistry( DIALECT, Collections.EMPTY_MAP ); + + public void testSQLReferences() { + String fragment = "sql asc, sql desc"; + String template = doStandardRendering( fragment ); + + assertEquals( Template.TEMPLATE + ".sql asc, " + Template.TEMPLATE + ".sql desc", template ); + } + + public void testQuotedSQLReferences() { + String fragment = "`sql` asc, `sql` desc"; + String template = doStandardRendering( fragment ); + + assertEquals( Template.TEMPLATE + ".\"sql\" asc, " + Template.TEMPLATE + ".\"sql\" desc", template ); + } + + public void testPropertyReference() { + String fragment = "property asc, property desc"; + String template = doStandardRendering( fragment ); + + assertEquals( Template.TEMPLATE + ".prop asc, " + Template.TEMPLATE + ".prop desc", template ); + } + + public void testFunctionReference() { + String fragment = "upper(sql) asc, lower(sql) desc"; + String template = doStandardRendering( fragment ); + + assertEquals( "upper(" + Template.TEMPLATE + ".sql) asc, lower(" + Template.TEMPLATE + ".sql) desc", template ); + } + + public void testQualifiedFunctionReference() { + String fragment = "qual.upper(property) asc, qual.lower(property) desc"; + String template = doStandardRendering( fragment ); + + assertEquals( "qual.upper(" + Template.TEMPLATE + ".prop) asc, qual.lower(" + Template.TEMPLATE + ".prop) desc", template ); + } + + public void testDoubleQualifiedFunctionReference() { + String fragment = "qual1.qual2.upper(property) asc, qual1.qual2.lower(property) desc"; + String template = doStandardRendering( fragment ); + + assertEquals( "qual1.qual2.upper(" + Template.TEMPLATE + ".prop) asc, qual1.qual2.lower(" + Template.TEMPLATE + ".prop) desc", template ); + } + + public void testFunctionWithPropertyReferenceAsParam() { + String fragment = "upper(property) asc, lower(property) desc"; + String template = doStandardRendering( fragment ); + + assertEquals( "upper(" + Template.TEMPLATE + ".prop) asc, lower(" + Template.TEMPLATE + ".prop) desc", template ); + } + + public void testNestedFunctionReferences() { + String fragment = "upper(lower(sql)) asc, lower(upper(sql)) desc"; + String template = doStandardRendering( fragment ); + + assertEquals( "upper(lower(" + Template.TEMPLATE + ".sql)) asc, lower(upper(" + Template.TEMPLATE + ".sql)) desc", template ); + } + + public void testComplexNestedFunctionReferences() { + String fragment = "mod(mod(sql,2),3) asc"; + String template = doStandardRendering( fragment ); + + assertEquals( "mod(mod(" + Template.TEMPLATE + ".sql, 2), 3) asc", template ); + } + + public void testCollation() { + String fragment = "`sql` COLLATE my_collation, `sql` COLLATE your_collation"; + String template = doStandardRendering( fragment ); + + assertEquals( Template.TEMPLATE + ".\"sql\" collate my_collation, " + Template.TEMPLATE + ".\"sql\" collate your_collation", template ); + } + + public void testCollationAndOrdering() { + String fragment = "sql COLLATE my_collation, upper(prop) COLLATE your_collation asc, `sql` desc"; + String template = doStandardRendering( fragment ); + + assertEquals( Template.TEMPLATE + ".sql collate my_collation, upper(" + Template.TEMPLATE + ".prop) collate your_collation asc, " + Template.TEMPLATE + ".\"sql\" desc", template ); + + } + + public void testComponentReferences() { + String fragment = "component asc"; + String template = doStandardRendering( fragment ); + + assertEquals( Template.TEMPLATE + ".comp_1 asc, " + Template.TEMPLATE + ".comp_2 asc", template ); + + } + + public void testComponentDerefReferences() { + String fragment = "component.prop1 asc"; + String template = doStandardRendering( fragment ); + + assertEquals( Template.TEMPLATE + ".comp_1 asc", template ); + } + + public String doStandardRendering(String fragment) { + return Template.renderOrderByStringTemplate( fragment, MAPPER, null, DIALECT, FUNCTION_REGISTRY ); + } +} \ No newline at end of file diff --git a/core/src/test/resources/log4j.properties b/core/src/test/resources/log4j.properties index adaf3f2a02..3522e064dd 100644 --- a/core/src/test/resources/log4j.properties +++ b/core/src/test/resources/log4j.properties @@ -21,7 +21,6 @@ # 51 Franklin Street, Fifth Floor # Boston, MA 02110-1301 USA # -# log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout @@ -31,3 +30,4 @@ log4j.rootLogger=info, stdout log4j.logger.org.hibernate.test=info log4j.logger.org.hibernate.tool.hbm2ddl=debug +log4j.logger.org.hibernate.sql.ordering.antlr.OrderByFragmentTranslator=trace \ No newline at end of file