move away from magical unsaved-value strings

introduce NullValueSemantic

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-11-09 10:52:48 +01:00
parent 529cb279d0
commit cb38afc773
5 changed files with 120 additions and 50 deletions

View File

@ -385,7 +385,7 @@ public class PropertyBinder {
buildingContext
);
rootClass.setIdentifier( identifier );
identifier.setNullValue( "undefined" );
identifier.setNullValueUndefined();
rootClass.setEmbeddedIdentifier( true );
rootClass.setIdentifierMapper( identifier );
return identifier;

View File

@ -923,7 +923,7 @@ public class ModelBinder {
versionValue.setNullValue( versionAttributeSource.getUnsavedValue() );
}
else {
versionValue.setNullValue( "undefined" );
versionValue.setNullValueUndefined();
}
if ( versionAttributeSource.getSource().equals("db") ) {
property.setValueGeneratorCreator(

View File

@ -6,10 +6,10 @@ package org.hibernate.engine.internal;
import java.util.function.Supplier;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.IdentifierValue;
import org.hibernate.engine.spi.VersionValue;
import org.hibernate.mapping.KeyValue;
import org.hibernate.mapping.KeyValue.NullValueSemantic;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.VersionJavaType;
@ -35,28 +35,31 @@ public class UnsavedValueFactory {
JavaType<?> idJavaType,
Getter getter,
Supplier<?> templateInstanceAccess) {
final String unsavedValue = bootIdMapping.getNullValue();
if ( unsavedValue == null ) {
if ( getter != null && templateInstanceAccess != null ) {
// use the id value of a newly instantiated instance as the unsaved-value
final Object defaultValue = getter.get( templateInstanceAccess.get() );
return new IdentifierValue( defaultValue );
}
else if ( idJavaType instanceof PrimitiveJavaType<?> primitiveJavaType ) {
return new IdentifierValue( primitiveJavaType.getDefaultValue() );
}
else {
return IdentifierValue.NULL;
}
final NullValueSemantic nullValueSemantic = bootIdMapping.getNullValueSemantic();
return nullValueSemantic == null
? inferUnsavedIdentifierValue( idJavaType, getter, templateInstanceAccess )
: switch ( nullValueSemantic ) {
case UNDEFINED -> IdentifierValue.UNDEFINED;
case NULL -> IdentifierValue.NULL;
case ANY -> IdentifierValue.ANY;
case NONE -> IdentifierValue.NONE;
case VALUE -> new IdentifierValue( idJavaType.fromString( bootIdMapping.getNullValue() ) );
default -> throw new IllegalArgumentException( "Illegal null-value semantic: " + nullValueSemantic );
};
}
private static IdentifierValue inferUnsavedIdentifierValue(
JavaType<?> idJavaType, Getter getter, Supplier<?> templateInstanceAccess) {
if ( getter != null && templateInstanceAccess != null ) {
// use the id value of a newly instantiated instance as the unsaved-value
final Object defaultValue = getter.get( templateInstanceAccess.get() );
return new IdentifierValue( defaultValue );
}
else if ( idJavaType instanceof PrimitiveJavaType<?> primitiveJavaType ) {
return new IdentifierValue( primitiveJavaType.getDefaultValue() );
}
else {
return switch ( unsavedValue ) {
case "null" -> IdentifierValue.NULL;
case "undefined" -> IdentifierValue.UNDEFINED;
case "none" -> IdentifierValue.NONE;
case "any" -> IdentifierValue.ANY;
default -> new IdentifierValue( idJavaType.fromString( unsavedValue ) );
};
return IdentifierValue.NULL;
}
}
@ -72,31 +75,33 @@ public class UnsavedValueFactory {
VersionJavaType<T> versionJavaType,
Getter getter,
Supplier<?> templateInstanceAccess) {
final String unsavedValue = bootVersionMapping.getNullValue();
if ( unsavedValue == null ) {
if ( getter != null && templateInstanceAccess != null ) {
final Object defaultValue = getter.get( templateInstanceAccess.get() );
// if the version of a newly instantiated object is null
// or a negative number, use that value as the unsaved-value,
// otherwise assume it's the initial version set by program
return isNullInitialVersion( defaultValue )
? new VersionValue( defaultValue )
: VersionValue.UNDEFINED;
}
else {
return VersionValue.UNDEFINED;
}
final NullValueSemantic nullValueSemantic = bootVersionMapping.getNullValueSemantic();
return nullValueSemantic == null
? inferUnsavedVersionValue( versionJavaType, getter, templateInstanceAccess )
: switch ( nullValueSemantic ) {
case UNDEFINED -> VersionValue.UNDEFINED;
case NULL -> VersionValue.NULL;
case NEGATIVE -> VersionValue.NEGATIVE;
// this should not happen since the DTD prevents it
case VALUE -> new VersionValue( versionJavaType.fromString( bootVersionMapping.getNullValue() ) );
default -> throw new IllegalArgumentException( "Illegal null-value semantic: " + nullValueSemantic );
};
}
private static VersionValue inferUnsavedVersionValue(
VersionJavaType<?> versionJavaType, Getter getter, Supplier<?> templateInstanceAccess) {
if ( getter != null && templateInstanceAccess != null ) {
final Object defaultValue = getter.get( templateInstanceAccess.get() );
// if the version of a newly instantiated object is null
// or a negative number, use that value as the unsaved-value,
// otherwise assume it's the initial version set by program
return isNullInitialVersion( defaultValue )
? new VersionValue( defaultValue )
: VersionValue.UNDEFINED;
}
else {
// this should not happen since the DTD prevents it
return switch ( unsavedValue ) {
case "undefined" -> VersionValue.UNDEFINED;
case "null" -> VersionValue.NULL;
case "negative" -> VersionValue.NEGATIVE;
default -> throw new MappingException( "Could not parse version unsaved-value: " + unsavedValue );
};
return VersionValue.UNDEFINED;
}
}
private UnsavedValueFactory() {

View File

@ -22,10 +22,18 @@ public interface KeyValue extends Value {
boolean isCascadeDeleteEnabled();
enum NullValueSemantic { VALUE, NULL, NEGATIVE, UNDEFINED, NONE, ANY }
NullValueSemantic getNullValueSemantic();
String getNullValue();
boolean isUpdateable();
/**
* @deprecated No longer called, except from tests.
* Use {@link #createGenerator(Dialect, RootClass, Property, GeneratorSettings)}
*/
@Deprecated(since = "7.0", forRemoval = true)
Generator createGenerator(Dialect dialect, RootClass rootClass);

View File

@ -91,6 +91,7 @@ public abstract class SimpleValue implements KeyValue {
private boolean isNationalized;
private boolean isLob;
private NullValueSemantic nullValueSemantic;
private String nullValue;
private Table table;
@ -410,8 +411,8 @@ public abstract class SimpleValue implements KeyValue {
final IdGeneratorCreationContext context =
new IdGeneratorCreationContext( rootClass, property, defaults );
final Generator generator = customIdGeneratorCreator.createGenerator( context );
if ( generator.allowAssignedIdentifiers() && getNullValue() == null ) {
setNullValue( "undefined" );
if ( generator.allowAssignedIdentifiers() && nullValue == null ) {
setNullValueUndefined();
}
return generator;
}
@ -449,17 +450,73 @@ public abstract class SimpleValue implements KeyValue {
return table;
}
/**
* The property or field value which indicates that field
* or property has never been set.
*
* @see org.hibernate.engine.internal.UnsavedValueFactory
* @see org.hibernate.engine.spi.IdentifierValue
* @see org.hibernate.engine.spi.VersionValue
*/
@Override
public String getNullValue() {
return nullValue;
}
/**
* Sets the nullValue.
* @param nullValue The nullValue to set
* Set the property or field value indicating that field
* or property has never been set.
*
* @see org.hibernate.engine.internal.UnsavedValueFactory
* @see org.hibernate.engine.spi.IdentifierValue
* @see org.hibernate.engine.spi.VersionValue
*/
public void setNullValue(String nullValue) {
this.nullValue = nullValue;
nullValueSemantic = switch (nullValue) {
// magical values (legacy of hbm.xml)
case "null" -> NullValueSemantic.NULL;
case "none" -> NullValueSemantic.NONE;
case "any" -> NullValueSemantic.ANY;
case "undefined" -> NullValueSemantic.UNDEFINED;
default -> NullValueSemantic.VALUE;
};
if ( nullValueSemantic == NullValueSemantic.VALUE ) {
this.nullValue = nullValue;
}
}
/**
* The rule for determining if the field or
* property has been set.
*
* @see org.hibernate.engine.internal.UnsavedValueFactory
*/
@Override
public NullValueSemantic getNullValueSemantic() {
return nullValueSemantic;
}
/**
* Specifies the rule for determining if the field or
* property has been set.
*
* @see org.hibernate.engine.internal.UnsavedValueFactory
*/
public void setNullValueSemantic(NullValueSemantic nullValueSemantic) {
this.nullValueSemantic = nullValueSemantic;
}
/**
* Specifies that there is no well-defined property or
* field value indicating that field or property has never
* been set.
*
* @see org.hibernate.engine.internal.UnsavedValueFactory
* @see org.hibernate.engine.spi.IdentifierValue#UNDEFINED
* @see org.hibernate.engine.spi.VersionValue#UNDEFINED
*/
public void setNullValueUndefined() {
nullValueSemantic = NullValueSemantic.UNDEFINED;
}
public String getForeignKeyName() {