From f85fe137b28b0dcf6cb9035739b918c744b082c2 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 5 Sep 2019 11:56:44 +0100 Subject: [PATCH] Initial working support for building and executing JdbcSelect operation from simple HQL --- .../internal/NamedEnumValueConverter.java | 13 +++ .../internal/OrdinalEnumValueConverter.java | 11 ++ .../model/convert/spi/EnumValueConverter.java | 10 ++ .../entity/AbstractEntityPersister.java | 2 + .../sql/ast/tree/from/StandardTableGroup.java | 9 ++ .../sql/ast/tree/from/TableGroup.java | 4 +- .../java/org/hibernate/type/EnumType.java | 5 +- .../test/query/hql/CaseExpressionsTest.java | 4 +- .../orm/test/sql/ast/SmokeTests.java | 34 +++++- .../orm/test/sql/exec/SmokeTests.java | 103 ++++++++++++++++++ .../testing/orm/junit/FailureExpected.java | 2 +- .../orm/junit/FailureExpectedExtension.java | 7 +- 12 files changed, 190 insertions(+), 14 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/SmokeTests.java diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/NamedEnumValueConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/NamedEnumValueConverter.java index 2ddaf8d844..5d8f14242a 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/NamedEnumValueConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/NamedEnumValueConverter.java @@ -9,9 +9,12 @@ package org.hibernate.metamodel.model.convert.internal; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.sql.Types; import java.util.Locale; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.model.convert.spi.EnumValueConverter; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; @@ -82,4 +85,14 @@ public class NamedEnumValueConverter implements EnumValueConvert this.valueExtractor = sqlTypeDescriptor.getExtractor( relationalTypeDescriptor ); this.valueBinder = sqlTypeDescriptor.getBinder( relationalTypeDescriptor ); } + + @Override + public void writeValue( + PreparedStatement statement, + Enum value, + int position, + SharedSessionContractImplementor session) throws SQLException { + final String jdbcValue = value == null ? null : value.name(); + valueBinder.bind( statement, jdbcValue, position, session ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/OrdinalEnumValueConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/OrdinalEnumValueConverter.java index 546ae39e77..afc59b9fb1 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/OrdinalEnumValueConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/OrdinalEnumValueConverter.java @@ -9,8 +9,11 @@ package org.hibernate.metamodel.model.convert.internal; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.sql.Types; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.model.convert.spi.EnumValueConverter; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; @@ -82,4 +85,12 @@ public class OrdinalEnumValueConverter implements EnumValueConve this.valueExtractor = sqlTypeDescriptor.getExtractor( relationalJavaDescriptor ); this.valueBinder = sqlTypeDescriptor.getBinder( relationalJavaDescriptor ); } + + @Override + public void writeValue( + PreparedStatement statement, Enum value, int position, SharedSessionContractImplementor session) + throws SQLException { + final Integer jdbcValue = value == null ? null : value.ordinal(); + valueBinder.bind( statement, jdbcValue, position, session ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/spi/EnumValueConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/spi/EnumValueConverter.java index bd91ffe6f3..8aa2401eed 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/spi/EnumValueConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/spi/EnumValueConverter.java @@ -6,6 +6,10 @@ */ package org.hibernate.metamodel.model.convert.spi; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.type.descriptor.java.EnumJavaTypeDescriptor; /** @@ -20,4 +24,10 @@ public interface EnumValueConverter extends BasicValueConvert int getJdbcTypeCode(); String toSqlLiteral(Object value); + + void writeValue( + PreparedStatement statement, + Enum value, + int position, + SharedSessionContractImplementor session) throws SQLException; } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 86d691efbd..4f212e5436 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -172,6 +172,8 @@ import org.hibernate.sql.ast.tree.from.TableReferenceJoin; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; import org.hibernate.sql.ast.tree.predicate.Junction; import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.sql.results.spi.DomainResult; +import org.hibernate.sql.results.spi.DomainResultCreationState; import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.tuple.GenerationTiming; import org.hibernate.tuple.InDatabaseValueGenerationStrategy; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/StandardTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/StandardTableGroup.java index ee23f7ac47..c72cc5962f 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/StandardTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/StandardTableGroup.java @@ -12,6 +12,9 @@ import java.util.function.Consumer; import org.hibernate.LockMode; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.NavigablePath; +import org.hibernate.query.sqm.sql.SqlAstCreationState; +import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; +import org.hibernate.query.sqm.sql.internal.DomainResultProducer; import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.sql.ast.spi.SqlAstWalker; @@ -74,4 +77,10 @@ public class StandardTableGroup extends AbstractTableGroup { public List getTableReferenceJoins() { return tableJoins; } + + @Override + public DomainResultProducer getDomainResultProducer( + SqmToSqlAstConverter walker, SqlAstCreationState sqlAstCreationState) { + return this; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroup.java index 3083d19520..217f2169ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroup.java @@ -14,6 +14,7 @@ import org.hibernate.LockMode; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.query.NavigablePath; import org.hibernate.query.sqm.sql.internal.DomainResultProducer; +import org.hibernate.query.sqm.sql.internal.SqmSelectableInterpretation; import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.sql.ast.spi.SqlAstWalker; import org.hibernate.sql.ast.tree.SqlAstNode; @@ -27,7 +28,8 @@ import org.hibernate.sql.results.spi.DomainResultCreationState; * * @author Steve Ebersole */ -public interface TableGroup extends SqlAstNode, ColumnReferenceQualifier, DomainResultProducer { +public interface TableGroup + extends SqlAstNode, ColumnReferenceQualifier, DomainResultProducer, SqmSelectableInterpretation { NavigablePath getNavigablePath(); ModelPart getModelPart(); diff --git a/hibernate-core/src/main/java/org/hibernate/type/EnumType.java b/hibernate-core/src/main/java/org/hibernate/type/EnumType.java index c05161beca..f6eb09c806 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EnumType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EnumType.java @@ -333,9 +333,8 @@ public class EnumType @Override public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException { - throw new NotYetImplementedFor6Exception( getClass() ); -// verifyConfigured(); -// enumValueConverter.writeValue( st, (Enum) value, index, session ); + verifyConfigured(); + enumValueConverter.writeValue( st, (Enum) value, index, session ); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/CaseExpressionsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/CaseExpressionsTest.java index dd4bc25f3c..349e4689dd 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/CaseExpressionsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/CaseExpressionsTest.java @@ -87,7 +87,7 @@ public class CaseExpressionsTest extends BaseSqmUnitTest { } @Test - @FailureExpected( "Support for functions not yet defined" ) + @FailureExpected( reason = "Support for functions not yet defined" ) public void testBasicCoalesceExpression() { SqmSelectStatement select = interpretSelect( "select coalesce(p.nickName, p.mate.nickName) from Person p" @@ -105,7 +105,7 @@ public class CaseExpressionsTest extends BaseSqmUnitTest { } @Test - @FailureExpected( "Support for functions not yet defined" ) + @FailureExpected( reason = "Support for functions not yet defined" ) public void testBasicNullifExpression() { SqmSelectStatement select = interpretSelect( "select nullif(p.nickName, p.mate.nickName) from Person p" diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java index 9b121b1050..ec059c7b48 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java @@ -8,11 +8,11 @@ package org.hibernate.orm.test.sql.ast; import org.hibernate.cfg.AvailableSettings; import org.hibernate.metamodel.mapping.MappingModelExpressable; -import org.hibernate.metamodel.mapping.internal.BasicValuedSingularAttributeMapping; 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.orm.test.metamodel.mapping.SmokeTests.Gender; +import org.hibernate.orm.test.metamodel.mapping.SmokeTests.SimpleEntity; import org.hibernate.query.NavigablePath; import org.hibernate.query.hql.spi.HqlQueryImplementor; import org.hibernate.query.spi.QueryImplementor; @@ -20,6 +20,7 @@ import org.hibernate.query.sqm.internal.QuerySqmImpl; import org.hibernate.query.sqm.sql.internal.SqmSelectInterpretation; import org.hibernate.query.sqm.sql.internal.SqmSelectToSqlAstConverter; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; +import org.hibernate.sql.ast.spi.SqlAstSelectToJdbcSelectConverter; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; @@ -27,6 +28,7 @@ import org.hibernate.sql.ast.tree.from.FromClause; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.select.SelectClause; import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.results.internal.BasicResultAssembler; import org.hibernate.sql.results.internal.ScalarDomainResultImpl; import org.hibernate.sql.results.internal.SqlSelectionImpl; @@ -53,7 +55,7 @@ import static org.hamcrest.MatcherAssert.assertThat; */ @SuppressWarnings("WeakerAccess") @DomainModel( - annotatedClasses = org.hibernate.orm.test.metamodel.mapping.SmokeTests.SimpleEntity.class + annotatedClasses = SimpleEntity.class ) @ServiceRegistry( settings = @ServiceRegistry.Setting( @@ -67,7 +69,10 @@ public class SmokeTests { public void testSimpleHqlInterpretation(SessionFactoryScope scope) { scope.inTransaction( session -> { - final QueryImplementor query = session.createQuery( "select e.name from SimpleEntity e", String.class ); + final QueryImplementor query = session.createQuery( + "select e.name from SimpleEntity e", + String.class + ); final HqlQueryImplementor hqlQuery = (HqlQueryImplementor) query; //noinspection unchecked final SqmSelectStatement sqmStatement = (SqmSelectStatement) hqlQuery.getSqmStatement(); @@ -105,6 +110,16 @@ public class SmokeTests { assertThat( sqlSelection.getJdbcResultSetIndex(), is( 1 ) ); assertThat( sqlSelection.getValuesArrayPosition(), is( 0 ) ); assertThat( sqlSelection.getJdbcValueExtractor(), notNullValue() ); + + final JdbcSelect jdbcSelectOperation = SqlAstSelectToJdbcSelectConverter.interpret( + sqlAst, + session.getSessionFactory() + ); + + assertThat( + jdbcSelectOperation.getSql(), + is( "select s1_0.name from mapping_simple_entity as s1_0" ) + ); } ); } @@ -155,7 +170,7 @@ public class SmokeTests { assertThat( rootTableGroup.getPrimaryTableReference().getIdentificationVariable(), is( "s1_0" ) ); final SelectClause selectClause = sqlAst.getQuerySpec().getSelectClause(); - assertThat( selectClause.getSqlSelections().size(), is( 1 ) ) ; + assertThat( selectClause.getSqlSelections().size(), is( 1 ) ); final SqlSelection sqlSelection = selectClause.getSqlSelections().get( 0 ); assertThat( sqlSelection.getJdbcResultSetIndex(), is( 1 ) ); @@ -197,6 +212,17 @@ public class SmokeTests { final BasicValueConverter valueConverter = ( (BasicResultAssembler) resultAssembler ).getValueConverter(); assertThat( valueConverter, notNullValue() ); assertThat( valueConverter, instanceOf( OrdinalEnumValueConverter.class ) ); + + + final JdbcSelect jdbcSelectOperation = SqlAstSelectToJdbcSelectConverter.interpret( + sqlAst, + session.getSessionFactory() + ); + + assertThat( + jdbcSelectOperation.getSql(), + is( "select s1_0.gender from mapping_simple_entity as s1_0" ) + ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/SmokeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/SmokeTests.java new file mode 100644 index 0000000000..1e927bd04b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/SmokeTests.java @@ -0,0 +1,103 @@ +/* + * 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.orm.test.sql.exec; + +import java.sql.Statement; +import java.util.List; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.orm.test.metamodel.mapping.SmokeTests.SimpleEntity; +import org.hibernate.query.spi.QueryImplementor; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.FailureExpected; +import org.hibernate.testing.orm.junit.ServiceRegistry; +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.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hibernate.orm.test.metamodel.mapping.SmokeTests.Gender.FEMALE; +import static org.hibernate.orm.test.metamodel.mapping.SmokeTests.Gender.MALE; + +/** + * @author Andrea Boriero + * @author Steve Ebersole + */ +@DomainModel( + annotatedClasses = SimpleEntity.class +) +@ServiceRegistry( + settings = @ServiceRegistry.Setting( + name = AvailableSettings.HBM2DDL_AUTO, + value = "create-drop" + ) +) +@SessionFactory +public class SmokeTests { + + @BeforeEach + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + SimpleEntity simpleEntity = new SimpleEntity(); + simpleEntity.setId( 1 ); + simpleEntity.setGender( FEMALE ); + simpleEntity.setName( "Fab" ); + simpleEntity.setGender2( MALE ); + session.save( simpleEntity ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + session -> + session.doWork( + work -> { + Statement statement = work.createStatement(); + statement.execute( "delete from mapping_simple_entity" ); + statement.close(); + } + ) + ); + } + + @Test + public void testSelectEntityFieldHqlExecution(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final QueryImplementor query = session.createQuery( + "select e.name from SimpleEntity e", + String.class + ); + List simpleEntities = query.list(); + assertThat( simpleEntities.size(), is( 1 ) ); + assertThat( simpleEntities.get( 0 ), is( "Fab" ) ); + } + ); + } + + @Test + @FailureExpected( reason = "Support for entity-values DomainResults not yet implemented") + public void testSelectEntityHqlExecution(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final QueryImplementor query = session.createQuery( + "select e from SimpleEntity e", + SimpleEntity.class + ); + List simpleEntities = query.list(); + assertThat( simpleEntities.size(), is( 1 ) ); + } + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpected.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpected.java index 931e672c1d..aaaef2ade9 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpected.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpected.java @@ -45,7 +45,7 @@ public @interface FailureExpected { /** * A reason why the failure is expected */ - String value() default ""; + String reason() default ""; /** * The key of a JIRA issue which covers this expected failure. diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedExtension.java index a7c4045071..406c658896 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedExtension.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedExtension.java @@ -14,7 +14,6 @@ import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; -import org.junit.platform.commons.support.AnnotationSupport; import org.jboss.logging.Logger; @@ -58,7 +57,8 @@ public class FailureExpectedExtension log.debugf( "Evaluating context - %s [failureExpectedValidation = %s]", context.getDisplayName(), failureExpectedValidation ); - if ( AnnotationSupport.findAnnotation( context.getElement().get(), FailureExpected.class ).isPresent() ) { + if ( TestingUtil.hasEffectiveAnnotation( context, FailureExpected.class ) + || TestingUtil.hasEffectiveAnnotation( context, FailureExpectedGroup.class ) ) { // The test is marked as `FailureExpected`... if ( failureExpectedValidation ) { log.debugf( "Executing test marked with `@FailureExpected` for validation" ); @@ -82,7 +82,8 @@ public class FailureExpectedExtension public void beforeEach(ExtensionContext context) { log.tracef( "#beforeEach(%s)", context.getDisplayName() ); - final boolean markedExpectedFailure = TestingUtil.hasEffectiveAnnotation( context, FailureExpected.class ); + final boolean markedExpectedFailure = TestingUtil.hasEffectiveAnnotation( context, FailureExpected.class ) + || TestingUtil.hasEffectiveAnnotation( context, FailureExpectedGroup.class ); log.debugf( "Checking for @FailureExpected [%s] - %s", context.getDisplayName(), markedExpectedFailure );