HHH-16829 add @Collate annotation

This commit is contained in:
Gavin King 2023-06-24 18:23:21 +02:00
parent f32f6b5515
commit ae1215ca35
10 changed files with 196 additions and 9 deletions

View File

@ -0,0 +1,36 @@
/*
* 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.annotations;
import org.hibernate.Incubating;
import org.hibernate.binder.internal.CollateBinder;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Specifies a collation to use when generating DDL for
* the column mapped by the annotated field or property.
*
* @author Gavin King
*
* @since 6.3
*/
@Incubating
@AttributeBinderType(binder = CollateBinder.class)
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface Collate {
/**
* The name of the collation.
*/
String value();
}

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.binder.internal;
import org.hibernate.AnnotationException;
import org.hibernate.annotations.Collate;
import org.hibernate.binder.AttributeBinder;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.OneToMany;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Value;
/**
* Handles {@link Collate} annotations.
*
* @author Gavin King
*/
public class CollateBinder implements AttributeBinder<Collate> {
@Override
public void bind(Collate collate, MetadataBuildingContext context, PersistentClass entity, Property property) {
Value value = property.getValue();
if ( value instanceof OneToMany ) {
throw new AnnotationException( "One to many association '" + property.getName()
+ "' was annotated '@Collate'");
}
else if ( value instanceof Collection ) {
throw new AnnotationException( "Collection '" + property.getName()
+ "' was annotated '@Collate'");
}
else {
for ( Column column : value.getColumns() ) {
column.setCollation( collate.value() );
}
}
}
}

View File

@ -3413,6 +3413,13 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
return getNullColumnString();
}
/**
* Quote the given collation name if necessary.
*/
public String quoteCollation(String collation) {
return collation;
}
/**
* Does this dialect support commenting on tables and columns?
*

View File

@ -660,4 +660,9 @@ public class HSQLDialect extends Dialect {
public UniqueDelegate getUniqueDelegate() {
return uniqueDelegate;
}
@Override
public String quoteCollation(String collation) {
return '\"' + collation + '\"';
}
}

View File

@ -861,6 +861,11 @@ public class PostgreSQLDialect extends Dialect {
return "null::" + typeConfiguration.getDdlTypeRegistry().getDescriptor( sqlType ).getRawTypeName();
}
@Override
public String quoteCollation(String collation) {
return '\"' + collation + '\"';
}
@Override
public boolean supportsCommentOn() {
return true;

View File

@ -36,6 +36,7 @@ public class AggregateColumn extends Column {
addCheckConstraint( constraint );
}
setComment( column.getComment() );
setCollation( column.getCollation() );
setDefaultValue( column.getDefaultValue() );
setGeneratedAs( column.getGeneratedAs() );
setAssignmentExpression( column.getAssignmentExpression() );

View File

@ -65,7 +65,7 @@ public class Column implements Selectable, Serializable, Cloneable, ColumnTypeIn
private String customWrite;
private String customRead;
private Size columnSize;
// private String specializedTypeDeclaration;
private String collation;
private java.util.List<CheckConstraint> checkConstraints = new ArrayList<>();
public Column() {
@ -614,6 +614,14 @@ public class Column implements Selectable, Serializable, Cloneable, ColumnTypeIn
this.comment = comment;
}
public String getCollation() {
return collation;
}
public void setCollation(String collation) {
this.collation = collation;
}
public String getDefaultValue() {
return defaultValue;
}

View File

@ -183,6 +183,11 @@ class ColumnDefinitions {
definition.append( ' ' ).append( columnType );
}
String collation = column.getCollation();
if ( collation != null ) {
definition.append(" collate ").append( dialect.quoteCollation( collation ) );
}
final String defaultValue = column.getDefaultValue();
if ( defaultValue != null ) {
definition.append( " default " ).append( defaultValue );
@ -205,7 +210,7 @@ class ColumnDefinitions {
private static boolean isIdentityColumn(Column column, Table table, Metadata metadata, Dialect dialect) {
// Try to find out the name of the primary key in case the dialect needs it to create an identity
return isPrimaryKeyIdentity( table, metadata, dialect )
&& column.getQuotedName( dialect ).equals( getPrimaryKeyColumnName( table, dialect ) );
&& column.getQuotedName( dialect ).equals( getPrimaryKeyColumnName( table, dialect ) );
}
private static String getPrimaryKeyColumnName(Table table, Dialect dialect) {
@ -221,13 +226,13 @@ class ColumnDefinitions {
// && table.getPrimaryKey().getColumn( 0 ).isIdentity();
MetadataImplementor metadataImplementor = (MetadataImplementor) metadata;
return table.hasPrimaryKey()
&& table.getIdentifierValue() != null
&& table.getIdentifierValue()
.isIdentityColumn(
metadataImplementor.getMetadataBuildingOptions()
.getIdentifierGeneratorFactory(),
dialect
);
&& table.getIdentifierValue() != null
&& table.getIdentifierValue()
.isIdentityColumn(
metadataImplementor.getMetadataBuildingOptions()
.getIdentifierGeneratorFactory(),
dialect
);
}
private static String stripArgs(String string) {

View File

@ -0,0 +1,38 @@
package org.hibernate.orm.test.annotations.collate;
import jakarta.persistence.Basic;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import org.hibernate.annotations.Collate;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
@SessionFactory
@DomainModel(annotatedClasses = MySQLCollateTest.Message.class)
@RequiresDialect(MySQLDialect.class)
public class MySQLCollateTest {
@Test void test(SessionFactoryScope scope) {
scope.inTransaction(session -> session.persist(new Message("Hello, world!")));
}
@Entity(name = "msgs")
static class Message {
@Id @GeneratedValue
Long id;
@Basic(optional = false)
@Collate("utf8mb4_spanish2_ci")
@Column(length = 200)
String text;
public Message(String text) {
this.text = text;
}
}
}

View File

@ -0,0 +1,38 @@
package org.hibernate.orm.test.annotations.collate;
import jakarta.persistence.Basic;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import org.hibernate.annotations.Collate;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
@SessionFactory
@DomainModel(annotatedClasses = PostgresCollateTest.Message.class)
@RequiresDialect(PostgreSQLDialect.class)
public class PostgresCollateTest {
@Test void test(SessionFactoryScope scope) {
scope.inTransaction(session -> session.persist(new Message("Hello, world!")));
}
@Entity(name = "msgs")
static class Message {
@Id @GeneratedValue
Long id;
@Basic(optional = false)
@Collate("es_ES")
@Column(length = 200)
String text;
public Message(String text) {
this.text = text;
}
}
}