Initial working support for building and executing JdbcSelect operation from simple HQL

This commit is contained in:
Andrea Boriero 2019-09-05 11:56:44 +01:00
parent 5b1df3c6c9
commit f85fe137b2
12 changed files with 190 additions and 14 deletions

View File

@ -9,9 +9,12 @@
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 @@ private void readObject(ObjectInputStream stream) throws ClassNotFoundException,
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 );
}
}

View File

@ -9,8 +9,11 @@
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 @@ private void readObject(ObjectInputStream stream) throws ClassNotFoundException,
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 );
}
}

View File

@ -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<O extends Enum, R> extends BasicValueConvert
int getJdbcTypeCode();
String toSqlLiteral(Object value);
void writeValue(
PreparedStatement statement,
Enum value,
int position,
SharedSessionContractImplementor session) throws SQLException;
}

View File

@ -172,6 +172,8 @@
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;

View File

@ -12,6 +12,9 @@
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 TableReference getPrimaryTableReference() {
public List<TableReferenceJoin> getTableReferenceJoins() {
return tableJoins;
}
@Override
public DomainResultProducer getDomainResultProducer(
SqmToSqlAstConverter walker, SqlAstCreationState sqlAstCreationState) {
return this;
}
}

View File

@ -14,6 +14,7 @@
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 @@
*
* @author Steve Ebersole
*/
public interface TableGroup extends SqlAstNode, ColumnReferenceQualifier, DomainResultProducer {
public interface TableGroup
extends SqlAstNode, ColumnReferenceQualifier, DomainResultProducer, SqmSelectableInterpretation {
NavigablePath getNavigablePath();
ModelPart getModelPart();

View File

@ -333,9 +333,8 @@ private void verifyConfigured() {
@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

View File

@ -87,7 +87,7 @@ public void testBasicSearchedCaseExpression() {
}
@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 void testBasicCoalesceExpression() {
}
@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"

View File

@ -8,11 +8,11 @@
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.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.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 @@
*/
@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<String> query = session.createQuery( "select e.name from SimpleEntity e", String.class );
final QueryImplementor<String> query = session.createQuery(
"select e.name from SimpleEntity e",
String.class
);
final HqlQueryImplementor<String> hqlQuery = (HqlQueryImplementor<String>) query;
//noinspection unchecked
final SqmSelectStatement<String> sqmStatement = (SqmSelectStatement<String>) hqlQuery.getSqmStatement();
@ -105,6 +110,16 @@ public void testSimpleHqlInterpretation(SessionFactoryScope scope) {
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 void testConvertedHqlInterpretation(SessionFactoryScope scope) {
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 void testConvertedHqlInterpretation(SessionFactoryScope scope) {
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" )
);
}
);
}

View File

@ -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<String> query = session.createQuery(
"select e.name from SimpleEntity e",
String.class
);
List<String> 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<SimpleEntity> query = session.createQuery(
"select e from SimpleEntity e",
SimpleEntity.class
);
List<SimpleEntity> simpleEntities = query.list();
assertThat( simpleEntities.size(), is( 1 ) );
}
);
}
}

View File

@ -45,7 +45,7 @@
/**
* A reason why the failure is expected
*/
String value() default "";
String reason() default "";
/**
* The key of a JIRA issue which covers this expected failure.

View File

@ -14,7 +14,6 @@
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 ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con
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 ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con
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 );