redesign 'collate' and add tests

* and add tests and documentation for 'collate'
* much better and less-ambiguous syntax for collate(), consistent with cast() and treat()
* reimplement collate() using the function infrastructure
* implement collate() for HSQLDB

This feature was previously untested and at least partially broken, and was making a mess 
of the HQL grammar.
This commit is contained in:
Gavin King 2022-01-02 13:39:33 +01:00 committed by GitHub
parent 4509cad315
commit 5ae55d7bfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 283 additions and 273 deletions

View File

@ -850,6 +850,15 @@ include::{sourcedir}/HQLTest.java[tags=hql-year-function-example]
----
====
===== `collate()`
Selects a collation to be used for its string-valued argument.
Collations are useful for <<hql-relational-comparisons,binary comparisons>> with `<` or `>`, and in the <<hql-order-by,order by clause>>.
For example, `collate(p.name as ucs_basic)` specifies the SQL standard collation `ucs_basic`.
IMPORTANT: Collations aren't very portable between databases.
[[hql-function-format]]
===== `format()`

View File

@ -293,12 +293,12 @@ mapKeyNavigablePath
// GROUP BY clause
groupByClause
: GROUP BY groupByExpression ( COMMA groupByExpression )*
: GROUP BY groupByExpression ( COMMA groupByExpression )*
;
groupByExpression
: identifier collationSpecification?
| INTEGER_LITERAL collationSpecification?
: identifier
| INTEGER_LITERAL
| expression
;
@ -306,7 +306,7 @@ groupByExpression
//HAVING clause
havingClause
: HAVING predicate
: HAVING predicate
;
@ -333,22 +333,22 @@ nullsPrecedence
;
sortExpression
: identifier collationSpecification?
| INTEGER_LITERAL collationSpecification?
: identifier
| INTEGER_LITERAL
| expression
;
collationSpecification
: COLLATE collateName
collationExpression
: COLLATE LEFT_PAREN expression AS collation RIGHT_PAREN
;
collateName
: dotIdentifierSequence
collation
: dotIdentifierSequence
;
orderingSpecification
: ASC
| DESC
: ASC
| DESC
;
@ -384,7 +384,7 @@ parameterOrNumberLiteral
// WHERE clause & Predicates
whereClause
: WHERE predicate
: WHERE predicate
;
predicate
@ -436,7 +436,7 @@ expression
: LEFT_PAREN expression RIGHT_PAREN # GroupedExpression
| LEFT_PAREN expressionOrPredicate (COMMA expressionOrPredicate)+ RIGHT_PAREN # TupleExpression
| LEFT_PAREN subquery RIGHT_PAREN # SubqueryExpression
| primaryExpression collationSpecification? # CollateExpression
| primaryExpression # BarePrimaryExpression
| signOperator numericLiteral # UnaryNumericLiteralExpression
| signOperator expression # UnaryExpression
| expression datetimeField # ToDurationExpression
@ -707,6 +707,7 @@ standardFunction
| offsetDateTimeFunction
| cube
| rollup
| collationExpression
;

View File

@ -792,6 +792,7 @@ public abstract class Dialect implements ConversionContext {
*
* <ul>
* <li> format(datetime as pattern)
* <li> collate(string as collation)
* <li> str(arg) - synonym of cast(a as String)
* <li> ifnull(arg0, arg1) - synonym of coalesce(a, b)
* </ul>
@ -897,6 +898,10 @@ public abstract class Dialect implements ConversionContext {
)
);
//There is a 'collate' operator in a number of major databases
CommonFunctionFactory.collate( queryEngine );
//ANSI SQL extract() function is supported on the databases we care most
//about (though it is called datepart() in some of them) but HQL defines
//additional non-standard temporal field types, which must be emulated in

View File

@ -210,6 +210,7 @@ public class HSQLDialect extends Dialect {
CommonFunctionFactory.varPopSamp( queryEngine );
CommonFunctionFactory.addMonths( queryEngine );
CommonFunctionFactory.monthsBetween( queryEngine );
CommonFunctionFactory.collate_quoted( queryEngine );
if ( getVersion().isSameOrAfter( 2 ) ) {
//SYSDATE is similar to LOCALTIMESTAMP but it returns the timestamp when it is called

View File

@ -2185,6 +2185,32 @@ public class CommonFunctionFactory {
.register();
}
/**
* Use the 'collate' operator which exists on at least Postgres, MySQL, Oracle, and SQL Server
*/
public static void collate(QueryEngine queryEngine) {
queryEngine.getSqmFunctionRegistry().patternDescriptorBuilder("collate", "(?1 collate ?2)")
.setInvariantType(
queryEngine.getTypeConfiguration().getBasicTypeRegistry().resolve( StandardBasicTypes.STRING )
)
.setExactArgumentCount( 2 )
.setArgumentListSignature("(string as collation)")
.register();
}
/**
* HSQL requires quotes around certain collations
*/
public static void collate_quoted(QueryEngine queryEngine) {
queryEngine.getSqmFunctionRegistry().patternDescriptorBuilder("collate", "(?1 collate '?2')")
.setInvariantType(
queryEngine.getTypeConfiguration().getBasicTypeRegistry().resolve( StandardBasicTypes.STRING )
)
.setExactArgumentCount( 2 )
.setArgumentListSignature("(string as collation)")
.register();
}
public static void dateTrunc(QueryEngine queryEngine) {
queryEngine.getSqmFunctionRegistry().patternDescriptorBuilder( "date_trunc", "date_trunc('?1',?2)" )
.setInvariantType(

View File

@ -114,7 +114,7 @@ import org.hibernate.query.sqm.tree.expression.SqmByUnit;
import org.hibernate.query.sqm.tree.expression.SqmCaseSearched;
import org.hibernate.query.sqm.tree.expression.SqmCaseSimple;
import org.hibernate.query.sqm.tree.expression.SqmCastTarget;
import org.hibernate.query.sqm.tree.expression.SqmCollate;
import org.hibernate.query.sqm.tree.expression.SqmCollation;
import org.hibernate.query.sqm.tree.expression.SqmCollectionSize;
import org.hibernate.query.sqm.tree.expression.SqmDistinct;
import org.hibernate.query.sqm.tree.expression.SqmDurationUnit;
@ -1912,7 +1912,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
private Map<Class<?>, Enum<?>> getPossibleEnumValues(HqlParser.ExpressionContext expressionContext) {
ParseTree ctx;
// Traverse the expression structure according to the grammar
if ( expressionContext instanceof HqlParser.CollateExpressionContext && expressionContext.getChildCount() == 1 ) {
if ( expressionContext instanceof HqlParser.BarePrimaryExpressionContext && expressionContext.getChildCount() == 1 ) {
ctx = expressionContext.getChild( 0 );
while ( ctx instanceof HqlParser.PrimaryExpressionContext && ctx.getChildCount() == 1 ) {
@ -2326,17 +2326,30 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
}
@Override
public Object visitCollateExpression(HqlParser.CollateExpressionContext ctx) {
SqmExpression<?> expression = (SqmExpression<?>) ctx.getChild( 0 ).accept( this );
if ( ctx.getChildCount() == 1 ) {
return expression;
}
public Object visitCollationExpression(HqlParser.CollationExpressionContext ctx) {
if ( creationOptions.useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation(
StrictJpaComplianceViolation.Type.COLLATIONS
);
}
return new SqmCollate<>( expression, ctx.getChild( 1 ).getChild( 1 ).getText() );
final SqmExpression<?> expressionToCollate = (SqmExpression<?>) ctx.getChild( 2 ).accept( this );
final SqmCollation castTargetExpression = (SqmCollation) ctx.getChild( 4 ).accept( this );
return getFunctionDescriptor("collate").generateSqmExpression(
asList( expressionToCollate, castTargetExpression ),
null, //why not string?
creationContext.getQueryEngine(),
creationContext.getJpaMetamodel().getTypeConfiguration()
);
}
@Override
public Object visitCollation(HqlParser.CollationContext ctx) {
return new SqmCollation(
ctx.getChild( 0 ).getText(),
null,
creationContext.getNodeBuilder() );
}
@Override
@ -3466,7 +3479,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
return getFunctionDescriptor("format").generateSqmExpression(
asList( expressionToCast, format ),
null,
null, //why not string?
creationContext.getQueryEngine(),
creationContext.getJpaMetamodel().getTypeConfiguration()
);

View File

@ -27,6 +27,7 @@ import org.hibernate.query.sqm.tree.domain.SqmMinIndexPath;
import org.hibernate.query.sqm.tree.domain.SqmPluralPartJoin;
import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
import org.hibernate.query.sqm.tree.expression.SqmAny;
import org.hibernate.query.sqm.tree.expression.SqmBinaryArithmetic;
import org.hibernate.query.sqm.tree.expression.SqmByUnit;
@ -34,30 +35,29 @@ import org.hibernate.query.sqm.tree.expression.SqmCaseSearched;
import org.hibernate.query.sqm.tree.expression.SqmCaseSimple;
import org.hibernate.query.sqm.tree.expression.SqmCastTarget;
import org.hibernate.query.sqm.tree.expression.SqmCoalesce;
import org.hibernate.query.sqm.tree.expression.SqmCollate;
import org.hibernate.query.sqm.tree.expression.SqmCollation;
import org.hibernate.query.sqm.tree.expression.SqmCollectionSize;
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
import org.hibernate.query.sqm.tree.expression.SqmDistinct;
import org.hibernate.query.sqm.tree.expression.SqmDurationUnit;
import org.hibernate.query.sqm.tree.expression.SqmEnumLiteral;
import org.hibernate.query.sqm.tree.expression.SqmEvery;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmExtractUnit;
import org.hibernate.query.sqm.tree.expression.SqmFieldLiteral;
import org.hibernate.query.sqm.tree.expression.SqmFormat;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
import org.hibernate.query.sqm.tree.expression.SqmParameterizedEntityType;
import org.hibernate.query.sqm.tree.expression.SqmEnumLiteral;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmFieldLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
import org.hibernate.query.sqm.tree.expression.SqmParameterizedEntityType;
import org.hibernate.query.sqm.tree.expression.SqmPositionalParameter;
import org.hibernate.query.sqm.tree.expression.SqmStar;
import org.hibernate.query.sqm.tree.expression.SqmSummarization;
import org.hibernate.query.sqm.tree.expression.SqmToDuration;
import org.hibernate.query.sqm.tree.expression.SqmTrimSpecification;
import org.hibernate.query.sqm.tree.expression.SqmTuple;
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
import org.hibernate.query.sqm.tree.expression.SqmDistinct;
import org.hibernate.query.sqm.tree.expression.SqmExtractUnit;
import org.hibernate.query.sqm.tree.expression.SqmStar;
import org.hibernate.query.sqm.tree.expression.SqmTrimSpecification;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
import org.hibernate.query.sqm.tree.from.SqmEntityJoin;
@ -197,7 +197,7 @@ public interface SemanticQueryWalker<T> {
T visitTuple(SqmTuple<?> sqmTuple);
T visitCollate(SqmCollate<?> sqmCollate);
T visitCollation(SqmCollation sqmCollate);
T visitBinaryArithmeticExpression(SqmBinaryArithmetic<?> expression);

View File

@ -107,7 +107,7 @@ public class SqmFunctionRegistry {
* Get a builder for creating and registering a pattern-based function descriptor.
*
* @param registrationKey The name under which the descriptor will get registered
* @param pattern The pattern defining the the underlying function call
* @param pattern The pattern defining the underlying function call
*
* @return The builder
*/
@ -119,7 +119,7 @@ public class SqmFunctionRegistry {
* Get a builder for creating and registering a pattern-based aggregate function descriptor.
*
* @param registrationKey The name under which the descriptor will get registered
* @param pattern The pattern defining the the underlying function call
* @param pattern The pattern defining the underlying function call
*
* @return The builder
*/

View File

@ -40,7 +40,7 @@ import org.hibernate.query.sqm.tree.expression.SqmCaseSearched;
import org.hibernate.query.sqm.tree.expression.SqmCaseSimple;
import org.hibernate.query.sqm.tree.expression.SqmCastTarget;
import org.hibernate.query.sqm.tree.expression.SqmCoalesce;
import org.hibernate.query.sqm.tree.expression.SqmCollate;
import org.hibernate.query.sqm.tree.expression.SqmCollation;
import org.hibernate.query.sqm.tree.expression.SqmCollectionSize;
import org.hibernate.query.sqm.tree.expression.SqmDistinct;
import org.hibernate.query.sqm.tree.expression.SqmDurationUnit;
@ -53,10 +53,10 @@ import org.hibernate.query.sqm.tree.expression.SqmFormat;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
import org.hibernate.query.sqm.tree.expression.SqmParameterizedEntityType;
import org.hibernate.query.sqm.tree.expression.SqmPositionalParameter;
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
import org.hibernate.query.sqm.tree.expression.SqmStar;
import org.hibernate.query.sqm.tree.expression.SqmSummarization;
import org.hibernate.query.sqm.tree.expression.SqmToDuration;
@ -977,7 +977,7 @@ public class SqmTreePrinter implements SemanticQueryWalker<Object> {
}
@Override
public Object visitCollate(SqmCollate<?> sqmCollate) {
public Object visitCollation(SqmCollation sqmCollate) {
return null;
}

View File

@ -829,7 +829,6 @@ public class CteInsertHandler implements InsertHandler {
insertSelectSpec.addSortSpecification(
new SortSpecification(
rowNumberColumnReference,
null,
SortOrder.ASCENDING
)
);
@ -996,7 +995,6 @@ public class CteInsertHandler implements InsertHandler {
finalResultQuery.addSortSpecification(
new SortSpecification(
idColumnReference,
null,
SortOrder.ASCENDING
)
);

View File

@ -317,7 +317,6 @@ public class InsertExecutionDelegate implements TableBasedInsertHandler.Executio
idSelectQuerySpec.addSortSpecification(
new SortSpecification(
columnReference,
null,
SortOrder.ASCENDING
)
);

View File

@ -40,29 +40,29 @@ import org.hibernate.query.sqm.tree.expression.SqmCaseSearched;
import org.hibernate.query.sqm.tree.expression.SqmCaseSimple;
import org.hibernate.query.sqm.tree.expression.SqmCastTarget;
import org.hibernate.query.sqm.tree.expression.SqmCoalesce;
import org.hibernate.query.sqm.tree.expression.SqmCollate;
import org.hibernate.query.sqm.tree.expression.SqmCollation;
import org.hibernate.query.sqm.tree.expression.SqmCollectionSize;
import org.hibernate.query.sqm.tree.expression.SqmDistinct;
import org.hibernate.query.sqm.tree.expression.SqmDurationUnit;
import org.hibernate.query.sqm.tree.expression.SqmEnumLiteral;
import org.hibernate.query.sqm.tree.expression.SqmEvery;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmExtractUnit;
import org.hibernate.query.sqm.tree.expression.SqmFieldLiteral;
import org.hibernate.query.sqm.tree.expression.SqmFormat;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
import org.hibernate.query.sqm.tree.expression.SqmParameterizedEntityType;
import org.hibernate.query.sqm.tree.expression.SqmPositionalParameter;
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
import org.hibernate.query.sqm.tree.expression.SqmStar;
import org.hibernate.query.sqm.tree.expression.SqmSummarization;
import org.hibernate.query.sqm.tree.expression.SqmToDuration;
import org.hibernate.query.sqm.tree.expression.SqmTrimSpecification;
import org.hibernate.query.sqm.tree.expression.SqmTuple;
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
import org.hibernate.query.sqm.tree.expression.SqmDistinct;
import org.hibernate.query.sqm.tree.expression.SqmExtractUnit;
import org.hibernate.query.sqm.tree.expression.SqmStar;
import org.hibernate.query.sqm.tree.expression.SqmTrimSpecification;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
import org.hibernate.query.sqm.tree.from.SqmEntityJoin;
@ -676,8 +676,7 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker<Obj
}
@Override
public Object visitCollate(SqmCollate<?> sqmCollate) {
sqmCollate.getExpression().accept( this );
public Object visitCollation(SqmCollation sqmCollate) {
return sqmCollate;
}

View File

@ -101,7 +101,7 @@ import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.domain.SqmCorrelatedRootJoin;
import org.hibernate.query.sqm.tree.domain.SqmCorrelation;
import org.hibernate.query.sqm.tree.domain.SqmPluralPartJoin;
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
import org.hibernate.query.sqm.tree.expression.*;
import org.hibernate.query.sqm.tree.insert.SqmInsertStatement;
import org.hibernate.sql.ast.tree.expression.ModifiedSubQueryExpression;
import org.hibernate.query.sqm.tree.domain.SqmTreatedRoot;
@ -175,40 +175,6 @@ import org.hibernate.query.sqm.tree.domain.SqmMinIndexPath;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
import org.hibernate.query.sqm.tree.expression.Conversion;
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef;
import org.hibernate.query.sqm.tree.expression.SqmAny;
import org.hibernate.query.sqm.tree.expression.SqmBinaryArithmetic;
import org.hibernate.query.sqm.tree.expression.SqmByUnit;
import org.hibernate.query.sqm.tree.expression.SqmCaseSearched;
import org.hibernate.query.sqm.tree.expression.SqmCaseSimple;
import org.hibernate.query.sqm.tree.expression.SqmCastTarget;
import org.hibernate.query.sqm.tree.expression.SqmCollate;
import org.hibernate.query.sqm.tree.expression.SqmCollectionSize;
import org.hibernate.query.sqm.tree.expression.SqmDistinct;
import org.hibernate.query.sqm.tree.expression.SqmDurationUnit;
import org.hibernate.query.sqm.tree.expression.SqmEnumLiteral;
import org.hibernate.query.sqm.tree.expression.SqmEvery;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmExtractUnit;
import org.hibernate.query.sqm.tree.expression.SqmFieldLiteral;
import org.hibernate.query.sqm.tree.expression.SqmFormat;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
import org.hibernate.query.sqm.tree.expression.SqmLiteralNull;
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.expression.SqmParameterizedEntityType;
import org.hibernate.query.sqm.tree.expression.SqmPositionalParameter;
import org.hibernate.query.sqm.tree.expression.SqmStar;
import org.hibernate.query.sqm.tree.expression.SqmSummarization;
import org.hibernate.query.sqm.tree.expression.SqmToDuration;
import org.hibernate.query.sqm.tree.expression.SqmTrimSpecification;
import org.hibernate.query.sqm.tree.expression.SqmTuple;
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
import org.hibernate.query.sqm.tree.from.SqmEntityJoin;
@ -281,7 +247,7 @@ import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.Collate;
import org.hibernate.sql.ast.tree.expression.Collation;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Distinct;
import org.hibernate.sql.ast.tree.expression.Duration;
@ -3707,7 +3673,6 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
subQuerySpec.addSortSpecification(
new SortSpecification(
columnReference,
null,
max ? SortOrder.DESCENDING : SortOrder.ASCENDING
)
);
@ -4559,11 +4524,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
@Override
public Object visitCollate(SqmCollate<?> sqmCollate) {
return new Collate(
(Expression) sqmCollate.getExpression().accept( this ),
sqmCollate.getCollation()
);
public Object visitCollation(SqmCollation sqmCollation) {
return new Collation( sqmCollation.getLiteralValue() );
}
@Override
@ -4628,10 +4590,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
@Override
public Object visitFormat(SqmFormat sqmFormat) {
return new Format(
sqmFormat.getLiteralValue(),
(BasicValuedMapping) sqmFormat.getNodeType()
);
return new Format( sqmFormat.getLiteralValue() );
}
@Override

View File

@ -1,45 +0,0 @@
/*
* 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.query.sqm.tree.expression;
import org.hibernate.query.sqm.SemanticQueryWalker;
/**
* @author Christian Beikov
*/
public class SqmCollate<T> extends AbstractSqmExpression<T> {
private final SqmExpression<T> expression;
private final String collation;
public SqmCollate(SqmExpression<T> expression, String collation) {
super( expression.getNodeType(), expression.nodeBuilder() );
assert !(expression instanceof SqmTuple);
this.expression = expression;
this.collation = collation;
}
public SqmExpression<T> getExpression() {
return expression;
}
public String getCollation() {
return collation;
}
@Override
public <X> X accept(SemanticQueryWalker<X> walker) {
return walker.visitCollate( this );
}
@Override
public void appendHqlString(StringBuilder sb) {
expression.appendHqlString( sb );
sb.append( " collate " );
sb.append( collation );
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.query.sqm.tree.expression;
import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.SqmExpressable;
/**
* @author Christian Beikov
*/
public class SqmCollation extends SqmLiteral<String> {
public SqmCollation(String value, SqmExpressable<String> inherentType, NodeBuilder nodeBuilder) {
super(value, inherentType, nodeBuilder);
}
@Override
public <R> R accept(SemanticQueryWalker<R> walker) {
return walker.visitCollation( this );
}
@Override
public void appendHqlString(StringBuilder sb) {
sb.append( getLiteralValue() );
}
}

View File

@ -16,7 +16,7 @@ import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.Collate;
import org.hibernate.sql.ast.tree.expression.Collation;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Distinct;
import org.hibernate.sql.ast.tree.expression.Duration;
@ -149,7 +149,7 @@ public interface SqlAstWalker {
void visitTuple(SqlTuple tuple);
void visitCollate(Collate collate);
void visitCollation(Collation collation);
void visitParameter(JdbcParameter jdbcParameter);

View File

@ -85,7 +85,7 @@ import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.Collate;
import org.hibernate.sql.ast.tree.expression.Collation;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Distinct;
import org.hibernate.sql.ast.tree.expression.Duration;
@ -4335,10 +4335,8 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
@Override
public void visitCollate(Collate collate) {
collate.getExpression().accept( this );
appendSql( " collate " );
appendSql( collate.getCollation() );
public void visitCollation(Collation collation) {
appendSql( collation.getCollation() );
}
@Override

View File

@ -19,7 +19,7 @@ import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.Collate;
import org.hibernate.sql.ast.tree.expression.Collation;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Distinct;
import org.hibernate.sql.ast.tree.expression.Duration;
@ -162,8 +162,7 @@ public class AbstractSqlAstWalker implements SqlAstWalker {
}
@Override
public void visitCollate(Collate collate) {
collate.getExpression().accept( this );
public void visitCollation(Collation collation) {
}
@Override

View File

@ -1,106 +0,0 @@
/*
* 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.sql.ast.tree.expression;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.metamodel.mapping.SqlExpressable;
import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.type.descriptor.java.JavaType;
/**
* @author Christian Beikov
*/
public class Collate implements Expression, SqlExpressable, SqlAstNode, DomainResultProducer {
private final Expression expression;
private final String collation;
public Collate(Expression expression, String collation) {
this.expression = expression;
this.collation = collation;
}
public Expression getExpression() {
return expression;
}
public String getCollation() {
return collation;
}
@Override
public JdbcMapping getJdbcMapping() {
if ( expression instanceof SqlExpressable ) {
return ( (SqlExpressable) expression ).getJdbcMapping();
}
if ( getExpressionType() instanceof SqlExpressable ) {
return ( (SqlExpressable) getExpressionType() ).getJdbcMapping();
}
if ( getExpressionType() != null ) {
final JdbcMappingContainer mappingContainer = getExpressionType();
assert mappingContainer.getJdbcTypeCount() == 1;
return mappingContainer.getJdbcMappings().get( 0 );
}
return null;
}
@Override
public JdbcMappingContainer getExpressionType() {
return expression.getExpressionType();
}
@Override
public void accept(SqlAstWalker sqlTreeWalker) {
sqlTreeWalker.visitCollate( this );
}
@Override
public DomainResult createDomainResult(
String resultVariable,
DomainResultCreationState creationState) {
final JavaType javaTypeDescriptor = expression.getExpressionType().getJdbcMappings().get( 0 ).getJavaTypeDescriptor();
return new BasicResult(
creationState.getSqlAstCreationState().getSqlExpressionResolver().resolveSqlSelection(
this,
javaTypeDescriptor,
creationState.getSqlAstCreationState().getCreationContext().getDomainModel().getTypeConfiguration()
).getValuesArrayPosition(),
resultVariable,
javaTypeDescriptor
);
}
@Override
public void applySqlSelections(DomainResultCreationState creationState) {
final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState();
final SqlExpressionResolver sqlExpressionResolver = sqlAstCreationState.getSqlExpressionResolver();
sqlExpressionResolver.resolveSqlSelection(
this,
expression.getExpressionType().getJdbcMappings().get( 0 ).getJavaTypeDescriptor(),
sqlAstCreationState.getCreationContext().getDomainModel().getTypeConfiguration()
);
}
@Override
public int forEachJdbcType(int offset, IndexedConsumer<JdbcMapping> action) {
action.accept( offset, getJdbcMapping() );
return getJdbcTypeCount();
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.sql.ast.tree.expression;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.SqlExpressable;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.tree.SqlAstNode;
/**
* @author Christian Beikov
*/
public class Collation implements SqlExpressable, SqlAstNode {
private final String collation;
public Collation(String collation) {
this.collation = collation;
}
public String getCollation() {
return collation;
}
@Override
public void accept(SqlAstWalker walker) {
walker.visitCollation( this );
}
@Override
public int forEachJdbcType(int offset, IndexedConsumer<JdbcMapping> action) {
return getJdbcTypeCount();
}
@Override
public JdbcMapping getJdbcMapping() {
return null;
}
}

View File

@ -7,7 +7,6 @@
package org.hibernate.sql.ast.tree.expression;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.SqlExpressable;
import org.hibernate.sql.ast.SqlAstWalker;
@ -20,11 +19,9 @@ import org.hibernate.sql.ast.tree.SqlAstNode;
*/
public class Format implements SqlExpressable, SqlAstNode {
private String format;
private BasicValuedMapping type;
public Format(String format, BasicValuedMapping type) {
public Format(String format) {
this.format = format;
this.type = type;
}
public String getFormat() {
@ -33,7 +30,7 @@ public class Format implements SqlExpressable, SqlAstNode {
@Override
public JdbcMapping getJdbcMapping() {
return type.getJdbcMapping();
return null;
}
@Override
@ -43,7 +40,6 @@ public class Format implements SqlExpressable, SqlAstNode {
@Override
public int forEachJdbcType(int offset, IndexedConsumer<JdbcMapping> action) {
action.accept( offset, type.getJdbcMapping() );
return getJdbcTypeCount();
}
}

View File

@ -10,7 +10,7 @@ import org.hibernate.query.NullPrecedence;
import org.hibernate.query.SortOrder;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Collate;
import org.hibernate.sql.ast.tree.expression.Collation;
import org.hibernate.sql.ast.tree.expression.Expression;
/**
@ -20,18 +20,15 @@ public class SortSpecification implements SqlAstNode {
private final Expression sortExpression;
private final SortOrder sortOrder;
private final NullPrecedence nullPrecedence;
private final Collation collation;
public SortSpecification(Expression sortExpression, String collation, SortOrder sortOrder) {
this( sortExpression, collation, sortOrder, NullPrecedence.NONE );
public SortSpecification(Expression sortExpression, SortOrder sortOrder) {
this( sortExpression, null, sortOrder, NullPrecedence.NONE );
}
public SortSpecification(Expression sortExpression, String collation, SortOrder sortOrder, NullPrecedence nullPrecedence) {
if ( collation == null ) {
this.sortExpression = sortExpression;
}
else {
this.sortExpression = new Collate( sortExpression, collation );
}
this.sortExpression = sortExpression;
this.collation = collation == null ? null : new Collation(collation);
this.sortOrder = sortOrder;
this.nullPrecedence = nullPrecedence;
}
@ -40,6 +37,10 @@ public class SortSpecification implements SqlAstNode {
return sortExpression;
}
public Collation getCollation() {
return collation;
}
public SortOrder getSortOrder() {
return sortOrder;
}

View File

@ -0,0 +1,83 @@
/*
* 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.orm.test.query.hql;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.domain.gambit.EntityOfBasics;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author Gavin King
*/
@ServiceRegistry
@DomainModel( standardModels = StandardDomainModel.GAMBIT )
@SessionFactory
public class CollateTests {
@BeforeAll
public void prepareData(SessionFactoryScope scope) {
scope.inTransaction(
em -> {
EntityOfBasics entity = new EntityOfBasics();
entity.setTheString("1234589");
entity.setId(123);
entity.setTheDate( new Date( 74, 2, 25 ) );
entity.setTheTime( new Time( 20, 10, 8 ) );
entity.setTheTimestamp( new Timestamp( 121, 4, 27, 13, 22, 50, 123456789 ) );
em.persist(entity);
}
);
}
@Test @RequiresDialect(PostgreSQLDialect.class)
public void testCollatePostgreSQL(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery("from EntityOfBasics e where e.theString is not null order by collate(e.theString as ucs_basic)").getResultList();
assertThat( session.createQuery("select collate('bar' as ucs_basic) < 'foo'").getSingleResult(), is(true) );
}
);
}
@Test @RequiresDialect(MySQLDialect.class)
public void testCollateMySQL(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery("from EntityOfBasics e order by collate(e.theString as utf8mb4_bin)").getResultList();
assertThat( session.createQuery("select collate('bar' as utf8mb4_bin) < 'foo'").getSingleResult(), is(true) );
}
);
}
@Test @RequiresDialect(HSQLDialect.class)
public void testCollateHSQL(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery("from EntityOfBasics e order by collate(e.theString as SQL_TEXT_UCC)").getResultList();
session.createQuery("from EntityOfBasics e order by collate(e.theString as English)").getResultList();
assertThat( session.createQuery("select collate('bar' as English) < 'foo'").getSingleResult(), is(true) );
}
);
}
}