Skip to content

Commit

Permalink
Merge pull request #3116 from micronaut-projects/subq
Browse files Browse the repository at this point in the history
Support subquery Criteria API
  • Loading branch information
dstepanov committed Sep 11, 2024
2 parents 05a06ee + f738646 commit a05fe25
Show file tree
Hide file tree
Showing 47 changed files with 1,946 additions and 578 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import io.micronaut.data.document.mongo.MongoAnnotations;
import io.micronaut.data.exceptions.MappingException;
import io.micronaut.data.model.Association;
import io.micronaut.data.model.Embedded;
import io.micronaut.data.model.Pageable;
import io.micronaut.data.model.PersistentEntity;
import io.micronaut.data.model.PersistentEntityUtils;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import io.micronaut.data.model.jpa.criteria.IPredicate;
import io.micronaut.data.model.jpa.criteria.ISelection;
import io.micronaut.data.model.jpa.criteria.PersistentEntityRoot;
import io.micronaut.data.model.jpa.criteria.PersistentEntitySubquery;
import io.micronaut.data.model.jpa.criteria.impl.CriteriaUtils;
import io.micronaut.data.model.jpa.criteria.impl.SelectionVisitor;
import io.micronaut.data.model.jpa.criteria.impl.expression.BinaryExpression;
Expand All @@ -46,6 +47,7 @@
import io.micronaut.data.model.jpa.criteria.impl.expression.UnaryExpression;
import io.micronaut.data.model.jpa.criteria.impl.predicate.ConjunctionPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.DisjunctionPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.ExistsSubqueryPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.LikePredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.NegatedPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.InPredicate;
Expand Down Expand Up @@ -1050,6 +1052,11 @@ public void visit(LikePredicate likePredicate) {
likePredicate.getPattern());
}

@Override
public void visit(ExistsSubqueryPredicate existsSubqueryPredicate) {
throw new UnsupportedOperationException("ExistsSubquery is not supported by this implementation.");
}

@Override
public void visitEquals(Expression<?> leftExpression, Expression<?> rightExpression, boolean ignoreCase) {
if (ignoreCase) {
Expand Down Expand Up @@ -1288,6 +1295,11 @@ public void visit(PersistentEntityRoot<?> entityRoot) {
// The default is the entity projection
}

@Override
public void visit(PersistentEntitySubquery<?> subquery) {
throw new IllegalStateException("Subquery not supported by MongoDB");
}

@Override
public void visit(CompoundSelection<?> compoundSelection) {
for (Selection<?> selection : compoundSelection.getCompoundSelectionItems()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,19 @@ import io.micronaut.data.hibernate.entities.UserWithWhere
import io.micronaut.data.jpa.repository.criteria.Specification
import io.micronaut.data.model.Pageable
import io.micronaut.data.model.Sort
import io.micronaut.data.repository.jpa.criteria.CriteriaQueryBuilder
import io.micronaut.data.repository.jpa.criteria.PredicateSpecification
import io.micronaut.data.tck.entities.Author
import io.micronaut.data.tck.entities.Book
import io.micronaut.data.tck.entities.EntityIdClass
import io.micronaut.data.tck.entities.EntityWithIdClass
import io.micronaut.data.tck.entities.Product
import io.micronaut.data.tck.entities.Student
import io.micronaut.data.tck.repositories.BookSpecifications
import io.micronaut.data.tck.tests.AbstractQuerySpec
import jakarta.inject.Inject
import jakarta.persistence.criteria.CriteriaBuilder
import jakarta.persistence.criteria.CriteriaQuery
import org.hibernate.LazyInitializationException
import spock.lang.Issue
import spock.lang.Shared
Expand Down Expand Up @@ -878,6 +882,13 @@ abstract class AbstractHibernateQuerySpec extends AbstractQuerySpec {
cnt == 2
}

void "test subquery criteria"() {
when:
def book = bookRepository.findOne(BookSpecifications.findUsingASubquery("The Stand"))
then:
book.title == "The Stand"
}

private static Specification<Book> testJoin(String value) {
return ((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.join("author").get("name"), value))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ package io.micronaut.data.hibernate
import io.micronaut.context.annotation.Property
import io.micronaut.data.hibernate.entities.RelPerson
import io.micronaut.data.hibernate.entities.UserWithWhere
import io.micronaut.data.repository.jpa.criteria.CriteriaQueryBuilder
import io.micronaut.data.tck.entities.Book
import io.micronaut.data.tck.repositories.BookSpecifications
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import jakarta.inject.Inject
import jakarta.persistence.criteria.CriteriaBuilder
import jakarta.persistence.criteria.CriteriaQuery

@MicronautTest(packages = "io.micronaut.data.tck.entities", rollback = false, transactional = false)
@Property(name = "datasources.default.name", value = "mydb")
Expand Down
2 changes: 2 additions & 0 deletions data-hibernate-jpa/src/test/resources/logback.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
</encoder>
</appender>

<!-- <logger name="org.hibernate.SQL" level="trace" />-->

<root level="error">
<appender-ref ref="STDOUT" />
</root>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ import io.micronaut.data.tck.repositories.TimezoneBasicTypesRepository
import io.micronaut.data.tck.repositories.UserRepository
import io.micronaut.data.tck.repositories.UserRoleRepository
import io.micronaut.data.tck.tests.AbstractRepositorySpec
import spock.lang.PendingFeature
import spock.lang.Shared

import static io.micronaut.data.tck.repositories.PersonRepository.Specifications.findNameSubqueryEq
import static io.micronaut.data.tck.repositories.PersonRepository.Specifications.findNameSubqueryIn
import static io.micronaut.data.tck.repositories.PersonRepository.Specifications.nameEqualsCaseInsensitive
import static io.micronaut.data.tck.repositories.PersonRepository.Specifications.subqueriesWithJoinReferencingOuter

class H2RepositorySpec extends AbstractRepositorySpec implements H2TestPropertyProvider {

Expand Down Expand Up @@ -248,6 +250,31 @@ class H2RepositorySpec extends AbstractRepositorySpec implements H2TestPropertyP
return true
}

void "test subquery with JOIN" () {
given:
saveSampleBooks()
when:
def books = bookRepository.findAll(subqueriesWithJoinReferencingOuter())
then:
books.size() == 6
}

void "test subquery IN" () {
when:
savePersons(["Jeff", "James"])
def person = personRepository.findOne(findNameSubqueryIn("James"))
then:
person
}

void "test subquery EQ" () {
when:
savePersons(["Jeff", "James"])
def person = personRepository.findOne(findNameSubqueryEq("James"))
then:
person
}

void "test criteria lower select" () {
when:
savePersons(["Jeff", "James"])
Expand Down
2 changes: 2 additions & 0 deletions data-jdbc/src/test/resources/logback.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
</encoder>
</appender>

<!-- <logger name="io.micronaut.data.query" level="trace" />-->

<root level="warn">
<appender-ref ref="STDOUT" />
</root>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2017-2024 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.data.model.jpa.criteria;

import io.micronaut.core.annotation.Experimental;
import jakarta.persistence.criteria.CommonAbstractCriteria;

/**
* The persistent entity {@link CommonAbstractCriteria}.
*
* @author Denis Stepanov
* @since 4.10
*/
@Experimental
public interface PersistentEntityCommonAbstractCriteria extends CommonAbstractCriteria {
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
* @since 3.2
*/
@Experimental
public interface PersistentEntityCriteriaDelete<T> extends CriteriaDelete<T> {
public interface PersistentEntityCriteriaDelete<T> extends CriteriaDelete<T>, PersistentEntityCommonAbstractCriteria {

PersistentEntityRoot<T> from(PersistentEntity persistentEntity);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
* @since 3.2
*/
@Experimental
public interface PersistentEntityCriteriaQuery<T> extends CriteriaQuery<T> {
public interface PersistentEntityCriteriaQuery<T> extends CriteriaQuery<T>, PersistentEntityCommonAbstractCriteria {

@NonNull
<X> PersistentEntityRoot<X> from(@NonNull PersistentEntity persistentEntity);
Expand Down Expand Up @@ -109,4 +109,6 @@ default PersistentEntityCriteriaQuery<T> forUpdate(boolean forUpdate) {
@NonNull
PersistentEntityCriteriaQuery<T> distinct(boolean distinct);

@Override
<U> PersistentEntitySubquery<U> subquery(Class<U> type);
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
* @since 3.2
*/
@Experimental
public interface PersistentEntityCriteriaUpdate<T> extends CriteriaUpdate<T> {
public interface PersistentEntityCriteriaUpdate<T> extends CriteriaUpdate<T>, PersistentEntityCommonAbstractCriteria {

@NonNull
PersistentEntityRoot<T> from(@NonNull PersistentEntity persistentEntity);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright 2017-2021 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.data.model.jpa.criteria;

import io.micronaut.core.annotation.Experimental;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.data.model.PersistentEntity;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Subquery;
import jakarta.persistence.metamodel.EntityType;

import java.util.List;

/**
* The persistent entity {@link Subquery}.
*
* @param <T> The type of the selected item
* @author Denis Stepanov
* @since 4.10
*/
@Experimental
public interface PersistentEntitySubquery<T> extends Subquery<T>, PersistentEntityCommonAbstractCriteria {

/**
* Sets the max rows value.
* @param max The max value
* @return This instance
*/
@NonNull
PersistentEntitySubquery<T> max(int max);

/**
* Sets the offset rows value.
* @param offset The offset value
* @return This instance
*/
@NonNull
PersistentEntitySubquery<T> offset(int offset);

/**
* Create a root using {@link PersistentEntity}.
* @param persistentEntity The persistent entity
* @param <X> The root type
* @return The root
*/
@NonNull
<X> PersistentEntityRoot<X> from(@NonNull PersistentEntity persistentEntity);

@Override
@NonNull
<X> PersistentEntityRoot<X> from(@NonNull Class<X> entityClass);

@Override
@NonNull
<X> PersistentEntityRoot<X> from(@NonNull EntityType<X> entity);

@Override
PersistentEntitySubquery<T> select(@NonNull Expression<T> expression);

@Override
@NonNull
PersistentEntitySubquery<T> where(@NonNull Expression<Boolean> restriction);

@Override
@NonNull
PersistentEntitySubquery<T> where(@NonNull Predicate... restrictions);

@Override
@NonNull
PersistentEntitySubquery<T> groupBy(@NonNull Expression<?>... grouping);

@Override
@NonNull
PersistentEntitySubquery<T> groupBy(@NonNull List<Expression<?>> grouping);

@Override
@NonNull
PersistentEntitySubquery<T> having(@NonNull Expression<Boolean> restriction);

@Override
@NonNull
PersistentEntitySubquery<T> having(@NonNull Predicate... restrictions);

@Override
@NonNull
PersistentEntitySubquery<T> distinct(boolean distinct);

/**
* @return The expression type
*/
ExpressionType<T> getExpressionType();

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
import io.micronaut.data.model.jpa.criteria.impl.expression.BinaryExpressionType;
import io.micronaut.data.model.jpa.criteria.impl.expression.FunctionExpression;
import io.micronaut.data.model.jpa.criteria.impl.expression.LiteralExpression;
import io.micronaut.data.model.jpa.criteria.impl.expression.SubqueryExpression;
import io.micronaut.data.model.jpa.criteria.impl.predicate.ConjunctionPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.DisjunctionPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.ExistsSubqueryPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.LikePredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.NegatedPredicate;
import io.micronaut.data.model.jpa.criteria.impl.predicate.BetweenPredicate;
Expand Down Expand Up @@ -223,48 +225,28 @@ public Expression<Long> countDistinct(@NonNull Expression<?> x) {
return new UnaryExpression<>(x, UnaryExpressionType.COUNT_DISTINCT, Long.class);
}

/**
* Not supported yet.
*
* {@inheritDoc}
*/
@Override
@NonNull
public Predicate exists(@NonNull Subquery<?> subquery) {
throw notSupportedOperation();
return new ExistsSubqueryPredicate(CriteriaUtils.requirePersistentEntitySubquery(subquery));
}

/**
* Not supported yet.
*
* {@inheritDoc}
*/
@Override
@NonNull
public <Y> Expression<Y> all(@NonNull Subquery<Y> subquery) {
throw notSupportedOperation();
return new SubqueryExpression<>(SubqueryExpression.Type.ALL, CriteriaUtils.requirePersistentEntitySubquery(subquery));
}

/**
* Not supported yet.
*
* {@inheritDoc}
*/
@Override
@NonNull
public <Y> Expression<Y> some(@NonNull Subquery<Y> subquery) {
throw notSupportedOperation();
return new SubqueryExpression<>(SubqueryExpression.Type.SOME, CriteriaUtils.requirePersistentEntitySubquery(subquery));
}

/**
* Not supported yet.
*
* {@inheritDoc}
*/
@Override
@NonNull
public <Y> Expression<Y> any(@NonNull Subquery<Y> subquery) {
throw notSupportedOperation();
return new SubqueryExpression<>(SubqueryExpression.Type.ANY, CriteriaUtils.requirePersistentEntitySubquery(subquery));
}

@Override
Expand Down
Loading

0 comments on commit a05fe25

Please sign in to comment.