6 - SQM based on JPA type system

- SQM tests
This commit is contained in:
Steve Ebersole 2019-08-05 11:55:37 -05:00 committed by Andrea Boriero
parent 5359a7b5fd
commit 4dd7c280ca
14 changed files with 330 additions and 37 deletions

View File

@ -10,6 +10,8 @@ import java.io.Serializable;
import java.sql.Connection;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.naming.Referenceable;
import javax.persistence.EntityManagerFactory;
@ -103,6 +105,108 @@ public interface SessionFactory extends EntityManagerFactory, HibernateEntityMan
*/
StatelessSession openStatelessSession(Connection connection);
/**
* Open a Session and perform a action using it
*/
default void inSession(Consumer<Session> action) {
try (Session session = openSession()) {
action.accept( session );
}
}
/**
* Open a Session and perform a action using it within the bounds of a transaction
*/
default void inTransaction(Consumer<Session> action) {
inSession(
session -> {
final Transaction txn = session.beginTransaction();
try {
action.accept( session );
if ( !txn.isActive() ) {
throw new TransactionManagementException( "Execution of action caused managed transaction to be completed" );
}
}
catch (RuntimeException e) {
// an error happened in the action
if ( txn.isActive() ) {
try {
txn.rollback();
}
catch (Exception ignore) {
}
}
throw e;
}
// action completed with no errors - attempt to commit the transaction allowing
// any RollbackException to propagate. Note that when we get here we know the
// txn is active
txn.commit();
}
);
}
class TransactionManagementException extends RuntimeException {
TransactionManagementException(String message) {
super( message );
}
}
/**
* Open a Session and perform a action using it
*/
default <R> R fromSession(Function<Session,R> action) {
try (Session session = openSession()) {
return action.apply( session );
}
}
/**
* Open a Session and perform a action using it within the bounds of a transaction
*/
default <R> R fromTransaction(Function<Session,R> action) {
return fromSession(
session -> {
R result = null;
final Transaction txn = session.beginTransaction();
try {
result = action.apply( session );
if ( !txn.isActive() ) {
throw new TransactionManagementException( "Execution of action caused managed transaction to be completed" );
}
}
catch (RuntimeException e) {
// an error happened in the action
if ( txn.isActive() ) {
try {
txn.rollback();
}
catch (Exception ignore) {
}
}
throw e;
}
// action completed with no errors - attempt to commit the transaction allowing
// any RollbackException to propagate. Note that when we get here we know the
// txn is active
txn.commit();
return result;
}
);
}
/**
* Retrieve the statistics fopr this factory.
*

View File

@ -7,24 +7,19 @@
package org.hibernate.metamodel.model.domain;
import org.hibernate.Incubating;
import org.hibernate.metamodel.model.mapping.spi.Writeable;
import org.hibernate.metamodel.model.mapping.spi.ModelPart;
import org.hibernate.metamodel.model.mapping.spi.ValueMapping;
import org.hibernate.query.Query;
import org.hibernate.query.sqm.SqmExpressable;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
/**
* Specialization of DomainType for types that can be used as {@link Query} parameter bind values
* Specialization of DomainType for types that can be used as {@link Query} parameter bind values.
*
* todo (6.0) : extend Writeable? or expose Writeable as "component"?
* i.e.
* ````
* Writeable getWriteable();
* ````
* todo (6.0) : Need a resolution between AllowableParameterType and {@link ValueMapping} / {@link ModelPart}
*
* @author Steve Ebersole
*/
@Incubating
public interface AllowableParameterType<J> extends Writeable, SqmExpressable<J> {
public interface AllowableParameterType<J> extends SimpleDomainType<J> {
JavaTypeDescriptor<J> getExpressableJavaTypeDescriptor();
}

View File

@ -6,17 +6,17 @@
*/
package org.hibernate.metamodel.model.domain.internal;
import org.hibernate.metamodel.model.domain.AllowableParameterType;
import org.hibernate.metamodel.model.domain.EmbeddableDomainType;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.produce.spi.SqmCreationState;
import org.hibernate.query.sqm.tree.domain.SqmAnyValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmPath;
/**
* @author Steve Ebersole
*/
public class EmbeddedSqmPathSource<J> extends AbstractSqmPathSource<J> {
public class EmbeddedSqmPathSource<J> extends AbstractSqmPathSource<J> implements AllowableParameterType<J> {
public EmbeddedSqmPathSource(
String localPathName,
EmbeddableDomainType<J> domainType,
@ -30,6 +30,16 @@ public class EmbeddedSqmPathSource<J> extends AbstractSqmPathSource<J> {
return (EmbeddableDomainType<J>) super.getSqmPathType();
}
@Override
public PersistenceType getPersistenceType() {
return PersistenceType.EMBEDDABLE;
}
@Override
public Class<J> getJavaType() {
return getBindableJavaType();
}
@Override
public SqmPathSource<?> findSubPathSource(String name) {
return (SqmPathSource<?>) getSqmPathType().findAttribute( name );

View File

@ -73,14 +73,15 @@ public class StandardCallableStatementSupport implements CallableStatementSuppor
sep = ",";
}
else {
parameter.getHibernateType().visitJdbcTypes(
sqlExpressableType -> {
buffer.append( sep ).append( "?" );
sep = ",";
},
Clause.IRRELEVANT,
session.getFactory().getTypeConfiguration()
);
throw new NotYetImplementedFor6Exception( getClass() );
// parameter.getHibernateType().visitJdbcTypes(
// sqlExpressableType -> {
// buffer.append( sep ).append( "?" );
// sep = ",";
// },
// Clause.IRRELEVANT,
// session.getFactory().getTypeConfiguration()
// );
}
}
}

View File

@ -104,6 +104,7 @@ import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
import org.hibernate.query.sqm.tree.predicate.SqmAndPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmBetweenPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmComparisonPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmEmptinessPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmGroupedPredicate;
@ -165,9 +166,6 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
private final SqmCreationOptions creationOptions;
private final SqmCreationContext creationContext;
private final ImplicitAliasGenerator implicitAliasGenerator;
private final UniqueIdGenerator uidGenerator;
private final Stack<DotIdentifierConsumer> dotIdentifierConsumerStack;
private final Stack<TreatHandler> treatHandlerStack = new StandardStack<>( new TreatHandlerNormal() );
@ -186,11 +184,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
this.creationOptions = creationOptions;
this.creationContext = creationContext;
this.implicitAliasGenerator = new ImplicitAliasGenerator();
this.uidGenerator = new UniqueIdGenerator();
this.dotIdentifierConsumerStack = new StandardStack<>( new BasicDotIdentifierConsumer( this ) );
}
@Override
@ -1092,6 +1086,18 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
}
}
@Override
public SqmBetweenPredicate visitBetweenPredicate(HqlParser.BetweenPredicateContext ctx) {
return new SqmBetweenPredicate(
(SqmExpression) ctx.expression( 0 ).accept( this ),
(SqmExpression) ctx.expression( 1 ).accept( this ),
(SqmExpression) ctx.expression( 2 ).accept( this ),
ctx.NOT() != null,
creationContext.getNodeBuilder()
);
}
@Override
public SqmNullnessPredicate visitIsNullPredicate(HqlParser.IsNullPredicateContext ctx) {
return new SqmNullnessPredicate(
@ -1187,7 +1193,6 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
else {
throw new SemanticException( "Path argument to MEMBER OF must be a plural attribute" );
}
}
@Override

View File

@ -861,4 +861,14 @@ public class ComponentType extends AbstractType implements CompositeType, Proced
public JavaTypeDescriptor getExpressableJavaTypeDescriptor() {
throw new NotYetImplementedFor6Exception( getClass() );
}
@Override
public PersistenceType getPersistenceType() {
return PersistenceType.EMBEDDABLE;
}
@Override
public Class getJavaType() {
return getReturnedClass();
}
}

View File

@ -33,7 +33,7 @@ public class JdbcDateTypeDescriptor extends AbstractTypeDescriptor<Date> {
* @see #DATE_FORMAT
*/
@SuppressWarnings("unused")
public static final DateTimeFormatter LITERAL_FORMATTER = DateTimeFormatter.ofPattern( DATE_FORMAT );
public static final DateTimeFormatter LITERAL_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE;
/**
* Alias for {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE}.

View File

@ -28,7 +28,7 @@ public class JdbcTimeTypeDescriptor extends AbstractTypeDescriptor<Date> {
@SuppressWarnings("WeakerAccess")
public static final String TIME_FORMAT = "HH:mm:ss.SSS";
public static final DateTimeFormatter LITERAL_FORMATTER = DateTimeFormatter.ofPattern( TIME_FORMAT );
public static final DateTimeFormatter LITERAL_FORMATTER = DateTimeFormatter.ISO_LOCAL_TIME;
/**
* Alias for {@link java.time.format.DateTimeFormatter#ISO_LOCAL_TIME}.

View File

@ -34,7 +34,7 @@ public class JdbcTimestampTypeDescriptor extends AbstractTypeDescriptor<Date> {
* @see #TIMESTAMP_FORMAT
*/
@SuppressWarnings("unused")
public static final DateTimeFormatter LITERAL_FORMATTER = DateTimeFormatter.ofPattern( TIMESTAMP_FORMAT );
public static final DateTimeFormatter LITERAL_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
/**
* Alias for {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE_TIME}.

View File

@ -17,6 +17,7 @@ import org.hibernate.query.sqm.tree.predicate.SqmComparisonPredicate;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
import org.hibernate.testing.orm.junit.FailureExpected;
import org.hibernate.testing.orm.junit.TestingUtil;
import org.junit.jupiter.api.Test;
@ -86,6 +87,7 @@ public class CaseExpressionsTest extends BaseSqmUnitTest {
}
@Test
@FailureExpected( "Support for functions not yet defined" )
public void testBasicCoalesceExpression() {
SqmSelectStatement select = interpretSelect(
"select coalesce(p.nickName, p.mate.nickName) from Person p"
@ -103,6 +105,7 @@ public class CaseExpressionsTest extends BaseSqmUnitTest {
}
@Test
@FailureExpected( "Support for functions not yet defined" )
public void testBasicNullifExpression() {
SqmSelectStatement select = interpretSelect(
"select nullif(p.nickName, p.mate.nickName) from Person p"

View File

@ -0,0 +1,40 @@
/*
* 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.query.hql;
import org.hibernate.boot.MetadataSources;
import org.hibernate.orm.test.query.sqm.BaseSqmUnitTest;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.junit.jupiter.api.Test;
/**
* @author Steve Ebersole
*/
public class LiteralTests extends BaseSqmUnitTest {
@Override
protected void applyMetadataSources(MetadataSources metadataSources) {
StandardDomainModel.GAMBIT.getDescriptor().applyDomainModel( metadataSources );
}
@Test
public void testTimestampLiteral() {
final SqmSelectStatement sqm = interpretSelect( "from EntityOfBasics e1 where e1.theTimestamp = {ts '2018-01-01T12:30:00'}" );
}
@Test
public void testDateLiteral() {
final SqmSelectStatement sqm = interpretSelect( "from EntityOfBasics e1 where e1.theDate = {d '2018-01-01'}" );
}
@Test
public void testTimeLiteral() {
final SqmSelectStatement sqm = interpretSelect( "from EntityOfBasics e1 where e1.theTime = {t '12:30:00'}" );
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.query.hql;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.junit.DomainModel;
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.Test;
import static org.hibernate.orm.test.query.sqm.BaseSqmUnitTest.interpretSelect;
/**
* @author Steve Ebersole
*/
@SuppressWarnings("WeakerAccess")
@DomainModel(
standardModels = StandardDomainModel.GAMBIT
)
@ServiceRegistry(
settings = @ServiceRegistry.Setting(
name = AvailableSettings.HBM2DDL_AUTO,
value = "create-drop"
)
)
@SessionFactory
public class PagingTests {
@Test
public void testPagingByParameter(SessionFactoryScope scope) {
interpretSelect( "select o from SimpleEntity o offset :param", scope.getSessionFactory() );
interpretSelect( "select o from SimpleEntity o limit :param", scope.getSessionFactory() );
interpretSelect( "select o from SimpleEntity o limit :param offset :param", scope.getSessionFactory() );
}
@Test
public void testPagingByConstant(SessionFactoryScope scope) {
interpretSelect( "select o from SimpleEntity o offset 1", scope.getSessionFactory() );
interpretSelect( "select o from SimpleEntity o limit 1", scope.getSessionFactory() );
interpretSelect( "select o from SimpleEntity o limit 1 offset 1", scope.getSessionFactory() );
}
@Test
public void testPagingOnSubQuery(SessionFactoryScope scope) {
interpretSelect( "select o from SimpleEntity o where o.someString = ( select oSub.someString from SimpleEntity oSub order by oSub.someString limit 1 )", scope.getSessionFactory() );
}
}

View File

@ -7,6 +7,8 @@
package org.hibernate.orm.test.query.hql;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.Entity;
@ -15,19 +17,24 @@ import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
import org.hibernate.Session;
import org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource;
import org.hibernate.orm.test.query.sqm.BaseSqmUnitTest;
import org.hibernate.query.Query;
import org.hibernate.query.SemanticException;
import org.hibernate.query.spi.QueryParameterBinding;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.sql.exec.spi.DomainParameterBindingContext;
import org.hibernate.testing.orm.junit.ExpectedException;
import org.hibernate.testing.orm.junit.ExpectedExceptionExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
@ -35,6 +42,8 @@ import static org.hibernate.testing.hamcrest.CollectionMatchers.hasSize;
/**
* @author Steve Ebersole
* @author Andrea Boriero
* @author Chris Cranford
*/
@SuppressWarnings("WeakerAccess")
@ExtendWith( ExpectedExceptionExtension.class )
@ -81,6 +90,60 @@ public class ParameterTests extends BaseSqmUnitTest {
assertThat( parameter.allowMultiValuedBinding(), is(true) );
}
@Test
public void testWideningTemporalPrecision() {
try (Session session = sessionFactory().openSession()) {
final Query query = session.createQuery( "select p.id from Person p where p.anniversary between :start and :end" );
query.setParameter( "start", Instant.now().minus( 7, ChronoUnit.DAYS ), TemporalType.TIMESTAMP );
query.setParameter( "end", Instant.now().plus( 7, ChronoUnit.DAYS ), TemporalType.TIMESTAMP );
final QueryParameterBindings bindings = ( (DomainParameterBindingContext) query ).getQueryParameterBindings();
final QueryParameterBinding<?> startBinding = bindings.getBinding( "start" );
assertThat( startBinding.getExplicitTemporalPrecision(), equalTo( TemporalType.TIMESTAMP ) );
final QueryParameterBinding<?> endBinding = bindings.getBinding( "end" );
assertThat( endBinding.getExplicitTemporalPrecision(), equalTo( TemporalType.TIMESTAMP ) );
}
}
@Test
public void testNarrowingTemporalPrecision() {
try (Session session = sessionFactory().openSession()) {
final Query query = session.createQuery( "select p.id from Person p where p.dob between :start and :end" );
query.setParameter( "start", Instant.now().minus( 7, ChronoUnit.DAYS ), TemporalType.DATE );
query.setParameter( "end", Instant.now().plus( 7, ChronoUnit.DAYS ), TemporalType.DATE );
final QueryParameterBindings bindings = ( (DomainParameterBindingContext) query ).getQueryParameterBindings();
final QueryParameterBinding<?> startBinding = bindings.getBinding( "start" );
assertThat( startBinding.getExplicitTemporalPrecision(), equalTo( TemporalType.DATE ) );
final QueryParameterBinding<?> endBinding = bindings.getBinding( "end" );
assertThat( endBinding.getExplicitTemporalPrecision(), equalTo( TemporalType.DATE ) );
}
}
@Test
public void testEmbeddableUseInPredicates() {
{
final SqmSelectStatement<?> sqm = interpretSelect( "select p.id from Person p where p.name.first = :fname" );
assertThat( sqm.getSqmParameters().size(), equalTo( 1 ) );
final SqmParameter<?> parameter = sqm.getSqmParameters().iterator().next();
assertThat( parameter.getAnticipatedType(), instanceOf( BasicSqmPathSource.class ) );
}
{
final SqmSelectStatement<?> sqm = interpretSelect( "select p.id from Person p where p.name = :name" );
assertThat( sqm.getSqmParameters().size(), equalTo( 1 ) );
final SqmParameter<?> parameter = sqm.getSqmParameters().iterator().next();
assertThat( parameter.getAnticipatedType(), instanceOf( EmbeddedSqmPathSource.class ) );
}
}
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] {
@ -104,12 +167,15 @@ public class ParameterTests extends BaseSqmUnitTest {
public String nickName;
@ManyToOne
Person mate;
@Temporal( TemporalType.TIMESTAMP )
public Instant dob;
@ManyToOne
public Person mate;
@Temporal( TemporalType.DATE )
public Date anniversary;
public int numberOfToes;
}

View File

@ -8,6 +8,7 @@ package org.hibernate.orm.test.query.sqm;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.loader.spi.AfterLoadAction;
import org.hibernate.metamodel.spi.MetamodelImplementor;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
@ -51,7 +52,11 @@ public abstract class BaseSqmUnitTest
}
protected SqmSelectStatement interpretSelect(String hql) {
return (SqmSelectStatement) sessionFactory().getQueryEngine().getSemanticQueryProducer().interpret( hql );
return interpretSelect( hql, sessionFactory() );
}
public static SqmSelectStatement interpretSelect(String hql, SessionFactoryImplementor sessionFactory) {
return (SqmSelectStatement) sessionFactory.getQueryEngine().getSemanticQueryProducer().interpret( hql );
}
@Override