HHH-16638 allow @DialectOverride for @SQLInsert and friends

This commit is contained in:
Gavin 2023-05-19 10:41:26 +02:00 committed by Gavin King
parent 62c05eadb9
commit 47d8a63f16
3 changed files with 185 additions and 4 deletions

View File

@ -380,12 +380,108 @@ public interface DialectOverride {
org.hibernate.annotations.SQLSelect override(); org.hibernate.annotations.SQLSelect override();
} }
@Target({METHOD, FIELD}) @Target({METHOD, FIELD, TYPE})
@Retention(RUNTIME) @Retention(RUNTIME)
@interface SQLSelects { @interface SQLSelects {
SQLSelect[] value(); SQLSelect[] value();
} }
/**
* Specializes a {@link org.hibernate.annotations.SQLInsert}
* in a certain dialect.
*/
@Target({METHOD, FIELD, TYPE})
@Retention(RUNTIME)
@Repeatable(SQLInserts.class)
@OverridesAnnotation(org.hibernate.annotations.SQLInsert.class)
@interface SQLInsert {
/**
* The {@link Dialect} in which this override applies.
*/
Class<? extends Dialect> dialect();
Version before() default @Version(major = MAX_VALUE);
Version sameOrAfter() default @Version(major = MIN_VALUE);
org.hibernate.annotations.SQLInsert override();
}
@Target({METHOD, FIELD, TYPE})
@Retention(RUNTIME)
@interface SQLInserts {
SQLInsert[] value();
}
/**
* Specializes a {@link org.hibernate.annotations.SQLUpdate}
* in a certain dialect.
*/
@Target({METHOD, FIELD, TYPE})
@Retention(RUNTIME)
@Repeatable(SQLUpdates.class)
@OverridesAnnotation(org.hibernate.annotations.SQLUpdate.class)
@interface SQLUpdate {
/**
* The {@link Dialect} in which this override applies.
*/
Class<? extends Dialect> dialect();
Version before() default @Version(major = MAX_VALUE);
Version sameOrAfter() default @Version(major = MIN_VALUE);
org.hibernate.annotations.SQLUpdate override();
}
@Target({METHOD, FIELD, TYPE})
@Retention(RUNTIME)
@interface SQLUpdates {
SQLUpdate[] value();
}
/**
* Specializes a {@link org.hibernate.annotations.SQLDelete}
* in a certain dialect.
*/
@Target({METHOD, FIELD, TYPE})
@Retention(RUNTIME)
@Repeatable(SQLDeletes.class)
@OverridesAnnotation(org.hibernate.annotations.SQLDelete.class)
@interface SQLDelete {
/**
* The {@link Dialect} in which this override applies.
*/
Class<? extends Dialect> dialect();
Version before() default @Version(major = MAX_VALUE);
Version sameOrAfter() default @Version(major = MIN_VALUE);
org.hibernate.annotations.SQLDelete override();
}
@Target({METHOD, FIELD, TYPE})
@Retention(RUNTIME)
@interface SQLDeletes {
SQLDelete[] value();
}
/**
* Specializes a {@link org.hibernate.annotations.SQLDeleteAll}
* in a certain dialect.
*/
@Target({METHOD, FIELD, TYPE})
@Retention(RUNTIME)
@Repeatable(SQLDeleteAlls.class)
@OverridesAnnotation(org.hibernate.annotations.SQLDeleteAll.class)
@interface SQLDeleteAll {
/**
* The {@link Dialect} in which this override applies.
*/
Class<? extends Dialect> dialect();
Version before() default @Version(major = MAX_VALUE);
Version sameOrAfter() default @Version(major = MIN_VALUE);
org.hibernate.annotations.SQLDeleteAll override();
}
@Target({METHOD, FIELD, TYPE})
@Retention(RUNTIME)
@interface SQLDeleteAlls {
SQLDeleteAll[] value();
}
/** /**
* Marks an annotation type as a dialect-specific override for * Marks an annotation type as a dialect-specific override for
* some other annotation type. * some other annotation type.

View File

@ -1252,8 +1252,6 @@ public class EntityBinder {
private void bindCustomSql() { private void bindCustomSql() {
//TODO: tolerate non-empty table() member here if it explicitly names the main table //TODO: tolerate non-empty table() member here if it explicitly names the main table
//TODO: would be nice to add these guys to @DialectOverride, but getOverridableAnnotation()
// does not yet handle repeatable annotations
final SQLInsert sqlInsert = findMatchingSqlAnnotation( "", SQLInsert.class, SQLInserts.class ); final SQLInsert sqlInsert = findMatchingSqlAnnotation( "", SQLInsert.class, SQLInserts.class );
if ( sqlInsert != null ) { if ( sqlInsert != null ) {
@ -1950,12 +1948,13 @@ public class EntityBinder {
String tableName, String tableName,
Class<T> annotationType, Class<T> annotationType,
Class<R> repeatableType) { Class<R> repeatableType) {
final T sqlAnnotation = annotatedClass.getAnnotation( annotationType ); final T sqlAnnotation = getOverridableAnnotation( annotatedClass, annotationType, context );
if ( sqlAnnotation != null ) { if ( sqlAnnotation != null ) {
if ( tableName.equals( tableMember( annotationType, sqlAnnotation ) ) ) { if ( tableName.equals( tableMember( annotationType, sqlAnnotation ) ) ) {
return sqlAnnotation; return sqlAnnotation;
} }
} }
//TODO: getOverridableAnnotation() does not yet handle @Repeatable annotations
final R repeatable = annotatedClass.getAnnotation(repeatableType); final R repeatable = annotatedClass.getAnnotation(repeatableType);
if ( repeatable != null ) { if ( repeatable != null ) {
for ( Annotation current : valueMember( repeatableType, repeatable ) ) { for ( Annotation current : valueMember( repeatableType, repeatable ) ) {

View File

@ -0,0 +1,86 @@
package org.hibernate.orm.test.customsql;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.hibernate.annotations.DialectOverride;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.SQLInsert;
import org.hibernate.annotations.SQLUpdate;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.dialect.SQLServerDialect;
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;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@SessionFactory
@DomainModel(annotatedClasses = CustomSqlOverrideTest.Custom.class)
@RequiresDialect(H2Dialect.class)
@RequiresDialect(MySQLDialect.class)
@RequiresDialect(PostgreSQLDialect.class)
@RequiresDialect(SQLServerDialect.class)
public class CustomSqlOverrideTest {
@Test
public void testCustomSql(SessionFactoryScope scope) {
Custom c = new Custom();
scope.inTransaction(s->{
s.persist(c);
c.whatever = "old value";
s.flush();
assertNotNull(c.id);
assertNotNull(c.uid);
s.clear();
Custom cc = s.find(Custom.class, c.id);
assertNotNull(cc.id);
assertNotNull(cc.uid);
assertEquals("old value", cc.whatever);
cc.whatever = "new value";
s.flush();
s.clear();
Custom ccc = s.find(Custom.class, c.id);
assertNotNull(cc.id);
assertNotNull(cc.uid);
assertEquals("new value", ccc.whatever);
assertEquals(cc.id, ccc.id);
assertNotEquals(cc.uid, ccc.uid);
});
}
@Entity
@Table(name = "CustomTable")
// @SQLInsert(sql="")
@DialectOverride.SQLInsert(dialect = H2Dialect.class,
override = @SQLInsert(sql="insert into CustomTable (uid,whatever) values (random_uuid(),?)"))
@DialectOverride.SQLInsert(dialect = MySQLDialect.class,
override = @SQLInsert(sql="insert into CustomTable (uid,whatever) values (uuid(),?)"))
@DialectOverride.SQLInsert(dialect = PostgreSQLDialect.class,
override = @SQLInsert(sql="insert into CustomTable (uid,whatever) values (gen_random_uuid(),?)"))
@DialectOverride.SQLInsert(dialect = SQLServerDialect.class,
override = @SQLInsert(sql="insert into CustomTable (uid,whatever) values (newid(),?)"))
// @SQLUpdate(sql="")
@DialectOverride.SQLUpdate(dialect = H2Dialect.class,
override = @SQLUpdate(sql="update CustomTable set uid = random_uuid(), whatever = ? where id = ?"))
@DialectOverride.SQLUpdate(dialect = MySQLDialect.class,
override = @SQLUpdate(sql="update CustomTable set uid = uuid(), whatever = ? where id = ?"))
@DialectOverride.SQLUpdate(dialect = PostgreSQLDialect.class,
override = @SQLUpdate(sql="update CustomTable set uid = gen_random_uuid(), whatever = ? where id = ?"))
@DialectOverride.SQLUpdate(dialect = SQLServerDialect.class,
override = @SQLUpdate(sql="update CustomTable set uid = newid(), whatever = ? where id = ?"))
static class Custom {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@Generated
String uid;
String whatever;
}
}