HHH-10999 Add support for SQL array types mapped as Java arrays and collections

This commit is contained in:
Christian Beikov 2022-04-05 20:45:38 +02:00
parent 39d8fa0662
commit 45fc49314e
130 changed files with 7840 additions and 306 deletions

View File

@ -396,6 +396,12 @@ Can also specify the name of the constant in `org.hibernate.type.SqlTypes` inste
Global setting identifying the preferred JDBC type code for storing instant values.
Can also specify the name of the constant in `org.hibernate.type.SqlTypes` instead.
`*hibernate.type.preferred_array_jdbc_type*` (e.g. `2003` for `java.sql.Types.ARRAY`)::
Global setting identifying the preferred JDBC type code for storing basic array and collection values.
Can also specify the name of the constant in `org.hibernate.type.SqlTypes` instead.
+
The default value is determined via `org.hibernate.dialect.Dialect.getPreferredSqlTypeCodeForArray()`.
==== Bean Validation options
`*jakarta.persistence.validation.factory*` (e.g. `jakarta.validation.ValidationFactory` implementation)::
Specify the `javax.validation.ValidationFactory` implementation to use for Bean Validation.

View File

@ -1374,6 +1374,42 @@ include::{sourcedir}/basic/XmlMappingTests.java[tags=basic-xml-example]
----
====
[[basic-mapping-array]]
==== Basic array mapping
Basic arrays, other than `byte[]`/Byte[] and `char[]`/`Character[]`, map to the type code `SqlTypes.ARRAY` by default,
which maps to the SQL standard `array` type if possible,
as determined via the new methods `getArrayTypeName` and `supportsStandardArrays` of `org.hibernate.dialect.Dialect`.
If SQL standard array types are not available, data will be modeled as `SqlTypes.JSON`, `SqlTypes.XML` or `SqlTypes.VARBINARY`,
depending on the database support as determined via the new method `org.hibernate.dialect.Dialect.getPreferredSqlTypeCodeForArray`.
[[basic-array-example]]
.Mapping basic arrays
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BasicArrayMappingTests.java[tags=basic-array-example]
----
====
[[basic-mapping-collection]]
==== Basic collection mapping
Basic collections (only subtypes of `Collection`), which are not annotated with `@ElementCollection`,
map to the type code `SqlTypes.ARRAY` by default, which maps to the SQL standard `array` type if possible,
as determined via the new methods `getArrayTypeName` and `supportsStandardArrays` of `org.hibernate.dialect.Dialect`.
If SQL standard array types are not available, data will be modeled as `SqlTypes.JSON`, `SqlTypes.XML` or `SqlTypes.VARBINARY`,
depending on the database support as determined via the new method `org.hibernate.dialect.Dialect.getPreferredSqlTypeCodeForArray`.
[[basic-collection-example]]
.Mapping basic collections
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BasicCollectionMappingTests.java[tags=basic-collection-example]
----
====
[[basic-mapping-composition]]
==== Compositional basic mapping

View File

@ -0,0 +1,95 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.mapping.basic;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
/**
* Tests for mapping basic arrays
*/
@DomainModel(annotatedClasses = BasicArrayMappingTests.EntityOfArrays.class)
@SessionFactory
public class BasicArrayMappingTests {
@Test
public void testMappings(SessionFactoryScope scope) {
// first, verify the type selections...
final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
final EntityPersister entityDescriptor = mappingMetamodel.findEntityDescriptor( EntityOfArrays.class);
{
final BasicAttributeMapping attribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("wrapper");
assertThat( attribute.getJavaType().getJavaTypeClass(), equalTo( Short[].class));
final JdbcMapping jdbcMapping = attribute.getJdbcMapping();
assertThat(jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo(Short[].class));
}
{
final BasicAttributeMapping attribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("primitive");
assertThat( attribute.getJavaType().getJavaTypeClass(), equalTo( short[].class));
final JdbcMapping jdbcMapping = attribute.getJdbcMapping();
assertThat(jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo(short[].class));
}
// and try to use the mapping
scope.inTransaction(
(session) -> session.persist(new EntityOfArrays( 1, new Short[]{ (short) 3 }, new short[]{ (short) 5 }))
);
scope.inTransaction(
(session) -> session.get( EntityOfArrays.class, 1)
);
}
@AfterEach
public void dropData(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> session.createQuery("delete EntityOfArrays").executeUpdate()
);
}
@Entity(name = "EntityOfArrays")
@Table(name = "EntityOfArrays")
public static class EntityOfArrays {
@Id
Integer id;
//tag::basic-array-example[]
Short[] wrapper;
short[] primitive;
//end::basic-array-example[]
public EntityOfArrays() {
}
public EntityOfArrays(Integer id, Short[] wrapper, short[] primitive) {
this.id = id;
this.wrapper = wrapper;
this.primitive = primitive;
}
}
}

View File

@ -0,0 +1,106 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.userguide.mapping.basic;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
/**
* Tests for mapping basic collections
*/
@DomainModel(annotatedClasses = BasicCollectionMappingTests.EntityOfCollections.class)
@SessionFactory
public class BasicCollectionMappingTests {
@Test
public void testMappings(SessionFactoryScope scope) {
// first, verify the type selections...
final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
final EntityPersister entityDescriptor = mappingMetamodel.findEntityDescriptor( EntityOfCollections.class);
{
final BasicAttributeMapping attribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("list");
assertThat( attribute.getJavaType().getJavaTypeClass(), equalTo( List.class));
final JdbcMapping jdbcMapping = attribute.getJdbcMapping();
assertThat(jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo(List.class));
}
{
final BasicAttributeMapping attribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("sortedSet");
assertThat( attribute.getJavaType().getJavaTypeClass(), equalTo( SortedSet.class));
final JdbcMapping jdbcMapping = attribute.getJdbcMapping();
assertThat(jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo(SortedSet.class));
}
// and try to use the mapping
scope.inTransaction(
(session) -> session.persist(
new EntityOfCollections(
1,
List.of( (short) 3 ),
new TreeSet<>( Set.of( (short) 5 ) )
)
)
);
scope.inTransaction(
(session) -> session.get( EntityOfCollections.class, 1)
);
}
@AfterEach
public void dropData(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> session.createQuery("delete EntityOfCollections").executeUpdate()
);
}
@Entity(name = "EntityOfCollections")
@Table(name = "EntityOfCollections")
public static class EntityOfCollections {
@Id
Integer id;
//tag::basic-collection-example[]
List<Short> list;
SortedSet<Short> sortedSet;
//end::basic-collection-example[]
public EntityOfCollections() {
}
public EntityOfCollections(Integer id, List<Short> list, SortedSet<Short> sortedSet) {
this.id = id;
this.list = list;
this.sortedSet = sortedSet;
}
}
}

View File

@ -218,6 +218,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
private final int preferredSqlTypeCodeForDuration;
private final int preferredSqlTypeCodeForUuid;
private final int preferredSqlTypeCodeForInstant;
private final int preferredSqlTypeCodeForArray;
private final TimeZoneStorageStrategy defaultTimeZoneStorageStrategy;
// Caching
@ -428,6 +429,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
this.preferredSqlTypeCodeForDuration = ConfigurationHelper.getPreferredSqlTypeCodeForDuration( serviceRegistry );
this.preferredSqlTypeCodeForUuid = ConfigurationHelper.getPreferredSqlTypeCodeForUuid( serviceRegistry );
this.preferredSqlTypeCodeForInstant = ConfigurationHelper.getPreferredSqlTypeCodeForInstant( serviceRegistry );
this.preferredSqlTypeCodeForArray = ConfigurationHelper.getPreferredSqlTypeCodeForArray( serviceRegistry );
this.defaultTimeZoneStorageStrategy = context.getMetadataBuildingOptions().getDefaultTimeZoneStorage();
final RegionFactory regionFactory = serviceRegistry.getService( RegionFactory.class );
@ -1242,6 +1244,11 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
return preferredSqlTypeCodeForInstant;
}
@Override
public int getPreferredSqlTypeCodeForArray() {
return preferredSqlTypeCodeForArray;
}
@Override
public TimeZoneStorageStrategy getDefaultTimeZoneStorageStrategy() {
return defaultTimeZoneStorageStrategy;

View File

@ -207,7 +207,7 @@ public class TypeDefinition implements Serializable {
@Override
public BasicValueConverter getValueConverter() {
return null;
return resolvedBasicType.getValueConverter();
}
@Override
@ -259,7 +259,7 @@ public class TypeDefinition implements Serializable {
@Override
public BasicValueConverter getValueConverter() {
return null;
return resolved.getValueConverter();
}
@Override

View File

@ -8,23 +8,24 @@ package org.hibernate.boot.model.process.internal;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.function.Function;
import java.util.function.Supplier;
import jakarta.persistence.EnumType;
import jakarta.persistence.TemporalType;
import org.hibernate.MappingException;
import org.hibernate.dialect.Dialect;
import org.hibernate.mapping.BasicValue;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.Table;
import org.hibernate.metamodel.model.convert.internal.NamedEnumValueConverter;
import org.hibernate.metamodel.model.convert.internal.OrdinalEnumValueConverter;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.AdjustableBasicType;
import org.hibernate.type.BasicType;
import org.hibernate.type.CustomType;
import org.hibernate.type.SerializableType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.BasicPluralJavaType;
import org.hibernate.type.descriptor.java.BasicJavaType;
import org.hibernate.type.descriptor.java.EnumJavaType;
import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan;
@ -52,6 +53,7 @@ public class InferredBasicValueResolver {
Selectable selectable,
String ownerName,
String propertyName,
Dialect dialect,
TypeConfiguration typeConfiguration) {
final JavaType<T> reflectedJtd = reflectedJtdResolver.get();
@ -64,8 +66,26 @@ public class InferredBasicValueResolver {
if ( explicitJavaType != null ) {
// we have an explicit JavaType
if ( explicitJdbcType != null ) {
if ( explicitJavaType instanceof EnumJavaType ) {
return fromEnum(
(EnumJavaType) explicitJavaType,
null,
explicitJdbcType,
stdIndicators,
typeConfiguration
);
}
else if ( explicitJavaType instanceof TemporalJavaType ) {
return fromTemporal(
(TemporalJavaType<T>) explicitJavaType,
null,
explicitJdbcType,
resolvedJavaType,
stdIndicators,
typeConfiguration
);
}
else if ( explicitJdbcType != null ) {
// we also have an explicit JdbcType
jdbcMapping = typeConfiguration.getBasicTypeRegistry().resolve(
@ -75,41 +95,12 @@ public class InferredBasicValueResolver {
legacyType = jdbcMapping;
}
else {
if ( explicitJavaType instanceof TemporalJavaType ) {
return fromTemporal(
(TemporalJavaType<T>) explicitJavaType,
null,
null,
resolvedJavaType,
stdIndicators,
typeConfiguration
);
}
// we need to infer the JdbcType and use that to build the value-mapping
final JdbcType inferredJdbcType = explicitJavaType.getRecommendedJdbcType( stdIndicators );
if ( inferredJdbcType instanceof ObjectJdbcType ) {
// have a "fallback" JDBC type... see if we can decide a better choice
if ( explicitJavaType instanceof EnumJavaType ) {
return fromEnum(
(EnumJavaType) reflectedJtd,
explicitJavaType,
null,
stdIndicators,
typeConfiguration
);
}
else if ( explicitJavaType instanceof TemporalJavaType ) {
return fromTemporal(
(TemporalJavaType<T>) reflectedJtd,
explicitJavaType,
null,
resolvedJavaType,
stdIndicators,
typeConfiguration
);
}
else if ( explicitJavaType instanceof SerializableJavaType
if ( explicitJavaType instanceof SerializableJavaType
|| explicitJavaType.getJavaType() instanceof Serializable ) {
legacyType = new SerializableType( explicitJavaType );
jdbcMapping = legacyType;
@ -133,7 +124,28 @@ public class InferredBasicValueResolver {
}
else if ( reflectedJtd != null ) {
// we were able to determine the "reflected java-type"
if ( explicitJdbcType != null ) {
// Use JTD if we know it to apply any specialized resolutions
if ( reflectedJtd instanceof EnumJavaType ) {
return fromEnum(
(EnumJavaType) reflectedJtd,
null,
explicitJdbcType,
stdIndicators,
typeConfiguration
);
}
else if ( reflectedJtd instanceof TemporalJavaType ) {
return fromTemporal(
(TemporalJavaType<T>) reflectedJtd,
null,
explicitJdbcType,
resolvedJavaType,
stdIndicators,
typeConfiguration
);
}
else if ( explicitJdbcType != null ) {
// we also have an explicit JdbcType
jdbcMapping = typeConfiguration.getBasicTypeRegistry().resolve(
@ -144,32 +156,59 @@ public class InferredBasicValueResolver {
legacyType = jdbcMapping;
}
else {
// Use JTD if we know it to apply any specialized resolutions
if ( reflectedJtd instanceof EnumJavaType ) {
return fromEnum(
(EnumJavaType) reflectedJtd,
// see if there is a registered BasicType for this JavaType and, if so, use it.
// this mimics the legacy handling
final BasicType registeredType;
if ( reflectedJtd instanceof BasicPluralJavaType<?> ) {
final BasicPluralJavaType<?> containerJtd = (BasicPluralJavaType<?>) reflectedJtd;
final JavaType<?> elementJtd = containerJtd.getElementJavaType();
final BasicType registeredElementType;
if ( elementJtd instanceof EnumJavaType ) {
final InferredBasicValueResolution resolution = InferredBasicValueResolver.fromEnum(
(EnumJavaType) elementJtd,
null,
null,
stdIndicators,
typeConfiguration
);
registeredElementType = resolution.getLegacyResolvedBasicType();
}
else if ( reflectedJtd instanceof TemporalJavaType ) {
return fromTemporal(
(TemporalJavaType<T>) reflectedJtd,
else if ( elementJtd instanceof TemporalJavaType ) {
final InferredBasicValueResolution resolution = InferredBasicValueResolver.fromTemporal(
(TemporalJavaType<T>) elementJtd,
null,
null,
resolvedJavaType,
stdIndicators,
typeConfiguration
);
registeredElementType = resolution.getLegacyResolvedBasicType();
}
else {
// see if there is a registered BasicType for this JavaType and, if so, use it.
// this mimics the legacy handling
final BasicType<T> registeredType = typeConfiguration.getBasicTypeRegistry()
registeredElementType = typeConfiguration.getBasicTypeRegistry()
.getRegisteredType( elementJtd.getJavaType() );
}
final ColumnTypeInformation columnTypeInformation;
if ( selectable instanceof ColumnTypeInformation ) {
columnTypeInformation = (ColumnTypeInformation) selectable;
}
else {
columnTypeInformation = null;
}
registeredType = registeredElementType == null ? null : containerJtd.resolveType(
typeConfiguration,
dialect,
registeredElementType,
columnTypeInformation
);
if ( registeredType != null ) {
typeConfiguration.getBasicTypeRegistry().register( registeredType );
}
}
else {
registeredType = typeConfiguration.getBasicTypeRegistry()
.getRegisteredType( reflectedJtd.getJavaType() );
}
if ( registeredType != null ) {
// so here is the legacy resolution
@ -200,7 +239,6 @@ public class InferredBasicValueResolver {
}
}
}
}
else {
if ( explicitJdbcType != null ) {
// we have an explicit STD, but no JTD - infer JTD
@ -271,40 +309,40 @@ public class InferredBasicValueResolver {
);
}
/**
* Create an inference-based resolution
*/
public static <T> BasicValue.Resolution<T> from(
Function<TypeConfiguration, BasicJavaType> explicitJavaTypeAccess,
Function<TypeConfiguration, JdbcType> explicitSqlTypeAccess,
Type resolvedJavaType,
Supplier<JavaType<T>> reflectedJtdResolver,
JdbcTypeIndicators stdIndicators,
Table table,
Selectable selectable,
String ownerName,
String propertyName,
TypeConfiguration typeConfiguration) {
final BasicJavaType<T> explicitJavaType = explicitJavaTypeAccess != null
? explicitJavaTypeAccess.apply( typeConfiguration )
: null;
final JdbcType explicitJdbcType = explicitSqlTypeAccess
!= null ? explicitSqlTypeAccess.apply( typeConfiguration )
: null;
return InferredBasicValueResolver.from(
explicitJavaType,
explicitJdbcType,
resolvedJavaType,
reflectedJtdResolver,
stdIndicators,
table,
selectable,
ownerName,
propertyName,
typeConfiguration
);
}
// /**
// * Create an inference-based resolution
// */
// public static <T> BasicValue.Resolution<T> from(
// Function<TypeConfiguration, BasicJavaType> explicitJavaTypeAccess,
// Function<TypeConfiguration, JdbcType> explicitSqlTypeAccess,
// Type resolvedJavaType,
// Supplier<JavaType<T>> reflectedJtdResolver,
// JdbcTypeIndicators stdIndicators,
// Table table,
// Selectable selectable,
// String ownerName,
// String propertyName,
// TypeConfiguration typeConfiguration) {
//
// final BasicJavaType<T> explicitJavaType = explicitJavaTypeAccess != null
// ? explicitJavaTypeAccess.apply( typeConfiguration )
// : null;
// final JdbcType explicitJdbcType = explicitSqlTypeAccess
// != null ? explicitSqlTypeAccess.apply( typeConfiguration )
// : null;
// return InferredBasicValueResolver.from(
// explicitJavaType,
// explicitJdbcType,
// resolvedJavaType,
// reflectedJtdResolver,
// stdIndicators,
// table,
// selectable,
// ownerName,
// propertyName,
// typeConfiguration
// );
// }
public static <T> BasicType<T> resolveSqlTypeIndicators(
JdbcTypeIndicators stdIndicators,
@ -345,6 +383,7 @@ public class InferredBasicValueResolver {
enumJavaType,
explicitJavaType,
explicitJdbcType,
stdIndicators,
typeConfiguration
);
}
@ -354,12 +393,13 @@ public class InferredBasicValueResolver {
}
}
private static <E extends Enum<E>> InferredBasicValueResolution<E, Integer> ordinalEnumValueResolution(
private static <E extends Enum<E>> InferredBasicValueResolution<E, Number> ordinalEnumValueResolution(
EnumJavaType<E> enumJavaType,
BasicJavaType<?> explicitJavaType,
JdbcType explicitJdbcType,
JdbcTypeIndicators stdIndicators,
TypeConfiguration typeConfiguration) {
final JavaType<Integer> relationalJtd;
final JavaType<Number> relationalJtd;
if ( explicitJavaType != null ) {
if ( ! Integer.class.isAssignableFrom( explicitJavaType.getJavaTypeClass() ) ) {
throw new MappingException(
@ -369,7 +409,7 @@ public class InferredBasicValueResolver {
);
}
//noinspection unchecked
relationalJtd = (BasicJavaType<Integer>) explicitJavaType;
relationalJtd = (BasicJavaType<Number>) explicitJavaType;
}
else {
relationalJtd = typeConfiguration.getJavaTypeRegistry().getDescriptor( Integer.class );
@ -377,7 +417,7 @@ public class InferredBasicValueResolver {
final JdbcType jdbcType = explicitJdbcType != null
? explicitJdbcType
: typeConfiguration.getJdbcTypeRegistry().getDescriptor( SqlTypes.TINYINT );
: typeConfiguration.getJdbcTypeRegistry().getDescriptor( stdIndicators.getPreferredSqlTypeCodeForEnum() );
final OrdinalEnumValueConverter<E> valueConverter = new OrdinalEnumValueConverter<>(
enumJavaType,
@ -386,7 +426,7 @@ public class InferredBasicValueResolver {
);
return new InferredBasicValueResolution<>(
typeConfiguration.getBasicTypeRegistry().resolve(relationalJtd, jdbcType),
typeConfiguration.getBasicTypeRegistry().resolve( relationalJtd, jdbcType ),
enumJavaType,
relationalJtd,
jdbcType,

View File

@ -58,6 +58,9 @@ public class UserTypeResolution implements BasicValue.Resolution {
@Override
public BasicValueConverter getValueConverter() {
// Even though we could expose the value converter of the user type here,
// we can not do it, as the conversion is done behind the scenes in the binder/extractor,
// whereas the converter returned here would, AFAIU, be used to construct a converted attribute mapping
return null;
}

View File

@ -13,6 +13,7 @@ import java.sql.SQLException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.ConvertedBasicType;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
@ -22,7 +23,7 @@ import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
/**
* @author Steve Ebersole
*/
public class ValueConverterTypeAdapter<J> extends AbstractSingleColumnStandardBasicType<J> {
public class ValueConverterTypeAdapter<J> extends AbstractSingleColumnStandardBasicType<J> implements ConvertedBasicType<J> {
private final String description;
private final BasicValueConverter<J, Object> converter;
@ -75,6 +76,11 @@ public class ValueConverterTypeAdapter<J> extends AbstractSingleColumnStandardBa
return ( (JavaType<Object>) converter.getDomainJavaType() ).areEqual( one, another );
}
@Override
public BasicValueConverter getValueConverter() {
return converter;
}
@Override
public String toString() {
return description;

View File

@ -134,7 +134,7 @@ public class VersionResolution<E> implements BasicValue.Resolution<E> {
@Override
public BasicValueConverter<E,E> getValueConverter() {
return null;
return legacyType.getValueConverter();
}
@Override

View File

@ -401,6 +401,15 @@ public class MetadataBuildingProcess {
}
jdbcTypeRegistry.addDescriptorIfAbsent( JsonJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptorIfAbsent( XmlAsStringJdbcType.INSTANCE );
final int preferredSqlTypeCodeForArray = ConfigurationHelper.getPreferredSqlTypeCodeForArray( bootstrapContext.getServiceRegistry() );
if ( preferredSqlTypeCodeForArray != SqlTypes.ARRAY ) {
jdbcTypeRegistry.addDescriptor( SqlTypes.ARRAY, jdbcTypeRegistry.getDescriptor( preferredSqlTypeCodeForArray ) );
}
else if ( jdbcTypeRegistry.findDescriptor( SqlTypes.ARRAY ) == null ) {
// Fallback to VARBINARY
jdbcTypeRegistry.addDescriptor( SqlTypes.ARRAY, jdbcTypeRegistry.getDescriptor( SqlTypes.VARBINARY ) );
}
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INET, SqlTypes.VARBINARY );
final int preferredSqlTypeCodeForDuration = ConfigurationHelper.getPreferredSqlTypeCodeForDuration( bootstrapContext.getServiceRegistry() );
if ( preferredSqlTypeCodeForDuration != SqlTypes.INTERVAL_SECOND ) {

View File

@ -12,6 +12,7 @@ import java.util.function.Supplier;
import org.hibernate.CustomEntityDirtinessStrategy;
import org.hibernate.EntityNameResolver;
import org.hibernate.Incubating;
import org.hibernate.Interceptor;
import org.hibernate.SessionFactoryObserver;
import org.hibernate.TimeZoneStorageStrategy;
@ -452,6 +453,11 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
return delegate.getPreferredSqlTypeCodeForInstant();
}
@Override
public int getPreferredSqlTypeCodeForArray() {
return delegate.getPreferredSqlTypeCodeForArray();
}
@Override
public TimeZoneStorageStrategy getDefaultTimeZoneStorageStrategy() {
return delegate.getDefaultTimeZoneStorageStrategy();

View File

@ -72,6 +72,11 @@ public interface MetadataBuildingContext {
return ConfigurationHelper.getPreferredSqlTypeCodeForInstant( getBootstrapContext().getServiceRegistry() );
}
@Incubating
default int getPreferredSqlTypeCodeForArray() {
return ConfigurationHelper.getPreferredSqlTypeCodeForArray( getBootstrapContext().getServiceRegistry() );
}
TypeDefinitionRegistry getTypeDefinitionRegistry();
/**

View File

@ -311,6 +311,9 @@ public interface SessionFactoryOptions extends QueryEngineOptions {
@Incubating
int getPreferredSqlTypeCodeForInstant();
@Incubating
int getPreferredSqlTypeCodeForArray();
TimeZoneStorageStrategy getDefaultTimeZoneStorageStrategy();
FormatMapper getJsonFormatMapper();

View File

@ -2556,6 +2556,23 @@ public interface AvailableSettings {
@Incubating
String PREFERRED_INSTANT_JDBC_TYPE = "hibernate.type.preferred_instant_jdbc_type";
/**
* Specifies the preferred JDBC type for storing basic array and collection values. When no
* type is explicitly specified, a sensible
* {@link org.hibernate.dialect.Dialect#getPreferredSqlTypeCodeForArray()
* dialect-specific default type code} is used.
*
* Can be overridden locally using {@link org.hibernate.annotations.JdbcType},
* {@link org.hibernate.annotations.JdbcTypeCode} and friends
*
* Can also specify the name of the {@link org.hibernate.type.SqlTypes} constant field. E.g.
* {@code hibernate.type.preferred_array_jdbc_type=VARBINARY}
*
* @since 6.1
*/
@Incubating
String PREFERRED_ARRAY_JDBC_TYPE = "hibernate.type.preferred_array_jdbc_type";
/**
* Specifies a {@link org.hibernate.type.FormatMapper} used for JSON serialization
* and deserialization, either:

View File

@ -231,6 +231,11 @@ public class BasicValueBinder implements JdbcTypeIndicators {
return buildingContext.getPreferredSqlTypeCodeForInstant();
}
@Override
public int getPreferredSqlTypeCodeForArray() {
return buildingContext.getPreferredSqlTypeCodeForArray();
}
@Override
public boolean isNationalized() {
return isNationalized;
@ -719,10 +724,10 @@ public class BasicValueBinder implements JdbcTypeIndicators {
this.temporalPrecision = null;
}
if ( javaTypeClass.isEnum() ) {
final Enumerated enumeratedAnn = attributeDescriptor.getAnnotation( Enumerated.class );
if ( enumeratedAnn != null ) {
this.enumType = enumeratedAnn.value();
if ( javaTypeClass.isEnum() || javaTypeClass.isArray() && javaTypeClass.getComponentType().isEnum() ) {
if ( this.enumType == null ) {
throw new IllegalStateException(
"jakarta.persistence.EnumType was null on @jakarta.persistence.Enumerated " +
@ -731,9 +736,7 @@ public class BasicValueBinder implements JdbcTypeIndicators {
);
}
}
}
else {
if ( attributeDescriptor.isAnnotationPresent( Enumerated.class ) ) {
throw new AnnotationException(
String.format(
"Attribute [%s.%s] was annotated as enumerated, but its java type is not an enum [%s]",
@ -743,6 +746,8 @@ public class BasicValueBinder implements JdbcTypeIndicators {
)
);
}
}
else {
this.enumType = null;
}

View File

@ -43,7 +43,9 @@ public class StandardSortedMapSemantics<K,V> extends AbstractMapSemantics<Sorted
public TreeMap<K,V> instantiateRaw(
int anticipatedSize,
CollectionPersister collectionDescriptor) {
return new TreeMap<K,V>( (Comparator) collectionDescriptor.getSortingComparator() );
return new TreeMap<K,V>(
collectionDescriptor == null ? null : (Comparator) collectionDescriptor.getSortingComparator()
);
}
@Override

View File

@ -44,7 +44,9 @@ public class StandardSortedSetSemantics<E> extends AbstractSetSemantics<SortedSe
public SortedSet<E> instantiateRaw(
int anticipatedSize,
CollectionPersister collectionDescriptor) {
return new TreeSet<E>( (Comparator) collectionDescriptor.getSortingComparator() );
return new TreeSet<E>(
collectionDescriptor == null ? null : (Comparator) collectionDescriptor.getSortingComparator()
);
}
@Override

View File

@ -309,6 +309,11 @@ public class CockroachDialect extends Dialect {
return " cascade";
}
@Override
public boolean supportsDistinctFromPredicate() {
return true;
}
@Override
public boolean supportsIfExistsBeforeTableName() {
return true;
@ -427,6 +432,11 @@ public class CockroachDialect extends Dialect {
return 63;
}
@Override
public boolean supportsStandardArrays() {
return true;
}
@Override
public void appendDateTimeLiteral(
SqlAppender appender,

View File

@ -191,6 +191,11 @@ public class DB2Dialect extends Dialect {
return 31;
}
@Override
public boolean supportsDistinctFromPredicate() {
return getDB2Version().isSameOrAfter( 11, 1 );
}
@Override
public void initializeFunctionRegistry(QueryEngine queryEngine) {
super.initializeFunctionRegistry( queryEngine );

View File

@ -73,6 +73,11 @@ public class DB2iDialect extends DB2Dialect {
: super.createUniqueDelegate();
}
@Override
public boolean supportsDistinctFromPredicate() {
return true;
}
/**
* No support for sequences.
*/

View File

@ -78,6 +78,12 @@ public class DB2zDialect extends DB2Dialect {
return DB2_LUW_VERSION9;
}
@Override
public boolean supportsDistinctFromPredicate() {
// Supported at least since DB2 z/OS 9.0
return true;
}
@Override
public TimeZoneSupport getTimeZoneSupport() {
return getVersion().isAfter(10) ? TimeZoneSupport.NATIVE : TimeZoneSupport.NONE;

View File

@ -146,11 +146,14 @@ import org.hibernate.type.BasicType;
import org.hibernate.type.BasicTypeRegistry;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.ClobJdbcType;
import org.hibernate.type.descriptor.jdbc.InstantAsTimestampJdbcType;
import org.hibernate.type.descriptor.jdbc.InstantAsTimestampWithTimeZoneJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.LongNVarcharJdbcType;
import org.hibernate.type.descriptor.jdbc.NCharJdbcType;
@ -472,13 +475,57 @@ public abstract class Dialect implements ConversionContext {
return version;
}
/**
* Resolves the {@link SqlTypes} type code for the given column type name as reported by the database,
* or <code>null</code> if it can't be resolved.
*/
protected Integer resolveSqlTypeCode(String columnTypeName, TypeConfiguration typeConfiguration) {
final int parenthesisIndex = columnTypeName.lastIndexOf( '(' );
final String baseTypeName;
if ( parenthesisIndex == -1 ) {
baseTypeName = columnTypeName;
}
else {
baseTypeName = columnTypeName.substring( 0, parenthesisIndex ).trim();
}
return resolveSqlTypeCode( columnTypeName, baseTypeName, typeConfiguration );
}
/**
* Resolves the {@link SqlTypes} type code for the given column type name as reported by the database
* and the base type name (i.e. without precision/length and scale), or <code>null</code> if it can't be resolved.
*/
protected Integer resolveSqlTypeCode(String typeName, String baseTypeName, TypeConfiguration typeConfiguration) {
return typeConfiguration.getDdlTypeRegistry().getSqlTypeCode( baseTypeName );
}
public JdbcType resolveSqlTypeDescriptor(
String columnTypeName,
int jdbcTypeCode,
int precision,
int scale,
JdbcTypeRegistry jdbcTypeRegistry) {
return jdbcTypeRegistry.getDescriptor( jdbcTypeCode );
final JdbcType jdbcType = jdbcTypeRegistry.getDescriptor( jdbcTypeCode );
if ( jdbcTypeCode == Types.ARRAY && jdbcType instanceof ArrayJdbcType ) {
// Special handling for array types, because we need the proper element/component type
// To determine the element JdbcType, we pass the database reported type to #resolveSqlTypeCode
final int arraySuffixIndex = columnTypeName.toLowerCase( Locale.ROOT ).indexOf( " array" );
if ( arraySuffixIndex != -1 ) {
final String componentTypeName = columnTypeName.substring( 0, arraySuffixIndex );
final Integer sqlTypeCode = resolveSqlTypeCode( componentTypeName, jdbcTypeRegistry.getTypeConfiguration() );
if ( sqlTypeCode != null ) {
return ( (ArrayJdbcType) jdbcType ).resolveType(
jdbcTypeRegistry.getTypeConfiguration(),
jdbcTypeRegistry.getTypeConfiguration().getServiceRegistry()
.getService( JdbcServices.class )
.getDialect(),
jdbcTypeRegistry.getDescriptor( sqlTypeCode ),
null
);
}
}
}
return jdbcType;
}
public int resolveSqlTypeLength(
@ -1170,8 +1217,8 @@ public abstract class Dialect implements ConversionContext {
* {@link Types#LONGVARBINARY LONGVARBINARY} as the same type, since
* Hibernate doesn't really differentiate these types.
*
* @param typeCode1 the first JDBC type code
* @param typeCode2 the second JDBC type code
* @param column1 the first column type info
* @param column2 the second column type info
*
* @return {@code true} if the two type codes are equivalent
*/
@ -1255,6 +1302,10 @@ public abstract class Dialect implements ConversionContext {
else {
typeContributions.contributeJdbcType( InstantAsTimestampJdbcType.INSTANCE );
}
if ( supportsStandardArrays() ) {
typeContributions.contributeJdbcType( ArrayJdbcType.INSTANCE );
}
}
/**
@ -3239,6 +3290,84 @@ public abstract class Dialect implements ConversionContext {
return NationalizationSupport.EXPLICIT;
}
/**
* Database has native support for SQL standard arrays which can be referred to through base type name.
* Oracle for example doesn't support this, but instead has support for named arrays.
*
* @return boolean
* @since 6.1
*/
public boolean supportsStandardArrays() {
return false;
}
/**
* The SQL type name for the array of the given type name.
*
* @since 6.1
*/
public String getArrayTypeName(String elementTypeName) {
if ( supportsStandardArrays() ) {
return elementTypeName + " array";
}
return null;
}
public void appendArrayLiteral(
SqlAppender appender,
Object[] literal,
JdbcLiteralFormatter<Object> elementFormatter,
WrapperOptions wrapperOptions) {
if ( !supportsStandardArrays() ) {
throw new UnsupportedOperationException( getClass().getName() + " does not support array literals" );
}
appender.appendSql( "ARRAY[" );
if ( literal.length != 0 ) {
if ( literal[0] == null ) {
appender.appendSql( "null" );
}
else {
elementFormatter.appendJdbcLiteral( appender, literal[0], this, wrapperOptions );
}
for ( int i = 1; i < literal.length; i++ ) {
appender.appendSql( ',' );
if ( literal[i] == null ) {
appender.appendSql( "null" );
}
else {
elementFormatter.appendJdbcLiteral( appender, literal[i], this, wrapperOptions );
}
}
}
appender.appendSql( ']' );
}
/**
* Is this SQL dialect known to support some kind of distinct from predicate.
* <p/>
* Basically, does it support syntax like
* "... where FIRST_NAME IS DISTINCT FROM LAST_NAME"
*
* @return True if this SQL dialect is known to support some kind of distinct from predicate; false otherwise
* @since 6.1
*/
public boolean supportsDistinctFromPredicate() {
return false;
}
/**
* The JDBC {@link SqlTypes type code} to use for mapping
* properties of basic Java array or {@code Collection} types.
* <p>
* Usually {@link SqlTypes#ARRAY} or {@link SqlTypes#VARBINARY}.
*
* @return one of the type codes defined by {@link SqlTypes}.
* @since 6.1
*/
public int getPreferredSqlTypeCodeForArray() {
return supportsStandardArrays() ? ARRAY : VARBINARY;
}
/**
* The JDBC {@link Types type code} to use for mapping
* properties of Java type {@code boolean}.
@ -3644,6 +3773,7 @@ public abstract class Dialect implements ConversionContext {
case SqlTypes.DOUBLE:
case SqlTypes.REAL:
// this is almost always the thing we use:
length = null;
size.setPrecision( javaType.getDefaultSqlPrecision( Dialect.this, jdbcType ) );
if ( scale != null && scale != 0 ) {
throw new IllegalArgumentException("scale has no meaning for floating point numbers");
@ -3658,6 +3788,7 @@ public abstract class Dialect implements ConversionContext {
case SqlTypes.TIMESTAMP:
case SqlTypes.TIMESTAMP_WITH_TIMEZONE:
case SqlTypes.TIMESTAMP_UTC:
length = null;
size.setPrecision( javaType.getDefaultSqlPrecision( Dialect.this, jdbcType ) );
if ( scale != null && scale != 0 ) {
throw new IllegalArgumentException("scale has no meaning for timestamps");

View File

@ -8,6 +8,7 @@ package org.hibernate.dialect;
import java.sql.CallableStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
import org.hibernate.PessimisticLockException;
@ -64,6 +65,7 @@ import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType;
@ -168,6 +170,11 @@ public class H2Dialect extends Dialect {
return true;
}
@Override
public boolean supportsStandardArrays() {
return getVersion().isSameOrAfter( 2 );
}
@Override
protected String columnType(int sqlTypeCode) {
switch ( sqlTypeCode ) {
@ -326,6 +333,16 @@ public class H2Dialect extends Dialect {
}
}
@Override
protected Integer resolveSqlTypeCode(String columnTypeName, TypeConfiguration typeConfiguration) {
switch ( columnTypeName ) {
case "FLOAT(24)":
// Use REAL instead of FLOAT to get Float as recommended Java type
return Types.REAL;
}
return super.resolveSqlTypeCode( columnTypeName, typeConfiguration );
}
@Override
public JdbcType resolveSqlTypeDescriptor(
String columnTypeName,
@ -340,6 +357,15 @@ public class H2Dialect extends Dialect {
return super.resolveSqlTypeDescriptor( columnTypeName, jdbcTypeCode, precision, scale, jdbcTypeRegistry );
}
@Override
protected Integer resolveSqlTypeCode(String typeName, String baseTypeName, TypeConfiguration typeConfiguration) {
switch ( baseTypeName ) {
case "CHARACTER VARYING":
return VARCHAR;
}
return super.resolveSqlTypeCode( typeName, baseTypeName, typeConfiguration );
}
@Override
public int getMaxVarcharLength() {
return 1_048_576;
@ -420,6 +446,11 @@ public class H2Dialect extends Dialect {
return limitHandler;
}
@Override
public boolean supportsDistinctFromPredicate() {
return true;
}
@Override
public boolean supportsIfExistsAfterTableName() {
return !supportsIfExistsBeforeTableName();

View File

@ -76,6 +76,7 @@ import jakarta.persistence.TemporalType;
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
import static org.hibernate.type.SqlTypes.BLOB;
import static org.hibernate.type.SqlTypes.CLOB;
import static org.hibernate.type.SqlTypes.DOUBLE;
import static org.hibernate.type.SqlTypes.NCLOB;
import static org.hibernate.type.SqlTypes.NUMERIC;
@ -145,6 +146,15 @@ public class HSQLDialect extends Dialect {
return super.columnType( sqlTypeCode );
}
@Override
protected Integer resolveSqlTypeCode(String typeName, String baseTypeName, TypeConfiguration typeConfiguration) {
switch ( baseTypeName ) {
case "DOUBLE":
return DOUBLE;
}
return super.resolveSqlTypeCode( typeName, baseTypeName, typeConfiguration );
}
@Override
public int getDefaultStatementBatchSize() {
return 15;
@ -364,6 +374,11 @@ public class HSQLDialect extends Dialect {
return pattern.toString();
}
@Override
public boolean supportsDistinctFromPredicate() {
return true;
}
@Override
public boolean supportsLockTimeouts() {
return false;
@ -420,6 +435,11 @@ public class HSQLDialect extends Dialect {
return SequenceInformationExtractorHSQLDBDatabaseImpl.INSTANCE;
}
@Override
public boolean supportsStandardArrays() {
return true;
}
@Override
public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
return getVersion().isBefore( 2 ) ? EXTRACTOR_18 : EXTRACTOR_20;

View File

@ -28,6 +28,7 @@ import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
/**
* A SQL AST translator for HSQL.
@ -215,10 +216,18 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
switch ( operator ) {
case DISTINCT_FROM:
case NOT_DISTINCT_FROM:
if ( lhs.getExpressionType().getJdbcMappings().get( 0 ).getJdbcType() instanceof ArrayJdbcType ) {
// HSQL implements distinct from semantics for arrays
lhs.accept( this );
appendSql( operator == ComparisonOperator.DISTINCT_FROM ? "<>" : "=" );
rhs.accept( this );
}
else {
// HSQL does not like parameters in the distinct from predicate
render( lhs, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER );
appendSql( operator.sqlText() );
render( rhs, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER );
}
break;
default:
renderComparisonStandard( lhs, operator, rhs );

View File

@ -0,0 +1,190 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.dialect;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.BasicPluralJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.BasicBinder;
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.ObjectJdbcType;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Descriptor for {@link Types#ARRAY ARRAY} handling.
*
* @author Christian Beikov
* @author Jordan Gigov
*/
public class OracleArrayJdbcType extends ArrayJdbcType {
public static final OracleArrayJdbcType INSTANCE = new OracleArrayJdbcType( null, ObjectJdbcType.INSTANCE );
private static final ClassValue<Method> NAME_BINDER = new ClassValue<Method>() {
@Override
protected Method computeValue(Class<?> type) {
try {
return type.getMethod( "setArray", String.class, java.sql.Array.class );
}
catch ( Exception ex ) {
// add logging? Did we get NoSuchMethodException or SecurityException?
// Doesn't matter which. We can't use it.
}
return null;
}
};
private static final Class<?> ORACLE_CONNECTION_CLASS;
private static final Method CREATE_ARRAY_METHOD;
static {
try {
ORACLE_CONNECTION_CLASS = Class.forName( "oracle.jdbc.OracleConnection" );
CREATE_ARRAY_METHOD = ORACLE_CONNECTION_CLASS.getMethod( "createOracleArray", String.class, Object.class );
}
catch (Exception e) {
throw new RuntimeException( "Couldn't initialize OracleArrayJdbcType", e );
}
}
private final String typeName;
public OracleArrayJdbcType(String typeName, JdbcType elementJdbcType) {
super( elementJdbcType );
this.typeName = typeName;
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaTypeDescriptor) {
// No array literal support
return null;
}
@Override
public JdbcType resolveType(
TypeConfiguration typeConfiguration,
Dialect dialect,
JdbcType elementType,
ColumnTypeInformation columnTypeInformation) {
String typeName = columnTypeInformation.getTypeName();
if ( typeName == null || typeName.isBlank() ) {
typeName = dialect.getArrayTypeName(
typeConfiguration.getDdlTypeRegistry().getTypeName(
elementType.getDefaultSqlTypeCode(),
dialect
)
);
}
if ( typeName == null ) {
// Fallback to XML type for the representation of arrays as the native JSON type was only introduced in 21
return typeConfiguration.getJdbcTypeRegistry().getDescriptor( SqlTypes.SQLXML );
}
return new OracleArrayJdbcType( typeName, elementType );
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaTypeDescriptor) {
//noinspection unchecked
final BasicPluralJavaType<X> containerJavaType = (BasicPluralJavaType<X>) javaTypeDescriptor;
return new BasicBinder<X>( javaTypeDescriptor, this ) {
@Override
protected void doBindNull(PreparedStatement st, int index, WrapperOptions options) throws SQLException {
st.setNull( index, Types.ARRAY, typeName );
}
@Override
protected void doBindNull(CallableStatement st, String name, WrapperOptions options) throws SQLException {
st.setNull( name, Types.ARRAY, typeName );
}
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
final java.sql.Array arr = getArray( value, containerJavaType, options );
st.setArray( index, arr );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
final java.sql.Array arr = getArray( value, containerJavaType, options );
final Method nameBinder = NAME_BINDER.get( st.getClass() );
if ( nameBinder == null ) {
try {
st.setObject( name, arr, Types.ARRAY );
return;
}
catch (SQLException ex) {
throw new HibernateException( "JDBC driver does not support named parameters for setArray. Use positional.", ex );
}
}
// Not that it's supposed to have setArray(String,Array) by standard.
// There are numerous missing methods that only have versions for positional parameter,
// but not named ones.
try {
nameBinder.invoke( st, name, arr );
}
catch ( Throwable t ) {
throw new HibernateException( t );
}
}
private java.sql.Array getArray(
X value,
BasicPluralJavaType<X> containerJavaType,
WrapperOptions options) throws SQLException {
//noinspection unchecked
final Class<Object[]> arrayClass = (Class<Object[]>) Array.newInstance(
getElementJdbcType().getPreferredJavaTypeClass( options ),
0
).getClass();
final Object[] objects = javaTypeDescriptor.unwrap( value, arrayClass, options );
final SharedSessionContractImplementor session = options.getSession();
final Object oracleConnection = session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection()
.unwrap( ORACLE_CONNECTION_CLASS );
try {
return (java.sql.Array) CREATE_ARRAY_METHOD.invoke( oracleConnection, typeName, objects );
}
catch (Exception e) {
throw new HibernateException( "Couldn't create a java.sql.Array", e );
}
}
};
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
OracleArrayJdbcType that = (OracleArrayJdbcType) o;
return typeName.equals( that.typeName );
}
@Override
public int hashCode() {
return typeName.hashCode();
}
}

View File

@ -15,6 +15,7 @@ import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.hibernate.HibernateException;
import org.hibernate.LockOptions;
import org.hibernate.QueryTimeoutException;
import org.hibernate.boot.model.TypeContributions;
@ -683,6 +684,20 @@ public class OracleDialect extends Dialect {
return false;
}
@Override
public String getArrayTypeName(String elementTypeName) {
// Return null to signal that there is no array type since Oracle only has named array types
// TODO: discuss if it makes sense to parse a config parameter to a map which we can query here
// e.g. `hibernate.oracle.array_types=numeric(10,0)=intarray,...`
return null;
}
@Override
public int getPreferredSqlTypeCodeForArray() {
// Prefer to resolve to the OracleArrayJdbcType, since that will fall back to XML later if needed
return ARRAY;
}
@Override
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.contributeTypes( typeContributions, serviceRegistry );
@ -705,6 +720,7 @@ public class OracleDialect extends Dialect {
typeContributions.contributeJdbcType( descriptor );
}
typeContributions.contributeJdbcType( OracleArrayJdbcType.INSTANCE );
// Oracle requires a custom binder for binding untyped nulls with the NULL type
typeContributions.contributeJdbcType( NullJdbcType.INSTANCE );
typeContributions.contributeJdbcType( ObjectNullAsNullTypeJdbcType.INSTANCE );

View File

@ -10,6 +10,7 @@ import java.util.List;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.FetchClauseType;
@ -40,6 +41,7 @@ import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.type.SqlTypes;
/**
* A SQL AST translator for Oracle.
@ -352,7 +354,56 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
if ( lhs.getExpressionType() == null ) {
renderComparisonEmulateDecode( lhs, operator, rhs );
return;
}
final JdbcMapping lhsMapping = lhs.getExpressionType().getJdbcMappings().get( 0 );
switch ( lhsMapping.getJdbcType().getJdbcTypeCode() ) {
case SqlTypes.SQLXML:
// In Oracle, XMLTYPE is not "comparable", so we have to use the xmldiff function for this purpose
switch ( operator ) {
case EQUAL:
case NOT_DISTINCT_FROM:
appendSql( "0=" );
break;
case NOT_EQUAL:
case DISTINCT_FROM:
appendSql( "1=" );
break;
default:
renderComparisonEmulateDecode( lhs, operator, rhs );
return;
}
appendSql( "existsnode(xmldiff(" );
lhs.accept( this );
appendSql( ',' );
rhs.accept( this );
appendSql( "),'/*[local-name()=''xdiff'']/*')" );
break;
case SqlTypes.BLOB:
// In Oracle, BLOB types are not "comparable", so we have to use the dbms_lob.compare function for this purpose
switch ( operator ) {
case EQUAL:
appendSql( "0=" );
break;
case NOT_EQUAL:
appendSql( "-1=" );
break;
default:
renderComparisonEmulateDecode( lhs, operator, rhs );
return;
}
appendSql( "dbms_lob.compare(" );
lhs.accept( this );
appendSql( ',' );
rhs.accept( this );
appendSql( ')' );
break;
default:
renderComparisonEmulateDecode( lhs, operator, rhs );
break;
}
}
@Override

View File

@ -16,6 +16,7 @@ import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import jakarta.persistence.TemporalType;
@ -38,6 +39,7 @@ import org.hibernate.engine.jdbc.env.spi.IdentifierCaseStrategy;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.exception.LockAcquisitionException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
@ -66,6 +68,7 @@ import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.type.JavaObjectType;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
import org.hibernate.type.descriptor.jdbc.ClobJdbcType;
import org.hibernate.type.descriptor.jdbc.InstantAsTimestampWithTimeZoneJdbcType;
@ -74,6 +77,7 @@ import org.hibernate.type.descriptor.jdbc.ObjectNullAsBinaryTypeJdbcType;
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
import org.hibernate.type.descriptor.jdbc.XmlJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType;
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
import org.hibernate.type.descriptor.sql.internal.Scale6IntervalSecondDdlType;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
@ -85,11 +89,13 @@ import static org.hibernate.query.sqm.TemporalUnit.EPOCH;
import static org.hibernate.query.sqm.TemporalUnit.MONTH;
import static org.hibernate.query.sqm.TemporalUnit.QUARTER;
import static org.hibernate.query.sqm.TemporalUnit.YEAR;
import static org.hibernate.type.SqlTypes.ARRAY;
import static org.hibernate.type.SqlTypes.BINARY;
import static org.hibernate.type.SqlTypes.BLOB;
import static org.hibernate.type.SqlTypes.CHAR;
import static org.hibernate.type.SqlTypes.CLOB;
import static org.hibernate.type.SqlTypes.GEOGRAPHY;
import static org.hibernate.type.SqlTypes.FLOAT;
import static org.hibernate.type.SqlTypes.GEOMETRY;
import static org.hibernate.type.SqlTypes.INET;
import static org.hibernate.type.SqlTypes.JSON;
@ -205,6 +211,16 @@ public class PostgreSQLDialect extends Dialect {
super.registerColumnTypes( typeContributions, serviceRegistry );
final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry();
// Register this type to be able to support Float[]
// The issue is that the JDBC driver can't handle createArrayOf( "float(24)", ... )
// It requires the use of "real" or "float4"
// Alternatively we could introduce a new API in Dialect for creating such base names
ddlTypeRegistry.addDescriptor(
CapacityDependentDdlType.builder( FLOAT, columnType( FLOAT ), castType( FLOAT ), this )
.withTypeCapacity( 24, "float4" )
.build()
);
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( INET, "inet", this ) );
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "geometry", this ) );
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOGRAPHY, "geography", this ) );
@ -249,7 +265,8 @@ public class PostgreSQLDialect extends Dialect {
int precision,
int scale,
JdbcTypeRegistry jdbcTypeRegistry) {
if ( jdbcTypeCode == OTHER ) {
switch ( jdbcTypeCode ) {
case OTHER:
switch ( columnTypeName ) {
case "uuid":
jdbcTypeCode = UUID;
@ -271,10 +288,49 @@ public class PostgreSQLDialect extends Dialect {
jdbcTypeCode = GEOGRAPHY;
break;
}
break;
case ARRAY:
final JdbcType jdbcType = jdbcTypeRegistry.getDescriptor( jdbcTypeCode );
// PostgreSQL names array types by prepending an underscore to the base name
if ( jdbcType instanceof ArrayJdbcType && columnTypeName.charAt( 0 ) == '_' ) {
final String componentTypeName = columnTypeName.substring( 1 );
final Integer sqlTypeCode = resolveSqlTypeCode( componentTypeName, jdbcTypeRegistry.getTypeConfiguration() );
if ( sqlTypeCode != null ) {
return ( (ArrayJdbcType) jdbcType ).resolveType(
jdbcTypeRegistry.getTypeConfiguration(),
jdbcTypeRegistry.getTypeConfiguration().getServiceRegistry()
.getService( JdbcServices.class )
.getDialect(),
jdbcTypeRegistry.getDescriptor( sqlTypeCode ),
null
);
}
}
return jdbcType;
}
return jdbcTypeRegistry.getDescriptor( jdbcTypeCode );
}
@Override
protected Integer resolveSqlTypeCode(String columnTypeName, TypeConfiguration typeConfiguration) {
switch ( columnTypeName ) {
case "bool":
return Types.BOOLEAN;
case "float4":
// Use REAL instead of FLOAT to get Float as recommended Java type
return Types.REAL;
case "float8":
return Types.DOUBLE;
case "int2":
return Types.SMALLINT;
case "int4":
return Types.INTEGER;
case "int8":
return Types.BIGINT;
}
return super.resolveSqlTypeCode( columnTypeName, typeConfiguration );
}
@Override
public String currentTime() {
return "localtime";
@ -507,6 +563,11 @@ public class PostgreSQLDialect extends Dialect {
return "select current_schema()";
}
@Override
public boolean supportsDistinctFromPredicate() {
return true;
}
@Override
public boolean supportsIfExistsBeforeTableName() {
return getVersion().isSameOrAfter( 8, 2 );
@ -835,6 +896,11 @@ public class PostgreSQLDialect extends Dialect {
return 63;
}
@Override
public boolean supportsStandardArrays() {
return true;
}
@Override
public boolean supportsJdbcConnectionLobCreation(DatabaseMetaData databaseMetaData) {
return false;

View File

@ -22,6 +22,7 @@ import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.BasicBinder;
import org.hibernate.type.descriptor.jdbc.BasicExtractor;
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
import org.hibernate.type.descriptor.jdbc.JdbcType;
/**
@ -81,6 +82,12 @@ public abstract class PostgreSQLPGObjectJdbcType implements JdbcType {
return javaType.unwrap( value, String.class, options );
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
// No literal support for now
return null;
}
@Override
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -155,6 +155,16 @@ public class SpannerDialect extends Dialect {
return 10_485_760;
}
@Override
public boolean supportsStandardArrays() {
return true;
}
@Override
public String getArrayTypeName(String elementTypeName) {
return "ARRAY<" + elementTypeName + ">";
}
@Override
public void initializeFunctionRegistry(QueryEngine queryEngine) {
super.initializeFunctionRegistry( queryEngine );

View File

@ -208,6 +208,11 @@ public class SybaseASEDialect extends SybaseDialect {
return false;
}
@Override
public boolean supportsDistinctFromPredicate() {
return getVersion().isSameOrAfter( 16, 3 );
}
@Override
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.contributeTypes( typeContributions, serviceRegistry );

View File

@ -236,11 +236,6 @@ public class SybaseASESqlAstTranslator<T extends JdbcOperation> extends Abstract
}
}
@Override
protected boolean supportsDistinctFromPredicate() {
return getDialect().getVersion().isSameOrAfter( 16, 3 );
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
// I think intersect is only supported in 16.0 SP3

View File

@ -102,12 +102,24 @@ public final class TypeNames {
* the default type name otherwise
*/
public String get(int typeCode, Long size, Integer precision, Integer scale) {
final Long compareValue;
if ( size != null ) {
compareValue = size;
}
else if ( precision != null ) {
// In case size is null, but a precision is available, use that to search a type for that capacity
compareValue = precision.longValue();
}
else {
compareValue = null;
}
if ( compareValue != null ) {
final Map<Long, String> map = weighted.get( typeCode );
if ( map != null && map.size() > 0 ) {
final long value = compareValue;
// iterate entries ordered by capacity to find first fit
for ( Map.Entry<Long, String> entry : map.entrySet() ) {
if ( size <= entry.getKey() ) {
if ( value <= entry.getKey() ) {
return replace( entry.getValue(), size, precision, scale );
}
}

View File

@ -576,6 +576,24 @@ public final class ConfigurationHelper {
return SqlTypes.TIMESTAMP_UTC;
}
@Incubating
public static synchronized int getPreferredSqlTypeCodeForArray(StandardServiceRegistry serviceRegistry) {
final Integer typeCode = serviceRegistry.getService( ConfigurationService.class ).getSetting(
AvailableSettings.PREFERRED_ARRAY_JDBC_TYPE,
TypeCodeConverter.INSTANCE
);
if ( typeCode != null ) {
INCUBATION_LOGGER.incubatingSetting( AvailableSettings.PREFERRED_ARRAY_JDBC_TYPE );
return typeCode;
}
// default to the Dialect answer
return serviceRegistry.getService( JdbcServices.class )
.getJdbcEnvironment()
.getDialect()
.getPreferredSqlTypeCodeForArray();
}
private static class TypeCodeConverter implements ConfigurationService.Converter<Integer> {
public static final TypeCodeConverter INSTANCE = new TypeCodeConverter();

View File

@ -478,6 +478,7 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
getColumn(),
ownerName,
propertyName,
getMetadata().getDatabase().getDialect(),
typeConfiguration
);
@ -694,6 +695,11 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
return getBuildingContext().getPreferredSqlTypeCodeForInstant();
}
@Override
public int getPreferredSqlTypeCodeForArray() {
return getBuildingContext().getPreferredSqlTypeCodeForArray();
}
@Override
public TimeZoneStorageStrategy getDefaultTimeZoneStorageStrategy() {
if ( timeZoneStorageType != null ) {

View File

@ -7,10 +7,12 @@
package org.hibernate.mapping;
import java.io.Serializable;
import java.sql.Types;
import java.util.Locale;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.boot.model.TruthValue;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.spi.Mapping;
@ -19,9 +21,13 @@ import org.hibernate.loader.internal.AliasConstantsHelper;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
import org.hibernate.sql.Template;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.BasicPluralType;
import org.hibernate.type.BasicType;
import org.hibernate.type.ComponentType;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.spi.TypeConfiguration;
import static org.hibernate.internal.util.StringHelper.safeInterning;
@ -31,7 +37,7 @@ import static org.hibernate.internal.util.StringHelper.safeInterning;
*
* @author Gavin King
*/
public class Column implements Selectable, Serializable, Cloneable {
public class Column implements Selectable, Serializable, Cloneable, ColumnTypeInformation {
private Long length;
private Integer precision;
@ -224,6 +230,44 @@ public class Column implements Selectable, Serializable, Cloneable {
}
}
private String getSqlTypeName(TypeConfiguration typeConfiguration, Dialect dialect, Mapping mapping) {
final Type type = getValue().getType();
if ( type instanceof BasicPluralType<?, ?> && ( (BasicType<?>) type ).getJdbcType() instanceof ArrayJdbcType ) {
final BasicPluralType<?, ?> containerType = (BasicPluralType<?, ?>) type;
final BasicType<?> elementType = containerType.getElementType();
final int sqlTypeCode;
try {
sqlTypeCode = elementType.getJdbcType().getDefaultSqlTypeCode();
}
catch (Exception e) {
throw new MappingException(
"Could not determine type for column " +
name +
" of type " +
type.getClass().getName() +
": " +
e.getClass().getName(),
e
);
}
final String elementTypeName = typeConfiguration.getDdlTypeRegistry().getTypeName(
sqlTypeCode,
dialect.getSizeStrategy().resolveSize(
elementType.getJdbcMapping().getJdbcType(),
elementType.getJavaTypeDescriptor(),
precision,
scale,
length
)
);
return dialect.getArrayTypeName( elementTypeName );
}
else {
return typeConfiguration.getDdlTypeRegistry().getTypeName( getSqlTypeCode( mapping ), getColumnSize( dialect, mapping ) );
}
}
/**
* Returns the underlying columns SqlTypeCode.
* If null, it is because the SqlTypeCode is unknown.
@ -244,7 +288,7 @@ public class Column implements Selectable, Serializable, Cloneable {
public String getSqlType(TypeConfiguration typeConfiguration, Dialect dialect, Mapping mapping) throws HibernateException {
if ( sqlType == null ) {
try {
sqlType = typeConfiguration.getDdlTypeRegistry().getTypeName( getSqlTypeCode( mapping ), getColumnSize( dialect, mapping ) );
sqlType = getSqlTypeName( typeConfiguration, dialect, mapping );
}
catch (HibernateException cause) {
throw new HibernateException(
@ -261,6 +305,34 @@ public class Column implements Selectable, Serializable, Cloneable {
return sqlType;
}
@Override
public String getTypeName() {
return sqlType;
}
@Override
public TruthValue getNullable() {
return nullable ? TruthValue.TRUE : TruthValue.FALSE;
}
@Override
public int getTypeCode() {
return sqlTypeCode == null ? Types.OTHER : sqlTypeCode;
}
@Override
public int getColumnSize() {
if ( length == null ) {
return precision == null ? 0 : precision;
}
return length.intValue();
}
@Override
public int getDecimalDigits() {
return scale == null ? 0 : scale;
}
public Size getColumnSize(Dialect dialect, Mapping mapping) {
if ( columnSize != null ) {
return columnSize;

View File

@ -26,19 +26,19 @@ import org.hibernate.type.descriptor.jdbc.JdbcType;
*
* @author Steve Ebersole
*/
public class OrdinalEnumValueConverter<E extends Enum<E>> implements EnumValueConverter<E,Integer>, Serializable {
public class OrdinalEnumValueConverter<E extends Enum<E>> implements EnumValueConverter<E, Number>, Serializable {
private final EnumJavaType<E> enumJavaType;
private final JdbcType jdbcType;
private final JavaType<Integer> relationalJavaType;
private final JavaType<Number> relationalJavaType;
private transient ValueExtractor<Integer> valueExtractor;
private transient ValueBinder<Integer> valueBinder;
private transient ValueExtractor<Number> valueExtractor;
private transient ValueBinder<Number> valueBinder;
public OrdinalEnumValueConverter(
EnumJavaType<E> enumJavaType,
JdbcType jdbcType,
JavaType<Integer> relationalJavaType) {
JavaType<Number> relationalJavaType) {
this.enumJavaType = enumJavaType;
this.jdbcType = jdbcType;
this.relationalJavaType = relationalJavaType;
@ -48,12 +48,12 @@ public class OrdinalEnumValueConverter<E extends Enum<E>> implements EnumValueCo
}
@Override
public E toDomainValue(Integer relationalForm) {
return enumJavaType.fromOrdinal( relationalForm );
public E toDomainValue(Number relationalForm) {
return enumJavaType.fromOrdinal( relationalForm == null ? null : relationalForm.intValue() );
}
@Override
public Integer toRelationalValue(E domainForm) {
public Number toRelationalValue(E domainForm) {
return enumJavaType.toOrdinal( domainForm );
}
@ -68,7 +68,7 @@ public class OrdinalEnumValueConverter<E extends Enum<E>> implements EnumValueCo
}
@Override
public JavaType<Integer> getRelationalJavaType() {
public JavaType<Number> getRelationalJavaType() {
return relationalJavaType;
}
@ -86,8 +86,7 @@ public class OrdinalEnumValueConverter<E extends Enum<E>> implements EnumValueCo
}
@Override
public void writeValue(
PreparedStatement statement, E value, int position, SharedSessionContractImplementor session)
public void writeValue(PreparedStatement statement, E value, int position, SharedSessionContractImplementor session)
throws SQLException {
valueBinder.bind( statement, toRelationalValue( value ), position, session );
}

View File

@ -77,6 +77,7 @@ import org.hibernate.type.BasicType;
import org.hibernate.type.ComponentType;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.TypeConfiguration;
@ -859,8 +860,8 @@ public class MappingMetamodelImpl implements MappingMetamodelImplementor, Metamo
return managedType;
}
final JavaType<T> javaType = getTypeConfiguration().getJavaTypeRegistry()
.findDescriptor( javaClass );
final JavaTypeRegistry javaTypeRegistry = getTypeConfiguration().getJavaTypeRegistry();
final JavaType<T> javaType = javaTypeRegistry.findDescriptor( javaClass );
if ( javaType != null ) {
final JdbcType recommendedJdbcType = javaType.getRecommendedJdbcType( getTypeConfiguration().getCurrentBaseSqlTypeIndicators() );
if ( recommendedJdbcType != null ) {
@ -871,6 +872,17 @@ public class MappingMetamodelImpl implements MappingMetamodelImplementor, Metamo
}
}
if ( javaClass.isArray() && javaTypeRegistry.findDescriptor( javaClass.getComponentType() ) != null ) {
final JavaType<T> resolvedJavaType = javaTypeRegistry.resolveDescriptor( javaClass );
final JdbcType recommendedJdbcType = resolvedJavaType.getRecommendedJdbcType( getTypeConfiguration().getCurrentBaseSqlTypeIndicators() );
if ( recommendedJdbcType != null ) {
return getTypeConfiguration().getBasicTypeRegistry().resolve(
resolvedJavaType,
recommendedJdbcType
);
}
}
return null;
}
}

View File

@ -103,7 +103,7 @@ public class QueryParameterBindingImpl<T> implements QueryParameterBinding<T>, J
@Override
public void setBindValue(T value, boolean resolveJdbcTypeIfNecessary) {
if ( handleAsMultiValue( value ) ) {
if ( handleAsMultiValue( value, null ) ) {
return;
}
@ -149,7 +149,7 @@ public class QueryParameterBindingImpl<T> implements QueryParameterBinding<T>, J
return sqmExpressible.getExpressibleJavaType().coerce( value, this );
}
private boolean handleAsMultiValue(T value) {
private boolean handleAsMultiValue(T value, BindableType<T> bindableType) {
if ( ! queryParameter.allowsMultiValuedBinding() ) {
return false;
}
@ -158,7 +158,9 @@ public class QueryParameterBindingImpl<T> implements QueryParameterBinding<T>, J
return false;
}
if ( value instanceof Collection && !isRegisteredAsBasicType( value.getClass() ) ) {
if ( value instanceof Collection
&& ( bindableType != null && !bindableType.getBindableJavaType().isInstance( value )
|| ( bindableType == null && !isRegisteredAsBasicType( value.getClass() ) ) ) ) {
//noinspection unchecked
setBindValues( (Collection<T>) value );
return true;
@ -184,7 +186,7 @@ public class QueryParameterBindingImpl<T> implements QueryParameterBinding<T>, J
@Override
public void setBindValue(T value, BindableType<T> clarifiedType) {
if ( handleAsMultiValue( value ) ) {
if ( handleAsMultiValue( value, clarifiedType ) ) {
return;
}
@ -206,7 +208,7 @@ public class QueryParameterBindingImpl<T> implements QueryParameterBinding<T>, J
@Override
public void setBindValue(T value, TemporalType temporalTypePrecision) {
if ( handleAsMultiValue( value ) ) {
if ( handleAsMultiValue( value, null ) ) {
return;
}

View File

@ -5953,7 +5953,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final EnumJavaType<?> enumJtd = sqmEnumLiteral.getExpressibleJavaType();
final JdbcType jdbcType = getTypeConfiguration().getJdbcTypeRegistry().getDescriptor( SqlTypes.TINYINT );
final BasicJavaType<Integer> relationalJtd = (BasicJavaType) getTypeConfiguration()
final BasicJavaType<Number> relationalJtd = (BasicJavaType) getTypeConfiguration()
.getJavaTypeRegistry()
.getDescriptor( Integer.class );
final BasicType<?> jdbcMappingType = getTypeConfiguration().getBasicTypeRegistry().resolve( relationalJtd, jdbcType );

View File

@ -172,6 +172,7 @@ import org.hibernate.sql.exec.spi.JdbcSelect;
import org.hibernate.sql.exec.spi.JdbcUpdate;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.sql.results.jdbc.internal.JdbcValuesMappingProducerStandard;
import org.hibernate.type.BasicPluralType;
import org.hibernate.type.BasicType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.StandardBasicTypes;
@ -3582,6 +3583,9 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
protected void renderCasted(Expression expression) {
if ( expression instanceof SqmParameterInterpretation ) {
expression = ( (SqmParameterInterpretation) expression ).getResolvedExpression();
}
final List<SqlAstNode> arguments = new ArrayList<>( 2 );
arguments.add( expression );
if ( expression instanceof SqlTypedMappingJdbcParameter ) {
@ -4296,6 +4300,23 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
else {
final SqlExpressible expressionType = (SqlExpressible) castTarget.getExpressionType();
if ( expressionType instanceof BasicPluralType<?, ?> ) {
final BasicPluralType<?, ?> containerType = (BasicPluralType<?, ?>) expressionType;
final BasicType<?> elementType = containerType.getElementType();
final String elementTypeName = sessionFactory.getTypeConfiguration().getDdlTypeRegistry()
.getDescriptor( elementType.getJdbcType().getDefaultSqlTypeCode() )
.getCastTypeName(
elementType,
castTarget.getLength(),
castTarget.getPrecision(),
castTarget.getScale()
);
final String arrayTypeName = dialect.getArrayTypeName( elementTypeName );
if ( arrayTypeName != null ) {
appendSql( arrayTypeName );
return;
}
}
final DdlTypeRegistry ddlTypeRegistry = getSessionFactory().getTypeConfiguration().getDdlTypeRegistry();
DdlType ddlType = ddlTypeRegistry
.getDescriptor( expressionType.getJdbcMapping().getJdbcType().getDefaultSqlTypeCode() );
@ -5483,7 +5504,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
* @return True if this SQL dialect is known to support some kind of distinct from predicate; false otherwise
*/
protected boolean supportsDistinctFromPredicate() {
return true;
return dialect.supportsDistinctFromPredicate();
}
/**

View File

@ -15,7 +15,7 @@ import org.hibernate.boot.model.naming.Identifier;
* @author Christoph Sturm
* @author Steve Ebersole
*/
public interface ColumnInformation {
public interface ColumnInformation extends ColumnTypeInformation {
/**
* Access to the containing table.
*

View File

@ -0,0 +1,86 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.tool.schema.extract.spi;
import java.sql.Types;
import org.hibernate.Incubating;
import org.hibernate.boot.model.TruthValue;
/**
* Provides access to information about existing table columns
*
* @author Christoph Sturm
* @author Steve Ebersole
*/
@Incubating
public interface ColumnTypeInformation {
ColumnTypeInformation EMPTY = new ColumnTypeInformation() {
@Override
public TruthValue getNullable() {
return TruthValue.UNKNOWN;
}
@Override
public int getTypeCode() {
return Types.OTHER;
}
@Override
public String getTypeName() {
return null;
}
@Override
public int getColumnSize() {
return 0;
}
@Override
public int getDecimalDigits() {
return 0;
}
};
/**
* Is the column nullable. The database is allowed to report unknown, hence the use of TruthValue
*
* @return nullability.
*/
TruthValue getNullable();
/**
* The JDBC type-code.
*
* @return JDBC type-code
*/
int getTypeCode();
/**
* The database specific type name.
*
* @return Type name
*/
public String getTypeName();
// todo : wrap these in org.hibernate.metamodel.spi.relational.Size ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* The column size (length).
*
* @return The column length
*/
int getColumnSize();
/**
* The precision, for numeric types
*
* @return The numeric precision
*/
int getDecimalDigits();
}

View File

@ -210,7 +210,7 @@ public abstract class AbstractStandardBasicType<T>
}
protected void nullSafeSet(PreparedStatement st, T value, int index, WrapperOptions options) throws SQLException {
jdbcType.getBinder( javaType ).bind( st, value, index, options );
getJdbcValueBinder().bind( st, value, index, options );
}
@Override
@ -283,7 +283,7 @@ public abstract class AbstractStandardBasicType<T>
@Override
public T extract(CallableStatement statement, int startIndex, final SharedSessionContractImplementor session) throws SQLException {
return jdbcType.getExtractor( javaType ).extract(
return getJdbcValueExtractor().extract(
statement,
startIndex,
session
@ -292,7 +292,7 @@ public abstract class AbstractStandardBasicType<T>
@Override
public T extract(CallableStatement statement, String paramName, final SharedSessionContractImplementor session) throws SQLException {
return jdbcType.getExtractor( javaType ).extract(
return getJdbcValueExtractor().extract(
statement,
paramName,
session
@ -316,7 +316,7 @@ public abstract class AbstractStandardBasicType<T>
@SuppressWarnings("unchecked")
protected final void nullSafeSet(CallableStatement st, Object value, String name, WrapperOptions options) throws SQLException {
jdbcType.getBinder( javaType ).bind( st, (T) value, name, options );
getJdbcValueBinder().bind( st, (T) value, name, options );
}
@Override

View File

@ -0,0 +1,175 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type;
import java.lang.reflect.Array;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.spi.TypeConfiguration;
/**
* A type that maps between {@link java.sql.Types#ARRAY ARRAY} and {@code T[]}
*
* @author Jordan Gigov
* @author Christian Beikov
*/
public class BasicArrayType<T>
extends AbstractSingleColumnStandardBasicType<T[]>
implements AdjustableBasicType<T[]>, BasicPluralType<T[], T> {
private final BasicType<T> baseDescriptor;
private final String name;
private final ValueBinder<T[]> jdbcValueBinder;
private final ValueExtractor<T[]> jdbcValueExtractor;
public BasicArrayType(BasicType<T> baseDescriptor, JdbcType arrayJdbcType, JavaType<T[]> arrayTypeDescriptor) {
super( arrayJdbcType, arrayTypeDescriptor );
this.baseDescriptor = baseDescriptor;
this.name = baseDescriptor.getName() + "[]";
final ValueBinder<T[]> jdbcValueBinder = super.getJdbcValueBinder();
final ValueExtractor<T[]> jdbcValueExtractor = super.getJdbcValueExtractor();
//noinspection unchecked
final BasicValueConverter<T, Object> valueConverter = (BasicValueConverter<T, Object>) baseDescriptor.getValueConverter();
if ( valueConverter != null ) {
this.jdbcValueBinder = new ValueBinder<T[]>() {
@Override
public void bind(PreparedStatement st, T[] value, int index, WrapperOptions options)
throws SQLException {
jdbcValueBinder.bind( st, getValue( value, valueConverter, options ), index, options );
}
@Override
public void bind(CallableStatement st, T[] value, String name, WrapperOptions options)
throws SQLException {
jdbcValueBinder.bind( st, getValue( value, valueConverter, options ), name, options );
}
private T[] getValue(
T[] value,
BasicValueConverter<T, Object> valueConverter,
WrapperOptions options) {
if ( value == null ) {
return null;
}
final JdbcType elementJdbcType = baseDescriptor.getJdbcType();
final TypeConfiguration typeConfiguration = options.getSessionFactory().getTypeConfiguration();
final JdbcType underlyingJdbcType = typeConfiguration.getJdbcTypeRegistry()
.getDescriptor( elementJdbcType.getDefaultSqlTypeCode() );
final Class<?> preferredJavaTypeClass = underlyingJdbcType.getPreferredJavaTypeClass( options );
final Class<?> elementJdbcJavaTypeClass;
if ( preferredJavaTypeClass == null ) {
elementJdbcJavaTypeClass = underlyingJdbcType.getJdbcRecommendedJavaTypeMapping(
null,
null,
typeConfiguration
).getJavaTypeClass();
}
else {
elementJdbcJavaTypeClass = preferredJavaTypeClass;
}
if ( value.getClass().getComponentType() == elementJdbcJavaTypeClass ) {
return value;
}
final Object[] array = (Object[]) Array.newInstance( elementJdbcJavaTypeClass, value.length );
for ( int i = 0; i < value.length; i++ ) {
array[i] = valueConverter.getRelationalJavaType().unwrap(
valueConverter.toRelationalValue( value[i] ),
elementJdbcJavaTypeClass,
options
);
}
//noinspection unchecked
return (T[]) array;
}
};
this.jdbcValueExtractor = new ValueExtractor<T[]>() {
@Override
public T[] extract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
return getValue( jdbcValueExtractor.extract( rs, paramIndex, options ), valueConverter );
}
@Override
public T[] extract(CallableStatement statement, int paramIndex, WrapperOptions options)
throws SQLException {
return getValue( jdbcValueExtractor.extract( statement, paramIndex, options ), valueConverter );
}
@Override
public T[] extract(CallableStatement statement, String paramName, WrapperOptions options)
throws SQLException {
return getValue( jdbcValueExtractor.extract( statement, paramName, options ), valueConverter );
}
private T[] getValue(T[] value, BasicValueConverter<T, Object> valueConverter) {
if ( value == null ) {
return null;
}
if ( value.getClass().getComponentType() == valueConverter.getDomainJavaType().getJavaTypeClass() ) {
return value;
}
//noinspection unchecked
final T[] array = (T[]) Array.newInstance(
valueConverter.getDomainJavaType().getJavaTypeClass(),
value.length
);
for ( int i = 0; i < value.length; i++ ) {
array[i] = valueConverter.toDomainValue( value[i] );
}
return array;
}
};
}
else {
this.jdbcValueBinder = jdbcValueBinder;
this.jdbcValueExtractor = jdbcValueExtractor;
}
}
@Override
public BasicType<T> getElementType() {
return baseDescriptor;
}
@Override
public String getName() {
return name;
}
@Override
protected boolean registerUnderJavaType() {
return true;
}
@Override
public ValueExtractor<T[]> getJdbcValueExtractor() {
return jdbcValueExtractor;
}
@Override
public ValueBinder<T[]> getJdbcValueBinder() {
return jdbcValueBinder;
}
@Override
public <X> BasicType<X> resolveIndicatedType(JdbcTypeIndicators indicators, JavaType<X> domainJtd) {
// TODO: maybe fallback to some encoding by default if the DB doesn't support arrays natively?
// also, maybe move that logic into the ArrayJdbcType
//noinspection unchecked
return (BasicType<X>) this;
}
}

View File

@ -0,0 +1,187 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.spi.BasicCollectionJavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.spi.TypeConfiguration;
/**
* A type that maps between {@link java.sql.Types#ARRAY ARRAY} and {@code Collection<T>}
*
* @author Christian Beikov
*/
public class BasicCollectionType<C extends Collection<E>, E>
extends AbstractSingleColumnStandardBasicType<C>
implements AdjustableBasicType<C>, BasicPluralType<C, E> {
private final BasicType<E> baseDescriptor;
private final String name;
private final ValueBinder<C> jdbcValueBinder;
private final ValueExtractor<C> jdbcValueExtractor;
public BasicCollectionType(BasicType<E> baseDescriptor, JdbcType arrayJdbcType, BasicCollectionJavaType<C, E> collectionTypeDescriptor) {
super( arrayJdbcType, collectionTypeDescriptor );
this.baseDescriptor = baseDescriptor;
this.name = determineName( collectionTypeDescriptor, baseDescriptor );
final ValueBinder<C> jdbcValueBinder = super.getJdbcValueBinder();
final ValueExtractor<C> jdbcValueExtractor = super.getJdbcValueExtractor();
//noinspection unchecked
final BasicValueConverter<E, Object> valueConverter = (BasicValueConverter<E, Object>) baseDescriptor.getValueConverter();
if ( valueConverter != null ) {
this.jdbcValueBinder = new ValueBinder<C>() {
@Override
public void bind(PreparedStatement st, C value, int index, WrapperOptions options)
throws SQLException {
jdbcValueBinder.bind( st, getValue( value, valueConverter, options ), index, options );
}
@Override
public void bind(CallableStatement st, C value, String name, WrapperOptions options)
throws SQLException {
jdbcValueBinder.bind( st, getValue( value, valueConverter, options ), name, options );
}
private C getValue(
C value,
BasicValueConverter<E, Object> valueConverter,
WrapperOptions options) {
if ( value == null ) {
return null;
}
final JdbcType elementJdbcType = baseDescriptor.getJdbcType();
final TypeConfiguration typeConfiguration = options.getSessionFactory().getTypeConfiguration();
final JdbcType underlyingJdbcType = typeConfiguration.getJdbcTypeRegistry()
.getDescriptor( elementJdbcType.getDefaultSqlTypeCode() );
final Class<?> preferredJavaTypeClass = underlyingJdbcType.getPreferredJavaTypeClass( options );
final Class<?> elementJdbcJavaTypeClass;
if ( preferredJavaTypeClass == null ) {
elementJdbcJavaTypeClass = underlyingJdbcType.getJdbcRecommendedJavaTypeMapping(
null,
null,
typeConfiguration
).getJavaTypeClass();
}
else {
elementJdbcJavaTypeClass = preferredJavaTypeClass;
}
//noinspection unchecked
final Collection<Object> converted = (Collection<Object>) collectionTypeDescriptor.getSemantics()
.instantiateRaw( value.size(), null );
for ( E element : value ) {
converted.add(
valueConverter.getRelationalJavaType().unwrap(
valueConverter.toRelationalValue( element ),
elementJdbcJavaTypeClass,
options
)
);
}
//noinspection unchecked
return (C) converted;
}
};
this.jdbcValueExtractor = new ValueExtractor<C>() {
@Override
public C extract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
return getValue( jdbcValueExtractor.extract( rs, paramIndex, options ), valueConverter );
}
@Override
public C extract(CallableStatement statement, int paramIndex, WrapperOptions options)
throws SQLException {
return getValue( jdbcValueExtractor.extract( statement, paramIndex, options ), valueConverter );
}
@Override
public C extract(CallableStatement statement, String paramName, WrapperOptions options)
throws SQLException {
return getValue( jdbcValueExtractor.extract( statement, paramName, options ), valueConverter );
}
private C getValue(C value, BasicValueConverter<E, Object> valueConverter) {
if ( value == null ) {
return null;
}
final C converted = collectionTypeDescriptor.getSemantics()
.instantiateRaw( value.size(), null );
for ( E element : value ) {
converted.add( valueConverter.toDomainValue( element ) );
}
return converted;
}
};
}
else {
this.jdbcValueBinder = jdbcValueBinder;
this.jdbcValueExtractor = jdbcValueExtractor;
}
}
private static String determineName(BasicCollectionJavaType<?, ?> collectionTypeDescriptor, BasicType<?> baseDescriptor) {
switch ( collectionTypeDescriptor.getSemantics().getCollectionClassification() ) {
case BAG:
case ID_BAG:
return "Collection<" + baseDescriptor.getName() + ">";
case LIST:
return "List<" + baseDescriptor.getName() + ">";
case SET:
return "Set<" + baseDescriptor.getName() + ">";
case SORTED_SET:
return "SortedSet<" + baseDescriptor.getName() + ">";
case ORDERED_SET:
return "OrderedSet<" + baseDescriptor.getName() + ">";
}
return null;
}
@Override
public BasicType<E> getElementType() {
return baseDescriptor;
}
@Override
public String getName() {
return name;
}
@Override
protected boolean registerUnderJavaType() {
return true;
}
@Override
public ValueExtractor<C> getJdbcValueExtractor() {
return jdbcValueExtractor;
}
@Override
public ValueBinder<C> getJdbcValueBinder() {
return jdbcValueBinder;
}
@Override
public <X> BasicType<X> resolveIndicatedType(JdbcTypeIndicators indicators, JavaType<X> domainJtd) {
// TODO: maybe fallback to some encoding by default if the DB doesn't support arrays natively?
// also, maybe move that logic into the ArrayJdbcType
//noinspection unchecked
return (BasicType<X>) this;
}
}

View File

@ -0,0 +1,25 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type;
import org.hibernate.Incubating;
/**
* A basic plural type. Represents a type, that is mapped to a single column instead of multiple rows.
* This is used for array or collection types, that are backed by e.g. SQL array or JSON/XML DDL types.
*
* @see BasicCollectionType
* @see BasicArrayType
*/
@Incubating
public interface BasicPluralType<C, E> extends BasicType<C> {
/**
* Get element type
*/
BasicType<E> getElementType();
}

View File

@ -9,12 +9,14 @@ package org.hibernate.type;
import java.util.Collections;
import java.util.List;
import org.hibernate.Incubating;
import org.hibernate.cache.internal.CacheKeyValueDescriptor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.sql.ast.Clause;
import org.hibernate.type.descriptor.ValueBinder;
@ -75,6 +77,15 @@ public interface BasicType<T> extends Type, BasicDomainType<T>, MappingType, Bas
return getJavaTypeDescriptor();
}
/**
* Returns the converter that this basic type uses for transforming from the domain type, to the relational type,
* or <code>null</code> if there is no conversion.
*/
@Incubating
default BasicValueConverter<T, ?> getValueConverter() {
return null;
}
@Override
default ValueExtractor<T> getJdbcValueExtractor() {
return getJdbcType().getExtractor( this.getMappedJavaType() );

View File

@ -19,6 +19,7 @@ import org.hibernate.engine.spi.Mapping;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.java.BasicJavaType;
@ -30,6 +31,7 @@ import org.hibernate.type.internal.UserTypeSqlTypeAdapter;
import org.hibernate.type.internal.UserTypeVersionJavaTypeWrapper;
import org.hibernate.type.spi.TypeConfiguration;
import org.hibernate.usertype.EnhancedUserType;
import org.hibernate.usertype.LoggableUserType;
import org.hibernate.usertype.UserType;
import org.hibernate.usertype.UserVersionType;
@ -49,7 +51,7 @@ import org.hibernate.usertype.UserVersionType;
*/
public class CustomType<J>
extends AbstractType
implements BasicType<J>, ProcedureParameterNamedBinder<J>, ProcedureParameterExtractionAware<J> {
implements ConvertedBasicType<J>, ProcedureParameterNamedBinder<J>, ProcedureParameterExtractionAware<J> {
private final UserType<J> userType;
private final String[] registrationKeys;
@ -205,6 +207,9 @@ public class CustomType<J>
if ( value == null ) {
return "null";
}
else if ( userType instanceof LoggableUserType ) {
return ( (LoggableUserType) userType ).toLoggableString( value, factory );
}
else if ( userType instanceof EnhancedUserType<?> ) {
return ( (EnhancedUserType<Object>) userType ).toString( value );
}
@ -313,4 +318,9 @@ public class CustomType<J>
public JavaType<J> getJavaTypeDescriptor() {
return this.getMappedJavaType();
}
@Override
public BasicValueConverter<J, Object> getValueConverter() {
return userType.getValueConverter();
}
}

View File

@ -27,6 +27,7 @@ import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.metamodel.model.convert.internal.NamedEnumValueConverter;
import org.hibernate.metamodel.model.convert.internal.OrdinalEnumValueConverter;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.metamodel.model.convert.spi.EnumValueConverter;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
@ -106,6 +107,11 @@ public class EnumType<T extends Enum<T>>
return enumValueConverter;
}
@Override
public BasicValueConverter<T, Object> getValueConverter() {
return enumValueConverter;
}
@Override
public void setParameterValues(Properties parameters) {
// IMPL NOTE: we handle 2 distinct cases here:

View File

@ -8,11 +8,13 @@ package org.hibernate.type;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@ -21,11 +23,14 @@ import javax.xml.namespace.QName;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.BasicPluralJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.spi.UnknownBasicJavaType;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.JAXBIntrospector;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.Unmarshaller;
import jakarta.xml.bind.annotation.XmlAnyElement;
@ -65,30 +70,31 @@ public class JaxbXmlFormatMapper implements FormatMapper {
final Unmarshaller unmarshaller = context.createUnmarshaller();
final MapWrapper mapWrapper = (MapWrapper) unmarshaller
.unmarshal( new StringReader( charSequence.toString() ) );
final List<Object> elements = mapWrapper.elements;
final Collection<Object> elements = mapWrapper.elements;
final Map<Object, Object> map = CollectionHelper.linkedMapOfSize( elements.size() >> 1 );
for ( int i = 0; i < elements.size(); i += 2 ) {
final Object keyElement = unmarshaller.unmarshal( (Node) elements.get( i ), keyClass ).getValue();
final Object valueElement = unmarshaller.unmarshal( (Node) elements.get( i + 1 ), valueClass )
.getValue();
final Object key;
final Object value;
if ( keyElement instanceof Element ) {
key = ( (Element) keyElement ).getFirstChild().getTextContent();
final JAXBIntrospector jaxbIntrospector = context.createJAXBIntrospector();
final JAXBElementTransformer keyTransformer;
final JAXBElementTransformer valueTransformer;
if ( javaType instanceof BasicPluralJavaType<?> ) {
keyTransformer = createTransformer( keyClass, "key", null, jaxbIntrospector, wrapperOptions );
valueTransformer = createTransformer(
( (BasicPluralJavaType<?>) javaType ).getElementJavaType(),
"value",
null,
jaxbIntrospector,
wrapperOptions
);
}
else {
key = keyElement;
}
if ( valueElement instanceof Element ) {
value = ( (Element) valueElement ).getFirstChild().getTextContent();
}
else {
value = valueElement;
keyTransformer = createTransformer( keyClass, "key", null, jaxbIntrospector, wrapperOptions );
valueTransformer = createTransformer( valueClass, "value", null, jaxbIntrospector, wrapperOptions );
}
for ( final Iterator<Object> iterator = elements.iterator(); iterator.hasNext(); ) {
final Object key = keyTransformer.fromJAXBElement( iterator.next(), unmarshaller );
final Object value = valueTransformer.fromJAXBElement( iterator.next(), unmarshaller );
map.put( key, value );
}
//noinspection unchecked
return (T) map;
return javaType.wrap( map, wrapperOptions );
}
else if ( Collection.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
final JAXBContext context;
@ -105,22 +111,72 @@ public class JaxbXmlFormatMapper implements FormatMapper {
final Unmarshaller unmarshaller = context.createUnmarshaller();
final CollectionWrapper collectionWrapper = (CollectionWrapper) unmarshaller
.unmarshal( new StringReader( charSequence.toString() ) );
final List<Object> elements = collectionWrapper.elements;
final Collection<Object> collection = new ArrayList<>( elements.size() >> 1 );
for ( int i = 0; i < elements.size(); i++ ) {
final Object valueElement = unmarshaller.unmarshal( (Node) elements.get( i ), valueClass )
.getValue();
final Object value;
if ( valueElement instanceof Element ) {
value = ( (Element) valueElement ).getFirstChild().getTextContent();
final Collection<Object> elements = collectionWrapper.elements;
final Collection<Object> collection = new ArrayList<>( elements.size() );
final JAXBIntrospector jaxbIntrospector = context.createJAXBIntrospector();
final JAXBElementTransformer valueTransformer;
if ( javaType instanceof BasicPluralJavaType<?> ) {
valueTransformer = createTransformer(
( (BasicPluralJavaType<?>) javaType ).getElementJavaType(),
"value",
null,
jaxbIntrospector,
wrapperOptions
);
}
else {
value = valueElement;
valueTransformer = createTransformer( valueClass, "value", null, jaxbIntrospector, wrapperOptions );
}
for ( Object element : elements ) {
final Object value = valueTransformer.fromJAXBElement( element, unmarshaller );
collection.add( value );
}
return javaType.wrap( collection, wrapperOptions );
}
else if ( javaType.getJavaTypeClass().isArray() ) {
final Class<?> valueClass = javaType.getJavaTypeClass().getComponentType();
final JAXBContext context = JAXBContext.newInstance( CollectionWrapper.class, valueClass );
final Unmarshaller unmarshaller = context.createUnmarshaller();
final CollectionWrapper collectionWrapper = (CollectionWrapper) unmarshaller
.unmarshal( new StringReader( charSequence.toString() ) );
final Collection<Object> elements = collectionWrapper.elements;
final JAXBIntrospector jaxbIntrospector = context.createJAXBIntrospector();
final JAXBElementTransformer valueTransformer;
if ( javaType instanceof BasicPluralJavaType<?> ) {
valueTransformer = createTransformer(
( (BasicPluralJavaType<?>) javaType ).getElementJavaType(),
"value",
null,
jaxbIntrospector,
wrapperOptions
);
}
else {
valueTransformer = createTransformer( valueClass, "value", null, jaxbIntrospector, wrapperOptions );
}
final int length = elements.size();
if ( Object[].class.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
final Object[] array = (Object[]) Array.newInstance( valueClass, length );
int i = 0;
for ( Object element : elements ) {
final Object value = valueTransformer.fromJAXBElement( element, unmarshaller );
array[i] = value;
i++;
}
//noinspection unchecked
return (T) collection;
return (T) array;
}
else {
//noinspection unchecked
final T array = (T) Array.newInstance( valueClass, length );
int i = 0;
for ( Object element : elements ) {
final Object value = valueTransformer.fromJAXBElement( element, unmarshaller );
Array.set( array, i, value );
i++;
}
return array;
}
}
else {
final JAXBContext context = JAXBContext.newInstance( javaType.getJavaTypeClass() );
@ -164,21 +220,32 @@ public class JaxbXmlFormatMapper implements FormatMapper {
context = JAXBContext.newInstance( MapWrapper.class, keyClass, valueClass );
}
}
if ( !map.isEmpty() ) {
Object exampleKey = null;
Object exampleValue = null;
for ( Map.Entry<?, ?> entry : map.entrySet() ) {
mapWrapper.elements.add(
new JAXBElement<>(
new QName( "key" ),
keyClass,
entry.getKey()
)
);
mapWrapper.elements.add(
new JAXBElement<>(
new QName( "value" ),
valueClass,
entry.getValue()
)
);
final Object mapKey = entry.getKey();
final Object mapValue = entry.getValue();
if ( exampleKey == null && mapKey != null ) {
exampleKey = mapKey;
if ( exampleValue != null ) {
break;
}
}
if ( exampleValue == null && mapValue != null ) {
exampleValue = mapValue;
if ( exampleKey != null ) {
break;
}
}
}
final JAXBIntrospector jaxbIntrospector = context.createJAXBIntrospector();
final JAXBElementTransformer keyTransformer = createTransformer( keyClass, "key", exampleKey, jaxbIntrospector, wrapperOptions );
final JAXBElementTransformer valueTransformer = createTransformer( valueClass, "value", exampleValue, jaxbIntrospector, wrapperOptions );
for ( Map.Entry<?, ?> entry : map.entrySet() ) {
mapWrapper.elements.add( keyTransformer.toJAXBElement( entry.getKey() ) );
mapWrapper.elements.add( valueTransformer.toJAXBElement( entry.getValue() ) );
}
}
createMarshaller( context ).marshal( mapWrapper, stringWriter );
}
@ -186,7 +253,6 @@ public class JaxbXmlFormatMapper implements FormatMapper {
final JAXBContext context;
final Class<Object> valueClass;
final Collection<?> collection = (Collection<?>) value;
final CollectionWrapper collectionWrapper = new CollectionWrapper( new ArrayList<>( collection ) );
if ( javaType.getJavaType() instanceof ParameterizedType ) {
final Type[] typeArguments = ( (ParameterizedType) javaType.getJavaType() ).getActualTypeArguments();
valueClass = ReflectHelper.getClass( typeArguments[0] );
@ -203,22 +269,96 @@ public class JaxbXmlFormatMapper implements FormatMapper {
context = JAXBContext.newInstance( CollectionWrapper.class, valueClass );
}
}
// for ( Object element : collection ) {
// collectionWrapper.elements.add(
// new JAXBElement<>(
// new QName( "key" ),
// keyClass,
// entry.getKey()
// )
// );
// collectionWrapper.elements.add(
// new JAXBElement<>(
// new QName( "value" ),
// valueClass,
// entry.getValue()
// )
// );
// }
final CollectionWrapper collectionWrapper;
if ( collection.isEmpty() ) {
collectionWrapper = new CollectionWrapper();
}
else {
collectionWrapper = new CollectionWrapper( new ArrayList<>( collection.size() ) );
Object exampleValue = null;
for ( Object o : collection ) {
if ( o != null ) {
exampleValue = o;
break;
}
}
final JAXBElementTransformer valueTransformer;
if ( javaType instanceof BasicPluralJavaType<?> ) {
valueTransformer = createTransformer(
( (BasicPluralJavaType<?>) javaType ).getElementJavaType(),
"value",
exampleValue,
context.createJAXBIntrospector(),
wrapperOptions
);
}
else {
valueTransformer = createTransformer(
valueClass,
"value",
exampleValue,
context.createJAXBIntrospector(),
wrapperOptions
);
}
for ( Object o : collection ) {
collectionWrapper.elements.add( valueTransformer.toJAXBElement( o ) );
}
}
createMarshaller( context ).marshal( collectionWrapper, stringWriter );
}
else if ( javaType.getJavaTypeClass().isArray() ) {
//noinspection unchecked
final Class<Object> valueClass = (Class<Object>) javaType.getJavaTypeClass().getComponentType();
final JAXBContext context = JAXBContext.newInstance( CollectionWrapper.class, valueClass );
final CollectionWrapper collectionWrapper;
if ( Object[].class.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
final Object[] array = (Object[]) value;
final List<Object> list = new ArrayList<>( array.length );
Object exampleElement = null;
for ( Object o : array ) {
if ( o != null ) {
exampleElement = o;
break;
}
}
final JAXBElementTransformer transformer;
if ( javaType instanceof BasicPluralJavaType<?> ) {
transformer = createTransformer(
( (BasicPluralJavaType<?>) javaType ).getElementJavaType(),
"value",
exampleElement,
context.createJAXBIntrospector(),
wrapperOptions
);
}
else {
transformer = createTransformer(
valueClass,
"value",
exampleElement,
context.createJAXBIntrospector(),
wrapperOptions
);
}
for ( Object o : array ) {
list.add( transformer.toJAXBElement( o ) );
}
collectionWrapper = new CollectionWrapper( list );
}
else {
// Primitive arrays get a special treatment
final int length = Array.getLength( value );
final List<Object> list = new ArrayList<>( length );
final JavaTypeJAXBElementTransformer transformer = new JavaTypeJAXBElementTransformer(
( (BasicPluralJavaType<?>) javaType ).getElementJavaType(),
"value"
);
for ( int i = 0; i < length; i++ ) {
list.add( transformer.toJAXBElement( Array.get( value, i ) ) );
}
collectionWrapper = new CollectionWrapper( list );
}
createMarshaller( context ).marshal( collectionWrapper, stringWriter );
}
else {
@ -232,6 +372,54 @@ public class JaxbXmlFormatMapper implements FormatMapper {
}
}
private JAXBElementTransformer createTransformer(
Class<?> elementClass,
String tagName,
Object exampleElement,
JAXBIntrospector introspector,
WrapperOptions wrapperOptions) {
final JavaType<Object> elementJavaType = wrapperOptions.getSessionFactory()
.getTypeConfiguration()
.getJavaTypeRegistry()
.findDescriptor( elementClass );
if ( exampleElement == null && ( elementJavaType == null || elementJavaType instanceof UnknownBasicJavaType<?> ) ) {
try {
final Constructor<?> declaredConstructor = elementClass.getDeclaredConstructor();
exampleElement = declaredConstructor.newInstance();
}
catch (Exception ex) {
// Ignore
}
}
final QName elementName = exampleElement == null ? null : introspector.getElementName( exampleElement );
if ( elementName == null && elementClass != String.class && elementJavaType != null ) {
return createTransformer( elementJavaType, tagName, exampleElement, introspector, wrapperOptions );
}
return new SimpleJAXBElementTransformer( elementClass, tagName );
}
private JAXBElementTransformer createTransformer(
JavaType<?> elementJavaType,
String tagName,
Object exampleElement,
JAXBIntrospector introspector,
WrapperOptions wrapperOptions) {
if ( exampleElement == null && elementJavaType instanceof UnknownBasicJavaType<?> ) {
try {
final Constructor<?> declaredConstructor = elementJavaType.getJavaTypeClass().getDeclaredConstructor();
exampleElement = declaredConstructor.newInstance();
}
catch (Exception ex) {
// Ignore
}
}
final QName elementName = exampleElement == null ? null : introspector.getElementName( exampleElement );
if ( elementName == null && elementJavaType.getJavaTypeClass() != String.class ) {
return new JavaTypeJAXBElementTransformer( elementJavaType, tagName );
}
return new SimpleJAXBElementTransformer( elementJavaType.getJavaTypeClass(), tagName );
}
private Marshaller createMarshaller(JAXBContext context) throws JAXBException {
final Marshaller marshaller = context.createMarshaller();
marshaller.setProperty( Marshaller.JAXB_FRAGMENT, true );
@ -241,19 +429,88 @@ public class JaxbXmlFormatMapper implements FormatMapper {
@XmlRootElement(name = "Map")
public static class MapWrapper {
@XmlAnyElement
List<Object> elements = new ArrayList<>();
Collection<Object> elements;
public MapWrapper() {
this.elements = new ArrayList<>();
}
public MapWrapper(Collection<Object> elements) {
this.elements = elements;
}
}
@XmlRootElement(name = "Collection")
public static class CollectionWrapper {
@XmlAnyElement
List<Object> elements = new ArrayList<>();
Collection<Object> elements;
public CollectionWrapper() {
this.elements = new ArrayList<>();
}
public CollectionWrapper(List<Object> elements) {
public CollectionWrapper(Collection<Object> elements) {
this.elements = elements;
}
}
private static interface JAXBElementTransformer {
JAXBElement<?> toJAXBElement(Object o);
Object fromJAXBElement(Object element, Unmarshaller unmarshaller) throws JAXBException;
}
private static class SimpleJAXBElementTransformer implements JAXBElementTransformer {
private final Class<Object> elementClass;
private final QName tagName;
public SimpleJAXBElementTransformer(Class<?> elementClass, String tagName) {
//noinspection unchecked
this.elementClass = (Class<Object>) elementClass;
this.tagName = new QName( tagName );
}
@Override
public JAXBElement<?> toJAXBElement(Object o) {
return new JAXBElement<>( tagName, elementClass, o );
}
@Override
public Object fromJAXBElement(Object element, Unmarshaller unmarshaller) throws JAXBException {
final Object valueElement = unmarshaller.unmarshal( (Node) element, elementClass ).getValue();
final Object value;
if ( valueElement instanceof Element ) {
value = ( (Element) valueElement ).getFirstChild().getTextContent();
}
else {
value = valueElement;
}
return value;
}
}
private static class JavaTypeJAXBElementTransformer implements JAXBElementTransformer {
private final JavaType<Object> elementJavaType;
private final QName tagName;
public JavaTypeJAXBElementTransformer(JavaType<?> elementJavaType, String tagName) {
//noinspection unchecked
this.elementJavaType = (JavaType<Object>) elementJavaType;
this.tagName = new QName( tagName );
}
@Override
public JAXBElement<?> toJAXBElement(Object o) {
return new JAXBElement<>(
tagName,
String.class,
o == null ? null : elementJavaType.toString( o )
);
}
@Override
public Object fromJAXBElement(Object element, Unmarshaller unmarshaller) throws JAXBException {
final String value = unmarshaller.unmarshal( (Node) element, String.class ).getValue();
return value == null ? null : elementJavaType.fromString( value );
}
}
}

View File

@ -1252,9 +1252,8 @@ public final class StandardBasicTypes {
basicTypeRegistry.primed();
}
@SuppressWarnings("rawtypes")
private static void handle(
BasicType type,
BasicType<?> type,
String legacyTypeClassName,
BasicTypeRegistry basicTypeRegistry,
String... registrationKeys) {

View File

@ -65,6 +65,11 @@ public class AttributeConverterJdbcTypeAdapter implements JdbcType {
return "AttributeConverterSqlTypeDescriptorAdapter(" + converter.getClass().getName() + ")";
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return delegate.getPreferredJavaTypeClass( options );
}
// Binding ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,88 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type.descriptor.java;
import java.sql.Types;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.BasicArrayType;
import org.hibernate.type.BasicPluralType;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.spi.TypeConfiguration;
public abstract class AbstractArrayJavaType<T, E> extends AbstractClassJavaType<T>
implements BasicPluralJavaType<E> {
private final JavaType<E> componentJavaType;
public AbstractArrayJavaType(Class<T> clazz, BasicType<E> baseDescriptor, MutabilityPlan<T> mutabilityPlan) {
this( clazz, baseDescriptor.getJavaTypeDescriptor(), mutabilityPlan );
}
public AbstractArrayJavaType(Class<T> clazz, JavaType<E> baseDescriptor, MutabilityPlan<T> mutabilityPlan) {
super( clazz, mutabilityPlan );
this.componentJavaType = baseDescriptor;
}
@Override
public JavaType<E> getElementJavaType() {
return componentJavaType;
}
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
final int preferredSqlTypeCodeForArray = indicators.getPreferredSqlTypeCodeForArray();
// Always determine the recommended type to make sure this is a valid basic java type
final JdbcType recommendedComponentJdbcType = componentJavaType.getRecommendedJdbcType( indicators );
final TypeConfiguration typeConfiguration = indicators.getTypeConfiguration();
final JdbcType jdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( preferredSqlTypeCodeForArray );
if ( jdbcType instanceof ArrayJdbcType ) {
return ( (ArrayJdbcType) jdbcType ).resolveType(
typeConfiguration,
typeConfiguration.getServiceRegistry()
.getService( JdbcServices.class )
.getDialect(),
recommendedComponentJdbcType,
ColumnTypeInformation.EMPTY
);
}
return jdbcType;
}
@Override
public BasicType<?> resolveType(
TypeConfiguration typeConfiguration,
Dialect dialect,
BasicType<E> elementType,
ColumnTypeInformation columnTypeInformation) {
final Class<?> elementJavaTypeClass = elementType.getJavaTypeDescriptor().getJavaTypeClass();
if ( elementType instanceof BasicPluralType<?, ?> || elementJavaTypeClass != null && elementJavaTypeClass.isArray() ) {
return null;
}
return typeConfiguration.standardBasicTypeForJavaType(
getJavaType(),
javaType -> {
JdbcType arrayJdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( Types.ARRAY );
if ( arrayJdbcType instanceof ArrayJdbcType ) {
arrayJdbcType = ( (ArrayJdbcType) arrayJdbcType ).resolveType(
typeConfiguration,
dialect,
elementType,
columnTypeInformation
);
}
//noinspection unchecked,rawtypes
return new BasicArrayType( elementType, arrayJdbcType, javaType );
}
);
}
}

View File

@ -0,0 +1,368 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type.descriptor.java;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.sql.SQLException;
import java.sql.Types;
import org.hibernate.HibernateException;
import org.hibernate.SharedSessionContract;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.BasicArrayType;
import org.hibernate.type.BasicPluralType;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Descriptor for {@code T[]} handling.
*
* @author Christian Beikov
* @author Jordan Gigov
*/
public class ArrayJavaType<T> extends AbstractArrayJavaType<T[], T> {
public ArrayJavaType(BasicType<T> baseDescriptor) {
this( baseDescriptor.getJavaTypeDescriptor() );
}
public ArrayJavaType(JavaType<T> baseDescriptor) {
super(
(Class<T[]>) Array.newInstance( baseDescriptor.getJavaTypeClass(), 0 ).getClass(),
baseDescriptor,
new ArrayMutabilityPlan<>( baseDescriptor )
);
}
@Override
public BasicType<?> resolveType(
TypeConfiguration typeConfiguration,
Dialect dialect,
BasicType<T> elementType,
ColumnTypeInformation columnTypeInformation) {
final Class<?> elementJavaTypeClass = elementType.getJavaTypeDescriptor().getJavaTypeClass();
if ( elementType instanceof BasicPluralType<?, ?> || elementJavaTypeClass != null && elementJavaTypeClass.isArray() ) {
return null;
}
final ArrayJavaType<T> arrayJavaType;
if ( getElementJavaType() == elementType.getJavaTypeDescriptor() ) {
arrayJavaType = this;
}
else {
arrayJavaType = new ArrayJavaType<>( elementType.getJavaTypeDescriptor() );
// Register the array type as that will be resolved in the next step
typeConfiguration.getJavaTypeRegistry().addDescriptor( arrayJavaType );
}
return typeConfiguration.standardBasicTypeForJavaType(
arrayJavaType.getJavaType(),
javaType -> {
JdbcType arrayJdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( Types.ARRAY );
if ( arrayJdbcType instanceof ArrayJdbcType ) {
arrayJdbcType = ( (ArrayJdbcType) arrayJdbcType ).resolveType(
typeConfiguration,
dialect,
elementType,
columnTypeInformation
);
}
return new BasicArrayType<>( elementType, arrayJdbcType, javaType );
}
);
}
@Override
public String extractLoggableRepresentation(T[] value) {
if ( value == null ) {
return "null";
}
int iMax = value.length - 1;
if ( iMax == -1 ) {
return "[]";
}
final StringBuilder sb = new StringBuilder();
sb.append( '[' );
for ( int i = 0; ; i++ ) {
sb.append( getElementJavaType().extractLoggableRepresentation( value[i] ) );
if ( i == iMax ) {
return sb.append( ']' ).toString();
}
sb.append( ", " );
}
}
@Override
public boolean areEqual(T[] one, T[] another) {
if ( one == null && another == null ) {
return true;
}
if ( one == null || another == null ) {
return false;
}
if ( one.length != another.length ) {
return false;
}
int l = one.length;
for ( int i = 0; i < l; i++ ) {
if ( !getElementJavaType().areEqual( one[i], another[i] )) {
return false;
}
}
return true;
}
@Override
public int extractHashCode(T[] value) {
if ( value == null ) {
return 0;
}
int result = 1;
for ( T element : value ) {
result = 31 * result + ( element == null ? 0 : getElementJavaType().extractHashCode( element ) );
}
return result;
}
@Override
public String toString(T[] value) {
if ( value == null ) {
return null;
}
final StringBuilder sb = new StringBuilder();
sb.append( '{' );
String glue = "";
for ( T v : value ) {
sb.append( glue );
if ( v == null ) {
sb.append( "null" );
glue = ",";
continue;
}
sb.append( '"' );
String valstr = getElementJavaType().toString( v );
// using replaceAll is a shorter, but much slower way to do this
for (int i = 0, len = valstr.length(); i < len; i ++ ) {
char c = valstr.charAt( i );
// Surrogate pairs. This is how they're done.
if (c == '\\' || c == '"') {
sb.append( '\\' );
}
sb.append( c );
}
sb.append( '"' );
glue = ",";
}
sb.append( '}' );
final String result = sb.toString();
return result;
}
@Override
public T[] fromString(CharSequence charSequence) {
if ( charSequence == null ) {
return null;
}
java.util.ArrayList<String> lst = new java.util.ArrayList<>();
StringBuilder sb = null;
char lastChar = charSequence.charAt( charSequence.length() - 1 );
char firstChar = charSequence.charAt( 0 );
if ( firstChar != '{' || lastChar != '}' ) {
throw new IllegalArgumentException( "Cannot parse given string into array of strings. First and last character must be { and }" );
}
int len = charSequence.length();
boolean inquote = false;
for ( int i = 1; i < len; i ++ ) {
char c = charSequence.charAt( i );
if ( c == '"' ) {
if (inquote) {
lst.add( sb.toString() );
}
else {
sb = new StringBuilder();
}
inquote = !inquote;
continue;
}
else if ( !inquote ) {
if ( Character.isWhitespace( c ) ) {
continue;
}
else if ( c == ',' ) {
// treat no-value between commas to mean null
if ( sb == null ) {
lst.add( null );
}
else {
sb = null;
}
continue;
}
else {
// i + 4, because there has to be a comma or closing brace after null
if ( i + 4 < len
&& charSequence.charAt( i ) == 'n'
&& charSequence.charAt( i + 1 ) == 'u'
&& charSequence.charAt( i + 2 ) == 'l'
&& charSequence.charAt( i + 3 ) == 'l') {
lst.add( null );
i += 4;
continue;
}
if (i + 1 == len) {
break;
}
throw new IllegalArgumentException( "Cannot parse given string into array of strings."
+ " Outside of quote, but neither whitespace, comma, array end, nor null found." );
}
}
else if ( c == '\\' && i + 2 < len && (charSequence.charAt( i + 1 ) == '\\' || charSequence.charAt( i + 1 ) == '"')) {
c = charSequence.charAt( ++i );
}
// If there is ever a null-pointer here, the if-else logic before is incomplete
sb.append( c );
}
//noinspection unchecked
final T[] result = (T[]) Array.newInstance( getElementJavaType().getJavaTypeClass(), lst.size() );
for ( int i = 0; i < result.length; i ++ ) {
if ( lst.get( i ) != null ) {
result[i] = getElementJavaType().fromString( lst.get( i ) );
}
}
return result;
}
@Override
public <X> X unwrap(T[] value, Class<X> type, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( type.isInstance( value ) ) {
//noinspection unchecked
return (X) value;
}
else if ( type == byte[].class ) {
// byte[] can only be requested if the value should be serialized
return (X) SerializationHelper.serialize( value );
}
else if ( type == BinaryStream.class ) {
// BinaryStream can only be requested if the value should be serialized
//noinspection unchecked
return (X) new BinaryStreamImpl( SerializationHelper.serialize( value ) );
}
else if ( type.isArray() ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();
final Object[] unwrapped = (Object[]) Array.newInstance( preferredJavaTypeClass, value.length );
for ( int i = 0; i < value.length; i++ ) {
unwrapped[i] = getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options );
}
//noinspection unchecked
return (X) unwrapped;
}
throw unknownUnwrap( type );
}
@Override
public <X> T[] wrap(X value, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( value instanceof java.sql.Array ) {
try {
//noinspection unchecked
value = (X) ( (java.sql.Array) value ).getArray();
}
catch ( SQLException ex ) {
// This basically shouldn't happen unless you've lost connection to the database.
throw new HibernateException( ex );
}
}
if ( value instanceof Object[] ) {
final Object[] raw = (Object[]) value;
final Class<T> componentClass = getElementJavaType().getJavaTypeClass();
//noinspection unchecked
final T[] wrapped = (T[]) java.lang.reflect.Array.newInstance( componentClass, raw.length );
if ( componentClass.isAssignableFrom( value.getClass().getComponentType() ) ) {
for (int i = 0; i < raw.length; i++) {
//noinspection unchecked
wrapped[i] = (T) raw[i];
}
}
else {
for ( int i = 0; i < raw.length; i++ ) {
wrapped[i] = getElementJavaType().wrap( raw[i], options );
}
}
return wrapped;
}
else if ( value instanceof byte[] ) {
// When the value is a byte[], this is a deserialization request
//noinspection unchecked
return (T[]) SerializationHelper.deserialize( (byte[]) value );
}
else if ( value instanceof BinaryStream ) {
// When the value is a BinaryStream, this is a deserialization request
//noinspection unchecked
return (T[]) SerializationHelper.deserialize( ( (BinaryStream) value ).getBytes() );
}
throw unknownWrap( value.getClass() );
}
private static class ArrayMutabilityPlan<T> implements MutabilityPlan<T[]> {
private final Class<T> componentClass;
private final MutabilityPlan<T> componentPlan;
public ArrayMutabilityPlan(JavaType<T> baseDescriptor) {
this.componentClass = baseDescriptor.getJavaTypeClass();
this.componentPlan = baseDescriptor.getMutabilityPlan();
}
@Override
public boolean isMutable() {
return true;
}
@Override
public T[] deepCopy(T[] value) {
if ( value == null ) {
return null;
}
//noinspection unchecked
T[] copy = (T[]) Array.newInstance( componentClass, value.length );
for ( int i = 0; i < value.length; i ++ ) {
copy[ i ] = componentPlan.deepCopy( value[ i ] );
}
return copy;
}
@Override
public Serializable disassemble(T[] value, SharedSessionContract session) {
return deepCopy( value );
}
@Override
public T[] assemble(Serializable cached, SharedSessionContract session) {
//noinspection unchecked
return deepCopy( (T[]) cached );
}
}
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.type.descriptor.java;
import org.hibernate.type.descriptor.jdbc.AdjustableJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.descriptor.jdbc.JdbcTypeJavaClassMappings;
@ -24,9 +25,13 @@ public interface BasicJavaType<T> extends JavaType<T> {
*/
default JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
// match legacy behavior
return indicators.getTypeConfiguration().getJdbcTypeRegistry().getDescriptor(
final JdbcType descriptor = indicators.getTypeConfiguration().getJdbcTypeRegistry().getDescriptor(
JdbcTypeJavaClassMappings.INSTANCE.determineJdbcTypeCodeForJavaClass( getJavaTypeClass() )
);
if ( descriptor instanceof AdjustableJdbcType ) {
return ( (AdjustableJdbcType) descriptor ).resolveIndicatedType( indicators, this );
}
return descriptor;
}
@Override

View File

@ -0,0 +1,43 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type.descriptor.java;
import java.io.Serializable;
import org.hibernate.Incubating;
import org.hibernate.dialect.Dialect;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.BasicType;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Descriptor for a basic plural Java type.
* A basic plural type represents a type, that is mapped to a single column instead of multiple rows.
* This is used for array or collection types, that are backed by e.g. SQL array or JSON/XML DDL types.
*
* The interface can be implemented by a plural java type e.g. {@link org.hibernate.type.descriptor.java.spi.BasicCollectionJavaType}
* and provides access to the element java type, as well as a hook to resolve the {@link BasicType} based on the element {@link BasicType},
* in order to gain enough information to implement storage and retrieval of the composite data type via JDBC.
*
* @see org.hibernate.type.descriptor.java.spi.BasicCollectionJavaType
*/
@Incubating
public interface BasicPluralJavaType<T> extends Serializable {
/**
* Get the Java type descriptor for the element type
*/
JavaType<T> getElementJavaType();
/**
* Creates a container type for the given element type
*/
BasicType<?> resolveType(
TypeConfiguration typeConfiguration,
Dialect dialect,
BasicType<T> elementType,
ColumnTypeInformation columnTypeInformation);
}

View File

@ -0,0 +1,198 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type.descriptor.java;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.SharedSessionContract;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.type.descriptor.WrapperOptions;
/**
* Descriptor for {@code boolean[]} handling.
*
* @author Christian Beikov
*/
public class BooleanPrimitiveArrayJavaType extends AbstractArrayJavaType<boolean[], Boolean> {
public static final BooleanPrimitiveArrayJavaType INSTANCE = new BooleanPrimitiveArrayJavaType();
private BooleanPrimitiveArrayJavaType() {
this( BooleanJavaType.INSTANCE );
}
protected BooleanPrimitiveArrayJavaType(JavaType<Boolean> baseDescriptor) {
super( boolean[].class, baseDescriptor, new ArrayMutabilityPlan() );
}
@Override
public String extractLoggableRepresentation(boolean[] value) {
return value == null ? super.extractLoggableRepresentation( null ) : Arrays.toString( value );
}
@Override
public boolean areEqual(boolean[] one, boolean[] another) {
return Arrays.equals( one, another );
}
@Override
public int extractHashCode(boolean[] value) {
return Arrays.hashCode( value );
}
@Override
public String toString(boolean[] value) {
if ( value == null ) {
return null;
}
final StringBuilder sb = new StringBuilder();
sb.append( '{' );
sb.append( value[0] );
for ( int i = 1; i < value.length; i++ ) {
sb.append( value[i] );
sb.append( ',' );
}
sb.append( '}' );
return sb.toString();
}
@Override
public boolean[] fromString(CharSequence charSequence) {
if ( charSequence == null ) {
return null;
}
final List<Boolean> list = new ArrayList<>();
final char lastChar = charSequence.charAt( charSequence.length() - 1 );
final char firstChar = charSequence.charAt( 0 );
if ( firstChar != '{' || lastChar != '}' ) {
throw new IllegalArgumentException( "Cannot parse given string into array of strings. First and last character must be { and }" );
}
final int len = charSequence.length();
int elementStart = 1;
for ( int i = elementStart; i < len; i ++ ) {
final char c = charSequence.charAt( i );
if ( c == ',' ) {
list.add( Boolean.parseBoolean( charSequence.subSequence( elementStart, i ).toString() ) );
elementStart = i + 1;
}
}
final boolean[] result = new boolean[list.size()];
for ( int i = 0; i < result.length; i ++ ) {
result[ i ] = list.get( i );
}
return result;
}
@Override
public <X> X unwrap(boolean[] value, Class<X> type, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( type.isInstance( value ) ) {
return (X) value;
}
else if ( Object[].class.isAssignableFrom( type ) ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();
final Object[] unwrapped = (Object[]) Array.newInstance( preferredJavaTypeClass, value.length );
for ( int i = 0; i < value.length; i++ ) {
unwrapped[i] = getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options );
}
return (X) unwrapped;
}
else if ( type == byte[].class ) {
// byte[] can only be requested if the value should be serialized
return (X) SerializationHelper.serialize( value );
}
else if ( type == BinaryStream.class ) {
// BinaryStream can only be requested if the value should be serialized
//noinspection unchecked
return (X) new BinaryStreamImpl( SerializationHelper.serialize( value ) );
}
else if ( type.isArray() ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();
final Object unwrapped = Array.newInstance( preferredJavaTypeClass, value.length );
for ( int i = 0; i < value.length; i++ ) {
Array.set( unwrapped, i, getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options ) );
}
return (X) unwrapped;
}
throw unknownUnwrap( type );
}
@Override
public <X> boolean[] wrap(X value, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( value instanceof java.sql.Array ) {
try {
//noinspection unchecked
value = (X) ( (java.sql.Array) value ).getArray();
}
catch ( SQLException ex ) {
// This basically shouldn't happen unless you've lost connection to the database.
throw new HibernateException( ex );
}
}
if ( value instanceof boolean[] ) {
return (boolean[]) value;
}
else if ( value instanceof byte[] ) {
// When the value is a byte[], this is a deserialization request
return (boolean[]) SerializationHelper.deserialize( (byte[]) value );
}
else if ( value instanceof BinaryStream ) {
// When the value is a BinaryStream, this is a deserialization request
return (boolean[]) SerializationHelper.deserialize( ( (BinaryStream) value ).getBytes() );
}
else if ( value.getClass().isArray() ) {
final boolean[] wrapped = new boolean[Array.getLength( value )];
for ( int i = 0; i < wrapped.length; i++ ) {
wrapped[i] = getElementJavaType().wrap( Array.get( value, i ), options );
}
return wrapped;
}
throw unknownWrap( value.getClass() );
}
private static class ArrayMutabilityPlan implements MutabilityPlan<boolean[]> {
@Override
public boolean isMutable() {
return true;
}
@Override
public boolean[] deepCopy(boolean[] value) {
return value == null ? null : value.clone();
}
@Override
public Serializable disassemble(boolean[] value, SharedSessionContract session) {
return deepCopy( value );
}
@Override
public boolean[] assemble(Serializable cached, SharedSessionContract session) {
return deepCopy( (boolean[]) cached );
}
}
}

View File

@ -0,0 +1,198 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type.descriptor.java;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.SharedSessionContract;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.type.descriptor.WrapperOptions;
/**
* Descriptor for {@code double[]} handling.
*
* @author Christian Beikov
*/
public class DoublePrimitiveArrayJavaType extends AbstractArrayJavaType<double[], Double> {
public static final DoublePrimitiveArrayJavaType INSTANCE = new DoublePrimitiveArrayJavaType();
private DoublePrimitiveArrayJavaType() {
this( DoubleJavaType.INSTANCE );
}
protected DoublePrimitiveArrayJavaType(JavaType<Double> baseDescriptor) {
super( double[].class, baseDescriptor, new ArrayMutabilityPlan() );
}
@Override
public String extractLoggableRepresentation(double[] value) {
return value == null ? super.extractLoggableRepresentation( null ) : Arrays.toString( value );
}
@Override
public boolean areEqual(double[] one, double[] another) {
return Arrays.equals( one, another );
}
@Override
public int extractHashCode(double[] value) {
return Arrays.hashCode( value );
}
@Override
public String toString(double[] value) {
if ( value == null ) {
return null;
}
final StringBuilder sb = new StringBuilder();
sb.append( '{' );
sb.append( value[0] );
for ( int i = 1; i < value.length; i++ ) {
sb.append( value[i] );
sb.append( ',' );
}
sb.append( '}' );
return sb.toString();
}
@Override
public double[] fromString(CharSequence charSequence) {
if ( charSequence == null ) {
return null;
}
final List<Double> list = new ArrayList<>();
final char lastChar = charSequence.charAt( charSequence.length() - 1 );
final char firstChar = charSequence.charAt( 0 );
if ( firstChar != '{' || lastChar != '}' ) {
throw new IllegalArgumentException( "Cannot parse given string into array of strings. First and last character must be { and }" );
}
final int len = charSequence.length();
int elementStart = 1;
for ( int i = elementStart; i < len; i ++ ) {
final char c = charSequence.charAt( i );
if ( c == ',' ) {
list.add( Double.parseDouble( charSequence.subSequence( elementStart, i ).toString() ) );
elementStart = i + 1;
}
}
final double[] result = new double[list.size()];
for ( int i = 0; i < result.length; i ++ ) {
result[ i ] = list.get( i );
}
return result;
}
@Override
public <X> X unwrap(double[] value, Class<X> type, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( type.isInstance( value ) ) {
return (X) value;
}
else if ( Object[].class.isAssignableFrom( type ) ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();
final Object[] unwrapped = (Object[]) Array.newInstance( preferredJavaTypeClass, value.length );
for ( int i = 0; i < value.length; i++ ) {
unwrapped[i] = getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options );
}
return (X) unwrapped;
}
else if ( type == byte[].class ) {
// byte[] can only be requested if the value should be serialized
return (X) SerializationHelper.serialize( value );
}
else if ( type == BinaryStream.class ) {
// BinaryStream can only be requested if the value should be serialized
//noinspection unchecked
return (X) new BinaryStreamImpl( SerializationHelper.serialize( value ) );
}
else if ( type.isArray() ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();
final Object unwrapped = Array.newInstance( preferredJavaTypeClass, value.length );
for ( int i = 0; i < value.length; i++ ) {
Array.set( unwrapped, i, getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options ) );
}
return (X) unwrapped;
}
throw unknownUnwrap( type );
}
@Override
public <X> double[] wrap(X value, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( value instanceof java.sql.Array ) {
try {
//noinspection unchecked
value = (X) ( (java.sql.Array) value ).getArray();
}
catch ( SQLException ex ) {
// This basically shouldn't happen unless you've lost connection to the database.
throw new HibernateException( ex );
}
}
if ( value instanceof double[] ) {
return (double[]) value;
}
else if ( value instanceof byte[] ) {
// When the value is a byte[], this is a deserialization request
return (double[]) SerializationHelper.deserialize( (byte[]) value );
}
else if ( value instanceof BinaryStream ) {
// When the value is a BinaryStream, this is a deserialization request
return (double[]) SerializationHelper.deserialize( ( (BinaryStream) value ).getBytes() );
}
else if ( value.getClass().isArray() ) {
final double[] wrapped = new double[Array.getLength( value )];
for ( int i = 0; i < wrapped.length; i++ ) {
wrapped[i] = getElementJavaType().wrap( Array.get( value, i ), options );
}
return wrapped;
}
throw unknownWrap( value.getClass() );
}
private static class ArrayMutabilityPlan implements MutabilityPlan<double[]> {
@Override
public boolean isMutable() {
return true;
}
@Override
public double[] deepCopy(double[] value) {
return value == null ? null : value.clone();
}
@Override
public Serializable disassemble(double[] value, SharedSessionContract session) {
return deepCopy( value );
}
@Override
public double[] assemble(Serializable cached, SharedSessionContract session) {
return deepCopy( (double[]) cached );
}
}
}

View File

@ -0,0 +1,198 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type.descriptor.java;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.SharedSessionContract;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.type.descriptor.WrapperOptions;
/**
* Descriptor for {@code float[]} handling.
*
* @author Christian Beikov
*/
public class FloatPrimitiveArrayJavaType extends AbstractArrayJavaType<float[], Float> {
public static final FloatPrimitiveArrayJavaType INSTANCE = new FloatPrimitiveArrayJavaType();
private FloatPrimitiveArrayJavaType() {
this( FloatJavaType.INSTANCE );
}
protected FloatPrimitiveArrayJavaType(JavaType<Float> baseDescriptor) {
super( float[].class, baseDescriptor, new ArrayMutabilityPlan() );
}
@Override
public String extractLoggableRepresentation(float[] value) {
return value == null ? super.extractLoggableRepresentation( null ) : Arrays.toString( value );
}
@Override
public boolean areEqual(float[] one, float[] another) {
return Arrays.equals( one, another );
}
@Override
public int extractHashCode(float[] value) {
return Arrays.hashCode( value );
}
@Override
public String toString(float[] value) {
if ( value == null ) {
return null;
}
final StringBuilder sb = new StringBuilder();
sb.append( '{' );
sb.append( value[0] );
for ( int i = 1; i < value.length; i++ ) {
sb.append( value[i] );
sb.append( ',' );
}
sb.append( '}' );
return sb.toString();
}
@Override
public float[] fromString(CharSequence charSequence) {
if ( charSequence == null ) {
return null;
}
final List<Float> list = new ArrayList<>();
final char lastChar = charSequence.charAt( charSequence.length() - 1 );
final char firstChar = charSequence.charAt( 0 );
if ( firstChar != '{' || lastChar != '}' ) {
throw new IllegalArgumentException( "Cannot parse given string into array of strings. First and last character must be { and }" );
}
final int len = charSequence.length();
int elementStart = 1;
for ( int i = elementStart; i < len; i ++ ) {
final char c = charSequence.charAt( i );
if ( c == ',' ) {
list.add( Float.parseFloat( charSequence.subSequence( elementStart, i ).toString() ) );
elementStart = i + 1;
}
}
final float[] result = new float[list.size()];
for ( int i = 0; i < result.length; i ++ ) {
result[ i ] = list.get( i );
}
return result;
}
@Override
public <X> X unwrap(float[] value, Class<X> type, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( type.isInstance( value ) ) {
return (X) value;
}
else if ( Object[].class.isAssignableFrom( type ) ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();
final Object[] unwrapped = (Object[]) Array.newInstance( preferredJavaTypeClass, value.length );
for ( int i = 0; i < value.length; i++ ) {
unwrapped[i] = getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options );
}
return (X) unwrapped;
}
else if ( type == byte[].class ) {
// byte[] can only be requested if the value should be serialized
return (X) SerializationHelper.serialize( value );
}
else if ( type == BinaryStream.class ) {
// BinaryStream can only be requested if the value should be serialized
//noinspection unchecked
return (X) new BinaryStreamImpl( SerializationHelper.serialize( value ) );
}
else if ( type.isArray() ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();
final Object unwrapped = Array.newInstance( preferredJavaTypeClass, value.length );
for ( int i = 0; i < value.length; i++ ) {
Array.set( unwrapped, i, getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options ) );
}
return (X) unwrapped;
}
throw unknownUnwrap( type );
}
@Override
public <X> float[] wrap(X value, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( value instanceof java.sql.Array ) {
try {
//noinspection unchecked
value = (X) ( (java.sql.Array) value ).getArray();
}
catch ( SQLException ex ) {
// This basically shouldn't happen unless you've lost connection to the database.
throw new HibernateException( ex );
}
}
if ( value instanceof float[] ) {
return (float[]) value;
}
else if ( value instanceof byte[] ) {
// When the value is a byte[], this is a deserialization request
return (float[]) SerializationHelper.deserialize( (byte[]) value );
}
else if ( value instanceof BinaryStream ) {
// When the value is a BinaryStream, this is a deserialization request
return (float[]) SerializationHelper.deserialize( ( (BinaryStream) value ).getBytes() );
}
else if ( value.getClass().isArray() ) {
final float[] wrapped = new float[Array.getLength( value )];
for ( int i = 0; i < wrapped.length; i++ ) {
wrapped[i] = getElementJavaType().wrap( Array.get( value, i ), options );
}
return wrapped;
}
throw unknownWrap( value.getClass() );
}
private static class ArrayMutabilityPlan implements MutabilityPlan<float[]> {
@Override
public boolean isMutable() {
return true;
}
@Override
public float[] deepCopy(float[] value) {
return value == null ? null : value.clone();
}
@Override
public Serializable disassemble(float[] value, SharedSessionContract session) {
return deepCopy( value );
}
@Override
public float[] assemble(Serializable cached, SharedSessionContract session) {
return deepCopy( (float[]) cached );
}
}
}

View File

@ -0,0 +1,198 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type.descriptor.java;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.SharedSessionContract;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.type.descriptor.WrapperOptions;
/**
* Descriptor for {@code int[]} handling.
*
* @author Christian Beikov
*/
public class IntegerPrimitiveArrayJavaType extends AbstractArrayJavaType<int[], Integer> {
public static final IntegerPrimitiveArrayJavaType INSTANCE = new IntegerPrimitiveArrayJavaType();
private IntegerPrimitiveArrayJavaType() {
this( IntegerJavaType.INSTANCE );
}
protected IntegerPrimitiveArrayJavaType(JavaType<Integer> baseDescriptor) {
super( int[].class, baseDescriptor, new ArrayMutabilityPlan() );
}
@Override
public String extractLoggableRepresentation(int[] value) {
return value == null ? super.extractLoggableRepresentation( null ) : Arrays.toString( value );
}
@Override
public boolean areEqual(int[] one, int[] another) {
return Arrays.equals( one, another );
}
@Override
public int extractHashCode(int[] value) {
return Arrays.hashCode( value );
}
@Override
public String toString(int[] value) {
if ( value == null ) {
return null;
}
final StringBuilder sb = new StringBuilder();
sb.append( '{' );
sb.append( value[0] );
for ( int i = 1; i < value.length; i++ ) {
sb.append( value[i] );
sb.append( ',' );
}
sb.append( '}' );
return sb.toString();
}
@Override
public int[] fromString(CharSequence charSequence) {
if ( charSequence == null ) {
return null;
}
final List<Integer> list = new ArrayList<>();
final char lastChar = charSequence.charAt( charSequence.length() - 1 );
final char firstChar = charSequence.charAt( 0 );
if ( firstChar != '{' || lastChar != '}' ) {
throw new IllegalArgumentException( "Cannot parse given string into array of strings. First and last character must be { and }" );
}
final int len = charSequence.length();
int elementStart = 1;
for ( int i = elementStart; i < len; i ++ ) {
final char c = charSequence.charAt( i );
if ( c == ',' ) {
list.add( Integer.parseInt( charSequence, elementStart, i, 10 ) );
elementStart = i + 1;
}
}
final int[] result = new int[list.size()];
for ( int i = 0; i < result.length; i ++ ) {
result[ i ] = list.get( i );
}
return result;
}
@Override
public <X> X unwrap(int[] value, Class<X> type, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( type.isInstance( value ) ) {
return (X) value;
}
else if ( Object[].class.isAssignableFrom( type ) ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();
final Object[] unwrapped = (Object[]) Array.newInstance( preferredJavaTypeClass, value.length );
for ( int i = 0; i < value.length; i++ ) {
unwrapped[i] = getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options );
}
return (X) unwrapped;
}
else if ( type == byte[].class ) {
// byte[] can only be requested if the value should be serialized
return (X) SerializationHelper.serialize( value );
}
else if ( type == BinaryStream.class ) {
// BinaryStream can only be requested if the value should be serialized
//noinspection unchecked
return (X) new BinaryStreamImpl( SerializationHelper.serialize( value ) );
}
else if ( type.isArray() ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();
final Object unwrapped = Array.newInstance( preferredJavaTypeClass, value.length );
for ( int i = 0; i < value.length; i++ ) {
Array.set( unwrapped, i, getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options ) );
}
return (X) unwrapped;
}
throw unknownUnwrap( type );
}
@Override
public <X> int[] wrap(X value, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( value instanceof java.sql.Array ) {
try {
//noinspection unchecked
value = (X) ( (java.sql.Array) value ).getArray();
}
catch ( SQLException ex ) {
// This basically shouldn't happen unless you've lost connection to the database.
throw new HibernateException( ex );
}
}
if ( value instanceof int[] ) {
return (int[]) value;
}
else if ( value instanceof byte[] ) {
// When the value is a byte[], this is a deserialization request
return (int[]) SerializationHelper.deserialize( (byte[]) value );
}
else if ( value instanceof BinaryStream ) {
// When the value is a BinaryStream, this is a deserialization request
return (int[]) SerializationHelper.deserialize( ( (BinaryStream) value ).getBytes() );
}
else if ( value.getClass().isArray() ) {
final int[] wrapped = new int[Array.getLength( value )];
for ( int i = 0; i < wrapped.length; i++ ) {
wrapped[i] = getElementJavaType().wrap( Array.get( value, i ), options );
}
return wrapped;
}
throw unknownWrap( value.getClass() );
}
private static class ArrayMutabilityPlan implements MutabilityPlan<int[]> {
@Override
public boolean isMutable() {
return true;
}
@Override
public int[] deepCopy(int[] value) {
return value == null ? null : value.clone();
}
@Override
public Serializable disassemble(int[] value, SharedSessionContract session) {
return deepCopy( value );
}
@Override
public int[] assemble(Serializable cached, SharedSessionContract session) {
return deepCopy( (int[]) cached );
}
}
}

View File

@ -12,6 +12,7 @@ import java.lang.reflect.Type;
import java.util.Comparator;
import java.util.Objects;
import org.hibernate.Incubating;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
@ -253,8 +254,24 @@ public interface JavaType<T> extends Serializable {
/**
* Creates the {@link JavaType} for the given {@link ParameterizedType} based on this {@link JavaType} registered
* for the raw type.
*
* @deprecated Use {@link #createJavaType(ParameterizedType, TypeConfiguration)} instead
*/
@Deprecated(since = "6.1")
default JavaType<T> createJavaType(ParameterizedType parameterizedType) {
return this;
}
/**
* Creates the {@link JavaType} for the given {@link ParameterizedType} based on this {@link JavaType} registered
* for the raw type.
*
* @since 6.1
*/
@Incubating
default JavaType<T> createJavaType(
ParameterizedType parameterizedType,
TypeConfiguration typeConfiguration) {
return createJavaType( parameterizedType );
}
}

View File

@ -0,0 +1,198 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type.descriptor.java;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.SharedSessionContract;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.type.descriptor.WrapperOptions;
/**
* Descriptor for {@code long[]} handling.
*
* @author Christian Beikov
*/
public class LongPrimitiveArrayJavaType extends AbstractArrayJavaType<long[], Long> {
public static final LongPrimitiveArrayJavaType INSTANCE = new LongPrimitiveArrayJavaType();
private LongPrimitiveArrayJavaType() {
this( LongJavaType.INSTANCE );
}
protected LongPrimitiveArrayJavaType(JavaType<Long> baseDescriptor) {
super( long[].class, baseDescriptor, new ArrayMutabilityPlan() );
}
@Override
public String extractLoggableRepresentation(long[] value) {
return value == null ? super.extractLoggableRepresentation( null ) : Arrays.toString( value );
}
@Override
public boolean areEqual(long[] one, long[] another) {
return Arrays.equals( one, another );
}
@Override
public int extractHashCode(long[] value) {
return Arrays.hashCode( value );
}
@Override
public String toString(long[] value) {
if ( value == null ) {
return null;
}
final StringBuilder sb = new StringBuilder();
sb.append( '{' );
sb.append( value[0] );
for ( int i = 1; i < value.length; i++ ) {
sb.append( value[i] );
sb.append( ',' );
}
sb.append( '}' );
return sb.toString();
}
@Override
public long[] fromString(CharSequence charSequence) {
if ( charSequence == null ) {
return null;
}
final List<Long> list = new ArrayList<>();
final char lastChar = charSequence.charAt( charSequence.length() - 1 );
final char firstChar = charSequence.charAt( 0 );
if ( firstChar != '{' || lastChar != '}' ) {
throw new IllegalArgumentException( "Cannot parse given string into array of strings. First and last character must be { and }" );
}
final int len = charSequence.length();
int elementStart = 1;
for ( int i = elementStart; i < len; i ++ ) {
final char c = charSequence.charAt( i );
if ( c == ',' ) {
list.add( Long.parseLong( charSequence, elementStart, i, 10 ) );
elementStart = i + 1;
}
}
final long[] result = new long[list.size()];
for ( int i = 0; i < result.length; i ++ ) {
result[ i ] = list.get( i );
}
return result;
}
@Override
public <X> X unwrap(long[] value, Class<X> type, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( type.isInstance( value ) ) {
return (X) value;
}
else if ( Object[].class.isAssignableFrom( type ) ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();
final Object[] unwrapped = (Object[]) Array.newInstance( preferredJavaTypeClass, value.length );
for ( int i = 0; i < value.length; i++ ) {
unwrapped[i] = getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options );
}
return (X) unwrapped;
}
else if ( type == byte[].class ) {
// byte[] can only be requested if the value should be serialized
return (X) SerializationHelper.serialize( value );
}
else if ( type == BinaryStream.class ) {
// BinaryStream can only be requested if the value should be serialized
//noinspection unchecked
return (X) new BinaryStreamImpl( SerializationHelper.serialize( value ) );
}
else if ( type.isArray() ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();
final Object unwrapped = Array.newInstance( preferredJavaTypeClass, value.length );
for ( int i = 0; i < value.length; i++ ) {
Array.set( unwrapped, i, getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options ) );
}
return (X) unwrapped;
}
throw unknownUnwrap( type );
}
@Override
public <X> long[] wrap(X value, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( value instanceof java.sql.Array ) {
try {
//noinspection unchecked
value = (X) ( (java.sql.Array) value ).getArray();
}
catch ( SQLException ex ) {
// This basically shouldn't happen unless you've lost connection to the database.
throw new HibernateException( ex );
}
}
if ( value instanceof long[] ) {
return (long[]) value;
}
else if ( value instanceof byte[] ) {
// When the value is a byte[], this is a deserialization request
return (long[]) SerializationHelper.deserialize( (byte[]) value );
}
else if ( value instanceof BinaryStream ) {
// When the value is a BinaryStream, this is a deserialization request
return (long[]) SerializationHelper.deserialize( ( (BinaryStream) value ).getBytes() );
}
else if ( value.getClass().isArray() ) {
final long[] wrapped = new long[Array.getLength( value )];
for ( int i = 0; i < wrapped.length; i++ ) {
wrapped[i] = getElementJavaType().wrap( Array.get( value, i ), options );
}
return wrapped;
}
throw unknownWrap( value.getClass() );
}
private static class ArrayMutabilityPlan implements MutabilityPlan<long[]> {
@Override
public boolean isMutable() {
return true;
}
@Override
public long[] deepCopy(long[] value) {
return value == null ? null : value.clone();
}
@Override
public Serializable disassemble(long[] value, SharedSessionContract session) {
return deepCopy( value );
}
@Override
public long[] assemble(Serializable cached, SharedSessionContract session) {
return deepCopy( (long[]) cached );
}
}
}

View File

@ -0,0 +1,198 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type.descriptor.java;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.SharedSessionContract;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.type.descriptor.WrapperOptions;
/**
* Descriptor for {@code short[]} handling.
*
* @author Christian Beikov
*/
public class ShortPrimitiveArrayJavaType extends AbstractArrayJavaType<short[], Short> {
public static final ShortPrimitiveArrayJavaType INSTANCE = new ShortPrimitiveArrayJavaType();
private ShortPrimitiveArrayJavaType() {
this( ShortJavaType.INSTANCE );
}
protected ShortPrimitiveArrayJavaType(JavaType<Short> baseDescriptor) {
super( short[].class, baseDescriptor, new ArrayMutabilityPlan() );
}
@Override
public String extractLoggableRepresentation(short[] value) {
return value == null ? super.extractLoggableRepresentation( null ) : Arrays.toString( value );
}
@Override
public boolean areEqual(short[] one, short[] another) {
return Arrays.equals( one, another );
}
@Override
public int extractHashCode(short[] value) {
return Arrays.hashCode( value );
}
@Override
public String toString(short[] value) {
if ( value == null ) {
return null;
}
final StringBuilder sb = new StringBuilder();
sb.append( '{' );
sb.append( value[0] );
for ( int i = 1; i < value.length; i++ ) {
sb.append( value[i] );
sb.append( ',' );
}
sb.append( '}' );
return sb.toString();
}
@Override
public short[] fromString(CharSequence charSequence) {
if ( charSequence == null ) {
return null;
}
final List<Short> list = new ArrayList<>();
final char lastChar = charSequence.charAt( charSequence.length() - 1 );
final char firstChar = charSequence.charAt( 0 );
if ( firstChar != '{' || lastChar != '}' ) {
throw new IllegalArgumentException( "Cannot parse given string into array of strings. First and last character must be { and }" );
}
final int len = charSequence.length();
int elementStart = 1;
for ( int i = elementStart; i < len; i ++ ) {
final char c = charSequence.charAt( i );
if ( c == ',' ) {
list.add( Short.parseShort( charSequence.subSequence( elementStart, i ).toString(), 10 ) );
elementStart = i + 1;
}
}
final short[] result = new short[list.size()];
for ( int i = 0; i < result.length; i ++ ) {
result[ i ] = list.get( i );
}
return result;
}
@Override
public <X> X unwrap(short[] value, Class<X> type, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( type.isInstance( value ) ) {
return (X) value;
}
else if ( Object[].class.isAssignableFrom( type ) ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();
final Object[] unwrapped = (Object[]) Array.newInstance( preferredJavaTypeClass, value.length );
for ( int i = 0; i < value.length; i++ ) {
unwrapped[i] = getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options );
}
return (X) unwrapped;
}
else if ( type == byte[].class ) {
// byte[] can only be requested if the value should be serialized
return (X) SerializationHelper.serialize( value );
}
else if ( type == BinaryStream.class ) {
// BinaryStream can only be requested if the value should be serialized
//noinspection unchecked
return (X) new BinaryStreamImpl( SerializationHelper.serialize( value ) );
}
else if ( type.isArray() ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();
final Object unwrapped = Array.newInstance( preferredJavaTypeClass, value.length );
for ( int i = 0; i < value.length; i++ ) {
Array.set( unwrapped, i, getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options ) );
}
return (X) unwrapped;
}
throw unknownUnwrap( type );
}
@Override
public <X> short[] wrap(X value, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( value instanceof java.sql.Array ) {
try {
//noinspection unchecked
value = (X) ( (java.sql.Array) value ).getArray();
}
catch ( SQLException ex ) {
// This basically shouldn't happen unless you've lost connection to the database.
throw new HibernateException( ex );
}
}
if ( value instanceof short[] ) {
return (short[]) value;
}
else if ( value instanceof byte[] ) {
// When the value is a byte[], this is a deserialization request
return (short[]) SerializationHelper.deserialize( (byte[]) value );
}
else if ( value instanceof BinaryStream ) {
// When the value is a BinaryStream, this is a deserialization request
return (short[]) SerializationHelper.deserialize( ( (BinaryStream) value ).getBytes() );
}
else if ( value.getClass().isArray() ) {
final short[] wrapped = new short[Array.getLength( value )];
for ( int i = 0; i < wrapped.length; i++ ) {
wrapped[i] = getElementJavaType().wrap( Array.get( value, i ), options );
}
return wrapped;
}
throw unknownWrap( value.getClass() );
}
private static class ArrayMutabilityPlan implements MutabilityPlan<short[]> {
@Override
public boolean isMutable() {
return true;
}
@Override
public short[] deepCopy(short[] value) {
return value == null ? null : value.clone();
}
@Override
public Serializable disassemble(short[] value, SharedSessionContract session) {
return deepCopy( value );
}
@Override
public short[] assemble(Serializable cached, SharedSessionContract session) {
return deepCopy( (short[]) cached );
}
}
}

View File

@ -0,0 +1,473 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type.descriptor.java.spi;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import org.hibernate.HibernateException;
import org.hibernate.Incubating;
import org.hibernate.SharedSessionContract;
import org.hibernate.collection.spi.CollectionSemantics;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.BasicCollectionType;
import org.hibernate.type.BasicPluralType;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.AbstractJavaType;
import org.hibernate.type.descriptor.java.BasicPluralJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Descriptor for {@code Collection<T>} handling.
*
* @author Christian Beikov
*/
@Incubating
public class BasicCollectionJavaType<C extends Collection<E>, E> extends AbstractJavaType<C> implements
BasicPluralJavaType<E> {
private final CollectionSemantics<C, E> semantics;
private final JavaType<E> componentJavaType;
public BasicCollectionJavaType(ParameterizedType type, JavaType<E> componentJavaType, CollectionSemantics<C, E> semantics) {
super( type, new CollectionMutabilityPlan<>( componentJavaType, semantics ) );
this.semantics = semantics;
this.componentJavaType = componentJavaType;
}
@Override
public JavaType<E> getElementJavaType() {
return componentJavaType;
}
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
final int preferredSqlTypeCodeForArray = indicators.getPreferredSqlTypeCodeForArray();
// Always determine the recommended type to make sure this is a valid basic java type
final JdbcType recommendedComponentJdbcType = componentJavaType.getRecommendedJdbcType( indicators );
final TypeConfiguration typeConfiguration = indicators.getTypeConfiguration();
final JdbcType jdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( preferredSqlTypeCodeForArray );
if ( jdbcType instanceof ArrayJdbcType ) {
return ( (ArrayJdbcType) jdbcType ).resolveType(
typeConfiguration,
typeConfiguration.getServiceRegistry()
.getService( JdbcServices.class )
.getDialect(),
recommendedComponentJdbcType,
ColumnTypeInformation.EMPTY
);
}
return indicators.getTypeConfiguration().getJdbcTypeRegistry().getDescriptor( preferredSqlTypeCodeForArray );
}
public CollectionSemantics<C, E> getSemantics() {
return semantics;
}
@Override
public BasicType<?> resolveType(
TypeConfiguration typeConfiguration,
Dialect dialect,
BasicType<E> elementType,
ColumnTypeInformation columnTypeInformation) {
final Class<?> elementJavaTypeClass = elementType.getJavaTypeDescriptor().getJavaTypeClass();
if ( elementType instanceof BasicPluralType<?, ?> || elementJavaTypeClass != null && elementJavaTypeClass.isArray() ) {
return null;
}
final BasicCollectionJavaType<C, E> collectionJavaType;
if ( componentJavaType == elementType.getJavaTypeDescriptor() ) {
collectionJavaType = this;
}
else {
collectionJavaType = new BasicCollectionJavaType<>(
(ParameterizedType) getJavaType(),
elementType.getJavaTypeDescriptor(),
semantics
);
// Register the collection type as that will be resolved in the next step
typeConfiguration.getJavaTypeRegistry().addDescriptor( collectionJavaType );
}
return typeConfiguration.standardBasicTypeForJavaType(
collectionJavaType.getJavaType(),
javaType -> {
JdbcType arrayJdbcType = typeConfiguration.getJdbcTypeRegistry().getDescriptor( Types.ARRAY );
if ( arrayJdbcType instanceof ArrayJdbcType ) {
arrayJdbcType = ( (ArrayJdbcType) arrayJdbcType ).resolveType(
typeConfiguration,
dialect,
elementType,
columnTypeInformation
);
}
//noinspection unchecked,rawtypes
return new BasicCollectionType( elementType, arrayJdbcType, collectionJavaType );
}
);
}
@Override
public String extractLoggableRepresentation(C value) {
if ( value == null ) {
return "null";
}
final Iterator<E> iterator = value.iterator();
if ( !iterator.hasNext() ) {
return "[]";
}
final StringBuilder sb = new StringBuilder();
sb.append( '[' );
do {
final E element = iterator.next();
sb.append( componentJavaType.toString( element ) );
if ( !iterator.hasNext() ) {
return sb.append( ']' ).toString();
}
sb.append( ", " );
} while ( true );
}
@Override
public boolean areEqual(C one, C another) {
if ( one == null && another == null ) {
return true;
}
if ( one == null || another == null ) {
return false;
}
if ( one.size() != another.size() ) {
return false;
}
switch ( semantics.getCollectionClassification() ) {
case ARRAY:
case LIST:
case ORDERED_SET:
case SORTED_SET:
final Iterator<E> iterator1 = one.iterator();
final Iterator<E> iterator2 = another.iterator();
while ( iterator1.hasNext() ) {
if ( !componentJavaType.areEqual( iterator1.next(), iterator2.next() ) ) {
return false;
}
}
default: {
OUTER: for ( E e1 : one ) {
for ( E e2 : another ) {
if ( componentJavaType.areEqual( e1, e2 ) ) {
continue OUTER;
}
}
return false;
}
}
}
return true;
}
@Override
public int extractHashCode(C value) {
int result = 0;
if ( value != null && !value.isEmpty() ) {
for ( E element : value ) {
if ( element != null ) {
result += componentJavaType.extractHashCode( element );
}
}
}
return result;
}
@Override
public String toString(C value) {
if ( value == null ) {
return null;
}
final StringBuilder sb = new StringBuilder();
sb.append( '{' );
String glue = "";
for ( E v : value ) {
sb.append( glue );
if ( v == null ) {
sb.append( "null" );
glue = ",";
continue;
}
sb.append( '"' );
String valstr = this.componentJavaType.toString( v );
// using replaceAll is a shorter, but much slower way to do this
for (int i = 0, len = valstr.length(); i < len; i ++ ) {
char c = valstr.charAt( i );
// Surrogate pairs. This is how they're done.
if (c == '\\' || c == '"') {
sb.append( '\\' );
}
sb.append( c );
}
sb.append( '"' );
glue = ",";
}
sb.append( '}' );
final String result = sb.toString();
return result;
}
@Override
public C fromString(CharSequence charSequence) {
if ( charSequence == null ) {
return null;
}
java.util.ArrayList<String> lst = new java.util.ArrayList<>();
StringBuilder sb = null;
char lastChar = charSequence.charAt( charSequence.length() - 1 );
char firstChar = charSequence.charAt( 0 );
if ( firstChar != '{' || lastChar != '}' ) {
throw new IllegalArgumentException( "Cannot parse given string into array of strings. First and last character must be { and }" );
}
int len = charSequence.length();
boolean inquote = false;
for ( int i = 1; i < len; i ++ ) {
char c = charSequence.charAt( i );
if ( c == '"' ) {
if (inquote) {
lst.add( sb.toString() );
}
else {
sb = new StringBuilder();
}
inquote = !inquote;
continue;
}
else if ( !inquote ) {
if ( Character.isWhitespace( c ) ) {
continue;
}
else if ( c == ',' ) {
// treat no-value between commas to mean null
if ( sb == null ) {
lst.add( null );
}
else {
sb = null;
}
continue;
}
else {
// i + 4, because there has to be a comma or closing brace after null
if ( i + 4 < len
&& charSequence.charAt( i ) == 'n'
&& charSequence.charAt( i + 1 ) == 'u'
&& charSequence.charAt( i + 2 ) == 'l'
&& charSequence.charAt( i + 3 ) == 'l') {
lst.add( null );
i += 4;
continue;
}
if (i + 1 == len) {
break;
}
throw new IllegalArgumentException( "Cannot parse given string into array of strings."
+ " Outside of quote, but neither whitespace, comma, array end, nor null found." );
}
}
else if ( c == '\\' && i + 2 < len && (charSequence.charAt( i + 1 ) == '\\' || charSequence.charAt( i + 1 ) == '"')) {
c = charSequence.charAt( ++i );
}
// If there is ever a null-pointer here, the if-else logic before is incomplete
sb.append( c );
}
final C result = semantics.instantiateRaw( lst.size(), null );
for ( int i = 0; i < lst.size(); i ++ ) {
if ( lst.get( i ) != null ) {
result.add( componentJavaType.fromString( lst.get( i ) ) );
}
}
return result;
}
@Override
public <X> X unwrap(C value, Class<X> type, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( type.isInstance( value ) ) {
//noinspection unchecked
return (X) value;
}
else if ( type == byte[].class ) {
// byte[] can only be requested if the value should be serialized
return (X) SerializationHelper.serialize( asArrayList( value ) );
}
else if ( type == BinaryStream.class ) {
// BinaryStream can only be requested if the value should be serialized
//noinspection unchecked
return (X) new BinaryStreamImpl( SerializationHelper.serialize( asArrayList( value ) ) );
}
else if ( Object[].class.isAssignableFrom( type ) ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();
final Object[] unwrapped = (Object[]) Array.newInstance( preferredJavaTypeClass, value.size() );
int i = 0;
for ( E element : value ) {
unwrapped[i] = componentJavaType.unwrap( element, preferredJavaTypeClass, options );
i++;
}
//noinspection unchecked
return (X) unwrapped;
}
else if ( type.isArray() ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();
//noinspection unchecked
final X unwrapped = (X) Array.newInstance( preferredJavaTypeClass, value.size() );
int i = 0;
for ( E element : value ) {
Array.set( unwrapped, i, componentJavaType.unwrap( element, preferredJavaTypeClass, options ) );
i++;
}
return unwrapped;
}
throw unknownUnwrap( type );
}
@Override
public <X> C wrap(X value, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( value instanceof java.sql.Array ) {
try {
//noinspection unchecked
value = (X) ( (java.sql.Array) value ).getArray();
}
catch ( SQLException ex ) {
// This basically shouldn't happen unless you've lost connection to the database.
throw new HibernateException( ex );
}
}
if ( value instanceof Object[] ) {
final Object[] raw = (Object[]) value;
final C wrapped = semantics.instantiateRaw( raw.length, null );
if ( componentJavaType.getJavaTypeClass().isAssignableFrom( value.getClass().getComponentType() ) ) {
for ( Object o : raw ) {
//noinspection unchecked
wrapped.add( (E) o );
}
}
else {
for ( Object o : raw ) {
wrapped.add( componentJavaType.wrap( o, options ) );
}
}
return wrapped;
}
else if ( value instanceof byte[] ) {
// When the value is a byte[], this is a deserialization request
//noinspection unchecked
return fromCollection( (ArrayList<E>) SerializationHelper.deserialize( (byte[]) value ) );
}
else if ( value instanceof BinaryStream ) {
// When the value is a BinaryStream, this is a deserialization request
//noinspection unchecked
return fromCollection( (ArrayList<E>) SerializationHelper.deserialize( ( (BinaryStream) value ).getBytes() ) );
}
else if ( value instanceof Collection<?> ) {
//noinspection unchecked
return fromCollection( (Collection<E>) value );
}
else if ( value.getClass().isArray() ) {
final int length = Array.getLength( value );
final C wrapped = semantics.instantiateRaw( length, null );
for ( int i = 0; i < length; i++ ) {
wrapped.add( componentJavaType.wrap( Array.get( value, i ), options ) );
}
return wrapped;
}
throw unknownWrap( value.getClass() );
}
private ArrayList<E> asArrayList(C value) {
if ( value instanceof ArrayList ) {
//noinspection unchecked
return (ArrayList<E>) value;
}
return new ArrayList<>( value );
}
private C fromCollection(Collection<E> value) {
switch ( semantics.getCollectionClassification() ) {
case LIST:
case BAG:
if ( value instanceof ArrayList<?> ) {
//noinspection unchecked
return (C) value;
}
default:
final C collection = semantics.instantiateRaw( value.size(), null );
collection.addAll( value );
return collection;
}
}
private static class CollectionMutabilityPlan<C extends Collection<E>, E> implements MutabilityPlan<C> {
private final CollectionSemantics<C, E> semantics;
private final MutabilityPlan<E> componentPlan;
public CollectionMutabilityPlan(JavaType<E> baseDescriptor, CollectionSemantics<C, E> semantics) {
this.semantics = semantics;
this.componentPlan = baseDescriptor.getMutabilityPlan();
}
@Override
public boolean isMutable() {
return true;
}
@Override
public C deepCopy(C value) {
if ( value == null ) {
return null;
}
final C copy = semantics.instantiateRaw( value.size(), null );
for ( E element : value ) {
copy.add( componentPlan.deepCopy( element ) );
}
return copy;
}
@Override
public Serializable disassemble(C value, SharedSessionContract session) {
return (Serializable) deepCopy( value );
}
@Override
public C assemble(Serializable cached, SharedSessionContract session) {
//noinspection unchecked
return deepCopy( (C) cached );
}
}
}

View File

@ -17,6 +17,7 @@ import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Extension of the general JavaType for "collection types"
@ -47,9 +48,27 @@ public class CollectionJavaType<C> extends AbstractClassJavaType<C> {
}
@Override
public JavaType<C> createJavaType(ParameterizedType parameterizedType) {
//noinspection unchecked
public JavaType<C> createJavaType(
ParameterizedType parameterizedType,
TypeConfiguration typeConfiguration) {
switch ( semantics.getCollectionClassification() ) {
case ARRAY:
case BAG:
case ID_BAG:
case LIST:
case SET:
case SORTED_SET:
case ORDERED_SET:
//noinspection unchecked,rawtypes
return new BasicCollectionJavaType(
parameterizedType,
typeConfiguration.getJavaTypeRegistry()
.resolveDescriptor( parameterizedType.getActualTypeArguments()[0] ),
semantics
);
}
// Construct a basic java type that knows its parametrization
//noinspection unchecked
return new UnknownBasicJavaType<>( parameterizedType, (MutabilityPlan<C>) MutableMutabilityPlan.INSTANCE );
}

View File

@ -34,6 +34,7 @@ import org.hibernate.type.descriptor.java.BigDecimalJavaType;
import org.hibernate.type.descriptor.java.BigIntegerJavaType;
import org.hibernate.type.descriptor.java.BlobJavaType;
import org.hibernate.type.descriptor.java.BooleanJavaType;
import org.hibernate.type.descriptor.java.BooleanPrimitiveArrayJavaType;
import org.hibernate.type.descriptor.java.ByteArrayJavaType;
import org.hibernate.type.descriptor.java.ByteJavaType;
import org.hibernate.type.descriptor.java.CalendarJavaType;
@ -44,12 +45,16 @@ import org.hibernate.type.descriptor.java.ClobJavaType;
import org.hibernate.type.descriptor.java.CurrencyJavaType;
import org.hibernate.type.descriptor.java.DateJavaType;
import org.hibernate.type.descriptor.java.DoubleJavaType;
import org.hibernate.type.descriptor.java.DoublePrimitiveArrayJavaType;
import org.hibernate.type.descriptor.java.DurationJavaType;
import org.hibernate.type.descriptor.java.FloatJavaType;
import org.hibernate.type.descriptor.java.FloatPrimitiveArrayJavaType;
import org.hibernate.type.descriptor.java.InetAddressJavaType;
import org.hibernate.type.descriptor.java.InstantJavaType;
import org.hibernate.type.descriptor.java.IntegerPrimitiveArrayJavaType;
import org.hibernate.type.descriptor.java.IntegerJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.LongPrimitiveArrayJavaType;
import org.hibernate.type.descriptor.java.ObjectJavaType;
import org.hibernate.type.descriptor.java.JdbcDateJavaType;
import org.hibernate.type.descriptor.java.JdbcTimeJavaType;
@ -65,6 +70,7 @@ import org.hibernate.type.descriptor.java.OffsetTimeJavaType;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.java.PrimitiveCharacterArrayJavaType;
import org.hibernate.type.descriptor.java.ShortJavaType;
import org.hibernate.type.descriptor.java.ShortPrimitiveArrayJavaType;
import org.hibernate.type.descriptor.java.StringJavaType;
import org.hibernate.type.descriptor.java.TimeZoneJavaType;
import org.hibernate.type.descriptor.java.UUIDJavaType;
@ -96,7 +102,6 @@ public class JavaTypeBaseline {
/**
* The process of registering all the baseline registrations
*/
@SuppressWarnings("unchecked")
public static void prime(BaselineTarget target) {
primePrimitive( target, ByteJavaType.INSTANCE );
primePrimitive( target, BooleanJavaType.INSTANCE );
@ -123,6 +128,14 @@ public class JavaTypeBaseline {
target.addBaselineDescriptor( PrimitiveByteArrayJavaType.INSTANCE );
target.addBaselineDescriptor( PrimitiveCharacterArrayJavaType.INSTANCE );
// Register special ArrayJavaType implementations for primitive types
target.addBaselineDescriptor( BooleanPrimitiveArrayJavaType.INSTANCE );
target.addBaselineDescriptor( ShortPrimitiveArrayJavaType.INSTANCE );
target.addBaselineDescriptor( IntegerPrimitiveArrayJavaType.INSTANCE );
target.addBaselineDescriptor( LongPrimitiveArrayJavaType.INSTANCE );
target.addBaselineDescriptor( FloatPrimitiveArrayJavaType.INSTANCE );
target.addBaselineDescriptor( DoublePrimitiveArrayJavaType.INSTANCE );
target.addBaselineDescriptor( DurationJavaType.INSTANCE );
target.addBaselineDescriptor( InstantJavaType.INSTANCE );
target.addBaselineDescriptor( LocalDateJavaType.INSTANCE );
@ -173,8 +186,8 @@ public class JavaTypeBaseline {
target.addBaselineDescriptor( new CollectionJavaType( LinkedHashMap.class, StandardOrderedMapSemantics.INSTANCE ) );
}
private static void primePrimitive(BaselineTarget target, JavaType descriptor) {
private static void primePrimitive(BaselineTarget target, JavaType<?> descriptor) {
target.addBaselineDescriptor( descriptor );
target.addBaselineDescriptor( ( (PrimitiveJavaType) descriptor ).getPrimitiveClass(), descriptor );
target.addBaselineDescriptor( ( (PrimitiveJavaType<?>) descriptor ).getPrimitiveClass(), descriptor );
}
}

View File

@ -12,6 +12,7 @@ import java.lang.reflect.Type;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import org.hibernate.type.descriptor.java.ArrayJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
@ -136,13 +137,27 @@ public class JavaTypeRegistry implements JavaTypeBaseline.BaselineTarget, Serial
final ParameterizedType parameterizedType = (ParameterizedType) javaType;
final JavaType<J> rawType = findDescriptor( ( parameterizedType ).getRawType() );
if ( rawType != null ) {
return rawType.createJavaType( parameterizedType );
return rawType.createJavaType( parameterizedType, typeConfiguration );
}
}
return RegistryHelper.INSTANCE.createTypeDescriptor(
javaType,
final Type elementJavaType;
JavaType<J> elementTypeDescriptor;
if ( javaType instanceof Class<?> && ( (Class<?>) javaType ).isArray() ) {
elementJavaType = ( (Class<?>) javaType ).getComponentType();
elementTypeDescriptor = findDescriptor( elementJavaType );
}
else {
elementJavaType = javaType;
elementTypeDescriptor = null;
}
if ( elementTypeDescriptor == null ) {
elementTypeDescriptor = RegistryHelper.INSTANCE.createTypeDescriptor(
elementJavaType,
() -> {
final MutabilityPlan<J> determinedPlan = RegistryHelper.INSTANCE.determineMutabilityPlan( javaType, typeConfiguration );
final MutabilityPlan<J> determinedPlan = RegistryHelper.INSTANCE.determineMutabilityPlan(
elementJavaType,
typeConfiguration
);
if ( determinedPlan != null ) {
return determinedPlan;
}
@ -154,6 +169,12 @@ public class JavaTypeRegistry implements JavaTypeBaseline.BaselineTarget, Serial
typeConfiguration
);
}
if ( javaType != elementJavaType ) {
//noinspection unchecked
return (JavaType<J>) new ArrayJavaType<>( elementTypeDescriptor );
}
return elementTypeDescriptor;
}
);
}

View File

@ -52,6 +52,10 @@ public class UnknownBasicJavaType<T> extends AbstractJavaType<T> {
@Override
public <X> X unwrap(T value, Class<X> type, WrapperOptions options) {
if ( type.isAssignableFrom( getJavaTypeClass() ) ) {
//noinspection unchecked
return (X) value;
}
throw new UnsupportedOperationException(
"Unwrap strategy not known for this Java type : " + getJavaType().getTypeName()
);
@ -59,6 +63,10 @@ public class UnknownBasicJavaType<T> extends AbstractJavaType<T> {
@Override
public <X> T wrap(X value, WrapperOptions options) {
if ( getJavaTypeClass().isInstance( value ) ) {
//noinspection unchecked
return (T) value;
}
throw new UnsupportedOperationException(
"Wrap strategy not known for this Java type : " + getJavaType().getTypeName()
);

View File

@ -0,0 +1,248 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.type.descriptor.jdbc;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.hibernate.HibernateException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.BasicPluralJavaType;
import org.hibernate.type.descriptor.java.BasicJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterArray;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Descriptor for {@link Types#ARRAY ARRAY} handling.
*
* @author Christian Beikov
* @author Jordan Gigov
*/
public class ArrayJdbcType implements JdbcType {
public static final ArrayJdbcType INSTANCE = new ArrayJdbcType( ObjectJdbcType.INSTANCE );
private static final ClassValue<Method> NAME_BINDER = new ClassValue<Method>() {
@Override
protected Method computeValue(Class<?> type) {
try {
return type.getMethod( "setArray", String.class, java.sql.Array.class );
}
catch ( Exception ex ) {
// add logging? Did we get NoSuchMethodException or SecurityException?
// Doesn't matter which. We can't use it.
}
return null;
}
};
private final JdbcType elementJdbcType;
public ArrayJdbcType(JdbcType elementJdbcType) {
this.elementJdbcType = elementJdbcType;
}
public JdbcType resolveType(
TypeConfiguration typeConfiguration,
Dialect dialect,
BasicType<?> elementType,
ColumnTypeInformation columnTypeInformation) {
return resolveType( typeConfiguration, dialect, elementType.getJdbcType(), columnTypeInformation );
}
public JdbcType resolveType(
TypeConfiguration typeConfiguration,
Dialect dialect,
JdbcType elementType,
ColumnTypeInformation columnTypeInformation) {
return new ArrayJdbcType( elementType );
}
@Override
public int getJdbcTypeCode() {
return Types.ARRAY;
}
public JdbcType getElementJdbcType() {
return elementJdbcType;
}
@Override
public <T> BasicJavaType<T> getJdbcRecommendedJavaTypeMapping(
Integer precision,
Integer scale,
TypeConfiguration typeConfiguration) {
final BasicJavaType<Object> elementJavaType = elementJdbcType.getJdbcRecommendedJavaTypeMapping(
precision,
scale,
typeConfiguration
);
return (BasicJavaType<T>) typeConfiguration.getJavaTypeRegistry().resolveDescriptor(
Array.newInstance( elementJavaType.getJavaTypeClass(), 0 ).getClass()
);
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaTypeDescriptor) {
//noinspection unchecked
final BasicPluralJavaType<T> basicPluralJavaType = (BasicPluralJavaType<T>) javaTypeDescriptor;
final JdbcLiteralFormatter<T> elementFormatter = elementJdbcType.getJdbcLiteralFormatter( basicPluralJavaType.getElementJavaType() );
return new JdbcLiteralFormatterArray<>( javaTypeDescriptor, elementFormatter );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return java.sql.Array.class;
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaTypeDescriptor) {
//noinspection unchecked
final BasicPluralJavaType<X> containerJavaType = (BasicPluralJavaType<X>) javaTypeDescriptor;
return new BasicBinder<X>( javaTypeDescriptor, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
final java.sql.Array arr = getArray( value, containerJavaType, options );
st.setArray( index, arr );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
final java.sql.Array arr = getArray( value, containerJavaType, options );
final Method nameBinder = NAME_BINDER.get( st.getClass() );
if ( nameBinder == null ) {
try {
st.setObject( name, arr, java.sql.Types.ARRAY );
return;
}
catch (SQLException ex) {
throw new HibernateException( "JDBC driver does not support named parameters for setArray. Use positional.", ex );
}
}
// Not that it's supposed to have setArray(String,Array) by standard.
// There are numerous missing methods that only have versions for positional parameter,
// but not named ones.
try {
nameBinder.invoke( st, name, arr );
}
catch ( Throwable t ) {
throw new HibernateException( t );
}
}
private java.sql.Array getArray(
X value,
BasicPluralJavaType<X> containerJavaType,
WrapperOptions options) throws SQLException {
final TypeConfiguration typeConfiguration = options.getSessionFactory().getTypeConfiguration();
final JdbcType underlyingJdbcType = typeConfiguration.getJdbcTypeRegistry()
.getDescriptor( elementJdbcType.getDefaultSqlTypeCode() );
final Class<?> preferredJavaTypeClass = underlyingJdbcType.getPreferredJavaTypeClass( options );
final Class<?> elementJdbcJavaTypeClass;
if ( preferredJavaTypeClass == null ) {
elementJdbcJavaTypeClass = underlyingJdbcType.getJdbcRecommendedJavaTypeMapping(
null,
null,
typeConfiguration
).getJavaTypeClass();
}
else {
elementJdbcJavaTypeClass = preferredJavaTypeClass;
}
//noinspection unchecked
final Class<Object[]> arrayClass = (Class<Object[]>) Array.newInstance(
elementJdbcJavaTypeClass,
0
).getClass();
final Object[] objects = javaTypeDescriptor.unwrap( value, arrayClass, options );
final SharedSessionContractImplementor session = options.getSession();
// TODO: ideally, we would have the actual size or the actual type/column accessible
// this is something that we would need for supporting composite types anyway
final Size size = session.getJdbcServices()
.getDialect()
.getSizeStrategy()
.resolveSize( elementJdbcType, containerJavaType.getElementJavaType(), null, null, null );
String typeName = session.getTypeConfiguration()
.getDdlTypeRegistry()
.getDescriptor( elementJdbcType.getDefaultSqlTypeCode() )
.getTypeName( size );
int cutIndex = typeName.indexOf( '(' );
if ( cutIndex > 0 ) {
// getTypeName for this case required length, etc, parameters.
// Cut them out and use database defaults.
typeName = typeName.substring( 0, cutIndex );
}
return session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection().createArrayOf( typeName, objects );
}
};
}
@Override
public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaTypeDescriptor) {
return new BasicExtractor<X>( javaTypeDescriptor, this ) {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( rs.getArray( paramIndex ), options );
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getArray( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getArray( name ), options );
}
};
}
@Override
public String getFriendlyName() {
return "ARRAY";
}
@Override
public String toString() {
return "ArrayTypeDescriptor(" + getFriendlyName() + ")";
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
ArrayJdbcType that = (ArrayJdbcType) o;
return elementJdbcType.equals( that.elementJdbcType );
}
@Override
public int hashCode() {
return elementJdbcType.hashCode();
}
}

View File

@ -49,6 +49,11 @@ public class BigIntJdbcType implements JdbcType {
return new JdbcLiteralFormatterNumericData<>( javaType, Long.class );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return Long.class;
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -89,6 +89,11 @@ public abstract class BlobJdbcType implements JdbcType {
return "BlobTypeDescriptor(DEFAULT)";
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return byte[].class;
}
@Override
public <X> BasicBinder<X> getBlobBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@ -129,6 +134,11 @@ public abstract class BlobJdbcType implements JdbcType {
return "BlobTypeDescriptor(PRIMITIVE_ARRAY_BINDING)";
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return byte[].class;
}
@Override
public <X> BasicBinder<X> getBlobBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@ -153,6 +163,11 @@ public abstract class BlobJdbcType implements JdbcType {
return "BlobTypeDescriptor(BLOB_BINDING)";
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return Blob.class;
}
@Override
public <X> BasicBinder<X> getBlobBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@ -177,6 +192,11 @@ public abstract class BlobJdbcType implements JdbcType {
return "BlobTypeDescriptor(STREAM_BINDING)";
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return BinaryStream.class;
}
@Override
public <X> BasicBinder<X> getBlobBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -70,6 +70,11 @@ public class BooleanJdbcType implements AdjustableJdbcType {
return this;
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return Boolean.class;
}
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override

View File

@ -90,6 +90,13 @@ public abstract class ClobJdbcType implements AdjustableJdbcType {
return "ClobTypeDescriptor(DEFAULT)";
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return options.useStreamForLobBinding() ?
STREAM_BINDING.getPreferredJavaTypeClass( options ) :
CLOB_BINDING.getPreferredJavaTypeClass( options );
}
@Override
public <X> BasicBinder<X> getClobBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@ -124,6 +131,11 @@ public abstract class ClobJdbcType implements AdjustableJdbcType {
return "ClobTypeDescriptor(STRING_BINDING)";
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return String.class;
}
@Override
public <X> BasicBinder<X> getClobBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@ -170,6 +182,11 @@ public abstract class ClobJdbcType implements AdjustableJdbcType {
return "ClobTypeDescriptor(CLOB_BINDING)";
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return Clob.class;
}
@Override
public <X> BasicBinder<X> getClobBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@ -194,6 +211,11 @@ public abstract class ClobJdbcType implements AdjustableJdbcType {
return "ClobTypeDescriptor(STREAM_BINDING)";
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return CharacterStream.class;
}
@Override
public <X> BasicBinder<X> getClobBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@ -228,6 +250,11 @@ public abstract class ClobJdbcType implements AdjustableJdbcType {
return "ClobTypeDescriptor(STREAM_BINDING_EXTRACTING)";
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return CharacterStream.class;
}
@Override
public <X> BasicBinder<X> getClobBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -63,6 +63,11 @@ public class DateJdbcType implements JdbcType {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.DATE );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return Date.class;
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -60,6 +60,11 @@ public class DecimalJdbcType implements JdbcType {
return new JdbcLiteralFormatterNumericData<>( javaType, BigDecimal.class );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return BigDecimal.class;
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -70,6 +70,11 @@ public class DoubleJdbcType implements JdbcType {
return new JdbcLiteralFormatterNumericData<>( javaType, Double.class );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return Double.class;
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -63,6 +63,11 @@ public class FloatJdbcType implements JdbcType {
return new JdbcLiteralFormatterNumericData<>( javaType, Float.class );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return Float.class;
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -59,6 +59,11 @@ public class IntegerJdbcType implements JdbcType {
return new JdbcLiteralFormatterNumericData<>( javaType, Integer.class );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return Integer.class;
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -9,11 +9,13 @@ package org.hibernate.type.descriptor.jdbc;
import java.io.Serializable;
import java.sql.Types;
import org.hibernate.Incubating;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.query.sqm.CastType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.BasicJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
@ -109,6 +111,14 @@ public interface JdbcType extends Serializable {
*/
<X> ValueExtractor<X> getExtractor(JavaType<X> javaType);
/**
* The Java type class that is preferred by the binder or null.
*/
@Incubating
default Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return null;
}
default boolean isInteger() {
return isInteger( getJdbcTypeCode() );
}

View File

@ -70,7 +70,7 @@ public interface JdbcTypeIndicators {
* {@link JdbcTypeRegistry}.
*/
default int getPreferredSqlTypeCodeForBoolean() {
return Types.BOOLEAN;
return getTypeConfiguration().getSessionFactory().getSessionFactoryOptions().getPreferredSqlTypeCodeForBoolean();
}
/**
@ -80,7 +80,7 @@ public interface JdbcTypeIndicators {
* {@link JdbcTypeRegistry}.
*/
default int getPreferredSqlTypeCodeForDuration() {
return SqlTypes.INTERVAL_SECOND;
return getTypeConfiguration().getSessionFactory().getSessionFactoryOptions().getPreferredSqlTypeCodeForDuration();
}
/**
@ -90,7 +90,7 @@ public interface JdbcTypeIndicators {
* {@link JdbcTypeRegistry}.
*/
default int getPreferredSqlTypeCodeForUuid() {
return SqlTypes.UUID;
return getTypeConfiguration().getSessionFactory().getSessionFactoryOptions().getPreferredSqlTypeCodeForUuid();
}
/**
@ -100,7 +100,17 @@ public interface JdbcTypeIndicators {
* {@link JdbcTypeRegistry}.
*/
default int getPreferredSqlTypeCodeForInstant() {
return SqlTypes.TIMESTAMP_UTC;
return getTypeConfiguration().getSessionFactory().getSessionFactoryOptions().getPreferredSqlTypeCodeForInstant();
}
/**
* When mapping a basic array or collection type to the database what is the preferred SQL type code to use?
* <p/>
* Specifically names the key into the
* {@link JdbcTypeRegistry}.
*/
default int getPreferredSqlTypeCodeForArray() {
return getTypeConfiguration().getSessionFactory().getSessionFactoryOptions().getPreferredSqlTypeCodeForArray();
}
/**

View File

@ -43,6 +43,12 @@ public class JsonJdbcType implements JdbcType {
return "JsonJdbcType";
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
// No literal support for now
return null;
}
@Override
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -77,6 +77,13 @@ public abstract class NClobJdbcType implements JdbcType {
return "NClobTypeDescriptor(DEFAULT)";
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return options.useStreamForLobBinding() ?
STREAM_BINDING.getPreferredJavaTypeClass( options ) :
NCLOB_BINDING.getPreferredJavaTypeClass( options );
}
@Override
public <X> BasicBinder<X> getNClobBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@ -111,6 +118,11 @@ public abstract class NClobJdbcType implements JdbcType {
return "NClobTypeDescriptor(NCLOB_BINDING)";
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return NClob.class;
}
@Override
public <X> BasicBinder<X> getNClobBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@ -135,6 +147,11 @@ public abstract class NClobJdbcType implements JdbcType {
return "NClobTypeDescriptor(STREAM_BINDING)";
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return CharacterStream.class;
}
@Override
public <X> BasicBinder<X> getNClobBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -84,6 +84,11 @@ public class NVarcharJdbcType implements AdjustableJdbcType {
return jdbcTypeRegistry.getDescriptor( jdbcTypeCode );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return String.class;
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -8,6 +8,7 @@ package org.hibernate.type.descriptor.jdbc;
import java.sql.Types;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.BasicJavaType;
import org.hibernate.type.spi.TypeConfiguration;
@ -47,4 +48,10 @@ public class RealJdbcType extends FloatJdbcType {
TypeConfiguration typeConfiguration) {
return (BasicJavaType<T>) typeConfiguration.getJavaTypeRegistry().getDescriptor( Float.class );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return Float.class;
}
}

View File

@ -59,6 +59,11 @@ public class SmallIntJdbcType implements JdbcType {
return new JdbcLiteralFormatterNumericData<>( javaType, Short.class );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return Short.class;
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -63,6 +63,11 @@ public class TimeJdbcType implements JdbcType {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIME );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return Time.class;
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -63,6 +63,11 @@ public class TimestampJdbcType implements JdbcType {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIMESTAMP );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return Timestamp.class;
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -62,6 +62,11 @@ public class TimestampWithTimeZoneJdbcType implements JdbcType {
return new JdbcLiteralFormatterTemporal<>( javaType, TemporalType.TIMESTAMP );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return OffsetDateTime.class;
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -62,6 +62,11 @@ public class TinyIntJdbcType implements JdbcType {
return new JdbcLiteralFormatterNumericData<>( javaType, Byte.class );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return Byte.class;
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -45,6 +45,11 @@ public class UUIDJdbcType implements JdbcType {
return "UUIDJdbcType";
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return UUID.class;
}
@Override
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -62,6 +62,11 @@ public class VarbinaryJdbcType implements AdjustableJdbcType {
return (BasicJavaType<T>) typeConfiguration.getJavaTypeRegistry().getDescriptor( byte[].class );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return byte[].class;
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
return supportsLiterals ? new JdbcLiteralFormatterBinary<>( javaType ) : null;

View File

@ -84,6 +84,11 @@ public class VarcharJdbcType implements AdjustableJdbcType {
return jdbcTypeRegistry.getDescriptor( jdbcTypeCode );
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return String.class;
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -43,6 +43,12 @@ public class XmlAsStringJdbcType implements JdbcType {
return "XmlAsStringJdbcType";
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
// No literal support for now
return null;
}
@Override
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {

View File

@ -39,6 +39,12 @@ public class XmlJdbcType implements JdbcType {
return "XmlJdbcType";
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
// No literal support for now
return null;
}
@Override
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
return new XmlValueBinder<>( javaType, this );

View File

@ -0,0 +1,35 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.type.descriptor.jdbc.internal;
import org.hibernate.dialect.Dialect;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
import org.hibernate.type.descriptor.jdbc.spi.BasicJdbcLiteralFormatter;
public class JdbcLiteralFormatterArray<T> extends BasicJdbcLiteralFormatter<T> {
private final JdbcLiteralFormatter<Object> elementFormatter;
public JdbcLiteralFormatterArray(JavaType<T> javaType, JdbcLiteralFormatter<?> elementFormatter) {
super( javaType );
//noinspection unchecked
this.elementFormatter = (JdbcLiteralFormatter<Object>) elementFormatter;
}
@Override
public void appendJdbcLiteral(SqlAppender appender, Object value, Dialect dialect, WrapperOptions wrapperOptions) {
dialect.appendArrayLiteral(
appender,
unwrap( value, Object[].class, wrapperOptions ),
elementFormatter,
wrapperOptions
);
}
}

View File

@ -32,6 +32,7 @@ import org.hibernate.type.descriptor.jdbc.TimestampWithTimeZoneJdbcType;
import org.hibernate.type.descriptor.jdbc.TinyIntJdbcType;
import org.hibernate.type.descriptor.jdbc.VarbinaryJdbcType;
import org.hibernate.type.descriptor.jdbc.VarcharJdbcType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
/**
* Registers the base {@link JdbcType} instances.

View File

@ -29,14 +29,18 @@ import org.jboss.logging.Logger;
public class JdbcTypeRegistry implements JdbcTypeBaseline.BaselineTarget, Serializable {
private static final Logger log = Logger.getLogger( JdbcTypeRegistry.class );
private final TypeConfiguration typeConfiguration;
private final ConcurrentHashMap<Integer, JdbcType> descriptorMap = new ConcurrentHashMap<>();
public JdbcTypeRegistry(TypeConfiguration typeConfiguration) {
// this.typeConfiguration = typeConfiguration;
this.typeConfiguration = typeConfiguration;
JdbcTypeBaseline.prime( this );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public TypeConfiguration getTypeConfiguration() {
return typeConfiguration;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// baseline descriptors
@Override
@ -63,6 +67,10 @@ public class JdbcTypeRegistry implements JdbcTypeBaseline.BaselineTarget, Serial
descriptorMap.putIfAbsent( typeCode, jdbcType );
}
public JdbcType findDescriptor(int jdbcTypeCode) {
return descriptorMap.get( jdbcTypeCode );
}
public JdbcType getDescriptor(int jdbcTypeCode) {
JdbcType descriptor = descriptorMap.get( jdbcTypeCode );
if ( descriptor != null ) {

Some files were not shown because too many files have changed in this diff Show More