Skip to content

Commit

Permalink
move away from magical unsaved-value strings
Browse files Browse the repository at this point in the history
introduce NullValueSemantic

Signed-off-by: Gavin King <gavin@hibernate.org>
  • Loading branch information
gavinking committed Nov 9, 2024
1 parent 357b275 commit f057f91
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ private Component getOrCreateCompositeId(RootClass rootClass) {
buildingContext
);
rootClass.setIdentifier( identifier );
identifier.setNullValue( "undefined" );
identifier.setNullValueUndefined();
rootClass.setEmbeddedIdentifier( true );
rootClass.setIdentifierMapper( identifier );
return identifier;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,7 @@ private void bindEntityVersion(
versionValue.setNullValue( versionAttributeSource.getUnsavedValue() );
}
else {
versionValue.setNullValue( "undefined" );
versionValue.setNullValueUndefined();
}
if ( versionAttributeSource.getSource().equals("db") ) {
property.setValueGeneratorCreator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

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;
Expand All @@ -35,28 +35,31 @@ public static IdentifierValue getUnsavedIdentifierValue(
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;
}
}

Expand All @@ -72,31 +75,33 @@ public static <T> VersionValue getUnsavedVersionValue(
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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -410,8 +411,8 @@ public Generator createGenerator(
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;
}
Expand Down Expand Up @@ -449,17 +450,72 @@ public Table getTable() {
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
*/
public NullValueSemantic getNullValueSemantic() {

Check notice

Code scanning / CodeQL

Missing Override annotation Note

This method overrides
KeyValue.getNullValueSemantic
; it is advisable to add an Override annotation.
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() {
Expand Down

0 comments on commit f057f91

Please sign in to comment.