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.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import javax.persistence.EnumType; import javax.persistence.EnumType;
import javax.persistence.TemporalType;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.MappingException; import org.hibernate.MappingException;
@ -26,6 +25,8 @@
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.boot.spi.MetadataBuildingContext; 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.ReflectHelper;
import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.model.convert.internal.NamedEnumValueConverter; import org.hibernate.metamodel.model.convert.internal.NamedEnumValueConverter;
@ -72,7 +73,6 @@ public class BasicValue extends SimpleValue implements SqlTypeDescriptorIndicato
private boolean isNationalized; private boolean isNationalized;
private boolean isLob; private boolean isLob;
private EnumType enumerationStyle; private EnumType enumerationStyle;
private TemporalType temporalPrecision;
private ConverterDescriptor attributeConverterDescriptor; private ConverterDescriptor attributeConverterDescriptor;
@ -191,9 +191,7 @@ public Resolution<?> resolve() {
() -> { () -> {
if ( resolvedJavaClass != null ) { if ( resolvedJavaClass != null ) {
//noinspection unchecked //noinspection unchecked
return getBuildingContext().getBootstrapContext() return typeConfiguration.getJavaTypeDescriptorRegistry()
.getTypeConfiguration()
.getJavaTypeDescriptorRegistry()
.resolveDescriptor( resolvedJavaClass ); .resolveDescriptor( resolvedJavaClass );
} }
else if ( ownerName != null && propertyName != null ) { else if ( ownerName != null && propertyName != null ) {
@ -205,8 +203,7 @@ else if ( ownerName != null && propertyName != null ) {
// First resolve from the BasicTypeRegistry. // First resolve from the BasicTypeRegistry.
// If it does resolve, we can use the JTD instead of delegating to the JTD Regsitry. // If it does resolve, we can use the JTD instead of delegating to the JTD Regsitry.
final BasicType basicType = getBuildingContext().getBootstrapContext() final BasicType basicType = typeConfiguration
.getTypeConfiguration()
.getBasicTypeRegistry() .getBasicTypeRegistry()
.getRegisteredType( reflectedJavaType.getName() ); .getRegisteredType( reflectedJavaType.getName() );
if ( basicType != null ) { if ( basicType != null ) {
@ -214,8 +211,7 @@ else if ( ownerName != null && propertyName != null ) {
} }
//noinspection unchecked //noinspection unchecked
return getBuildingContext().getBootstrapContext() return typeConfiguration
.getTypeConfiguration()
.getJavaTypeDescriptorRegistry() .getJavaTypeDescriptorRegistry()
.resolveDescriptor( reflectedJavaType ); .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; return resolution;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -227,7 +227,7 @@ private EnumValueConverter interpretParameters(Properties parameters) {
return new OrdinalEnumValueConverter( return new OrdinalEnumValueConverter(
enumJavaDescriptor, enumJavaDescriptor,
integerJavaDescriptor.getJdbcRecommendedSqlType( localIndicators ), 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( return new OrdinalEnumValueConverter(
enumJavaDescriptor, enumJavaDescriptor,
integerJavaDescriptor.getJdbcRecommendedSqlType( localIndicators ), integerJavaDescriptor.getJdbcRecommendedSqlType( localIndicators ),
(BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Integer.class ) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Integer.class )
); );
} }
else if ( isCharacterType( type ) ) { else if ( isCharacterType( type ) ) {
@ -263,7 +263,7 @@ else if ( isCharacterType( type ) ) {
return new OrdinalEnumValueConverter( return new OrdinalEnumValueConverter(
enumJavaDescriptor, enumJavaDescriptor,
integerJavaDescriptor.getJdbcRecommendedSqlType( localIndicators ), 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.dialect.Dialect;
import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.spi.Primitive; 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.FALSE;
import static java.lang.Boolean.TRUE; import static java.lang.Boolean.TRUE;
@ -157,4 +159,11 @@ public int getDefaultSqlPrecision(Dialect dialect) {
public int getDefaultSqlScale() { public int getDefaultSqlScale() {
return 0; 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 java.sql.Types;
import javax.persistence.EnumType; import javax.persistence.EnumType;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.descriptor.WrapperOptions; 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.SqlTypeDescriptor;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptorIndicators; 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. * Describes a Java Enum type.
@ -32,7 +36,7 @@ public SqlTypeDescriptor getJdbcRecommendedSqlType(SqlTypeDescriptorIndicators c
: context.getTypeConfiguration().getSqlTypeDescriptorRegistry().getDescriptor( Types.VARCHAR ); : context.getTypeConfiguration().getSqlTypeDescriptorRegistry().getDescriptor( Types.VARCHAR );
} }
else { 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 ) ) { else if ( Integer.class.equals( type ) ) {
return (X) toOrdinal( value ); return (X) toOrdinal( value );
} }
else if ( Byte.class.equals( type ) ) {
return (X) toByte( value );
}
return (X) value; return (X) value;
} }
@ -72,10 +78,22 @@ else if ( value instanceof String ) {
else if ( value instanceof Integer ) { else if ( value instanceof Integer ) {
return fromOrdinal( (Integer) value ); return fromOrdinal( (Integer) value );
} }
else if ( value instanceof Byte ) {
return fromByte( (Byte) value );
}
return (T) 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 * 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(); 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 * 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() ); 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 @Deprecated
Class<T> getJavaTypeClass(); 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 CustomType basicType = (CustomType) selectedExpressable;
final EnumType enumType = (EnumType) basicType.getUserType(); final EnumType enumType = (EnumType) basicType.getUserType();
assertThat( enumType.getEnumValueConverter().getRelationalJavaDescriptor().getJavaType(), AssignableMatcher.assignableTo( Integer.class ) ); 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 ) ); assertThat( sqlAst.getDomainResultDescriptors().size(), is( 1 ) );