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

Fixes: [BUG] Using skip with DocumentTemplate does not have any effect #550 #551

Merged
merged 7 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ and this project adheres to https://semver.org/spec/v2.0.0.html[Semantic Version
- Include pagination with Query annotation
- Add support to array in the fields
- Add support to array in the fields of java record classes
- Include `selectOffSet` to pagination queryies at the `SemiStructuredTemplate`


=== Fixed

Expand All @@ -26,6 +28,7 @@ and this project adheres to https://semver.org/spec/v2.0.0.html[Semantic Version
- Make sure at the serialization to the field, the API does not return any communication layer, but standard Java types
- Fix the like query at the JDQL
- Fix recursion calling to avoid stack overflow on the custom repository's query methods with @Query annotation with predefined queries
- Fix documentation at `SemiStructuredTemplate` explaining how the cursor works.

=== Removed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,21 +306,23 @@ default Optional<CommunicationEntity> singleResult(SelectQuery query) {
/**
* Select entities using pagination with cursor-based paging.
*
* <p>This method retrieves entities based on cursor-based paging, where the cursor acts as a bookmark for the next page of results.
* If the provided {@link PageRequest} has a mode of {@link jakarta.data.page.PageRequest.Mode#OFFSET}, the method will consider
* the initial request as an offset-based pagination and extract the order key to create a new {@link PageRequest} with
* {@link jakarta.data.page.PageRequest.Mode#CURSOR_NEXT}. If the initial request is already cursor-based, the method will proceed as instructed.
* </p>
* <p>
* If the cursor-based pagination is used, at least one order key is required to be specified in the {@link SelectQuery} order
* clause; otherwise, an {@link IllegalStateException} will be thrown.
* </p>
* <p>This method retrieves entities based on cursor-based paging, where the cursor acts as a bookmark for the next or previous page of results.
* The method strictly supports cursor-based pagination and does not handle offset-based pagination. If the provided {@link PageRequest} is
* in {@link jakarta.data.page.PageRequest.Mode#OFFSET}, this method should not be used.</p>
*
* <p>The {@link SelectQuery} parameter will be overwritten based on the {@link PageRequest}, specifically using the cursor information to
* adjust the query condition accordingly. This method ignores the skip value in {@link PageRequest} since skip is not applicable in cursor-based
* pagination.</p>
*
* <p>For cursor-based pagination, at least one sort field must be specified in the {@link SelectQuery} order clause; otherwise, an
* {@link IllegalArgumentException} will be thrown.</p>
*
* @param query the query to retrieve entities
* @param pageRequest the page request defining the cursor-based paging
* @return a {@link CursoredPage} instance containing the entities within the specified page
* @throws NullPointerException if the query or pageRequest is null
* @throws IllegalStateException if the cursor-based pagination is used without any order key specified
* @throws IllegalArgumentException if cursor-based pagination is used without any sort field specified or if the cursor size does not match
* the sort size
*/
default CursoredPage<CommunicationEntity> selectCursor(SelectQuery query, PageRequest pageRequest){
Objects.requireNonNull(query, "query is required");
Expand All @@ -347,4 +349,4 @@ default CursoredPage<CommunicationEntity> selectCursor(SelectQuery query, PageRe
* Closes the database manager and releases any associated resources.
*/
void close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import jakarta.data.exceptions.NonUniqueResultException;
import jakarta.data.page.CursoredPage;
import jakarta.data.page.Page;
import jakarta.data.page.PageRequest;
import jakarta.data.page.impl.CursoredPageRecord;
import jakarta.nosql.QueryMapper;
Expand All @@ -28,6 +29,7 @@
import org.eclipse.jnosql.communication.semistructured.SelectQuery;
import org.eclipse.jnosql.mapping.core.Converters;
import org.eclipse.jnosql.mapping.IdNotFoundException;
import org.eclipse.jnosql.mapping.core.NoSQLPage;
import org.eclipse.jnosql.mapping.metadata.EntitiesMetadata;
import org.eclipse.jnosql.mapping.metadata.EntityMetadata;
import org.eclipse.jnosql.mapping.metadata.FieldMetadata;
Expand Down Expand Up @@ -333,6 +335,16 @@ public <T> CursoredPage<T> selectCursor(SelectQuery query, PageRequest pageReque
return new CursoredPageRecord<>(entities, cursors, -1, pageRequest, nextPageRequest, beforePageRequest);
}

@Override
public <T> Page<T> selectOffSet(SelectQuery query, PageRequest pageRequest) {
Objects.requireNonNull(query, "query is required");
Objects.requireNonNull(pageRequest, "pageRequest is required");
var queryPage = new MappingQuery(query.sorts(), pageRequest.size(), NoSQLPage.skip(pageRequest),
query.condition().orElse(null), query.name());
Stream<T> result = select(queryPage);
return NoSQLPage.of(result.toList(), pageRequest);
}

protected <T> T persist(T entity, UnaryOperator<CommunicationEntity> persistAction) {
return Stream.of(entity)
.map(toUnary(eventManager()::firePreEntity))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
package org.eclipse.jnosql.mapping.semistructured;

import jakarta.data.page.CursoredPage;
import jakarta.data.page.Page;
import jakarta.data.page.PageRequest;
import org.eclipse.jnosql.mapping.PreparedStatement;
import jakarta.nosql.Template;
Expand Down Expand Up @@ -204,22 +205,47 @@ public interface SemiStructuredTemplate extends Template {
/**
* Select entities using pagination with cursor-based paging.
*
* <p>This method retrieves entities based on cursor-based paging, where the cursor acts as a bookmark for the next page of results.
* If the provided {@link PageRequest} has a mode of {@link jakarta.data.page.PageRequest.Mode#OFFSET}, the method will consider
* the initial request as an offset-based pagination and extract the order key to create a new {@link PageRequest} with
* {@link jakarta.data.page.PageRequest.Mode#CURSOR_NEXT}. If the initial request is already cursor-based, the method will proceed as instructed.
* </p>
* <p>
* If the cursor-based pagination is used, at least one order key is required to be specified in the {@link SelectQuery} order
* clause; otherwise, an {@link IllegalStateException} will be thrown.
* </p>
* <p>This method retrieves entities based on cursor-based paging, where the cursor acts as a bookmark for the next or previous page of results.
* The method strictly supports cursor-based pagination and does not handle offset-based pagination. If the provided {@link PageRequest} is
* in {@link jakarta.data.page.PageRequest.Mode#OFFSET}, this method should not be used; instead, use {@link #selectOffSet} for offset-based
* pagination.</p>
*
* <p>The {@link SelectQuery} parameter will be overwritten based on the {@link PageRequest}, specifically using the cursor information to
* adjust the query condition accordingly. This method ignores the skip value in {@link PageRequest} since skip is not applicable in cursor-based
* pagination.</p>
*
* <p>For cursor-based pagination, at least one sort field must be specified in the {@link SelectQuery} order clause; otherwise, an
* {@link IllegalArgumentException} will be thrown.</p>
*
* @param query the query to retrieve entities
* @param pageRequest the page request defining the cursor-based paging
* @param <T> the entity type
* @return a {@link CursoredPage} instance containing the entities within the specified page
* @throws NullPointerException if the query or pageRequest is null
* @throws IllegalStateException if the cursor-based pagination is used without any order key specified
* @throws IllegalArgumentException if cursor-based pagination is used without any sort field specified or if the cursor size does not match
* the sort size
*/
<T> CursoredPage<T> selectCursor(SelectQuery query, PageRequest pageRequest);

/**
* Select entities using pagination with offset-based paging.
*
* <p>This method retrieves entities using traditional offset-based pagination. The results are determined by the offset and limit values
* specified in the provided {@link PageRequest}. This method is suitable when you want to paginate through a result set using an explicit
* starting point (offset) and a defined page size.</p>
*
* <p>The {@link SelectQuery} may be modified based on the provided {@link PageRequest}, specifically using the offset and limit to adjust
* the query accordingly. Unlike cursor-based pagination, the offset value in {@link PageRequest} is utilized to skip the specified number
* of rows before retrieving the next set of results.</p>
*
* <p>It is important to note that offset-based pagination may have performance implications on large datasets because it requires the database
* to scan and count a potentially large number of rows before returning the requested page.</p>
*
* @param query the query to retrieve entities
* @param pageRequest the page request defining the offset-based paging, including the offset and page size
* @param <T> the entity type
* @return a {@link Page} instance containing the entities within the specified page, along with paging information
* @throws NullPointerException if the query or pageRequest is null
*/
<T> Page<T> selectOffSet(SelectQuery query, PageRequest pageRequest);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import jakarta.data.exceptions.NonUniqueResultException;
import jakarta.data.page.CursoredPage;
import jakarta.data.page.Page;
import jakarta.data.page.PageRequest;
import jakarta.data.page.impl.CursoredPageRecord;
import jakarta.enterprise.inject.Instance;
Expand All @@ -30,6 +31,7 @@
import org.eclipse.jnosql.communication.semistructured.SelectQuery;
import org.eclipse.jnosql.mapping.core.Converters;
import org.eclipse.jnosql.mapping.IdNotFoundException;
import org.eclipse.jnosql.mapping.core.NoSQLPage;
import org.eclipse.jnosql.mapping.semistructured.entities.Job;
import org.eclipse.jnosql.mapping.semistructured.entities.Person;
import org.eclipse.jnosql.mapping.metadata.EntitiesMetadata;
Expand Down Expand Up @@ -115,7 +117,7 @@ void shouldInsert() {
communicationEntity.addAll(Stream.of(columns).collect(Collectors.toList()));

Mockito.when(managerMock
.insert(any(CommunicationEntity.class)))
.insert(any(CommunicationEntity.class)))
.thenReturn(communicationEntity);

template.insert(this.person);
Expand All @@ -134,7 +136,7 @@ void shouldMergeOnInsert() {
communicationEntity.addAll(Stream.of(columns).collect(Collectors.toList()));

Mockito.when(managerMock
.insert(any(CommunicationEntity.class)))
.insert(any(CommunicationEntity.class)))
.thenReturn(communicationEntity);

Person person = Person.builder().build();
Expand All @@ -143,21 +145,19 @@ void shouldMergeOnInsert() {
verify(eventPersistManager).firePostEntity(any(Person.class));
verify(eventPersistManager).firePreEntity(any(Person.class));
assertSame(person, result);
assertEquals(10, person.getAge());
assertEquals(10, person.getAge());

}




@Test
void shouldInsertTTL() {
var communicationEntity = CommunicationEntity.of("Person");
communicationEntity.addAll(Stream.of(columns).collect(Collectors.toList()));

Mockito.when(managerMock
.insert(any(CommunicationEntity.class),
any(Duration.class)))
.insert(any(CommunicationEntity.class),
any(Duration.class)))
.thenReturn(communicationEntity);

template.insert(this.person, Duration.ofHours(2));
Expand All @@ -175,7 +175,7 @@ void shouldUpdate() {
communicationEntity.addAll(Stream.of(columns).collect(Collectors.toList()));

Mockito.when(managerMock
.update(any(CommunicationEntity.class)))
.update(any(CommunicationEntity.class)))
.thenReturn(communicationEntity);

template.update(this.person);
Expand All @@ -193,7 +193,7 @@ void shouldMergeOnUpdate() {
communicationEntity.addAll(Stream.of(columns).collect(Collectors.toList()));

Mockito.when(managerMock
.update(any(CommunicationEntity.class)))
.update(any(CommunicationEntity.class)))
.thenReturn(communicationEntity);

Person person = Person.builder().build();
Expand All @@ -213,7 +213,7 @@ void shouldInsertEntitiesTTL() {
Duration duration = Duration.ofHours(2);

Mockito.when(managerMock
.insert(any(CommunicationEntity.class), Mockito.eq(duration)))
.insert(any(CommunicationEntity.class), Mockito.eq(duration)))
.thenReturn(communicationEntity);

template.insert(Arrays.asList(person, person), duration);
Expand All @@ -226,7 +226,7 @@ void shouldInsertEntities() {
communicationEntity.addAll(Stream.of(columns).collect(Collectors.toList()));

Mockito.when(managerMock
.insert(any(CommunicationEntity.class)))
.insert(any(CommunicationEntity.class)))
.thenReturn(communicationEntity);

template.insert(Arrays.asList(person, person));
Expand All @@ -239,7 +239,7 @@ void shouldUpdateEntities() {
communicationEntity.addAll(Stream.of(columns).collect(Collectors.toList()));

Mockito.when(managerMock
.update(any(CommunicationEntity.class)))
.update(any(CommunicationEntity.class)))
.thenReturn(communicationEntity);

template.update(Arrays.asList(person, person));
Expand Down Expand Up @@ -281,7 +281,7 @@ void shouldReturnSingleResult() {
columnEntity.addAll(Stream.of(columns).collect(Collectors.toList()));

Mockito.when(managerMock
.select(any(SelectQuery.class)))
.select(any(SelectQuery.class)))
.thenReturn(Stream.of(columnEntity));

SelectQuery query = select().from("person").build();
Expand All @@ -293,7 +293,7 @@ void shouldReturnSingleResult() {
@Test
void shouldReturnSingleResultIsEmpty() {
Mockito.when(managerMock
.select(any(SelectQuery.class)))
.select(any(SelectQuery.class)))
.thenReturn(Stream.empty());

SelectQuery query = select().from("person").build();
Expand Down Expand Up @@ -332,7 +332,7 @@ void shouldReturnErrorWhenThereMoreThanASingleResult() {
columnEntity.addAll(Stream.of(columns).collect(Collectors.toList()));

Mockito.when(managerMock
.select(any(SelectQuery.class)))
.select(any(SelectQuery.class)))
.thenReturn(Stream.of(columnEntity, columnEntity));

SelectQuery query = select().from("person").build();
Expand Down Expand Up @@ -430,8 +430,8 @@ void shouldConvertEntityNameClassName() {
}

@Test
void shouldConvertConvertFromAnnotationEntity(){
template.query("FROM Vendor" );
void shouldConvertConvertFromAnnotationEntity() {
template.query("FROM Vendor");
ArgumentCaptor<SelectQuery> queryCaptor = ArgumentCaptor.forClass(SelectQuery.class);
verify(managerMock).select(queryCaptor.capture());
SelectQuery query = queryCaptor.getValue();
Expand Down Expand Up @@ -472,26 +472,26 @@ void shouldCountFromEntityClass() {
var captor = ArgumentCaptor.forClass(SelectQuery.class);
verify(managerMock).count(captor.capture());
var query = captor.getValue();
SoftAssertions.assertSoftly(soft ->{
SoftAssertions.assertSoftly(soft -> {
soft.assertThat(query.condition()).isEmpty();
});
}


@Test
void shouldFindAll(){
void shouldFindAll() {
template.findAll(Person.class);
verify(managerMock).select(select().from("Person").build());
}

@Test
void shouldDeleteAll(){
void shouldDeleteAll() {
template.deleteAll(Person.class);
verify(managerMock).delete(delete().from("Person").build());
}

@Test
void shouldSelectCursor(){
void shouldSelectCursor() {
PageRequest request = PageRequest.ofSize(2);

PageRequest afterKey = PageRequest.afterCursor(PageRequest.Cursor.forKey("Ada"), 1, 2, false);
Expand All @@ -504,7 +504,7 @@ void shouldSelectCursor(){
PageRequest personRequest = PageRequest.ofSize(2);
CursoredPage<Person> result = template.selectCursor(query, personRequest);

SoftAssertions.assertSoftly(soft ->{
SoftAssertions.assertSoftly(soft -> {
soft.assertThat(result).isNotNull();
soft.assertThat(result.content()).hasSize(1);
soft.assertThat(result.hasNext()).isTrue();
Expand All @@ -517,8 +517,33 @@ void shouldSelectCursor(){

}

@Test
void shouldSelectOffSet() {
PageRequest request = PageRequest.ofPage(2).size(10);

SelectQuery query = select().from("Person").orderBy("name").asc().build();

Mockito.when(managerMock.select(Mockito.any())).thenReturn(Stream.empty());

Page<Person> result = template.selectOffSet(query, request);
var captor = ArgumentCaptor.forClass(SelectQuery.class);
Mockito.verify(managerMock).select(captor.capture());
SelectQuery value = captor.getValue();
SoftAssertions.assertSoftly(soft -> {
soft.assertThat(result).isNotNull();
soft.assertThat(result.content()).isEmpty();
soft.assertThat(result.hasNext()).isFalse();
soft.assertThat(value.columns()).isEmpty();
soft.assertThat(value.name()).isEqualTo("Person");
soft.assertThat(value.sorts()).isNotEmpty();
soft.assertThat(value.skip()).isEqualTo(10);
soft.assertThat(value.limit()).isEqualTo(10);

});
}


private List<CommunicationEntity> content(){
private List<CommunicationEntity> content() {
CommunicationEntity columnEntity = CommunicationEntity.of("Person");
columnEntity.addAll(Stream.of(columns).collect(Collectors.toList()));
return List.of(columnEntity);
Expand Down