allow collation names to be quoted

This commit is contained in:
Gavin King 2024-03-09 19:02:38 +01:00
parent 0e3791cb64
commit ef16d42c65
5 changed files with 34 additions and 9 deletions

View File

@ -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 | `@Struct` | Map an embeddable to a SQL UDT with the given name
| `@TimeZoneStorage` | Specify how the time zone information should be persisted | `@TimeZoneStorage` | Specify how the time zone information should be persisted
| `@JdbcType` or `@JdbcTypeCode` | Use an implementation of `JdbcType` to map an arbitrary SQL type | `@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: In addition, there are some configuration properties which have a _global_ affect on how basic types map to SQL column types:

View File

@ -876,13 +876,17 @@ Its BNF is given by:
[discrete] [discrete]
===== Collations ===== 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 <<relational-comparisons,binary comparisons>> with `<` or `>`, and in the <<order-by,order by clause>>. Collations are useful for <<relational-comparisons,binary comparisons>> with `<` or `>`, and in the <<order-by,order by clause>>.
For example, `collate(p.name as ucs_basic)` specifies the SQL standard collation `ucs_basic`. For example, `collate(p.name as ucs_basic)` specifies the SQL standard collation `ucs_basic`.
IMPORTANT: Collations aren't very portable between databases. 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]] [[functions-numeric]]
==== Numeric functions ==== Numeric functions

View File

@ -742,7 +742,7 @@ public final class StringHelper {
final char first = name.charAt( 0 ); final char first = name.charAt( 0 );
final char last = name.charAt( name.length() - 1 ); final char last = name.charAt( name.length() - 1 );
return ( ( first == last ) && ( first == '`' || first == '"' ) ); return first == last && ( first == '`' || first == '"' );
} }
/** /**

View File

@ -39,7 +39,6 @@ import org.hibernate.grammars.hql.HqlLexer;
import org.hibernate.grammars.hql.HqlParser; import org.hibernate.grammars.hql.HqlParser;
import org.hibernate.grammars.hql.HqlParserBaseVisitor; import org.hibernate.grammars.hql.HqlParserBaseVisitor;
import org.hibernate.internal.util.CharSequenceHelper; 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.Stack;
import org.hibernate.internal.util.collections.StandardStack; import org.hibernate.internal.util.collections.StandardStack;
import org.hibernate.metamodel.CollectionClassification; 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.ListaggFunctionContext;
import static org.hibernate.grammars.hql.HqlParser.OnOverflowClauseContext; import static org.hibernate.grammars.hql.HqlParser.OnOverflowClauseContext;
import static org.hibernate.grammars.hql.HqlParser.PLUS; 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.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.internal.util.QuotingHelper.unquoteStringLiteral;
import static org.hibernate.query.hql.internal.SqmTreeCreationHelper.extractJpaCompliantAlias; import static org.hibernate.query.hql.internal.SqmTreeCreationHelper.extractJpaCompliantAlias;
import static org.hibernate.query.sqm.TemporalUnit.DATE; import static org.hibernate.query.sqm.TemporalUnit.DATE;
@ -1953,8 +1955,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
public String visitNakedIdentifier(HqlParser.NakedIdentifierContext ctx) { public String visitNakedIdentifier(HqlParser.NakedIdentifierContext ctx) {
final TerminalNode node = (TerminalNode) ctx.getChild( 0 ); final TerminalNode node = (TerminalNode) ctx.getChild( 0 );
final String text = node.getText(); final String text = node.getText();
return node.getSymbol().getType() == HqlParser.QUOTED_IDENTIFIER return node.getSymbol().getType() == QUOTED_IDENTIFIER
? QuotingHelper.unquoteIdentifier( text ) ? unquoteIdentifier( text )
: text; : text;
} }
@ -3166,9 +3168,21 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override @Override
public Object visitCollation(HqlParser.CollationContext ctx) { public Object visitCollation(HqlParser.CollationContext ctx) {
return new SqmCollation( final StringBuilder collation = new StringBuilder();
ctx.simplePath().getText(), final HqlParser.SimplePathContext simplePathContext = ctx.simplePath();
null, 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() ); creationContext.getNodeBuilder() );
} }
@ -3721,7 +3735,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
} }
private SqmLiteral<String> javaStringLiteral(String text) { private SqmLiteral<String> javaStringLiteral(String text) {
String unquoted = QuotingHelper.unquoteJavaStringLiteral( text ); String unquoted = unquoteJavaStringLiteral( text );
return new SqmLiteral<>( return new SqmLiteral<>(
unquoted, unquoted,
resolveExpressibleTypeBasic( String.class ), resolveExpressibleTypeBasic( String.class ),

View File

@ -51,6 +51,12 @@ public class CollateTests {
@Test @RequiresDialect(PostgreSQLDialect.class) @Test @RequiresDialect(PostgreSQLDialect.class)
public void testCollatePostgreSQL(SessionFactoryScope scope) { 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( scope.inTransaction(
session -> { session -> {
session.createQuery("from EntityOfBasics e where e.theString is not null order by collate(e.theString as ucs_basic)").getResultList(); session.createQuery("from EntityOfBasics e where e.theString is not null order by collate(e.theString as ucs_basic)").getResultList();