From ef16d42c65a70de8cf41b89dd98531876fc2ae41 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sat, 9 Mar 2024 19:02:38 +0100 Subject: [PATCH] allow collation names to be quoted --- .../main/asciidoc/introduction/Mapping.adoc | 1 + .../asciidoc/querylanguage/Expressions.adoc | 6 +++- .../hibernate/internal/util/StringHelper.java | 2 +- .../hql/internal/SemanticQueryBuilder.java | 28 ++++++++++++++----- .../orm/test/query/hql/CollateTests.java | 6 ++++ 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/documentation/src/main/asciidoc/introduction/Mapping.adoc b/documentation/src/main/asciidoc/introduction/Mapping.adoc index cc2fbe0f07..fa5ae57af3 100644 --- a/documentation/src/main/asciidoc/introduction/Mapping.adoc +++ b/documentation/src/main/asciidoc/introduction/Mapping.adoc @@ -797,6 +797,7 @@ Here we summarize the ones we've just seen in the second half of this chapter, a | `@Struct` | Map an embeddable to a SQL UDT with the given name | `@TimeZoneStorage` | Specify how the time zone information should be persisted | `@JdbcType` or `@JdbcTypeCode` | Use an implementation of `JdbcType` to map an arbitrary SQL type +| `@Collate` | Specify a collation for a column |=== In addition, there are some configuration properties which have a _global_ affect on how basic types map to SQL column types: diff --git a/documentation/src/main/asciidoc/querylanguage/Expressions.adoc b/documentation/src/main/asciidoc/querylanguage/Expressions.adoc index 228ca4d2eb..8225eb89c9 100644 --- a/documentation/src/main/asciidoc/querylanguage/Expressions.adoc +++ b/documentation/src/main/asciidoc/querylanguage/Expressions.adoc @@ -876,13 +876,17 @@ Its BNF is given by: [discrete] ===== Collations -Selects a collation to be used for its string-valued argument. +The `collate()` function selects a collation to be used for its string-valued argument. Collations are useful for <> with `<` or `>`, and in the <>. For example, `collate(p.name as ucs_basic)` specifies the SQL standard collation `ucs_basic`. IMPORTANT: Collations aren't very portable between databases. +TIP: Some PostgreSQL collation names must be quoted with backticks, for example, ``collate(name as \`zh_TW.UTF-8`)``. + +TIP: The `@Collate` annotation may be used to specify the collation of a column, which is usually more convenient than using the `collate()` function. + [[functions-numeric]] ==== Numeric functions diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java index 68a0bba2fe..0999808f3d 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java @@ -742,7 +742,7 @@ public final class StringHelper { final char first = name.charAt( 0 ); final char last = name.charAt( name.length() - 1 ); - return ( ( first == last ) && ( first == '`' || first == '"' ) ); + return first == last && ( first == '`' || first == '"' ); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index 1b788d5c91..ed0f8490c4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -39,7 +39,6 @@ import org.hibernate.grammars.hql.HqlLexer; import org.hibernate.grammars.hql.HqlParser; import org.hibernate.grammars.hql.HqlParserBaseVisitor; import org.hibernate.internal.util.CharSequenceHelper; -import org.hibernate.internal.util.QuotingHelper; import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.StandardStack; import org.hibernate.metamodel.CollectionClassification; @@ -247,7 +246,10 @@ import static org.hibernate.grammars.hql.HqlParser.INTERSECT; import static org.hibernate.grammars.hql.HqlParser.ListaggFunctionContext; import static org.hibernate.grammars.hql.HqlParser.OnOverflowClauseContext; import static org.hibernate.grammars.hql.HqlParser.PLUS; +import static org.hibernate.grammars.hql.HqlParser.QUOTED_IDENTIFIER; import static org.hibernate.grammars.hql.HqlParser.UNION; +import static org.hibernate.internal.util.QuotingHelper.unquoteIdentifier; +import static org.hibernate.internal.util.QuotingHelper.unquoteJavaStringLiteral; import static org.hibernate.internal.util.QuotingHelper.unquoteStringLiteral; import static org.hibernate.query.hql.internal.SqmTreeCreationHelper.extractJpaCompliantAlias; import static org.hibernate.query.sqm.TemporalUnit.DATE; @@ -1953,8 +1955,8 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem public String visitNakedIdentifier(HqlParser.NakedIdentifierContext ctx) { final TerminalNode node = (TerminalNode) ctx.getChild( 0 ); final String text = node.getText(); - return node.getSymbol().getType() == HqlParser.QUOTED_IDENTIFIER - ? QuotingHelper.unquoteIdentifier( text ) + return node.getSymbol().getType() == QUOTED_IDENTIFIER + ? unquoteIdentifier( text ) : text; } @@ -3166,9 +3168,21 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem @Override public Object visitCollation(HqlParser.CollationContext ctx) { - return new SqmCollation( - ctx.simplePath().getText(), - null, + final StringBuilder collation = new StringBuilder(); + final HqlParser.SimplePathContext simplePathContext = ctx.simplePath(); + final boolean quoted = simplePathContext.getStart().getType() == QUOTED_IDENTIFIER; + if ( quoted ) { + collation.append("\""); + } + collation.append( visitIdentifier( simplePathContext.identifier() ) ); + for ( HqlParser.SimplePathElementContext pathElementContext + : simplePathContext.simplePathElement() ) { + collation.append( visitIdentifier( pathElementContext.identifier() ) ); + } + if ( quoted ) { + collation.append("\""); + } + return new SqmCollation( collation.toString(), null, creationContext.getNodeBuilder() ); } @@ -3721,7 +3735,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem } private SqmLiteral javaStringLiteral(String text) { - String unquoted = QuotingHelper.unquoteJavaStringLiteral( text ); + String unquoted = unquoteJavaStringLiteral( text ); return new SqmLiteral<>( unquoted, resolveExpressibleTypeBasic( String.class ), diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/CollateTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/CollateTests.java index 2e9d393ef8..c425724f64 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/CollateTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/CollateTests.java @@ -51,6 +51,12 @@ public class CollateTests { @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) ); + } + ); scope.inTransaction( session -> { session.createQuery("from EntityOfBasics e where e.theString is not null order by collate(e.theString as ucs_basic)").getResultList();