diff --git a/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/CosmosSqlQueryBuilder2.java b/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/CosmosSqlQueryBuilder2.java index afc6e9beb17..422298f5880 100644 --- a/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/CosmosSqlQueryBuilder2.java +++ b/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/CosmosSqlQueryBuilder2.java @@ -377,7 +377,7 @@ public Map getAdditionalRequiredParameters() { @NonNull @Override - public QueryResult buildPagination(@NonNull Pageable pageable) { + public String buildPagination(@NonNull Pageable pageable) { if (pageable.getMode() != Mode.OFFSET) { throw new UnsupportedOperationException("Pageable mode " + pageable.getMode() + " is not supported by cosmos operations"); } @@ -386,19 +386,9 @@ public QueryResult buildPagination(@NonNull Pageable pageable) { StringBuilder builder = new StringBuilder(" "); long from = pageable.getOffset(); builder.append("OFFSET ").append(from).append(" LIMIT ").append(size).append(" "); - return QueryResult.of( - builder.toString(), - Collections.emptyList(), - Collections.emptyList(), - Collections.emptyMap() - ); + return builder.toString(); } - return QueryResult.of( - "", - Collections.emptyList(), - Collections.emptyList(), - Collections.emptyMap() - ); + return ""; } } diff --git a/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/MongoQueryBuilder.java b/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/MongoQueryBuilder.java index 9f318165ba6..04a0c907a5d 100644 --- a/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/MongoQueryBuilder.java +++ b/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/MongoQueryBuilder.java @@ -451,35 +451,7 @@ public QueryResult buildQuery(AnnotationMetadata annotationMetadata, QueryModel } else { q = toJsonString(pipeline); } - return new QueryResult() { - - @NonNull - @Override - public String getQuery() { - return q; - } - - @Override - public int getMax() { - return query.getMax(); - } - - @Override - public long getOffset() { - return query.getOffset(); - } - - @Override - public List getQueryParts() { - return Collections.emptyList(); - } - - @Override - public List getParameterBindings() { - return queryState.getParameterBindings(); - } - - }; + return QueryResult.of(q, queryState.getParameterBindings()); } private void addLookups(Collection joins, QueryState queryState) { @@ -991,9 +963,7 @@ public QueryResult buildDelete(AnnotationMetadata annotationMetadata, QueryModel predicateQuery, Collections.emptyList(), queryState.getParameterBindings(), - queryState.getAdditionalRequiredParameters(), - query.getMax(), - query.getOffset() + queryState.getAdditionalRequiredParameters() ); } diff --git a/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/MongoQueryBuilder2.java b/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/MongoQueryBuilder2.java index 9cd11c16bc6..b6337f836bb 100644 --- a/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/MongoQueryBuilder2.java +++ b/data-document-model/src/main/java/io/micronaut/data/document/model/query/builder/MongoQueryBuilder2.java @@ -186,35 +186,7 @@ public QueryResult buildSelect(AnnotationMetadata annotationMetadata, SelectQuer } else { q = toJsonString(pipeline); } - return new QueryResult() { - - @NonNull - @Override - public String getQuery() { - return q; - } - - @Override - public int getMax() { - return selectQueryDefinition.limit(); - } - - @Override - public long getOffset() { - return selectQueryDefinition.offset(); - } - - @Override - public List getQueryParts() { - return Collections.emptyList(); - } - - @Override - public List getParameterBindings() { - return queryState.getParameterBindings(); - } - - }; + return QueryResult.of(q, queryState.getParameterBindings()); } private void addLookups(Collection joins, QueryState queryState) { @@ -646,14 +618,12 @@ public QueryResult buildDelete(AnnotationMetadata annotationMetadata, DeleteQuer predicateQuery, Collections.emptyList(), queryState.getParameterBindings(), - queryState.getAdditionalRequiredParameters(), - queryDefinition.limit(), - queryDefinition.offset() + queryState.getAdditionalRequiredParameters() ); } @Override - public QueryResult buildPagination(Pageable pageable) { + public String buildPagination(Pageable pageable) { throw new UnsupportedOperationException(); } diff --git a/data-document-processor/src/main/java/io/micronaut/data/document/processor/matchers/MongoRawQueryMethodMatcher.java b/data-document-processor/src/main/java/io/micronaut/data/document/processor/matchers/MongoRawQueryMethodMatcher.java index 394d330769b..511591a8ae3 100644 --- a/data-document-processor/src/main/java/io/micronaut/data/document/processor/matchers/MongoRawQueryMethodMatcher.java +++ b/data-document-processor/src/main/java/io/micronaut/data/document/processor/matchers/MongoRawQueryMethodMatcher.java @@ -221,27 +221,7 @@ private QueryResult getQueryResult(MethodMatchContext matchContext, } List parameterBindings = new ArrayList<>(parameters.size()); String filterQuery = processCustomQuery(matchContext, filterQueryString, parameters, entityParam, persistentEntity, parameterBindings); - return new QueryResult() { - @Override - public String getQuery() { - return filterQuery; - } - - @Override - public List getQueryParts() { - return Collections.emptyList(); - } - - @Override - public List getParameterBindings() { - return parameterBindings; - } - - @Override - public Map getAdditionalRequiredParameters() { - return Collections.emptyMap(); - } - }; + return QueryResult.of(filterQuery, parameterBindings); } private QueryResult getUpdateQueryResult(MethodMatchContext matchContext, @@ -278,10 +258,6 @@ public List getParameterBindings() { return parameterBindings; } - @Override - public Map getAdditionalRequiredParameters() { - return Collections.emptyMap(); - } }; } diff --git a/data-hibernate-jpa/src/main/java/io/micronaut/data/hibernate/operations/HibernateJpaOperations.java b/data-hibernate-jpa/src/main/java/io/micronaut/data/hibernate/operations/HibernateJpaOperations.java index da6724358e6..7de90a506ce 100644 --- a/data-hibernate-jpa/src/main/java/io/micronaut/data/hibernate/operations/HibernateJpaOperations.java +++ b/data-hibernate-jpa/src/main/java/io/micronaut/data/hibernate/operations/HibernateJpaOperations.java @@ -654,10 +654,10 @@ public List findAll(CriteriaQuery query) { public List findAll(CriteriaQuery query, int offset, int limit) { return executeRead(session -> { Query sessionQuery = session.createQuery(query); - if (offset != -1) { + if (offset > 0) { sessionQuery = sessionQuery.setFirstResult(offset); } - if (limit != -1) { + if (limit > 0) { sessionQuery = sessionQuery.setMaxResults(limit); } return sessionQuery.getResultList(); diff --git a/data-hibernate-reactive/src/main/java/io/micronaut/data/hibernate/reactive/operations/DefaultHibernateReactiveRepositoryOperations.java b/data-hibernate-reactive/src/main/java/io/micronaut/data/hibernate/reactive/operations/DefaultHibernateReactiveRepositoryOperations.java index 0447b54e5e1..4c741d3694c 100644 --- a/data-hibernate-reactive/src/main/java/io/micronaut/data/hibernate/reactive/operations/DefaultHibernateReactiveRepositoryOperations.java +++ b/data-hibernate-reactive/src/main/java/io/micronaut/data/hibernate/reactive/operations/DefaultHibernateReactiveRepositoryOperations.java @@ -440,10 +440,10 @@ public Flux findAll(CriteriaQuery query) { public Flux findAll(CriteriaQuery query, int offset, int limit) { return withSession(session -> helper.monoFromCompletionStage(() -> { Stage.SelectionQuery sessionQuery = session.createQuery(query); - if (offset != -1) { + if (offset > 0) { sessionQuery = sessionQuery.setFirstResult(offset); } - if (limit != -1) { + if (limit > 0) { sessionQuery = sessionQuery.setMaxResults(limit); } return sessionQuery.getResultList(); diff --git a/data-model/src/main/java/io/micronaut/data/intercept/annotation/DataMethod.java b/data-model/src/main/java/io/micronaut/data/intercept/annotation/DataMethod.java index 75cb3755e80..4dba1800a12 100644 --- a/data-model/src/main/java/io/micronaut/data/intercept/annotation/DataMethod.java +++ b/data-model/src/main/java/io/micronaut/data/intercept/annotation/DataMethod.java @@ -126,14 +126,28 @@ /** * The parameter that holds the pageSize value. + * @deprecated Replaced with {@link #META_MEMBER_LIMIT} */ + @Deprecated(forRemoval = true, since = "4.10") String META_MEMBER_PAGE_SIZE = "pageSize"; /** * The parameter that holds the offset value. + * @deprecated Replaced with {@link #META_MEMBER_OFFSET} */ + @Deprecated(forRemoval = true, since = "4.10") String META_MEMBER_PAGE_INDEX = "pageIndex"; + /** + * The parameter that holds the offset value. + */ + String META_MEMBER_OFFSET = "offset"; + + /** + * The parameter that holds the limit value. + */ + String META_MEMBER_LIMIT = "limit"; + /** * The parameter that references the entity. */ @@ -258,14 +272,18 @@ /** * An explicit pageSize (in absence of a pageable). * @return The pageSize + * @deprecated Not used */ + @Deprecated(forRemoval = true, since = "4.10") int pageSize() default -1; /** * An explicit offset (in absence of a pageable). * * @return The offset + * @deprecated Not used */ + @Deprecated(forRemoval = true, since = "4.10") long pageIndex() default 0; /** diff --git a/data-model/src/main/java/io/micronaut/data/model/Pageable.java b/data-model/src/main/java/io/micronaut/data/model/Pageable.java index 603e6216a66..3e4929c0fd7 100644 --- a/data-model/src/main/java/io/micronaut/data/model/Pageable.java +++ b/data-model/src/main/java/io/micronaut/data/model/Pageable.java @@ -182,6 +182,16 @@ default Pageable order(@NonNull String propertyName, @NonNull Order.Direction di return Pageable.from(getNumber(), getSize(), newSort); } + @NonNull + @Override + default Pageable order(@NonNull List orders) { + Sort newSort = getSort(); + for (Order order : orders) { + newSort = newSort.order(order); + } + return Pageable.from(getNumber(), getSize(), newSort); + } + @NonNull @Override @JsonIgnore diff --git a/data-model/src/main/java/io/micronaut/data/model/Sort.java b/data-model/src/main/java/io/micronaut/data/model/Sort.java index f9087f0ab00..51b8c20a70f 100644 --- a/data-model/src/main/java/io/micronaut/data/model/Sort.java +++ b/data-model/src/main/java/io/micronaut/data/model/Sort.java @@ -71,6 +71,21 @@ public interface Sort { */ @NonNull Sort order(@NonNull Sort.Order order); + /** + * Adds an orders. + * + * @param orders The orders + * @return A new sort with the order applied + * @since 4.10 + */ + @NonNull + default Sort order(@NonNull List orders) { + for (Order order : orders) { + order(order); + } + return this; + } + /** * Orders by the specified property name and direction. * diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentEntityQuery.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentEntityQuery.java index 6e067975e6c..38a551ae009 100644 --- a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentEntityQuery.java +++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/AbstractPersistentEntityQuery.java @@ -112,6 +112,10 @@ public QueryResult buildQuery(AnnotationMetadata annotationMetadata, QueryBuilde return queryBuilder.buildSelect(annotationMetadata, definition); } + protected boolean hasDynamicSort() { + return false; + } + /** * @return Build {@link io.micronaut.data.model.query.builder.QueryBuilder2.SelectQueryDefinition}. */ @@ -125,7 +129,8 @@ public QueryBuilder2.SelectQueryDefinition toSelectQueryDefinition() { distinct, orders == null ? List.of() : orders, max, - offset + offset, + hasDynamicSort() ); } @@ -140,7 +145,8 @@ public QueryResult buildCountQuery(AnnotationMetadata annotationMetadata, QueryB distinct, List.of(), -1, - -1 + -1, + false ); return queryBuilder.buildSelect(annotationMetadata, definition); } @@ -423,6 +429,7 @@ private static final class SelectQueryDefinitionImpl extends BaseQueryDefinition private final List order; private final int limit; private final int offset; + private final boolean hasDynamicSort; public SelectQueryDefinitionImpl(PersistentEntity persistentEntity, Predicate predicate, @@ -432,7 +439,8 @@ public SelectQueryDefinitionImpl(PersistentEntity persistentEntity, boolean isDistinct, List order, int limit, - int offset) { + int offset, + boolean hasDynamicSort) { super(persistentEntity, predicate, joinPaths); this.selection = selection; this.isForUpdate = isForUpdate; @@ -440,6 +448,12 @@ public SelectQueryDefinitionImpl(PersistentEntity persistentEntity, this.order = order; this.limit = limit; this.offset = offset; + this.hasDynamicSort = hasDynamicSort; + } + + @Override + public boolean hasDynamicSort() { + return hasDynamicSort; } @Override diff --git a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/expression/SubqueryExpression.java b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/expression/SubqueryExpression.java index f958d0fa145..a2c632ef701 100644 --- a/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/expression/SubqueryExpression.java +++ b/data-model/src/main/java/io/micronaut/data/model/jpa/criteria/impl/expression/SubqueryExpression.java @@ -33,7 +33,7 @@ public final class SubqueryExpression extends AbstractExpression { private final Type type; private final PersistentEntitySubquery subquery; - public SubqueryExpression(@NonNull Type type,@NonNull PersistentEntitySubquery subquery) { + public SubqueryExpression(@NonNull Type type, @NonNull PersistentEntitySubquery subquery) { super(subquery.getExpressionType()); this.type = type; this.subquery = subquery; diff --git a/data-model/src/main/java/io/micronaut/data/model/query/builder/QueryBuilder2.java b/data-model/src/main/java/io/micronaut/data/model/query/builder/QueryBuilder2.java index 7e2736bda04..ad000861992 100644 --- a/data-model/src/main/java/io/micronaut/data/model/query/builder/QueryBuilder2.java +++ b/data-model/src/main/java/io/micronaut/data/model/query/builder/QueryBuilder2.java @@ -87,7 +87,7 @@ public interface QueryBuilder2 { * @return The encoded query */ @NonNull - QueryResult buildPagination(@NonNull Pageable pageable); + String buildPagination(@NonNull Pageable pageable); /** * The select query definition. @@ -120,6 +120,13 @@ default boolean isDistinct() { return false; } + /** + * @return If the query is supposted to + */ + default boolean hasDynamicSort() { + return false; + } + } /** diff --git a/data-model/src/main/java/io/micronaut/data/model/query/builder/QueryResult.java b/data-model/src/main/java/io/micronaut/data/model/query/builder/QueryResult.java index b087c1befa3..c2ed67c0209 100644 --- a/data-model/src/main/java/io/micronaut/data/model/query/builder/QueryResult.java +++ b/data-model/src/main/java/io/micronaut/data/model/query/builder/QueryResult.java @@ -51,8 +51,10 @@ default String getUpdate() { /** * @return A string representation of the aggregate part. + * @deprecated Not used */ @Nullable + @Deprecated(forRemoval = true, since = "4.10") default String getAggregate() { return null; } @@ -116,6 +118,19 @@ default Collection getJoinPaths() { return Collections.emptyList(); } + /** + * Creates a new encoded query. + * + * @param query The query + * @param parameterBindings The parameters binding + * @return The query + * @since 4.10 + */ + @NonNull + static QueryResult of(@NonNull String query, @NonNull List parameterBindings) { + return of(query, List.of(), parameterBindings); + } + /** * Creates a new encoded query. * @@ -341,4 +356,48 @@ public Collection getJoinPaths() { }; } + /** + * Creates a new encoded query. + * + * @param query The query + * @param queryParts The queryParts + * @param parameterBindings The parameters binding + * @param joinPaths The join paths + * @return The query + */ + @NonNull + static QueryResult of( + @NonNull String query, + @NonNull List queryParts, + @NonNull List parameterBindings, + @Nullable + Collection joinPaths) { + ArgumentUtils.requireNonNull("query", query); + ArgumentUtils.requireNonNull("parameterBindings", parameterBindings); + + return new QueryResult() { + + @NonNull + @Override + public String getQuery() { + return query; + } + + @Override + public List getQueryParts() { + return queryParts; + } + + @Override + public List getParameterBindings() { + return parameterBindings; + } + + @Override + public Collection getJoinPaths() { + return joinPaths; + } + }; + } + } diff --git a/data-model/src/main/java/io/micronaut/data/model/query/builder/jpa/JpaQueryBuilder2.java b/data-model/src/main/java/io/micronaut/data/model/query/builder/jpa/JpaQueryBuilder2.java index 22629364939..31f3f6f25ef 100644 --- a/data-model/src/main/java/io/micronaut/data/model/query/builder/jpa/JpaQueryBuilder2.java +++ b/data-model/src/main/java/io/micronaut/data/model/query/builder/jpa/JpaQueryBuilder2.java @@ -34,6 +34,7 @@ import io.micronaut.data.model.query.JoinPath; import io.micronaut.data.model.query.builder.QueryResult; import io.micronaut.data.model.query.builder.sql.AbstractSqlLikeQueryBuilder2; +import io.micronaut.data.model.query.builder.sql.Dialect; import java.util.HashSet; import java.util.List; @@ -231,10 +232,30 @@ protected StringBuilder appendDeleteClause(StringBuilder queryString) { @NonNull @Override - public QueryResult buildPagination(@NonNull Pageable pageable) { + public String buildPagination(@NonNull Pageable pageable) { throw new UnsupportedOperationException("JPA-QL does not support pagination in query definitions"); } + @Override + public QueryResult buildSelect(@NonNull AnnotationMetadata annotationMetadata, @NonNull SelectQueryDefinition definition) { + QueryBuilder queryBuilder = new QueryBuilder(); + QueryState queryState = buildQuery(annotationMetadata, definition, queryBuilder, false, null); + + return QueryResult.of( + queryState.getFinalQuery(), + queryState.getQueryParts(), + queryState.getParameterBindings(), + definition.limit(), + definition.offset(), + queryState.getJoinPaths() + ); + } + + @Override + protected void appendLimitAndOffset(Dialect dialect, int limit, long offset, StringBuilder builder) { + // JPA doesn't support limit and offset in JPQL + } + @Override protected NamingStrategy getNamingStrategy(PersistentEntity entity) { return JPA_NAMING_STRATEGY; diff --git a/data-model/src/main/java/io/micronaut/data/model/query/builder/sql/AbstractSqlLikeQueryBuilder2.java b/data-model/src/main/java/io/micronaut/data/model/query/builder/sql/AbstractSqlLikeQueryBuilder2.java index ac617fe5ec7..1e84550ffaa 100644 --- a/data-model/src/main/java/io/micronaut/data/model/query/builder/sql/AbstractSqlLikeQueryBuilder2.java +++ b/data-model/src/main/java/io/micronaut/data/model/query/builder/sql/AbstractSqlLikeQueryBuilder2.java @@ -37,6 +37,7 @@ import io.micronaut.data.model.DataType; import io.micronaut.data.model.Embedded; import io.micronaut.data.model.JsonDataType; +import io.micronaut.data.model.Pageable; import io.micronaut.data.model.PersistentAssociationPath; import io.micronaut.data.model.PersistentEntity; import io.micronaut.data.model.PersistentEntityUtils; @@ -189,28 +190,12 @@ private PersistentPropertyPath asPersistentPropertyPath(PersistentProperty persi return PersistentPropertyPath.of(Collections.emptyList(), persistentProperty, persistentProperty.getName()); } - @Override - public QueryResult buildSelect(@NonNull AnnotationMetadata annotationMetadata, @NonNull SelectQueryDefinition definition) { - ArgumentUtils.requireNonNull("annotationMetadata", annotationMetadata); - ArgumentUtils.requireNonNull("definition", definition); - QueryBuilder queryBuilder = new QueryBuilder(); - QueryState queryState = buildQuery(annotationMetadata, definition, queryBuilder, null); - - return QueryResult.of( - queryState.getFinalQuery(), - queryState.getQueryParts(), - queryState.getParameterBindings(), - definition.limit(), - definition.offset(), - queryState.getJoinPaths() - ); - } - @NonNull - private QueryState buildQuery(AnnotationMetadata annotationMetadata, - SelectQueryDefinition definition, - QueryBuilder queryBuilder, - @Nullable String tableAliasPrefix) { + protected final QueryState buildQuery(AnnotationMetadata annotationMetadata, + SelectQueryDefinition definition, + QueryBuilder queryBuilder, + boolean appendLimitOffset, + @Nullable String tableAliasPrefix) { QueryState queryState = new QueryState(queryBuilder, definition, true, true, tableAliasPrefix); Predicate predicate = definition.predicate(); @@ -235,6 +220,9 @@ private QueryState buildQuery(AnnotationMetadata annotationMetadata, } appendOrder(annotationMetadata, definition, queryState); + if (appendLimitOffset) { + appendLimitAndOffset(getDialect(), definition.limit(), definition.offset(), queryState.getQuery()); + } appendForUpdate(QueryPosition.END_OF_QUERY, definition, queryState.getQuery()); return queryState; } @@ -971,7 +959,7 @@ protected StringBuilder appendDeleteClause(StringBuilder queryString) { * @return The encoded query */ @NonNull - public QueryResult buildOrderBy(String query, @NonNull PersistentEntity entity, @NonNull AnnotationMetadata annotationMetadata, @NonNull Sort sort, + public String buildOrderBy(String query, @NonNull PersistentEntity entity, @NonNull AnnotationMetadata annotationMetadata, @NonNull Sort sort, boolean nativeQuery) { ArgumentUtils.requireNonNull("entity", entity); ArgumentUtils.requireNonNull("sort", sort); @@ -1000,12 +988,7 @@ public QueryResult buildOrderBy(String query, @NonNull PersistentEntity entity, } } - return QueryResult.of( - buff.toString(), - Collections.emptyList(), - Collections.emptyList(), - Collections.emptyMap() - ); + return buff.toString(); } /** @@ -1324,12 +1307,74 @@ private void appendConcat(StringBuilder writer, Collection partsWriter } } - private record QueryBuilder(AtomicInteger position, - List parameterBindings, - StringBuilder query, - List queryParts) { + @NonNull + @Override + public String buildPagination(@NonNull Pageable pageable) { + int limit = pageable.getSize(); + long offset = pageable.getMode() == Pageable.Mode.OFFSET ? pageable.getOffset() : 0; + StringBuilder builder = new StringBuilder(); + appendLimitAndOffset(getDialect(), limit, offset, builder); + return builder.toString(); + } + + /** + * Append limit and offset. + * + * @param dialect The dialect + * @param limit The limit + * @param offset The offset + * @param builder The builder + */ + protected void appendLimitAndOffset(Dialect dialect, int limit, long offset, StringBuilder builder) { + boolean hasLimit = limit > 0; + boolean hasOffset = offset > 0; + if (!hasLimit && !hasOffset) { + return; + } + builder.append(' '); + switch (dialect) { + case SQL_SERVER -> { + // SQL server requires OFFSET always + if (hasOffset) { + builder.append("OFFSET ").append(offset).append(" ROWS "); + } else { + builder.append("OFFSET 0 ROWS "); + } + if (hasLimit) { + builder.append("FETCH NEXT ").append(limit).append(" ROWS ONLY"); + } + } + case ORACLE -> { + if (hasOffset) { + builder.append("OFFSET ").append(offset).append(" ROWS"); + } + if (hasLimit) { + if (hasOffset) { + builder.append(" "); + } + builder.append("FETCH NEXT ").append(limit).append(" ROWS ONLY"); + } + } + default -> { + if (hasLimit) { + builder.append("LIMIT ").append(limit); + } + if (hasOffset) { + if (hasLimit) { + builder.append(" "); + } + builder.append("OFFSET ").append(offset); + } + } + } + } + + protected record QueryBuilder(AtomicInteger position, + List parameterBindings, + StringBuilder query, + List queryParts) { - private QueryBuilder() { + public QueryBuilder() { this(new AtomicInteger(0), new ArrayList<>(), new StringBuilder(), new ArrayList<>()); } } @@ -2220,7 +2265,7 @@ public void visit(PersistentEntitySubquery subquery) { if (requiresBrackets) { query.append("("); } - buildQuery(AnnotationMetadata.EMPTY_METADATA, selectQueryDefinition, queryState.queryBuilder, outerAlias); + buildQuery(AnnotationMetadata.EMPTY_METADATA, selectQueryDefinition, queryState.queryBuilder, false, outerAlias); if (requiresBrackets) { query.append(")"); } diff --git a/data-model/src/main/java/io/micronaut/data/model/query/builder/sql/SqlQueryBuilder2.java b/data-model/src/main/java/io/micronaut/data/model/query/builder/sql/SqlQueryBuilder2.java index fe70c7ed604..eabbf4cec26 100644 --- a/data-model/src/main/java/io/micronaut/data/model/query/builder/sql/SqlQueryBuilder2.java +++ b/data-model/src/main/java/io/micronaut/data/model/query/builder/sql/SqlQueryBuilder2.java @@ -41,8 +41,6 @@ import io.micronaut.data.model.DataType; import io.micronaut.data.model.Embedded; import io.micronaut.data.model.JsonDataType; -import io.micronaut.data.model.Pageable; -import io.micronaut.data.model.Pageable.Mode; import io.micronaut.data.model.PersistentAssociationPath; import io.micronaut.data.model.PersistentEntity; import io.micronaut.data.model.PersistentEntityUtils; @@ -50,7 +48,6 @@ import io.micronaut.data.model.PersistentPropertyPath; import io.micronaut.data.model.naming.NamingStrategy; import io.micronaut.data.model.query.JoinPath; -import io.micronaut.data.model.query.builder.QueryBuilder; import io.micronaut.data.model.query.builder.QueryParameterBinding; import io.micronaut.data.model.query.builder.QueryResult; import jakarta.persistence.criteria.Selection; @@ -1068,60 +1065,6 @@ private String resolveSequenceName(PersistentProperty identity, String unescaped .orElseGet(() -> unescapedTableName + SEQ_SUFFIX); } - @NonNull - @Override - public QueryResult buildPagination(@NonNull Pageable pageable) { - int size = pageable.getSize(); - if (size > 0) { - StringBuilder builder = new StringBuilder(" "); - long from = pageable.getMode() == Mode.OFFSET ? pageable.getOffset() : 0; - switch (dialect) { - case H2: - case MYSQL: - if (from == 0) { - builder.append("LIMIT ").append(size); - } else { - builder.append("LIMIT ").append(from).append(',').append(size); - } - break; - case POSTGRES: - builder.append("LIMIT ").append(size).append(" "); - if (from != 0) { - builder.append("OFFSET ").append(from); - } - break; - - case SQL_SERVER: - // SQL server requires OFFSET always - if (from == 0) { - builder.append("OFFSET 0 ROWS "); - } - // intentional fall through - case ANSI: - case ORACLE: - default: - if (from != 0) { - builder.append("OFFSET ").append(from).append(" ROWS "); - } - builder.append("FETCH NEXT ").append(size).append(" ROWS ONLY "); - break; - } - return QueryResult.of( - builder.toString(), - Collections.emptyList(), - Collections.emptyList(), - Collections.emptyMap() - ); - } else { - return QueryResult.of( - "", - Collections.emptyList(), - Collections.emptyList(), - Collections.emptyMap() - ); - } - } - @Override protected String getAliasName(PersistentEntity entity) { return entity.getAliasName(); @@ -1552,6 +1495,32 @@ public Class annotationType() { return SqlQueryConfiguration.DialectConfiguration.class; } + @Override + public QueryResult buildSelect(@NonNull AnnotationMetadata annotationMetadata, @NonNull SelectQueryDefinition definition) { + QueryBuilder queryBuilder = new QueryBuilder(); + + if (definition.hasDynamicSort()) { + QueryState queryState = buildQuery(annotationMetadata, definition, queryBuilder, false, null); + + return QueryResult.of( + queryState.getFinalQuery(), + queryState.getQueryParts(), + queryState.getParameterBindings(), + definition.limit(), + definition.offset(), + queryState.getJoinPaths() + ); + } + QueryState queryState = buildQuery(annotationMetadata, definition, queryBuilder, true, null); + + return QueryResult.of( + queryState.getFinalQuery(), + queryState.getQueryParts(), + queryState.getParameterBindings(), + queryState.getJoinPaths() + ); + } + private static class DialectConfig { Boolean escapeQueries; String positionalFormatter; diff --git a/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/impl/MethodMatchSourcePersistentEntityCriteriaBuilderImpl.java b/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/impl/MethodMatchSourcePersistentEntityCriteriaBuilderImpl.java index bd37f6a0913..62121fbf3ed 100644 --- a/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/impl/MethodMatchSourcePersistentEntityCriteriaBuilderImpl.java +++ b/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/impl/MethodMatchSourcePersistentEntityCriteriaBuilderImpl.java @@ -50,6 +50,10 @@ public MethodMatchSourcePersistentEntityCriteriaBuilderImpl(MethodMatchContext m this.dataTypes = Utils.getConfiguredDataTypes(matchContext.getRepositoryClass()); } + public MethodMatchContext getMethodMatchContext() { + return methodMatchContext; + } + @Override public SourcePersistentEntityCriteriaQuery createQuery() { return new SourcePersistentEntityCriteriaQueryImpl<>(methodMatchContext::getEntity, this); diff --git a/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/impl/QueryResultAnalyzer.java b/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/impl/QueryResultAnalyzer.java index ba44e26231a..a935aa52cfc 100644 --- a/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/impl/QueryResultAnalyzer.java +++ b/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/impl/QueryResultAnalyzer.java @@ -20,7 +20,6 @@ import io.micronaut.data.model.jpa.criteria.PersistentEntityRoot; import io.micronaut.data.model.jpa.criteria.PersistentEntitySubquery; import io.micronaut.data.model.jpa.criteria.PersistentPropertyPath; -import io.micronaut.data.model.jpa.criteria.impl.IParameterExpression; import io.micronaut.data.model.jpa.criteria.impl.expression.BinaryExpression; import io.micronaut.data.model.jpa.criteria.impl.expression.FunctionExpression; import io.micronaut.data.model.jpa.criteria.impl.expression.IdExpression; diff --git a/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/impl/SourcePersistentEntityCriteriaQueryImpl.java b/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/impl/SourcePersistentEntityCriteriaQueryImpl.java index 278e2b88851..8f0054311f8 100644 --- a/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/impl/SourcePersistentEntityCriteriaQueryImpl.java +++ b/data-processor/src/main/java/io/micronaut/data/processor/model/criteria/impl/SourcePersistentEntityCriteriaQueryImpl.java @@ -15,7 +15,9 @@ */ package io.micronaut.data.processor.model.criteria.impl; +import io.micronaut.core.annotation.AnnotationMetadata; import io.micronaut.core.annotation.Internal; +import io.micronaut.data.annotation.TypeRole; import io.micronaut.data.model.PersistentEntity; import io.micronaut.data.model.jpa.criteria.ISelection; import io.micronaut.data.model.jpa.criteria.PersistentEntityRoot; @@ -81,4 +83,12 @@ public String getQueryResultTypeName() { public PersistentEntitySubquery subquery(Class type) { return new SourcePersistentEntitySubqueryImpl<>(this, entityResolver, criteriaBuilder); } + + @Override + protected boolean hasDynamicSort() { + if (criteriaBuilder instanceof MethodMatchSourcePersistentEntityCriteriaBuilderImpl mmcb) { + return mmcb.getMethodMatchContext().hasParameterInRole(TypeRole.SORT); + } + return false; + } } diff --git a/data-processor/src/main/java/io/micronaut/data/processor/visitors/RepositoryTypeElementVisitor.java b/data-processor/src/main/java/io/micronaut/data/processor/visitors/RepositoryTypeElementVisitor.java index 026d093ad91..7cce3f29f77 100644 --- a/data-processor/src/main/java/io/micronaut/data/processor/visitors/RepositoryTypeElementVisitor.java +++ b/data-processor/src/main/java/io/micronaut/data/processor/visitors/RepositoryTypeElementVisitor.java @@ -463,11 +463,11 @@ private void processMethodInfo(MethodMatchContext methodMatchContext, MethodMatc int max = queryResult.getMax(); if (max > -1) { - annotationBuilder.member(DataMethod.META_MEMBER_PAGE_SIZE, max); + annotationBuilder.member(DataMethod.META_MEMBER_LIMIT, max); } long offset = queryResult.getOffset(); if (offset > 0) { - annotationBuilder.member(DataMethod.META_MEMBER_PAGE_INDEX, offset); + annotationBuilder.member(DataMethod.META_MEMBER_OFFSET, offset); } } diff --git a/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/criteria/QueryCriteriaMethodMatch.java b/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/criteria/QueryCriteriaMethodMatch.java index 54b7bdd250d..22769ba7937 100644 --- a/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/criteria/QueryCriteriaMethodMatch.java +++ b/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/criteria/QueryCriteriaMethodMatch.java @@ -15,6 +15,7 @@ */ package io.micronaut.data.processor.visitors.finders.criteria; +import io.micronaut.core.annotation.AnnotationMetadata; import io.micronaut.core.annotation.Experimental; import io.micronaut.core.naming.NameUtils; import io.micronaut.core.util.StringUtils; @@ -40,7 +41,6 @@ import io.micronaut.data.processor.visitors.finders.MethodNameParser; import io.micronaut.data.processor.visitors.finders.QueryMatchId; import io.micronaut.data.processor.visitors.finders.TypeUtils; -import io.micronaut.inject.annotation.AnnotationMetadataHierarchy; import io.micronaut.inject.ast.ClassElement; import io.micronaut.inject.ast.ParameterElement; import jakarta.persistence.criteria.Order; @@ -203,12 +203,9 @@ protected MethodMatchInfo build(MethodMatchContext matchContext) { } } - final AnnotationMetadataHierarchy annotationMetadataHierarchy = new AnnotationMetadataHierarchy( - matchContext.getRepositoryClass().getAnnotationMetadata(), - matchContext.getAnnotationMetadata() - ); + final AnnotationMetadata annotationMetadata = matchContext.getMethodElement(); QueryBuilder queryBuilder = matchContext.getQueryBuilder(); - QueryResult queryResult = ((QueryResultPersistentEntityCriteriaQuery) criteriaQuery).buildQuery(annotationMetadataHierarchy, queryBuilder); + QueryResult queryResult = ((QueryResultPersistentEntityCriteriaQuery) criteriaQuery).buildQuery(annotationMetadata, queryBuilder); ClassElement genericReturnType = matchContext.getReturnType(); if (TypeUtils.isReactiveOrFuture(genericReturnType)) { @@ -216,7 +213,7 @@ protected MethodMatchInfo build(MethodMatchContext matchContext) { } QueryResult countQueryResult = null; if (matchContext.isTypeInRole(genericReturnType, TypeRole.PAGE) || matchContext.isTypeInRole(genericReturnType, TypeRole.CURSORED_PAGE)) { - countQueryResult = ((QueryResultPersistentEntityCriteriaQuery) criteriaQuery).buildCountQuery(annotationMetadataHierarchy, queryBuilder); + countQueryResult = ((QueryResultPersistentEntityCriteriaQuery) criteriaQuery).buildCountQuery(annotationMetadata, queryBuilder); } return new MethodMatchInfo( diff --git a/data-processor/src/test/groovy/io/micronaut/data/processor/sql/BuildQuerySpec.groovy b/data-processor/src/test/groovy/io/micronaut/data/processor/sql/BuildQuerySpec.groovy index fac6eee9d65..3014725d738 100644 --- a/data-processor/src/test/groovy/io/micronaut/data/processor/sql/BuildQuerySpec.groovy +++ b/data-processor/src/test/groovy/io/micronaut/data/processor/sql/BuildQuerySpec.groovy @@ -1765,7 +1765,7 @@ interface BookRepository extends GenericRepository { when: def queryTop3ByAuthorNameOrderByTitle = repository.findPossibleMethods("queryTop3ByAuthorNameOrderByTitle").findFirst().get() then: - getQuery(queryTop3ByAuthorNameOrderByTitle) == '''SELECT book_."id",book_."author_id",book_."genre_id",book_."title",book_."total_pages",book_."publisher_id",book_."last_updated" FROM "book" book_ INNER JOIN "author" book_author_ ON book_."author_id"=book_author_."id" WHERE (book_author_."name" = ?) ORDER BY book_."title" ASC''' + getQuery(queryTop3ByAuthorNameOrderByTitle) == '''SELECT book_."id",book_."author_id",book_."genre_id",book_."title",book_."total_pages",book_."publisher_id",book_."last_updated" FROM "book" book_ INNER JOIN "author" book_author_ ON book_."author_id"=book_author_."id" WHERE (book_author_."name" = ?) ORDER BY book_."title" ASC LIMIT 3''' getParameterBindingPaths(queryTop3ByAuthorNameOrderByTitle) == [""] as String[] getParameterPropertyPaths(queryTop3ByAuthorNameOrderByTitle) == ["author.name"] as String[] } diff --git a/data-processor/src/test/groovy/io/micronaut/data/processor/visitors/FindSpec.groovy b/data-processor/src/test/groovy/io/micronaut/data/processor/visitors/FindSpec.groovy index 56f361746e6..0e208362032 100644 --- a/data-processor/src/test/groovy/io/micronaut/data/processor/visitors/FindSpec.groovy +++ b/data-processor/src/test/groovy/io/micronaut/data/processor/visitors/FindSpec.groovy @@ -406,13 +406,60 @@ interface TestRepository extends CrudRepository { List findTop30OrderByTitle(); +} +""") + when: + def method = repository.findPossibleMethods("findTop30OrderByTitle").findFirst().get() + then: + method.stringValue(Query).get() == 'SELECT book_."id",book_."author_id",book_."genre_id",book_."title",book_."total_pages",book_."publisher_id",book_."last_updated" FROM "book" book_ ORDER BY book_."title" ASC LIMIT 30' + method.intValue(DataMethod, DataMethod.META_MEMBER_PAGE_SIZE).isEmpty() + method.intValue(DataMethod, DataMethod.META_MEMBER_LIMIT).isEmpty() + } + + void "test top with sort"() { + given: + def repository = buildRepository('test.TestRepository', """ +import io.micronaut.context.annotation.Executable; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.Sort; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.tck.entities.Book; + +@JdbcRepository(dialect= Dialect.POSTGRES) +@Executable +interface TestRepository extends CrudRepository { + + List findTop30OrderByTitle(Sort sort); + } """) when: def method = repository.findPossibleMethods("findTop30OrderByTitle").findFirst().get() then: method.stringValue(Query).get() == 'SELECT book_."id",book_."author_id",book_."genre_id",book_."title",book_."total_pages",book_."publisher_id",book_."last_updated" FROM "book" book_ ORDER BY book_."title" ASC' - method.intValue(DataMethod, DataMethod.META_MEMBER_PAGE_SIZE).getAsInt() == 30 + method.intValue(DataMethod, DataMethod.META_MEMBER_LIMIT).isPresent() + } + + void "test top JPA"() { + given: + def repository = buildRepository('test.TestRepository', """ +import io.micronaut.context.annotation.Executable; +import io.micronaut.data.annotation.Repository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.tck.entities.Book; + +@Repository +interface TestRepository extends CrudRepository { + + List findTop30OrderByTitle(); + +} +""") + when: + def method = repository.findPossibleMethods("findTop30OrderByTitle").findFirst().get() + then: + method.stringValue(Query).get() == 'SELECT book_ FROM io.micronaut.data.tck.entities.Book AS book_ ORDER BY book_.title ASC' + method.intValue(DataMethod, DataMethod.META_MEMBER_LIMIT).getAsInt() == 30 } void "test project association"() { diff --git a/data-processor/src/test/groovy/io/micronaut/data/processor/visitors/JpaOrderBySpec.groovy b/data-processor/src/test/groovy/io/micronaut/data/processor/visitors/JpaOrderBySpec.groovy index f54cb8d3e4c..3b7b390e701 100644 --- a/data-processor/src/test/groovy/io/micronaut/data/processor/visitors/JpaOrderBySpec.groovy +++ b/data-processor/src/test/groovy/io/micronaut/data/processor/visitors/JpaOrderBySpec.groovy @@ -84,7 +84,7 @@ interface MyInterface extends GenericRepository { listName.synthesize(Query).value() == "SELECT ${alias}.name FROM $Person.name AS ${alias} ORDER BY ${alias}.name ASC" listName.synthesize(DataMethod).resultType() == String listTop3.synthesize(Query).value() == "SELECT ${alias} FROM $Person.name AS ${alias} ORDER BY ${alias}.name ASC" - listTop3.synthesize(DataMethod).pageSize() == 3 + listTop3.intValue(DataMethod, DataMethod.META_MEMBER_LIMIT).getAsInt() == 3 findByCompanyUrlOrderByCompanyUrl.synthesize(Query).value() == "SELECT ${alias} FROM $Person.name AS ${alias} JOIN ${alias}.company ${alias}${companyAlias} WHERE (${alias}${companyAlias}.url = :p1) ORDER BY ${alias}${companyAlias}.url ASC" } diff --git a/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/AbstractQueryInterceptor.java b/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/AbstractQueryInterceptor.java index 10654460b1d..282e205091c 100644 --- a/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/AbstractQueryInterceptor.java +++ b/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/AbstractQueryInterceptor.java @@ -83,6 +83,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import static io.micronaut.data.intercept.annotation.DataMethod.META_MEMBER_LIMIT; +import static io.micronaut.data.intercept.annotation.DataMethod.META_MEMBER_OFFSET; import static io.micronaut.data.intercept.annotation.DataMethod.META_MEMBER_PAGE_SIZE; /** @@ -434,22 +436,53 @@ protected Optional getParameterInRole(MethodInvocationContext con */ @NonNull protected Pageable getPageable(MethodInvocationContext context) { - Pageable pageable = getParameterInRole(context, TypeRole.PAGEABLE, Pageable.class).orElse(null); + Pageable pageable = getPageableInRole(context); if (pageable == null) { + pageable = Pageable.UNPAGED; + int limit = context.intValue(DataMethod.NAME, META_MEMBER_PAGE_SIZE) + .orElseGet(() -> context.intValue(DataMethod.NAME, META_MEMBER_LIMIT).orElse(-1)); + if (limit > 0) { + pageable = Pageable.from(0, limit); + } Sort sort = getParameterInRole(context, TypeRole.SORT, Sort.class).orElse(null); - int max = context.intValue(DataMethod.NAME, META_MEMBER_PAGE_SIZE).orElse(-1); if (sort != null) { - int pageIndex = context.intValue(DataMethod.NAME, DataMethod.META_MEMBER_PAGE_INDEX).orElse(0); - if (max > 0) { - pageable = Pageable.from(pageIndex, max, sort); - } else { - pageable = Pageable.from(sort); - } - } else if (max > -1) { - return Pageable.from(0, max); + return pageable.order(sort.getOrderBy()); } } - return pageable != null ? pageable : Pageable.UNPAGED; + return pageable; + } + + /** + * Resolves the {@link Pageable} for the given context. + * + * @param context The context + * @return The pageable or null + */ + @Nullable + protected Pageable getPageableInRole(MethodInvocationContext context) { + return getParameterInRole(context, TypeRole.PAGEABLE, Pageable.class).orElse(null); + } + + /** + * Resolves the offset. + * + * @param context The context + * @return The offset or -1 + * @since 4.10 + */ + protected int getOffset(MethodInvocationContext context) { + return context.intValue(DataMethod.class, META_MEMBER_OFFSET).orElse(-1); + } + + /** + * Resolves the limit. + * + * @param context The context + * @return The limit or -1 + * @since 4.10 + */ + protected int getLimit(MethodInvocationContext context) { + return context.intValue(DataMethod.class, META_MEMBER_LIMIT).orElse(-1); } /** @@ -457,7 +490,9 @@ protected Pageable getPageable(MethodInvocationContext context) { * * @param metadata The metadata * @return True if it is nullable + * @deprecated Not used */ + @Deprecated(forRemoval = true, since = "4.10") protected boolean isNullable(@NonNull AnnotationMetadata metadata) { return metadata .getDeclaredAnnotationNames() diff --git a/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/criteria/AbstractSpecificationInterceptor.java b/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/criteria/AbstractSpecificationInterceptor.java index 08cb685d532..2d280a77606 100644 --- a/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/criteria/AbstractSpecificationInterceptor.java +++ b/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/criteria/AbstractSpecificationInterceptor.java @@ -143,15 +143,20 @@ public PreparedQuery decorate(PreparedQuery preparedQuery) { protected final Iterable findAll(RepositoryMethodKey methodKey, MethodInvocationContext context, Type type) { Set methodJoinPaths = getMethodJoinPaths(methodKey, context); if (criteriaRepositoryOperations != null) { - CriteriaQuery query = buildQuery(context, type, methodJoinPaths); - Pageable pageable = getPageable(context); + CriteriaQuery criteriaQuery = buildQuery(context, type, methodJoinPaths); + Pageable pageable = getPageableInRole(context); if (pageable != null) { if (pageable.getMode() != Mode.OFFSET) { - throw new UnsupportedOperationException("Pageable mode " + pageable.getMode() + " is not supported with specifications"); + throw new UnsupportedOperationException("Pageable mode " + pageable.getMode() + " is not supported by hibernate operations"); } - return criteriaRepositoryOperations.findAll(query, (int) pageable.getOffset(), pageable.getSize()); + return criteriaRepositoryOperations.findAll(criteriaQuery, (int) pageable.getOffset(), pageable.getSize()); } - return criteriaRepositoryOperations.findAll(query); + int offset = getOffset(context); + int limit = getLimit(context); + if (offset > 0 || limit > 0) { + return criteriaRepositoryOperations.findAll(criteriaQuery, offset, limit); + } + return criteriaRepositoryOperations.findAll(criteriaQuery); } PreparedQuery preparedQuery = preparedQueryForCriteria(methodKey, context, type, methodJoinPaths); context.setAttribute(PREPARED_QUERY_KEY, preparedQuery); diff --git a/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/criteria/reactive/AbstractReactiveSpecificationInterceptor.java b/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/criteria/reactive/AbstractReactiveSpecificationInterceptor.java index 7b08703262b..ee04231051b 100644 --- a/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/criteria/reactive/AbstractReactiveSpecificationInterceptor.java +++ b/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/criteria/reactive/AbstractReactiveSpecificationInterceptor.java @@ -76,13 +76,18 @@ protected final Publisher findAllReactive(RepositoryMethodKey methodKey, Set methodJoinPaths = getMethodJoinPaths(methodKey, context); if (reactiveCriteriaOperations != null) { CriteriaQuery criteriaQuery = buildQuery(context, type, methodJoinPaths); - Pageable pageable = getPageable(context); + Pageable pageable = getPageableInRole(context); if (pageable != null) { if (pageable.getMode() != Mode.OFFSET) { throw new UnsupportedOperationException("Pageable mode " + pageable.getMode() + " is not supported by hibernate operations"); } return reactiveCriteriaOperations.findAll(criteriaQuery, (int) pageable.getOffset(), pageable.getSize()); } + int offset = getOffset(context); + int limit = getLimit(context); + if (offset > 0 || limit > 0) { + return reactiveCriteriaOperations.findAll(criteriaQuery, offset, limit); + } return reactiveCriteriaOperations.findAll(criteriaQuery); } PreparedQuery preparedQuery = preparedQueryForCriteria(methodKey, context, type, methodJoinPaths); diff --git a/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/DefaultSqlPreparedQuery.java b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/DefaultSqlPreparedQuery.java index 1dc0fa14b42..ed345b0975c 100644 --- a/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/DefaultSqlPreparedQuery.java +++ b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/DefaultSqlPreparedQuery.java @@ -188,16 +188,16 @@ public void attachPageable(Pageable pageable, boolean isSingleResult) { added.append(buildCursorPagination(cursored.cursor().orElse(null), sort)); } if (sort.isSorted()) { - added.append(queryBuilder.buildOrderBy("", persistentEntity, sqlStoredQuery.getAnnotationMetadata(), sort, isNative()).getQuery()); + added.append(queryBuilder.buildOrderBy("", persistentEntity, sqlStoredQuery.getAnnotationMetadata(), sort, isNative())); } else if (isSqlServerWithoutOrderBy(query, sqlStoredQuery.getDialect())) { // SQL server requires order by sort = sortById(persistentEntity); - added.append(queryBuilder.buildOrderBy("", persistentEntity, sqlStoredQuery.getAnnotationMetadata(), sort, isNative()).getQuery()); + added.append(queryBuilder.buildOrderBy("", persistentEntity, sqlStoredQuery.getAnnotationMetadata(), sort, isNative())); } if (isSingleResult && pageable.getOffset() > 0) { pageable = Pageable.from(pageable.getNumber(), 1); } - added.append(queryBuilder.buildPagination(pageable).getQuery()); + added.append(queryBuilder.buildPagination(pageable)); int forUpdateIndex = query.lastIndexOf(SqlQueryBuilder.STANDARD_FOR_UPDATE_CLAUSE); if (forUpdateIndex == -1) { forUpdateIndex = query.lastIndexOf(SqlQueryBuilder.SQL_SERVER_FOR_UPDATE_CLAUSE); diff --git a/data-runtime/src/main/java/io/micronaut/data/runtime/query/internal/DefaultStoredQuery.java b/data-runtime/src/main/java/io/micronaut/data/runtime/query/internal/DefaultStoredQuery.java index 922d40441a1..1c6c15f148b 100644 --- a/data-runtime/src/main/java/io/micronaut/data/runtime/query/internal/DefaultStoredQuery.java +++ b/data-runtime/src/main/java/io/micronaut/data/runtime/query/internal/DefaultStoredQuery.java @@ -46,6 +46,7 @@ import java.util.Set; import java.util.stream.Collectors; +import static io.micronaut.data.intercept.annotation.DataMethod.META_MEMBER_LIMIT; import static io.micronaut.data.intercept.annotation.DataMethod.META_MEMBER_PAGE_SIZE; /** @@ -113,6 +114,7 @@ public DefaultStoredQuery( .map(c -> c == SqlQueryBuilder.class).orElse(false); this.hasPageable = method.stringValue(DATA_METHOD_ANN_NAME, TypeRole.PAGEABLE).isPresent() || method.stringValue(DATA_METHOD_ANN_NAME, TypeRole.SORT).isPresent() || + method.intValue(DATA_METHOD_ANN_NAME, META_MEMBER_LIMIT).orElse(-1) > -1 || method.intValue(DATA_METHOD_ANN_NAME, META_MEMBER_PAGE_SIZE).orElse(-1) > -1; if (isCount) {