Skip to content

Commit

Permalink
Support paging and sorting repository.
Browse files Browse the repository at this point in the history
Closes #77
  • Loading branch information
junghoon-vans authored May 25, 2024
1 parent 007999a commit 8b94824
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@ public interface MeilisearchOperations {
*/
<T> List<T> multiGet(Class<T> clazz);

/**
* Retrieves all entities of the given type with the given offset and limit.
*
* @param clazz must not be {@literal null}.
* @param offset must not be {@literal null}.
* @param limit must not be {@literal null}.
* @return entities
* @param <T> entity type
*/
<T> List<T> multiGet(Class<T> clazz, int offset, int limit);

/**
* Retrieves all entities of the given type with the given document ids.
*
Expand All @@ -84,6 +95,18 @@ public interface MeilisearchOperations {
*/
<T> List<T> multiGet(Class<T> clazz, List<String> documentIds);

/**
* Retrieves all entities of the given type with the given document ids and offset and limit.
*
* @param clazz must not be {@literal null}.
* @param documentIds must not be {@literal null}.
* @param offset must not be {@literal null}.
* @param limit must not be {@literal null}.
* @return entities
* @param <T> entity type
*/
<T> List<T> multiGet(Class<T> clazz, List<String> documentIds, int offset, int limit);

/**
* Checks whether an entity with the given document id exists.
*
Expand Down Expand Up @@ -162,6 +185,16 @@ public interface MeilisearchOperations {
*/
<T> List<T> search(SearchRequest searchRequest, Class<?> clazz);

/**
* Make the given attributes sortable.
*
* @param clazz the entity class, must be annotated with
* {@link io.vanslog.spring.data.meilisearch.annotations.Document}
* @param attributes the attributes to make sortable
* @param <T> the type of the entity
*/
<T> void makeSortable(Class<T> clazz, String[] attributes);

/**
* Return the {@link io.vanslog.spring.data.meilisearch.core.convert.MeilisearchConverter}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package io.vanslog.spring.data.meilisearch.core;

import com.meilisearch.sdk.model.DocumentsQuery;
import io.vanslog.spring.data.meilisearch.DocumentAccessException;
import io.vanslog.spring.data.meilisearch.TaskStatusException;
import io.vanslog.spring.data.meilisearch.UncategorizedMeilisearchException;
Expand Down Expand Up @@ -108,14 +109,28 @@ public <T> T get(String documentId, Class<T> clazz) {

@Override
public <T> List<T> multiGet(Class<T> clazz) {
return multiGet(clazz, -1, -1);
}

@Override
public <T> List<T> multiGet(Class<T> clazz, int offset, int limit) {
String indexUid = getIndexUidFor(clazz);
T[] results = execute(client -> client.index(indexUid).getDocuments(clazz).getResults());
DocumentsQuery query = new DocumentsQuery();
query.setOffset(offset);
query.setLimit(limit);

T[] results = execute(client -> client.index(indexUid).getDocuments(query, clazz).getResults());
return Arrays.asList(results);
}

@Override
public <T> List<T> multiGet(Class<T> clazz, List<String> documentIds) {
List<T> entities = multiGet(clazz);
return multiGet(clazz, documentIds, -1, -1);
}

@Override
public <T> List<T> multiGet(Class<T> clazz, List<String> documentIds, int offset, int limit) {
List<T> entities = multiGet(clazz, offset, limit);
return entities.stream().filter(entity -> documentIds.contains(getDocumentIdFor(entity))).toList();
}

Expand Down Expand Up @@ -176,6 +191,15 @@ public <T> List<T> search(SearchRequest searchRequest, Class<?> clazz) {
.toList();
}

public <T> void makeSortable(Class<T> clazz, String[] attributes) {
String indexUid = getIndexUidFor(clazz);
TaskInfo taskInfo = execute(client -> client.index(indexUid).updateSortableAttributesSettings(attributes));

if (!isTaskSucceeded(indexUid, taskInfo)) {
throw new TaskStatusException(taskInfo.getStatus(), "Failed to make sortable.");
}
}

/**
* Execute the given {@link MeilisearchCallback}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;

/**
* Repository interface for Meilisearch.
Expand All @@ -28,6 +29,6 @@
* @see org.springframework.data.repository.CrudRepository
*/
@NoRepositoryBean
public interface MeilisearchRepository<T, ID> extends CrudRepository<T, ID> {
public interface MeilisearchRepository<T, ID> extends CrudRepository<T, ID>, PagingAndSortingRepository<T, ID> {

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@

package io.vanslog.spring.data.meilisearch.repository.support;

import com.meilisearch.sdk.SearchRequest;
import io.vanslog.spring.data.meilisearch.core.MeilisearchOperations;
import io.vanslog.spring.data.meilisearch.repository.MeilisearchRepository;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
Expand Down Expand Up @@ -116,6 +121,37 @@ public void deleteAll() {
meilisearchOperations.deleteAll(entityType);
}

@Override
public Iterable<T> findAll(Sort sort) {
Assert.notNull(sort, "sort must not be null");

String[] sortOptions = convertSortToSortOptions(sort);
SearchRequest searchRequest = SearchRequest.builder() //
.sort(sortOptions).build();

return meilisearchOperations.search(searchRequest, entityType);
}

@Override
public Page<T> findAll(Pageable pageable) {
Assert.notNull(pageable, "pageable must not be null");

int intOffset = Math.toIntExact(pageable.getOffset());
String[] sortOptions = convertSortToSortOptions(pageable.getSort());
SearchRequest searchRequest = SearchRequest.builder() //
.limit(pageable.getPageSize()) //
.offset(intOffset) //
.sort(sortOptions).build();

List<T> entities = meilisearchOperations.search(searchRequest, entityType);
return new PageImpl<>(entities, pageable, meilisearchOperations.count(entityType));
}

private String[] convertSortToSortOptions(Sort sort) {
return sort.stream().map(order -> order.getProperty() + ":" + (order.isAscending() ? "asc" : "desc"))
.toArray(String[]::new);
}

protected @Nullable String stringIdRepresentation(@Nullable ID id) {
return id != null ? meilisearchOperations.getMeilisearchConverter().convertId(id) : null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,17 @@ void shouldGetEntities() {
assertThat(savedMovies).containsExactlyInAnyOrder(movie1, movie2, movie3);
}

@Test
void shouldGetEntitiesWithPagination() {

List<Movie> movies = List.of(movie1, movie2, movie3);
meilisearchTemplate.save(movies);

List<Movie> savedMovies = meilisearchTemplate.multiGet(Movie.class, 1, 2);

assertThat(savedMovies).containsExactlyInAnyOrder(movie2, movie3);
}

@Test
void shouldGetCertainEntities() {

Expand All @@ -109,6 +120,17 @@ void shouldGetCertainEntities() {
assertThat(savedMovies).containsExactlyInAnyOrder(movie1, movie3);
}

@Test
void shouldGetCertainEntitiesWithPagination() {

List<Movie> movies = List.of(movie1, movie2, movie3);
meilisearchTemplate.save(movies);

List<Movie> savedMovies = meilisearchTemplate.multiGet(Movie.class, List.of("1", "3"), 0, 1);

assertThat(savedMovies).containsExactlyInAnyOrder(movie1);
}

@Test
void returnTrueWhenDocumentExists() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import static org.assertj.core.api.Assertions.*;

import io.vanslog.spring.data.meilisearch.core.MeilisearchTemplate;
import io.vanslog.spring.data.meilisearch.entities.Movie;
import io.vanslog.spring.data.meilisearch.junit.jupiter.MeilisearchTest;
import io.vanslog.spring.data.meilisearch.junit.jupiter.MeilisearchTestConfiguration;
Expand All @@ -31,6 +32,9 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.ContextConfiguration;

/**
Expand All @@ -43,6 +47,7 @@
class MeilisearchRepositoryIntegrationTests {

@Autowired private MovieRepository movieRepository;
@Autowired private MeilisearchTemplate meilisearchTemplate;

@BeforeEach
void setUp() {
Expand Down Expand Up @@ -253,6 +258,134 @@ void shouldExistsDocument() {
assertThat(notExists).isFalse();
}

@Test
void shouldFindDocumentsWithPaging() {
// given
int pagingSize = 2;

int documentId1 = 1;
Movie movie1 = new Movie();
movie1.setId(documentId1);
movie1.setTitle("Carol");
movie1.setDescription("A love story");
movie1.setGenres(new String[] { "Romance", "Drama" });

int documentId2 = 2;
Movie movie2 = new Movie();
movie2.setId(documentId2);
movie2.setTitle("Wonder Woman");
movie2.setDescription("A superhero film");
movie2.setGenres(new String[] { "Action", "Adventure" });

int documentId3 = 3;
Movie movie3 = new Movie();
movie3.setId(documentId3);
movie3.setTitle("Life of Pi");
movie3.setDescription("A survival film");
movie3.setGenres(new String[] { "Adventure", "Drama" });

List<Movie> movies = List.of(movie1, movie2, movie3);
movieRepository.saveAll(movies);

// when
Page<Movie> page1 = movieRepository.findAll(PageRequest.of(0, pagingSize));
Page<Movie> page2 = movieRepository.findAll(PageRequest.of(1, pagingSize));

// then
assertThat(page1.getTotalElements()).isEqualTo(movies.size());

assertThat(page1.getContent()).hasSize(2);
assertThat(page1.getContent().get(0)).isEqualTo(movie1);
assertThat(page1.getContent().get(1)).isEqualTo(movie2);

assertThat(page2.getContent()).hasSize(1);
assertThat(page2.getContent().get(0)).isEqualTo(movie3);
}

@Test
void shouldFindDocumentsWithSorting() {
// given
int documentId1 = 1;
Movie movie1 = new Movie();
movie1.setId(documentId1);
movie1.setTitle("Carol");
movie1.setDescription("A love story");
movie1.setGenres(new String[] { "Romance", "Drama" });

int documentId2 = 2;
Movie movie2 = new Movie();
movie2.setId(documentId2);
movie2.setTitle("Wonder Woman");
movie2.setDescription("A superhero film");
movie2.setGenres(new String[] { "Action", "Adventure" });

int documentId3 = 3;
Movie movie3 = new Movie();
movie3.setId(documentId3);
movie3.setTitle("Life of Pi");
movie3.setDescription("A survival film");
movie3.setGenres(new String[] { "Adventure", "Drama" });

List<Movie> movies = List.of(movie1, movie3, movie2);
movieRepository.saveAll(movies);
meilisearchTemplate.makeSortable(Movie.class, new String[]{"title"});

// when
Iterable<Movie> descOrdered = movieRepository.findAll(Sort.by(Sort.Direction.DESC, "title"));
Iterable<Movie> ascOrdered = movieRepository.findAll(Sort.by(Sort.Direction.ASC, "title"));

// then
assertThat(descOrdered).hasSize(3).containsExactly(movie2, movie3, movie1);
assertThat(ascOrdered).hasSize(3).containsExactly(movie1, movie3, movie2);
}


@Test
void shouldFindDocumentsWithPagingAndSorting() {
// given
int pagingSize = 2;

int documentId1 = 1;
Movie movie1 = new Movie();
movie1.setId(documentId1);
movie1.setTitle("Carol");
movie1.setDescription("A love story");
movie1.setGenres(new String[] { "Romance", "Drama" });

int documentId2 = 2;
Movie movie2 = new Movie();
movie2.setId(documentId2);
movie2.setTitle("Wonder Woman");
movie2.setDescription("A superhero film");
movie2.setGenres(new String[] { "Action", "Adventure" });

int documentId3 = 3;
Movie movie3 = new Movie();
movie3.setId(documentId3);
movie3.setTitle("Life of Pi");
movie3.setDescription("A survival film");
movie3.setGenres(new String[] { "Adventure", "Drama" });

List<Movie> movies = List.of(movie1, movie2, movie3);
movieRepository.saveAll(movies);
meilisearchTemplate.makeSortable(Movie.class, new String[]{"title"});

// when
Page<Movie> page1 = movieRepository.findAll(PageRequest.of(0, pagingSize, Sort.by(Sort.Direction.ASC, "title")));
Page<Movie> page2 = movieRepository.findAll(PageRequest.of(1, pagingSize, Sort.by(Sort.Direction.ASC, "title")));

// then
assertThat(page1.getTotalElements()).isEqualTo(movies.size());

assertThat(page1.getContent()).hasSize(2);
assertThat(page1.getContent().get(0)).isEqualTo(movie1);
assertThat(page1.getContent().get(1)).isEqualTo(movie3);

assertThat(page2.getContent()).hasSize(1);
assertThat(page2.getContent().get(0)).isEqualTo(movie2);
}


interface MovieRepository extends MeilisearchRepository<Movie, Integer> {}

@Configuration
Expand Down

0 comments on commit 8b94824

Please sign in to comment.