Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial addition of cursored pagination for SQL #2884

Merged
merged 20 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/checkstyle/suppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@
<!-- files="DefaultBeanContext.java|BeanDefinitionWriter.java|DefaultHttpClient.java"/> -->

<suppress checks="MissingJavadocType" files=".*doc-examples.*" />
<suppress checks="FileLength" files=".*AbstractSqlLikeQueryBuilder.*" />

</suppressions>
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import io.micronaut.data.model.Association;
import io.micronaut.data.model.Embedded;
import io.micronaut.data.model.Pageable;
import io.micronaut.data.model.Pageable.Mode;
import io.micronaut.data.model.PersistentEntity;
import io.micronaut.data.model.PersistentProperty;
import io.micronaut.data.model.PersistentPropertyPath;
Expand Down Expand Up @@ -329,6 +330,9 @@ public Map<String, String> getAdditionalRequiredParameters() {
@NonNull
@Override
public QueryResult buildPagination(@NonNull Pageable pageable) {
if (pageable.getMode() != Mode.OFFSET) {
throw new UnsupportedOperationException("Pageable mode " + pageable.getMode() + " is not supported by cosmos operations");
}
int size = pageable.getSize();
if (size > 0) {
StringBuilder builder = new StringBuilder(" ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import io.micronaut.data.annotation.QueryHint;
import io.micronaut.data.jpa.annotation.EntityGraph;
import io.micronaut.data.model.Pageable;
import io.micronaut.data.model.Pageable.Mode;
import io.micronaut.data.model.Sort;
import io.micronaut.data.model.query.builder.jpa.JpaQueryBuilder;
import io.micronaut.data.model.runtime.PagedQuery;
Expand Down Expand Up @@ -335,6 +336,9 @@ protected <R> void collectFindAll(S session, PreparedQuery<?, R> preparedQuery,
String queryStr = preparedQuery.getQuery();
Pageable pageable = preparedQuery.getPageable();
if (pageable != Pageable.UNPAGED) {
if (pageable.getMode() != Mode.OFFSET) {
throw new UnsupportedOperationException("Pageable mode " + pageable.getMode() + " is not supported by hibernate operations");
}
Sort sort = pageable.getSort();
if (sort.isSorted()) {
queryStr += QUERY_BUILDER.buildOrderBy(queryStr, getEntity(preparedQuery.getRootEntity()), AnnotationMetadata.EMPTY_METADATA, sort,
Expand Down Expand Up @@ -599,6 +603,9 @@ private void bindPageable(P q, @NonNull Pageable pageable) {
// no pagination
return;
}
if (pageable.getMode() != Mode.OFFSET) {
throw new UnsupportedOperationException("Pageable mode " + pageable.getMode() + " is not supported by hibernate operations");
}

int max = pageable.getSize();
if (max > 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,18 @@ protected Publisher<?> interceptPublisher(RepositoryMethodKey methodKey, MethodI
return operations.withSession(session -> {
if (pageable.isUnpaged()) {
return Mono.fromCompletionStage(() -> session.createQuery(query).getResultList())
.map(resultList -> Page.of(resultList, pageable, resultList.size()));
.map(resultList -> Page.of(resultList, pageable, (long) resultList.size()));
}
return Mono.fromCompletionStage(() -> {
Stage.SelectionQuery<Object> q = session.createQuery(query);
q.setFirstResult((int) pageable.getOffset());
q.setMaxResults(pageable.getSize());
return q.getResultList();
}).flatMap(results -> {
if (!pageable.requestTotal()) {
return Mono.just(Page.of(results, pageable, null));
}

final CriteriaQuery<Long> countQuery = criteriaBuilder.createQuery(Long.class);
final Root<Object> countRoot = countQuery.from(rootEntity);
final Predicate countPredicate = specification != null ? specification.toPredicate(countRoot, countQuery, criteriaBuilder) : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,7 @@ public <T> Stream<T> findStream(@NonNull PagedQuery<T> query) {

@Override
public <R> Page<R> findPage(@NonNull PagedQuery<R> query) {
throw new UnsupportedOperationException("The findPage method without an explicit query is not supported. Use findPage(PreparedQuery) instead");
throw new UnsupportedOperationException("The findPage method without an explicit query is not supported. Use findAll(PreparedQuery) instead");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2017-2020 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.jdbc.h2

import io.micronaut.data.tck.repositories.BookRepository
import io.micronaut.data.tck.repositories.PersonRepository
import io.micronaut.data.tck.tests.AbstractCursoredPageSpec
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import jakarta.inject.Inject
import spock.lang.Shared

@MicronautTest
@H2DBProperties
class H2CursoredPaginationSpec extends AbstractCursoredPageSpec {
@Inject
@Shared
H2PersonRepository pr

@Inject
@Shared
H2BookRepository br

@Override
PersonRepository getPersonRepository() {
return pr
}

@Override
BookRepository getBookRepository() {
return br
}

@Override
void init() {
pr.deleteAll()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2017-2020 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.jdbc.mysql

import groovy.transform.Memoized
import io.micronaut.context.ApplicationContext
import io.micronaut.data.tck.repositories.BookRepository
import io.micronaut.data.tck.repositories.PersonRepository
import io.micronaut.data.tck.tests.AbstractCursoredPageSpec
import spock.lang.AutoCleanup
import spock.lang.Shared

class MysqlCursoredPaginationSpec extends AbstractCursoredPageSpec implements MySQLTestPropertyProvider {

@Shared @AutoCleanup ApplicationContext context

@Memoized
@Override
PersonRepository getPersonRepository() {
return context.getBean(MySqlPersonRepository)
}

@Memoized
@Override
BookRepository getBookRepository() {
return context.getBean(MySqlBookRepository)
}

@Override
void init() {
context = ApplicationContext.run(properties)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Less important, I think you could move context creation in the abstract class

@AutoCleanup
    @Shared
    ApplicationContext context = ApplicationContext.run(properties)

and then use it in all tests (usually how we do it) so init() method might not be needed.

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2017-2020 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.jdbc.oraclexe

import groovy.transform.Memoized
import io.micronaut.context.ApplicationContext
import io.micronaut.data.tck.repositories.BookRepository
import io.micronaut.data.tck.repositories.PersonRepository
import io.micronaut.data.tck.tests.AbstractCursoredPageSpec
import spock.lang.AutoCleanup
import spock.lang.Shared

class OracleXECursoredPaginationSpec extends AbstractCursoredPageSpec implements OracleTestPropertyProvider {

@Shared @AutoCleanup ApplicationContext context

@Override
@Memoized
PersonRepository getPersonRepository() {
return context.getBean(OracleXEPersonRepository)
}

@Override
@Memoized
BookRepository getBookRepository() {
return context.getBean(OracleXEBookRepository)
}

@Override
void init() {
context = ApplicationContext.run(properties)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2017-2020 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.jdbc.postgres

import groovy.transform.Memoized
import io.micronaut.context.ApplicationContext
import io.micronaut.data.tck.repositories.BookRepository
import io.micronaut.data.tck.repositories.PersonRepository
import io.micronaut.data.tck.tests.AbstractCursoredPageSpec
import spock.lang.AutoCleanup
import spock.lang.Ignore
import spock.lang.Shared

@Ignore("Causes error: 'FATAL: sorry, too many clients already'")
class PostgresCursoredPaginationSpec extends AbstractCursoredPageSpec implements PostgresTestPropertyProvider {
@Shared @AutoCleanup ApplicationContext context

@Memoized
@Override
PersonRepository getPersonRepository() {
return context.getBean(PostgresPersonRepository)
}

@Memoized
@Override
BookRepository getBookRepository() {
return context.getBean(PostgresBookRepository)
}

@Override
void init() {
context = ApplicationContext.run(getProperties())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2017-2020 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.jdbc.sqlserver

import io.micronaut.context.ApplicationContext
import io.micronaut.data.tck.repositories.BookRepository
import io.micronaut.data.tck.repositories.PersonRepository
import io.micronaut.data.tck.tests.AbstractCursoredPageSpec
import spock.lang.AutoCleanup
import spock.lang.Shared

class SqlServerCursoredPaginationSpec extends AbstractCursoredPageSpec implements MSSQLTestPropertyProvider {
andriy-dmytruk marked this conversation as resolved.
Show resolved Hide resolved

@Shared @AutoCleanup ApplicationContext context

@Override
PersonRepository getPersonRepository() {
return context.getBean(MSSQLPersonRepository)
}

@Override
BookRepository getBookRepository() {
return context.getBean(MSBookRepository)
}

@Override
void init() {
context = ApplicationContext.run(properties)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.micronaut.data.jdbc.sqlserver

import groovy.transform.Memoized
import io.micronaut.context.ApplicationContext
import io.micronaut.data.tck.repositories.BookRepository
import io.micronaut.data.tck.repositories.PersonRepository
Expand All @@ -26,11 +27,13 @@ class SqlServerPaginationSpec extends AbstractPageSpec implements MSSQLTestPrope

@Shared @AutoCleanup ApplicationContext context

@Memoized
@Override
PersonRepository getPersonRepository() {
return context.getBean(MSSQLPersonRepository)
}

@Memoized
@Override
BookRepository getBookRepository() {
return context.getBean(MSBookRepository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,29 +96,33 @@ public Page intercept(RepositoryMethodKey methodKey, MethodInvocationContext<Obj
return Page.of(
resultList,
pageable,
resultList.size()
(long) resultList.size()
);
} else {
typedQuery.setFirstResult((int) pageable.getOffset());
typedQuery.setMaxResults(pageable.getSize());
final List<Object> results = typedQuery.getResultList();
final CriteriaQuery<Long> countQuery = criteriaBuilder.createQuery(Long.class);
final Root<?> countRoot = countQuery.from(rootEntity);
final Predicate countPredicate = specification != null ? specification.toPredicate(countRoot, countQuery, criteriaBuilder) : null;
if (countPredicate != null) {
countQuery.where(countPredicate);
}
if (countQuery.isDistinct()) {
countQuery.select(criteriaBuilder.countDistinct(countRoot));
} else {
countQuery.select(criteriaBuilder.count(countRoot));

Long totalCount = null;
if (pageable.requestTotal()) {
final CriteriaQuery<Long> countQuery = criteriaBuilder.createQuery(Long.class);
final Root<?> countRoot = countQuery.from(rootEntity);
final Predicate countPredicate = specification != null ? specification.toPredicate(countRoot, countQuery, criteriaBuilder) : null;
if (countPredicate != null) {
countQuery.where(countPredicate);
}
if (countQuery.isDistinct()) {
countQuery.select(criteriaBuilder.countDistinct(countRoot));
} else {
countQuery.select(criteriaBuilder.count(countRoot));
}
totalCount = entityManager.createQuery(countQuery).getSingleResult();
}
Long singleResult = entityManager.createQuery(countQuery).getSingleResult();

return Page.of(
results,
pageable,
singleResult
totalCount
);
}

Expand Down
Loading
Loading