Automatically generate check constraints for boolean and enum attributes

And make enums map to TINYINT by default.
This commit is contained in:
gavinking 2020-01-27 23:12:53 +01:00 committed by Steve Ebersole
parent 992b390fce
commit effec02964
11 changed files with 124 additions and 31 deletions

View File

@ -11,7 +11,6 @@
import java.util.function.Function;
import java.util.function.Supplier;
import javax.persistence.EnumType;
import javax.persistence.TemporalType;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
@ -26,6 +25,8 @@
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.model.convert.internal.NamedEnumValueConverter;
@ -72,7 +73,6 @@ public class BasicValue extends SimpleValue implements SqlTypeDescriptorIndicato
private boolean isNationalized;
private boolean isLob;
private EnumType enumerationStyle;
private TemporalType temporalPrecision;
private ConverterDescriptor attributeConverterDescriptor;
@ -191,9 +191,7 @@ public Resolution<?> resolve() {
() -> {
if ( resolvedJavaClass != null ) {
//noinspection unchecked
return getBuildingContext().getBootstrapContext()
.getTypeConfiguration()
.getJavaTypeDescriptorRegistry()
return typeConfiguration.getJavaTypeDescriptorRegistry()
.resolveDescriptor( resolvedJavaClass );
}
else if ( ownerName != null && propertyName != null ) {
@ -205,8 +203,7 @@ else if ( ownerName != null && propertyName != null ) {
// First resolve from the BasicTypeRegistry.
// If it does resolve, we can use the JTD instead of delegating to the JTD Regsitry.
final BasicType basicType = getBuildingContext().getBootstrapContext()
.getTypeConfiguration()
final BasicType basicType = typeConfiguration
.getBasicTypeRegistry()
.getRegisteredType( reflectedJavaType.getName() );
if ( basicType != null ) {
@ -214,8 +211,7 @@ else if ( ownerName != null && propertyName != null ) {
}
//noinspection unchecked
return getBuildingContext().getBootstrapContext()
.getTypeConfiguration()
return typeConfiguration
.getJavaTypeDescriptorRegistry()
.resolveDescriptor( reflectedJavaType );
}
@ -234,6 +230,30 @@ else if ( dependentValue != null ) {
);
}
if ( resolution != null
//TODO: pass in resolution.getValueConverter()
//and use it to determine the range of values!
&& attributeConverterDescriptor == null
&& getColumnSpan() == 1) {
Selectable selectable = getColumnIterator().next();
if (selectable instanceof Column) {
BasicType<?> basicType = resolution.getResolvedBasicType();
Column col = (Column) selectable;
Dialect dialect = getServiceRegistry().getService(JdbcServices.class).getDialect();
String checkConstraint = col.getCheckConstraint();
if ( checkConstraint == null && dialect.supportsColumnCheck() ) {
col.setCheckConstraint(
basicType.getJavaTypeDescriptor()
.getCheckCondition(
col.getQuotedName( dialect ),
basicType.getSqlTypeDescriptor(),
dialect
)
);
}
}
}
return resolution;
}

View File

@ -292,6 +292,13 @@ public boolean hasCheckConstraint() {
return checkConstraint != null;
}
public String checkConstraint() {
if (checkConstraint==null) {
return null;
}
return " check (" + checkConstraint + ")";
}
@Override
public String getTemplate(Dialect dialect, SqmFunctionRegistry functionRegistry) {
return safeInterning(

View File

@ -494,10 +494,9 @@ public Iterator sqlAlterStrings(
.getColumnDefinitionUniquenessFragment( column ) );
}
if ( column.hasCheckConstraint() && dialect.supportsColumnCheck() ) {
alter.append( " check(" )
.append( column.getCheckConstraint() )
.append( ")" );
String checkConstraint = column.checkConstraint();
if ( checkConstraint !=null && dialect.supportsColumnCheck() ) {
alter.append( checkConstraint );
}
String columnComment = column.getComment();
@ -578,10 +577,9 @@ public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog,
.getColumnDefinitionUniquenessFragment( col ) );
}
if ( col.hasCheckConstraint() && dialect.supportsColumnCheck() ) {
buf.append( " check (" )
.append( col.getCheckConstraint() )
.append( ")" );
String checkConstraint = col.checkConstraint();
if ( checkConstraint!=null && dialect.supportsColumnCheck() ) {
buf.append( checkConstraint );
}
String columnComment = col.getComment();

View File

@ -26,7 +26,6 @@ public abstract class ToOne extends SimpleValue implements Fetchable {
protected String referencedPropertyName;
private String referencedEntityName;
private String propertyName;
private boolean embedded;
private boolean lazy = true;
protected boolean unwrapProxy;
protected boolean referenceToPrimaryKey = true;
@ -106,8 +105,7 @@ public boolean isSame(SimpleValue other) {
public boolean isSame(ToOne other) {
return super.isSame( other )
&& Objects.equals( referencedPropertyName, other.referencedPropertyName )
&& Objects.equals( referencedEntityName, other.referencedEntityName )
&& embedded == other.embedded;
&& Objects.equals( referencedEntityName, other.referencedEntityName );
}
public boolean isValid(Mapping mapping) throws MappingException {

View File

@ -60,7 +60,7 @@ public Integer toRelationalValue(E domainForm) {
@Override
public int getJdbcTypeCode() {
return Types.INTEGER;
return Types.TINYINT;
}
@Override

View File

@ -119,10 +119,9 @@ public String[] getSqlCreateStrings(Table table, Metadata metadata) {
);
}
if ( col.getCheckConstraint() != null && dialect.supportsColumnCheck() ) {
buf.append( " check (" )
.append( col.getCheckConstraint() )
.append( ")" );
String checkConstraint = col.checkConstraint();
if ( checkConstraint != null && dialect.supportsColumnCheck() ) {
buf.append( checkConstraint );
}
String columnComment = col.getComment();

View File

@ -227,7 +227,7 @@ private EnumValueConverter interpretParameters(Properties parameters) {
return new OrdinalEnumValueConverter(
enumJavaDescriptor,
integerJavaDescriptor.getJdbcRecommendedSqlType( localIndicators ),
(BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Integer.class )
typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Integer.class )
);
}
}
@ -238,7 +238,7 @@ private EnumValueConverter interpretParameters(Properties parameters) {
return new OrdinalEnumValueConverter(
enumJavaDescriptor,
integerJavaDescriptor.getJdbcRecommendedSqlType( localIndicators ),
(BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Integer.class )
typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Integer.class )
);
}
else if ( isCharacterType( type ) ) {
@ -263,7 +263,7 @@ else if ( isCharacterType( type ) ) {
return new OrdinalEnumValueConverter(
enumJavaDescriptor,
integerJavaDescriptor.getJdbcRecommendedSqlType( localIndicators ),
(BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Integer.class )
typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Integer.class )
);
}

View File

@ -9,6 +9,8 @@
import org.hibernate.dialect.Dialect;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.spi.Primitive;
import org.hibernate.type.descriptor.sql.BitTypeDescriptor;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
@ -157,4 +159,11 @@ public int getDefaultSqlPrecision(Dialect dialect) {
public int getDefaultSqlScale() {
return 0;
}
@Override
public String getCheckCondition(String columnName, SqlTypeDescriptor sqlTypeDescriptor, Dialect dialect) {
return sqlTypeDescriptor instanceof BitTypeDescriptor && !dialect.supportsBitType()
? columnName + " in (0,1)"
: null;
}
}

View File

@ -9,9 +9,13 @@
import java.sql.Types;
import javax.persistence.EnumType;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.sql.IntegerTypeDescriptor;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptorIndicators;
import org.hibernate.type.descriptor.sql.TinyIntTypeDescriptor;
import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor;
/**
* Describes a Java Enum type.
@ -32,7 +36,7 @@ public SqlTypeDescriptor getJdbcRecommendedSqlType(SqlTypeDescriptorIndicators c
: context.getTypeConfiguration().getSqlTypeDescriptorRegistry().getDescriptor( Types.VARCHAR );
}
else {
return context.getTypeConfiguration().getSqlTypeDescriptorRegistry().getDescriptor( Types.INTEGER );
return context.getTypeConfiguration().getSqlTypeDescriptorRegistry().getDescriptor( Types.TINYINT );
}
}
@ -56,7 +60,9 @@ public <X> X unwrap(T value, Class<X> type, WrapperOptions options) {
else if ( Integer.class.equals( type ) ) {
return (X) toOrdinal( value );
}
else if ( Byte.class.equals( type ) ) {
return (X) toByte( value );
}
return (X) value;
}
@ -72,10 +78,22 @@ else if ( value instanceof String ) {
else if ( value instanceof Integer ) {
return fromOrdinal( (Integer) value );
}
else if ( value instanceof Byte ) {
return fromByte( (Byte) value );
}
return (T) value;
}
/**
* Convert a value of the enum type to its ordinal value
*/
public <E extends Enum> Byte toByte(E domainForm) {
if ( domainForm == null ) {
return null;
}
return (byte) domainForm.ordinal();
}
/**
* Convert a value of the enum type to its ordinal value
@ -87,6 +105,17 @@ public <E extends Enum> Integer toOrdinal(E domainForm) {
return domainForm.ordinal();
}
/**
* Interpret a numeric value as the ordinal of the enum type
*/
@SuppressWarnings("unchecked")
public <E extends Enum> E fromByte(Byte relationalForm) {
if ( relationalForm == null ) {
return null;
}
return (E) getJavaType().getEnumConstants()[ relationalForm ];
}
/**
* Interpret a numeric value as the ordinal of the enum type
*/
@ -118,4 +147,24 @@ public T fromName(String relationalForm) {
}
return (T) Enum.valueOf( getJavaType(), relationalForm.trim() );
}
@Override
public String getCheckCondition(String columnName, SqlTypeDescriptor sqlTypeDescriptor, Dialect dialect) {
if (sqlTypeDescriptor instanceof TinyIntTypeDescriptor
|| sqlTypeDescriptor instanceof IntegerTypeDescriptor) {
int last = getJavaType().getEnumConstants().length - 1;
return columnName + " between 0 and " + last;
}
else if (sqlTypeDescriptor instanceof VarcharTypeDescriptor) {
StringBuilder types = new StringBuilder();
for ( Enum value : getJavaType().getEnumConstants() ) {
if (types.length() != 0) {
types.append(", ");
}
types.append("'").append( value.name() ).append("'");
}
return columnName + " in (" + types + ")";
}
return null;
}
}

View File

@ -186,4 +186,17 @@ default String toString(T value) {
*/
@Deprecated
Class<T> getJavaTypeClass();
/**
* The check constraint that should be added to the column
* definition in generated DDL.
*
* @param columnName the name of the column
* @param sqlType the {@link SqlTypeDescriptor} of the mapped column
* @param dialect the SQL {@link Dialect}
* @return a check constraint condition or null
*/
default String getCheckCondition(String columnName, SqlTypeDescriptor sqlType, Dialect dialect) {
return null;
}
}

View File

@ -180,7 +180,7 @@ public void testConvertedHqlInterpretation(SessionFactoryScope scope) {
final CustomType basicType = (CustomType) selectedExpressable;
final EnumType enumType = (EnumType) basicType.getUserType();
assertThat( enumType.getEnumValueConverter().getRelationalJavaDescriptor().getJavaType(), AssignableMatcher.assignableTo( Integer.class ) );
assertThat( enumType.sqlTypes()[0], is( Types.INTEGER ) );
assertThat( enumType.sqlTypes()[0], is( Types.TINYINT ) );
assertThat( sqlAst.getDomainResultDescriptors().size(), is( 1 ) );