From 6441c60255d25cbe0fa156f95cbece2fc030c04a Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sat, 16 Mar 2024 23:47:54 +0100 Subject: [PATCH] HHH-17859, HHH-17858 function() and column() functions --- .../dialect/SQLiteSqlAstTranslator.java | 1 - .../org/hibernate/grammars/hql/HqlLexer.g4 | 1 + .../org/hibernate/grammars/hql/HqlParser.g4 | 9 ++- .../hibernate/dialect/function/SqlColumn.java | 65 ++++++++++++++++++ .../connections/spi/ConnectionProvider.java | 2 +- .../hql/internal/SemanticQueryBuilder.java | 67 +++++++++++++------ .../sqm/internal/SqmCriteriaNodeBuilder.java | 3 +- .../hibernate/type/spi/TypeConfiguration.java | 6 +- .../orm/test/query/hql/FunctionTests.java | 41 +++++++++++- .../orm/domain/gambit/EntityOfBasics.java | 23 +++++++ .../validation/MockSessionFactory.java | 5 +- 11 files changed, 192 insertions(+), 31 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/SqlColumn.java diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLiteSqlAstTranslator.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLiteSqlAstTranslator.java index e17ba3b41b..0eb9dbd26b 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLiteSqlAstTranslator.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLiteSqlAstTranslator.java @@ -11,7 +11,6 @@ import org.hibernate.query.sqm.ComparisonOperator; import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.ast.tree.cte.CteMaterialization; -import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.expression.Any; import org.hibernate.sql.ast.tree.expression.Every; import org.hibernate.sql.ast.tree.expression.Expression; diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 index 9fc550567c..b4bd90e706 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 @@ -158,6 +158,7 @@ BY : [bB] [yY]; CASE : [cC] [aA] [sS] [eE]; CAST : [cC] [aA] [sS] [tT]; COLLATE : [cC] [oO] [lL] [lL] [aA] [tT] [eE]; +COLUMN : [cC] [oO] [lL] [uU] [mM] [nN]; CONFLICT : [cC] [oO] [nN] [fF] [lL] [iI] [cC] [tT]; CONSTRAINT : [cC] [oO] [nN] [sS] [tT] [rR] [aA] [iI] [nN] [tT]; COUNT : [cC] [oO] [uU] [nN] [tT]; diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 index bc32db043a..afb6790659 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 @@ -1083,6 +1083,7 @@ function | collectionAggregateFunction | collectionFunctionMisuse | jpaNonstandardFunction + | columnFunction | genericFunction ; @@ -1090,7 +1091,7 @@ function * A syntax for calling user-defined or native database functions, required by JPQL */ jpaNonstandardFunction - : FUNCTION LEFT_PAREN jpaNonstandardFunctionName (COMMA genericFunctionArguments)? RIGHT_PAREN + : FUNCTION LEFT_PAREN jpaNonstandardFunctionName (AS castTarget)? (COMMA genericFunctionArguments)? RIGHT_PAREN ; /** @@ -1098,8 +1099,13 @@ jpaNonstandardFunction */ jpaNonstandardFunctionName : STRING_LITERAL + | identifier ; +columnFunction + : COLUMN LEFT_PAREN path DOT jpaNonstandardFunctionName (AS castTarget)? RIGHT_PAREN + ; + /** * Any function invocation that follows the regular syntax * @@ -1618,6 +1624,7 @@ rollup | CASE | CAST | COLLATE + | COLUMN | CONFLICT | CONSTRAINT | COUNT diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/SqlColumn.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/SqlColumn.java new file mode 100644 index 0000000000..7e511bb827 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/SqlColumn.java @@ -0,0 +1,65 @@ +/* + * 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 . + */ +package org.hibernate.dialect.function; + +import org.hibernate.query.ReturnableType; +import org.hibernate.query.hql.HqlInterpretationException; +import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; +import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.update.Assignable; +import org.hibernate.type.BasicType; +import org.hibernate.type.JavaObjectType; + +import java.util.List; + +/** + * @author Gavin King + */ +public class SqlColumn extends AbstractSqmSelfRenderingFunctionDescriptor { + private final String columnName; + + public SqlColumn(String columnName, BasicType type) { + super( + "column", + StandardArgumentsValidators.min( 1 ), + StandardFunctionReturnTypeResolvers.invariant( type == null ? JavaObjectType.INSTANCE : type ), + null + ); + this.columnName = columnName; + } + + @Override + public void render( + SqlAppender sqlAppender, + List arguments, + ReturnableType returnType, + SqlAstTranslator walker) { + final SqlAstNode sqlAstNode = arguments.get(0); + final ColumnReference reference; + if ( sqlAstNode instanceof Assignable ) { + final Assignable assignable = (Assignable) sqlAstNode; + reference = assignable.getColumnReferences().get(0); + } + else if ( sqlAstNode instanceof Expression ) { + final Expression expression = (Expression) sqlAstNode; + reference = expression.getColumnReference(); + } + else { + throw new HqlInterpretationException( "path did not map to a column" ); + } + sqlAppender.appendSql( reference.getQualifier() ); + sqlAppender.appendSql( '.' ); + sqlAppender.appendSql( columnName ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/ConnectionProvider.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/ConnectionProvider.java index 542b0414c2..54655e306f 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/ConnectionProvider.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/ConnectionProvider.java @@ -62,7 +62,7 @@ public interface ConnectionProvider extends Service, Wrapped { *

* Typically, this is only true in managed environments where a container tracks connections * by transaction or thread. - * + *

* Note that JTA semantic depends on the fact that the underlying connection provider does * support aggressive release. * diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index ed0f8490c4..b7da1c7c99 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -35,6 +35,7 @@ import java.util.Set; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.dialect.function.SqlColumn; import org.hibernate.grammars.hql.HqlLexer; import org.hibernate.grammars.hql.HqlParser; import org.hibernate.grammars.hql.HqlParserBaseVisitor; @@ -58,7 +59,6 @@ import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor; import org.hibernate.query.NullPrecedence; import org.hibernate.query.ParameterLabelException; import org.hibernate.query.PathException; -import org.hibernate.query.ReturnableType; import org.hibernate.query.SemanticException; import org.hibernate.query.SortDirection; import org.hibernate.query.SyntaxException; @@ -277,6 +277,9 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem private static final Logger log = Logger.getLogger( SemanticQueryBuilder.class ); private static final Set JPA_STANDARD_FUNCTIONS; + private static final BasicTypeImpl OBJECT_BASIC_TYPE = + new BasicTypeImpl<>( new UnknownBasicJavaType<>(Object.class), ObjectJdbcType.INSTANCE ); + static { final Set jpaStandardFunctions = new HashSet<>(); // Extracted from the BNF in JPA spec 4.14. @@ -3910,43 +3913,64 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Functions + private String toName(HqlParser.JpaNonstandardFunctionNameContext ctx) { + return ctx.STRING_LITERAL() == null + ? ctx.identifier().getText().toLowerCase() + : unquoteStringLiteral( ctx.STRING_LITERAL().getText() ).toLowerCase(); + } + @Override public SqmExpression visitJpaNonstandardFunction(HqlParser.JpaNonstandardFunctionContext ctx) { - final String functionName = unquoteStringLiteral( ctx.jpaNonstandardFunctionName().getText() ).toLowerCase(); - final List> functionArguments; - if ( ctx.getChildCount() > 4 ) { - //noinspection unchecked - functionArguments = (List>) ctx.genericFunctionArguments().accept( this ); - } - else { - functionArguments = emptyList(); - } + final String functionName = toName( ctx.jpaNonstandardFunctionName() ); + final HqlParser.GenericFunctionArgumentsContext argumentsContext = ctx.genericFunctionArguments(); + @SuppressWarnings("unchecked") + final List> functionArguments = + argumentsContext == null + ? emptyList() + : (List>) argumentsContext.accept(this); + final BasicType returnableType = returnType( ctx.castTarget() ); SqmFunctionDescriptor functionTemplate = getFunctionDescriptor( functionName ); - if (functionTemplate == null) { + if ( functionTemplate == null ) { functionTemplate = new NamedSqmFunctionDescriptor( functionName, true, null, - StandardFunctionReturnTypeResolvers.invariant( - new BasicTypeImpl<>( - new UnknownBasicJavaType<>( Object.class ), - ObjectJdbcType.INSTANCE - ) - ), + StandardFunctionReturnTypeResolvers.invariant(returnableType), null ); } return functionTemplate.generateSqmExpression( functionArguments, - null, + returnableType, creationContext.getQueryEngine() ); } + @Override + public SqmExpression visitColumnFunction(HqlParser.ColumnFunctionContext ctx) { + final String columnName = toName( ctx.jpaNonstandardFunctionName() ); + final SemanticPathPart semanticPathPart = visitPath( ctx.path() ); + final BasicType resultType = returnType( ctx.castTarget() ); + return new SqlColumn( columnName, resultType ).generateSqmExpression( + (SqmTypedNode) semanticPathPart, + resultType, + creationContext.getQueryEngine() + ); + } + + private BasicType returnType(HqlParser.CastTargetContext castTarget) { + if ( castTarget == null ) { + return OBJECT_BASIC_TYPE; + } + else { + return (BasicType) visitCastTarget( castTarget ).getType(); + } + } + @Override public String visitGenericFunctionName(HqlParser.GenericFunctionNameContext ctx) { - StringBuilder functionName = new StringBuilder( visitIdentifier( ctx.simplePath().identifier() ) ); + final StringBuilder functionName = new StringBuilder( visitIdentifier( ctx.simplePath().identifier() ) ); for ( HqlParser.SimplePathElementContext sp: ctx.simplePath().simplePathElement() ) { // allow function names of form foo.bar to be located in the registry functionName.append('.').append( visitIdentifier( sp.identifier() ) ); @@ -4474,9 +4498,8 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem final Integer scale = secondArg == null ? null : Integer.valueOf( secondArg.getText() ); return new SqmCastTarget<>( - (ReturnableType) - creationContext.getTypeConfiguration() - .resolveCastTargetType( targetName ), + creationContext.getTypeConfiguration() + .resolveCastTargetType( targetName ), //TODO: is there some way to interpret as length vs precision/scale here at this point? length, precision, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java index 4afde125f9..fd645b964d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java @@ -41,7 +41,6 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.SessionFactoryRegistry; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.metamodel.model.domain.BasicDomainType; import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.metamodel.model.domain.JpaMetamodel; import org.hibernate.metamodel.model.domain.SingularPersistentAttribute; @@ -468,7 +467,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext, @Override public SqmExpression cast(JpaExpression expression, Class castTargetJavaType) { - final BasicDomainType type = getTypeConfiguration().standardBasicTypeForJavaType( castTargetJavaType ); + final BasicType type = getTypeConfiguration().standardBasicTypeForJavaType( castTargetJavaType ); return getFunctionDescriptor( "cast" ).generateSqmExpression( asList( (SqmTypedNode) expression, new SqmCastTarget<>( type, this ) ), type, diff --git a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java index cca1934a22..3f9e9f6298 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java +++ b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java @@ -27,6 +27,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; @@ -323,7 +324,7 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable { *

* The type names are not case-sensitive. */ - public BasicValuedMapping resolveCastTargetType(String name) { + public BasicType resolveCastTargetType(String name) { switch ( name.toLowerCase() ) { case "string": return getBasicTypeForJavaType( String.class ); case "character": return getBasicTypeForJavaType( Character.class ); @@ -346,6 +347,7 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable { case "duration": return getBasicTypeForJavaType( Duration.class ); case "instant": return getBasicTypeForJavaType( Instant.class ); case "binary": return getBasicTypeForJavaType( byte[].class ); + case "uuid": return getBasicTypeForJavaType( UUID.class ); //this one is very fragile ... works well for BIT or BOOLEAN columns only //works OK, I suppose, for integer columns, but not at all for char columns case "boolean": return getBasicTypeForJavaType( Boolean.class ); @@ -353,7 +355,7 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable { case "yesno": return basicTypeRegistry.getRegisteredType( StandardBasicTypes.YES_NO.getName() ); case "numericboolean": return basicTypeRegistry.getRegisteredType( StandardBasicTypes.NUMERIC_BOOLEAN.getName() ); default: { - final BasicType registeredBasicType = basicTypeRegistry.getRegisteredType( name ); + final BasicType registeredBasicType = basicTypeRegistry.getRegisteredType( name ); if ( registeredBasicType != null ) { return registeredBasicType; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java index 597def2cb5..9601c9764f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java @@ -54,6 +54,7 @@ import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Map; +import java.util.UUID; import org.hamcrest.Matchers; @@ -61,7 +62,6 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.isOneOf; -import static org.hibernate.cfg.QuerySettings.PORTABLE_INTEGER_DIVISION; import static org.hibernate.testing.orm.domain.gambit.EntityOfBasics.Gender.FEMALE; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -92,6 +92,7 @@ public class FunctionTests { entity.setTheTime( new Time( 20, 10, 8 ) ); entity.setTheDuration( Duration.of(3, ChronoUnit.SECONDS).plus( Duration.of(23,ChronoUnit.MILLIS) ) ); entity.setTheTimestamp( new Timestamp( 121, 4, 27, 13, 22, 50, 123456789 ) ); + entity.setTheUuid( UUID.randomUUID() ); em.persist(entity); EntityOfLists eol = new EntityOfLists(1,""); @@ -1280,10 +1281,14 @@ public class FunctionTests { session -> { assertThat( session.createQuery("select function('lower','HIBERNATE')", String.class).getSingleResult(), equalTo("hibernate") ); + assertThat( session.createQuery("select function(lower as String,'HIBERNATE')", String.class).getSingleResult(), + equalTo("hibernate") ); assertThat( session.createQuery("select 1 where function('lower','HIBERNATE') = 'hibernate'", Integer.class).getSingleResult(), equalTo(1) ); assertThat( session.createQuery("select function('current_user')", String.class).getSingleResult().toLowerCase(), isOneOf("hibernate_orm_test", "hibernateormtest", "sa", "hibernateormtest@%", "hibernate_orm_test@%", "root@%") ); + assertThat( session.createQuery("select function(current_user as String)", String.class).getSingleResult().toLowerCase(), + isOneOf("hibernate_orm_test", "hibernateormtest", "sa", "hibernateormtest@%", "hibernate_orm_test@%", "root@%") ); assertThat( session.createQuery("select lower(function('current_user'))", String.class).getSingleResult(), isOneOf("hibernate_orm_test", "hibernateormtest", "sa", "hibernateormtest@%", "hibernate_orm_test@%", "root@%") ); session.createQuery("select 1 where function('current_user') = 'hibernate_orm_test'", Integer.class).getSingleResultOrNull(); @@ -2268,4 +2273,38 @@ public class FunctionTests { .getSingleResultOrNull()); }); } + + @Test + public void testColumnFunction(SessionFactoryScope scope) { + scope.inTransaction(s -> { + assertEquals("the string", + s.createSelectionQuery("select column(e.the_column) from EntityOfBasics e", String.class) + .getSingleResultOrNull()); + assertEquals("the string", + s.createSelectionQuery("select column(e.'the_column') from EntityOfBasics e", String.class) + .getSingleResultOrNull()); + s.createSelectionQuery("from EntityOfBasics e where column(e.the_column as String) = 'the string'", EntityOfBasics.class) + .getSingleResult(); + }); + } + + @Test + public void testUUIDColumnFunction(SessionFactoryScope scope) { + scope.inTransaction(s -> { + byte[] bytes = s.createSelectionQuery("select column(e.theuuid as binary) from EntityOfBasics e", byte[].class) + .getSingleResultOrNull(); + UUID uuid = s.createSelectionQuery("select column(e.theuuid as UUID) from EntityOfBasics e", UUID.class) + .getSingleResultOrNull(); + }); + } + + @Test @RequiresDialect(PostgreSQLDialect.class) + public void testCtidColumnFunction(SessionFactoryScope scope) { + scope.inTransaction(s -> { + String string = s.createSelectionQuery("select column(e.ctid as String) from EntityOfBasics e", String.class) + .getSingleResultOrNull(); + byte[] bytes = s.createSelectionQuery("select column(e.ctid as binary) from EntityOfBasics e", byte[].class) + .getSingleResultOrNull(); + }); + } } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java index 2fd9afd4f9..219ef740ec 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java @@ -17,6 +17,8 @@ import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.ZonedDateTime; import java.util.Date; +import java.util.UUID; + import jakarta.persistence.AttributeConverter; import jakarta.persistence.Column; import jakarta.persistence.Convert; @@ -68,6 +70,7 @@ public class EntityOfBasics { private Gender convertedGender; private Gender ordinalGender; private Duration theDuration; + private UUID theUuid; private LocalDateTime theLocalDateTime; private LocalDate theLocalDate; @@ -77,6 +80,8 @@ public class EntityOfBasics { private MutableValue mutableValue; + private String theField = "the string"; + public EntityOfBasics() { } @@ -272,6 +277,15 @@ public class EntityOfBasics { this.theDuration = theDuration; } + @Column(name = "theuuid") + public UUID getTheUuid() { + return theUuid; + } + + public void setTheUuid(UUID theUuid) { + this.theUuid = theUuid; + } + public Boolean isTheBoolean() { return theBoolean; } @@ -298,6 +312,15 @@ public class EntityOfBasics { this.theStringBoolean = theStringBoolean; } + @Column(name = "the_column") + public String getTheField() { + return theField; + } + + public void setTheField(String theField) { + this.theField = theField; + } + @Convert( converter = MutableValueConverter.class ) public MutableValue getMutableValue() { return mutableValue; diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockSessionFactory.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockSessionFactory.java index e67d98416c..d4b0086ada 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockSessionFactory.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockSessionFactory.java @@ -135,6 +135,9 @@ public abstract class MockSessionFactory implements SessionFactoryImplementor, QueryEngine, RuntimeModelCreationContext, MetadataBuildingOptions, BootstrapContext, MetadataBuildingContext, FunctionContributions, SessionFactoryOptions, JdbcTypeIndicators { + private static final BasicTypeImpl OBJECT_BASIC_TYPE = + new BasicTypeImpl<>(new UnknownBasicJavaType<>(Object.class), ObjectJdbcType.INSTANCE); + // static so other things can get at it // TODO: make a static instance of this whole object instead! static TypeConfiguration typeConfiguration; @@ -972,7 +975,7 @@ public abstract class MockSessionFactory return (DomainType) elementType; } else { - return new BasicTypeImpl<>(new UnknownBasicJavaType<>(Object.class), ObjectJdbcType.INSTANCE); + return OBJECT_BASIC_TYPE; } }