diff --git a/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/api/ApplicationQuery.java b/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/api/ApplicationQuery.java index 877e6a82daa..69c35713af9 100644 --- a/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/api/ApplicationQuery.java +++ b/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/api/ApplicationQuery.java @@ -22,6 +22,7 @@ import io.swagger.annotations.ApiParam; import org.eclipse.collections.api.list.MutableList; import org.finos.legend.engine.application.query.model.DataCubeQuery; +import org.finos.legend.engine.application.query.model.PaginatedQuerySearchSpecification; import org.finos.legend.engine.application.query.model.Query; import org.finos.legend.engine.application.query.model.QueryEvent; import org.finos.legend.engine.application.query.model.QuerySearchSpecification; @@ -58,11 +59,32 @@ private static String getCurrentUser(ProfileManager profileManage return profile != null ? profile.getId() : null; } + @Deprecated @POST @Path("search") @ApiOperation(value = "Search queries") @Consumes({MediaType.APPLICATION_JSON}) public Response searchQueries(QuerySearchSpecification searchSpecification, @ApiParam(hidden = true) @Pac4JProfileManager ProfileManager profileManager) + { + try + { + return Response.ok().entity(this.queryStoreManager.searchQueries(searchSpecification, getCurrentUser(profileManager)).queries).build(); + } + catch (Exception e) + { + if (e instanceof ApplicationQueryException) + { + return ((ApplicationQueryException) e).toResponse(); + } + return ExceptionTool.exceptionManager(e, LoggingEventType.SEARCH_QUERIES_ERROR, null); + } + } + + @POST + @Path("search/paginated") + @ApiOperation(value = "Search queries with pagination") + @Consumes({MediaType.APPLICATION_JSON}) + public Response searchQueriesPaginated(PaginatedQuerySearchSpecification searchSpecification, @ApiParam(hidden = true) @Pac4JProfileManager ProfileManager profileManager) { try { @@ -98,6 +120,7 @@ public Response getQueries(@QueryParam("queryIds") @ApiParam("The list of query } } + @GET @Path("{queryId}") @ApiOperation(value = "Get the query with specified ID") diff --git a/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/api/QueryStoreManager.java b/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/api/QueryStoreManager.java index 0de678aa651..92250d268a8 100644 --- a/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/api/QueryStoreManager.java +++ b/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/api/QueryStoreManager.java @@ -39,7 +39,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -51,6 +53,7 @@ public class QueryStoreManager private static final int MAX_NUMBER_OF_QUERIES = 100; private static final int MAX_NUMBER_OF_EVENTS = 1000; private static final int QUERY_BATCH_SIZE = 1000; + private static final int MAX_PAGE_SIZE = 100; private final ObjectMapper objectMapper = new ObjectMapper(); private static final Document EMPTY_FILTER = Document.parse("{}"); @@ -207,7 +210,7 @@ else if (query.executionContext instanceof DataProductLakehouseAccessExecutionCo // TODO: we can potentially create a pattern check for version } - public List searchQueries(QuerySearchSpecification searchSpecification, String currentUser) + public PaginatedResult searchQueries(QuerySearchSpecification searchSpecification, String currentUser) { List filters = new ArrayList<>(); if (searchSpecification.searchTermSpecification != null) @@ -303,17 +306,58 @@ public List searchQueries(QuerySearchSpecification searchSpecification, S { builder.sortDesc(getSortByField(searchSpecification.sortByOption)); } + else + { + builder.sortDesc(getSortByField(QuerySearchSortBy.SORT_BY_VIEW)); + } builder.withExcludeFields(EXCLUDED_PROJECTION_FIELDS); - if (searchSpecification.limit != null && searchSpecification.limit <= 0) + + // Check if pagination is requested (spec is a PaginatedQuerySearchSpecification with pageSize specified) + boolean isPaginated = searchSpecification instanceof PaginatedQuerySearchSpecification + && ((PaginatedQuerySearchSpecification) searchSpecification).pageSize != null; + + if (isPaginated) { - throw new ApplicationQueryException("Limit should be greater than 0", Response.Status.BAD_REQUEST); + PaginatedQuerySearchSpecification paginatedSpec = (PaginatedQuerySearchSpecification) searchSpecification; + int pageSize = Math.min(paginatedSpec.pageSize, MAX_PAGE_SIZE); + int offset = paginatedSpec.cursor != null ? paginatedSpec.cursor : 0; + int pageNumber = (offset / pageSize) + 1; + + builder.withSkip(offset); + // Fetch one extra to determine if there's a next page + builder.withLimit(pageSize + 1); + + List queries = getQueryDao().find(filters.isEmpty() ? EMPTY_FILTER : Filters.and(filters), false, builder.build()) + .map(this::convertFromStoredQuery) + .sorted(Comparator.comparing(query -> query.owner != null && query.owner.equals(currentUser) ? 0 : 1)) + .collect(Collectors.toList()); + + boolean hasNextPage = queries.size() > pageSize; + if (hasNextPage) + { + queries = queries.subList(0, pageSize); + } + + Integer cursor = hasNextPage ? offset + pageSize : null; + + return new PaginatedResult(queries, hasNextPage, cursor, pageNumber); } - builder.withLimit(Math.min(MAX_NUMBER_OF_QUERIES, searchSpecification.limit == null ? Integer.MAX_VALUE : searchSpecification.limit)); + else + { + // Non-paginated: use limit parameter (backward compatible behavior) + if (searchSpecification.limit != null && searchSpecification.limit <= 0) + { + throw new ApplicationQueryException("Limit should be greater than 0", Response.Status.BAD_REQUEST); + } + builder.withLimit(Math.min(MAX_NUMBER_OF_QUERIES, searchSpecification.limit == null ? Integer.MAX_VALUE : searchSpecification.limit)); - return getQueryDao().find(filters.isEmpty() ? EMPTY_FILTER : Filters.and(filters), false, builder.build()) - .map(this::convertFromStoredQuery) - .sorted(Comparator.comparing(query -> query.owner != null && query.owner.equals(currentUser) ? 0 : 1)) - .collect(Collectors.toList()); + List queries = getQueryDao().find(filters.isEmpty() ? EMPTY_FILTER : Filters.and(filters), false, builder.build()) + .map(this::convertFromStoredQuery) + .sorted(Comparator.comparing(query -> query.owner != null && query.owner.equals(currentUser) ? 0 : 1)) + .collect(Collectors.toList()); + + return new PaginatedResult(queries, false, null, 1); + } } public String getSortByField(QuerySearchSortBy sortBy) @@ -559,4 +603,5 @@ public QueryStoreStats getQueryStoreStats() .countDocuments(dataSpaceFilter)); return storeStats; } + } diff --git a/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/ApplicationStoredQuery.java b/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/ApplicationStoredQuery.java index 3e0087bba66..8b2092cebf6 100644 --- a/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/ApplicationStoredQuery.java +++ b/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/ApplicationStoredQuery.java @@ -41,6 +41,7 @@ public class ApplicationStoredQuery implements StoredVersionedAssetContent stereotypes; public List defaultParameterValues; public Map gridConfig; + public List attributeMetadata; public Long lastOpenAt; public StoredAuditInformation audit; diff --git a/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/AttributeMetadata.java b/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/AttributeMetadata.java new file mode 100644 index 00000000000..d3b433343aa --- /dev/null +++ b/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/AttributeMetadata.java @@ -0,0 +1,22 @@ +// Copyright 2026 Goldman Sachs +// +// 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 +// +// http://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 org.finos.legend.engine.application.query.model; + +public class AttributeMetadata +{ + public String path; + public String alias; + public String description; +} diff --git a/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/PaginatedQuerySearchSpecification.java b/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/PaginatedQuerySearchSpecification.java new file mode 100644 index 00000000000..a96b92528d4 --- /dev/null +++ b/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/PaginatedQuerySearchSpecification.java @@ -0,0 +1,22 @@ +// Copyright 2026 Goldman Sachs +// +// 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 +// +// http://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 org.finos.legend.engine.application.query.model; + +public class PaginatedQuerySearchSpecification extends QuerySearchSpecification +{ + public Integer pageSize; + public Integer cursor; +} + diff --git a/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/PaginatedResult.java b/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/PaginatedResult.java new file mode 100644 index 00000000000..ade33168492 --- /dev/null +++ b/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/PaginatedResult.java @@ -0,0 +1,34 @@ +// Copyright 2026 Goldman Sachs +// +// 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 +// +// http://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 org.finos.legend.engine.application.query.model; + +import java.util.List; + +public class PaginatedResult +{ + public List queries; + public boolean hasNextPage; + public Integer cursor; + public int pageNumber; + + public PaginatedResult(List queries, boolean hasNextPage, Integer cursor, int pageNumber) + { + this.queries = queries; + this.hasNextPage = hasNextPage; + this.cursor = cursor; + this.pageNumber = pageNumber; + } + +} diff --git a/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/Query.java b/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/Query.java index f98ac94f7ba..aac9e47014f 100644 --- a/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/Query.java +++ b/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/Query.java @@ -56,4 +56,6 @@ public class Query public String owner; public Map gridConfig; + public List attributeMetadata; + } diff --git a/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/QueryModelConverter.java b/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/QueryModelConverter.java index ffd32eff64a..42a003e6279 100644 --- a/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/QueryModelConverter.java +++ b/legend-engine-application-query/src/main/java/org/finos/legend/engine/application/query/model/QueryModelConverter.java @@ -91,6 +91,7 @@ public static ApplicationStoredQuery toStoredQuery(Query query) stored.stereotypes = query.stereotypes; stored.defaultParameterValues = query.defaultParameterValues; stored.gridConfig = query.gridConfig; + stored.attributeMetadata = query.attributeMetadata; stored.lastOpenAt = Instant.now().toEpochMilli(); // Initialize audit with info from query diff --git a/legend-engine-application-query/src/test/java/org/finos/legend/engine/application/query/api/TestQueryStoreManager.java b/legend-engine-application-query/src/test/java/org/finos/legend/engine/application/query/api/TestQueryStoreManager.java index a12346e23de..67e20cc6a82 100644 --- a/legend-engine-application-query/src/test/java/org/finos/legend/engine/application/query/api/TestQueryStoreManager.java +++ b/legend-engine-application-query/src/test/java/org/finos/legend/engine/application/query/api/TestQueryStoreManager.java @@ -14,6 +14,7 @@ package org.finos.legend.engine.application.query.api; +import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.collections.api.block.function.Function0; import org.eclipse.collections.api.factory.Lists; import org.finos.legend.engine.application.query.model.*; @@ -429,9 +430,9 @@ public void testSearchQueries() throws Exception String currentUser = "testUser"; Query newQuery = TestQueryBuilder.create("1", "query1", currentUser).withExplicitExecution().withGroupId("test.group").withArtifactId("test-artifact").build(); store.createQuery(newQuery, currentUser); - List queries = store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser); - Assert.assertEquals(1, queries.size()); - Query lightQuery = queries.get(0); + PaginatedResult queries = store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser); + Assert.assertEquals(1, queries.queries.size()); + Query lightQuery = queries.queries.get(0); Assert.assertEquals("test-artifact", lightQuery.artifactId); Assert.assertEquals("test.group", lightQuery.groupId); Assert.assertEquals("1", lightQuery.id); @@ -544,10 +545,10 @@ public void testMatchExactNameQuery() throws Exception store.createQuery(newQuery, currentUser); store.createQuery(newQueryTwo, currentUser); store.createQuery(newQueryThree, currentUser); - List queriesGeneralSearch = store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("Test Query 1").build(), currentUser); - Assert.assertEquals(3, queriesGeneralSearch.size()); - List queriesExactSearch = store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("Test Query 1").withExactNameSearch(true).build(), currentUser); - Assert.assertEquals(1, queriesExactSearch.size()); + PaginatedResult queriesGeneralSearch = store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("Test Query 1").build(), currentUser); + Assert.assertEquals(3, queriesGeneralSearch.queries.size()); + PaginatedResult queriesExactSearch = store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("Test Query 1").withExactNameSearch(true).build(), currentUser); + Assert.assertEquals(1, queriesExactSearch.queries.size()); } @Test @@ -577,8 +578,8 @@ public void testGetQueriesWithLimit() throws Exception String currentUser = "testUser"; store.createQuery(TestQueryBuilder.create("1", "query1", currentUser).withExplicitExecution().build(), currentUser); store.createQuery(TestQueryBuilder.create("2", "query2", currentUser).withExplicitExecution().build(), currentUser); - Assert.assertEquals(1, store.searchQueries(new TestQuerySearchSpecificationBuilder().withLimit(1).build(), currentUser).size()); - Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).size()); + Assert.assertEquals(1, store.searchQueries(new TestQuerySearchSpecificationBuilder().withLimit(1).build(), currentUser).queries.size()); + Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).queries.size()); Assert.assertThrows(ApplicationQueryException.class, () -> store.searchQueries(new TestQuerySearchSpecificationBuilder().withLimit(0).build(), currentUser)); } @@ -596,22 +597,22 @@ public void testGetQueriesWithProjectCoordinates() throws Exception store.createQuery(testQuery4, currentUser); // When no projects specified, return all queries - Assert.assertEquals(4, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).size()); + Assert.assertEquals(4, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).queries.size()); QueryProjectCoordinates coordinate1 = createTestQueryProjectCoordinate("notfound", "notfound", null); - Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().withProjectCoordinates(Lists.fixedSize.of(coordinate1)).build(), currentUser).size()); + Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().withProjectCoordinates(Lists.fixedSize.of(coordinate1)).build(), currentUser).queries.size()); QueryProjectCoordinates coordinate2 = createTestQueryProjectCoordinate("test", "test", null); - Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withProjectCoordinates(Lists.fixedSize.of(coordinate2)).build(), currentUser).size()); - Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withProjectCoordinates(Lists.fixedSize.of(coordinate1, coordinate2)).build(), currentUser).size()); + Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withProjectCoordinates(Lists.fixedSize.of(coordinate2)).build(), currentUser).queries.size()); + Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withProjectCoordinates(Lists.fixedSize.of(coordinate1, coordinate2)).build(), currentUser).queries.size()); QueryProjectCoordinates coordinate3 = createTestQueryProjectCoordinate("something", "something", null); - Assert.assertEquals(1, store.searchQueries(new TestQuerySearchSpecificationBuilder().withProjectCoordinates(Lists.fixedSize.of(coordinate3)).build(), currentUser).size()); - Assert.assertEquals(3, store.searchQueries(new TestQuerySearchSpecificationBuilder().withProjectCoordinates(Lists.fixedSize.of(coordinate1, coordinate2, coordinate3)).build(), currentUser).size()); + Assert.assertEquals(1, store.searchQueries(new TestQuerySearchSpecificationBuilder().withProjectCoordinates(Lists.fixedSize.of(coordinate3)).build(), currentUser).queries.size()); + Assert.assertEquals(3, store.searchQueries(new TestQuerySearchSpecificationBuilder().withProjectCoordinates(Lists.fixedSize.of(coordinate1, coordinate2, coordinate3)).build(), currentUser).queries.size()); QueryProjectCoordinates coordinate4 = createTestQueryProjectCoordinate("something.another", "something-another", "1.0.0"); - Assert.assertEquals(1, store.searchQueries(new TestQuerySearchSpecificationBuilder().withProjectCoordinates(Lists.fixedSize.of(coordinate4)).build(), currentUser).size()); - Assert.assertEquals(4, store.searchQueries(new TestQuerySearchSpecificationBuilder().withProjectCoordinates(Lists.fixedSize.of(coordinate1, coordinate2, coordinate3, coordinate4)).build(), currentUser).size()); + Assert.assertEquals(1, store.searchQueries(new TestQuerySearchSpecificationBuilder().withProjectCoordinates(Lists.fixedSize.of(coordinate4)).build(), currentUser).queries.size()); + Assert.assertEquals(4, store.searchQueries(new TestQuerySearchSpecificationBuilder().withProjectCoordinates(Lists.fixedSize.of(coordinate1, coordinate2, coordinate3, coordinate4)).build(), currentUser).queries.size()); } @Test @@ -632,15 +633,15 @@ public void testGetQueriesWithSortBy() throws Exception Thread.sleep(100); store.createQuery(testQuery3, currentUser); - Assert.assertEquals(4, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSortByOption(QuerySearchSortBy.SORT_BY_CREATE).build(), currentUser).size()); - Assert.assertEquals(Arrays.asList("3", "2", "4", "1"), store.searchQueries(new TestQuerySearchSpecificationBuilder().withSortByOption(QuerySearchSortBy.SORT_BY_CREATE).build(), currentUser).stream().map(q -> q.id).collect(Collectors.toList())); + Assert.assertEquals(4, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSortByOption(QuerySearchSortBy.SORT_BY_CREATE).build(), currentUser).queries.size()); + Assert.assertEquals(Arrays.asList("3", "2", "4", "1"), store.searchQueries(new TestQuerySearchSpecificationBuilder().withSortByOption(QuerySearchSortBy.SORT_BY_CREATE).build(), currentUser).queries.stream().map(q -> q.id).collect(Collectors.toList())); testQuery2Created.name = "query2NewlyUpdated"; store.updateQuery("2", testQuery2Created, currentUser); - Assert.assertEquals(Arrays.asList("2", "3", "4", "1"), store.searchQueries(new TestQuerySearchSpecificationBuilder().withSortByOption(QuerySearchSortBy.SORT_BY_UPDATE).build(), currentUser).stream().map(q -> q.id).collect(Collectors.toList())); + Assert.assertEquals(Arrays.asList("2", "3", "4", "1"), store.searchQueries(new TestQuerySearchSpecificationBuilder().withSortByOption(QuerySearchSortBy.SORT_BY_UPDATE).build(), currentUser).queries.stream().map(q -> q.id).collect(Collectors.toList())); store.getQuery("1"); - Assert.assertEquals(Arrays.asList("1", "2", "3", "4"), store.searchQueries(new TestQuerySearchSpecificationBuilder().withSortByOption(QuerySearchSortBy.SORT_BY_VIEW).build(), currentUser).stream().map(q -> q.id).collect(Collectors.toList())); + Assert.assertEquals(Arrays.asList("1", "2", "3", "4"), store.searchQueries(new TestQuerySearchSpecificationBuilder().withSortByOption(QuerySearchSortBy.SORT_BY_VIEW).build(), currentUser).queries.stream().map(q -> q.id).collect(Collectors.toList())); } @Test @@ -660,12 +661,12 @@ public void testGetQueriesWithStereotypes() throws Exception store.createQuery(testQuery4, currentUser); // When no stereotype provided, return all queries - Assert.assertEquals(4, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).size()); - Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().withStereotypes(Lists.fixedSize.of(stereotype3)).build(), currentUser).size()); - Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withStereotypes(Lists.fixedSize.of(stereotype1)).build(), currentUser).size()); - Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withStereotypes(Lists.fixedSize.of(stereotype2)).build(), currentUser).size()); - Assert.assertEquals(3, store.searchQueries(new TestQuerySearchSpecificationBuilder().withStereotypes(Lists.fixedSize.of(stereotype1, stereotype2)).build(), currentUser).size()); - Assert.assertEquals(3, store.searchQueries(new TestQuerySearchSpecificationBuilder().withStereotypes(Lists.fixedSize.of(stereotype1, stereotype2, stereotype3)).build(), currentUser).size()); + Assert.assertEquals(4, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).queries.size()); + Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().withStereotypes(Lists.fixedSize.of(stereotype3)).build(), currentUser).queries.size()); + Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withStereotypes(Lists.fixedSize.of(stereotype1)).build(), currentUser).queries.size()); + Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withStereotypes(Lists.fixedSize.of(stereotype2)).build(), currentUser).queries.size()); + Assert.assertEquals(3, store.searchQueries(new TestQuerySearchSpecificationBuilder().withStereotypes(Lists.fixedSize.of(stereotype1, stereotype2)).build(), currentUser).queries.size()); + Assert.assertEquals(3, store.searchQueries(new TestQuerySearchSpecificationBuilder().withStereotypes(Lists.fixedSize.of(stereotype1, stereotype2, stereotype3)).build(), currentUser).queries.size()); } @Test @@ -877,12 +878,12 @@ public void testGetQueriesWithTaggedValues() throws Exception store.createQuery(testQuery4, currentUser); // When no tagged value provided, return all queries - Assert.assertEquals(4, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).size()); - Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue3)).build(), currentUser).size()); - Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue1)).build(), currentUser).size()); - Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue2)).build(), currentUser).size()); - Assert.assertEquals(3, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue1, taggedValue2)).build(), currentUser).size()); - Assert.assertEquals(3, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue1, taggedValue2, taggedValue3)).build(), currentUser).size()); + Assert.assertEquals(4, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).queries.size()); + Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue3)).build(), currentUser).queries.size()); + Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue1)).build(), currentUser).queries.size()); + Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue2)).build(), currentUser).queries.size()); + Assert.assertEquals(3, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue1, taggedValue2)).build(), currentUser).queries.size()); + Assert.assertEquals(3, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue1, taggedValue2, taggedValue3)).build(), currentUser).queries.size()); } @Test @@ -896,8 +897,8 @@ public void testGetDataSpaceQueriesWithExecutionContext() throws Exception store.createQuery(testQuery1, currentUser); store.createQuery(testQuery2, currentUser); - Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).size()); - Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue)).build(), currentUser).size()); + Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).queries.size()); + Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue)).build(), currentUser).queries.size()); } @Test @@ -932,10 +933,10 @@ public void testGetQueriesWithSearchText() throws Exception store.createQuery(TestQueryBuilder.create("1", "query1", currentUser).withExplicitExecution().build(), currentUser); store.createQuery(TestQueryBuilder.create("2", "query2", currentUser).withExplicitExecution().build(), currentUser); store.createQuery(TestQueryBuilder.create("3", "query2", currentUser).withExplicitExecution().build(), currentUser); - Assert.assertEquals(3, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).size()); - Assert.assertEquals(1, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("query1").build(), currentUser).size()); - Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("query2").build(), currentUser).size()); - Assert.assertEquals(3, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("query").build(), currentUser).size()); + Assert.assertEquals(3, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).queries.size()); + Assert.assertEquals(1, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("query1").build(), currentUser).queries.size()); + Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("query2").build(), currentUser).queries.size()); + Assert.assertEquals(3, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("query").build(), currentUser).queries.size()); } @@ -947,11 +948,11 @@ public void testGetQueriesWithSearchTextSpec() throws Exception store.createQuery(TestQueryBuilder.create("1", "query1", currentUser).withExplicitExecution().build(), currentUser); store.createQuery(TestQueryBuilder.create("2", "query2", currentUser).withExplicitExecution().build(), currentUser); store.createQuery(TestQueryBuilder.create("3", "query3", user2).withExplicitExecution().build(), user2); - Assert.assertEquals(1, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("user2").withIncludeOwner(true).build(), currentUser).size()); - Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("user1").withIncludeOwner(true).build(), currentUser).size()); - Assert.assertEquals(3, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("user").withIncludeOwner(true).build(), currentUser).size()); - Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("user").withExactNameSearch(true).withIncludeOwner(true).build(), currentUser).size()); - Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("user").withIncludeOwner(false).build(), currentUser).size()); + Assert.assertEquals(1, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("user2").withIncludeOwner(true).build(), currentUser).queries.size()); + Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("user1").withIncludeOwner(true).build(), currentUser).queries.size()); + Assert.assertEquals(3, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("user").withIncludeOwner(true).build(), currentUser).queries.size()); + Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("user").withExactNameSearch(true).withIncludeOwner(true).build(), currentUser).queries.size()); + Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("user").withIncludeOwner(false).build(), currentUser).queries.size()); } @Test @@ -960,9 +961,9 @@ public void testGetQueriesForCurrentUser() throws Exception String currentUser = "testUser"; store.createQuery(TestQueryBuilder.create("1", "query1", currentUser).withExplicitExecution().build(), "testUser1"); store.createQuery(TestQueryBuilder.create("2", "query2", currentUser).withExplicitExecution().build(), currentUser); - Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).size()); - Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withShowCurrentUserQueriesOnly(false).build(), currentUser).size()); - Assert.assertEquals(1, store.searchQueries(new TestQuerySearchSpecificationBuilder().withShowCurrentUserQueriesOnly(true).build(), currentUser).size()); + Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).queries.size()); + Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withShowCurrentUserQueriesOnly(false).build(), currentUser).queries.size()); + Assert.assertEquals(1, store.searchQueries(new TestQuerySearchSpecificationBuilder().withShowCurrentUserQueriesOnly(true).build(), currentUser).queries.size()); } @Test @@ -1091,7 +1092,7 @@ public void testDeleteQuery() throws Exception String currentUser = "testUser"; store.createQuery(TestQueryBuilder.create("1", "query1", currentUser).withExplicitExecution().build(), currentUser); store.deleteQuery("1", currentUser); - Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).size()); + Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).queries.size()); } @Test @@ -1107,7 +1108,7 @@ public void testAllowDeleteQueryWithoutOwner() throws Exception String currentUser = "testUser"; store.createQuery(TestQueryBuilder.create("1", "query1", null).withExplicitExecution().build(), null); store.deleteQuery("1", currentUser); - Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).size()); + Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).queries.size()); } @Test @@ -1134,9 +1135,9 @@ public void testSearchQueriesContainTimestamps() throws Exception String currentUser = "testUser"; Query newQuery = TestQueryBuilder.create("1", "query1", currentUser).withGroupId("test.group").withArtifactId("test-artifact").withExplicitExecution().build(); store.createQuery(newQuery, currentUser); - List queries = store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser); - Assert.assertEquals(1, queries.size()); - Query lightQuery = queries.get(0); + PaginatedResult queries = store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser); + Assert.assertEquals(1, queries.queries.size()); + Query lightQuery = queries.queries.get(0); Assert.assertNotNull(lightQuery.lastUpdatedAt); Assert.assertNotNull(lightQuery.createdAt); } @@ -1148,9 +1149,9 @@ public void testSearchQueriesWithSearchByQueryId() throws Exception store.createQuery(TestQueryBuilder.create("26929514-237c-11ed-861d-0242ac120002", "query_a", currentUser).withExplicitExecution().build(), currentUser); store.createQuery(TestQueryBuilder.create("26929515-237c-11bd-851d-0243ac120002", "query_b", currentUser).withExplicitExecution().build(), currentUser); store.createQuery(TestQueryBuilder.create("23929515-235c-11ad-851d-0143ac120002", "query_c", currentUser).withExplicitExecution().build(), currentUser); - Assert.assertEquals(3, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).size()); - Assert.assertEquals(1, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("23929515-235c-11ad-851d-0143ac120002").build(), currentUser).size()); - Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("23929515-235c-11ad").build(), currentUser).size()); + Assert.assertEquals(3, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).queries.size()); + Assert.assertEquals(1, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("23929515-235c-11ad-851d-0143ac120002").build(), currentUser).queries.size()); + Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().withSearchTerm("23929515-235c-11ad").build(), currentUser).queries.size()); } @Test @@ -1159,9 +1160,9 @@ public void testSearchQueriesSortedByCurrentUserFirst() throws Exception String currentUser = "testUser"; store.createQuery(TestQueryBuilder.create("1", "query1", "testUser1").withExplicitExecution().build(), "testUser1"); store.createQuery(TestQueryBuilder.create("2", "query2", currentUser).withExplicitExecution().build(), currentUser); - List queries = store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser); - Assert.assertEquals(2, queries.size()); - Assert.assertEquals(currentUser, queries.get(0).owner); + PaginatedResult queries = store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser); + Assert.assertEquals(2, queries.queries.size()); + Assert.assertEquals(currentUser, queries.queries.get(0).owner); } @Test @@ -1181,12 +1182,12 @@ public void testSearchQueriesWithCombineTaggedValuesCondition() throws Exception store.createQuery(testQuery4, currentUser); // When no tagged value provided, return all queries - Assert.assertEquals(4, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).size()); - Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue3)).withCombineTaggedValuesCondition(true).build(), currentUser).size()); - Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue1)).withCombineTaggedValuesCondition(true).build(), currentUser).size()); - Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue2)).withCombineTaggedValuesCondition(true).build(), currentUser).size()); - Assert.assertEquals(1, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue1, taggedValue2)).withCombineTaggedValuesCondition(true).build(), currentUser).size()); - Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue1, taggedValue2, taggedValue3)).withCombineTaggedValuesCondition(true).build(), currentUser).size()); + Assert.assertEquals(4, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).queries.size()); + Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue3)).withCombineTaggedValuesCondition(true).build(), currentUser).queries.size()); + Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue1)).withCombineTaggedValuesCondition(true).build(), currentUser).queries.size()); + Assert.assertEquals(2, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue2)).withCombineTaggedValuesCondition(true).build(), currentUser).queries.size()); + Assert.assertEquals(1, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue1, taggedValue2)).withCombineTaggedValuesCondition(true).build(), currentUser).queries.size()); + Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().withTaggedValues(Lists.fixedSize.of(taggedValue1, taggedValue2, taggedValue3)).withCombineTaggedValuesCondition(true).build(), currentUser).queries.size()); } @Test @@ -1368,7 +1369,7 @@ public void testGetQueryHistoryDeletedQueryStillVisible() throws Exception // Deleted query must no longer be live Assert.assertThrows(ApplicationQueryException.class, () -> store.getQuery("1")); - Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).size()); + Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).queries.size()); // Both old versions must still be visible in history List history = store.getQueryHistory("1"); @@ -1612,7 +1613,7 @@ public void testSearchQueriesReturnsCurrentVersionOnly() throws Exception store.updateQuery("1", createdQuery, currentUser); // Search should only return the latest version, not historical versions - List queries = store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser); + List queries = store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).queries; Assert.assertEquals(1, queries.size()); Assert.assertEquals("query1_v2", queries.get(0).name); } @@ -1653,7 +1654,7 @@ public void testDeleteQueryAfterMultipleVersions() throws Exception // Verify the query is gone Assert.assertEquals("Can't find query with ID '1'", Assert.assertThrows(ApplicationQueryException.class, () -> store.getQuery("1")).getMessage()); - Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).size()); + Assert.assertEquals(0, store.searchQueries(new TestQuerySearchSpecificationBuilder().build(), currentUser).queries.size()); } @Test @@ -1833,4 +1834,203 @@ public void testGetQueryEvents() throws Exception Assert.assertEquals(3, store.getQueryEvents(null, null, event2.timestamp, null, null).size()); Assert.assertEquals(4, store.getQueryEvents(null, null, null, event2.timestamp, null).size()); } + + // ---------------------------------------- + // PAGINATION TESTS + // ---------------------------------------- + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private QuerySearchSpecification createPaginatedSpec(QuerySearchSpecification searchSpec, Integer cursor, Integer pageSize) + { + PaginatedQuerySearchSpecification spec; + try + { + spec = searchSpec != null + ? OBJECT_MAPPER.convertValue(searchSpec, PaginatedQuerySearchSpecification.class) + : new PaginatedQuerySearchSpecification(); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + spec.cursor = cursor; + spec.pageSize = pageSize; + return spec; + } + + @Test + public void testPaginationWithMultiplePages() throws Exception + { + String currentUser = "testUser"; + int totalQueries = 14; + int pageSize = 4; + + for (int i = 0; i < totalQueries; i++) + { + store.createQuery(TestQueryBuilder.create(String.valueOf(i), "query" + i, currentUser).withExplicitExecution().build(), currentUser); + } + + // Page 1: should have 4 queries and hasNextPage = true + PaginatedResult page1 = store.searchQueries(createPaginatedSpec(new TestQuerySearchSpecificationBuilder().build(), null, pageSize), currentUser); + Assert.assertEquals(pageSize, page1.queries.size()); + Assert.assertTrue(page1.hasNextPage); + Assert.assertNotNull(page1.cursor); + + // Page 2: should have 4 queries and hasNextPage = true + PaginatedResult page2 = store.searchQueries(createPaginatedSpec(new TestQuerySearchSpecificationBuilder().build(), page1.cursor, pageSize), currentUser); + Assert.assertEquals(pageSize, page2.queries.size()); + Assert.assertTrue(page2.hasNextPage); + Assert.assertNotNull(page2.cursor); + + // Page 3: should have 4 queries and hasNextPage = true + PaginatedResult page3 = store.searchQueries(createPaginatedSpec(new TestQuerySearchSpecificationBuilder().build(), page2.cursor, pageSize), currentUser); + Assert.assertEquals(pageSize, page3.queries.size()); + Assert.assertTrue(page3.hasNextPage); + Assert.assertNotNull(page3.cursor); + + // Page 4 (last page): should have 2 queries and hasNextPage = false + PaginatedResult page4 = store.searchQueries(createPaginatedSpec(new TestQuerySearchSpecificationBuilder().build(), page3.cursor, pageSize), currentUser); + Assert.assertEquals(2, page4.queries.size()); + Assert.assertFalse(page4.hasNextPage); + Assert.assertNull(page4.cursor); + + // Verify total count across all pages + int totalFetched = page1.queries.size() + page2.queries.size() + page3.queries.size() + page4.queries.size(); + Assert.assertEquals(totalQueries, totalFetched); + } + + @Test + public void testPaginationWithNoQueries() throws Exception + { + String currentUser = "testUser"; + int pageSize = 4; + + PaginatedResult result = store.searchQueries(createPaginatedSpec(new TestQuerySearchSpecificationBuilder().build(), null, pageSize), currentUser); + Assert.assertEquals(0, result.queries.size()); + Assert.assertFalse(result.hasNextPage); + Assert.assertNull(result.cursor); + } + + @Test + public void testPaginationWithSearchFilter() throws Exception + { + String currentUser = "testUser"; + int pageSize = 4; + + // Create 10 queries, 6 with "alpha" in name, 4 with "beta" + for (int i = 0; i < 6; i++) + { + store.createQuery(TestQueryBuilder.create("alpha" + i, "alpha_query" + i, currentUser).withExplicitExecution().build(), currentUser); + } + for (int i = 0; i < 4; i++) + { + store.createQuery(TestQueryBuilder.create("beta" + i, "beta_query" + i, currentUser).withExplicitExecution().build(), currentUser); + } + + // Search for "alpha" queries with pagination + PaginatedResult page1 = store.searchQueries( + createPaginatedSpec(new TestQuerySearchSpecificationBuilder().withSearchTerm("alpha").build(), null, pageSize), currentUser); + Assert.assertEquals(pageSize, page1.queries.size()); + Assert.assertTrue(page1.hasNextPage); + // Verify all queries contain "alpha" + for (Query q : page1.queries) + { + Assert.assertTrue(q.name.contains("alpha")); + } + + // Get second page + PaginatedResult page2 = store.searchQueries( + createPaginatedSpec(new TestQuerySearchSpecificationBuilder().withSearchTerm("alpha").build(), page1.cursor, pageSize), currentUser); + Assert.assertEquals(2, page2.queries.size()); + Assert.assertFalse(page2.hasNextPage); + for (Query q : page2.queries) + { + Assert.assertTrue(q.name.contains("alpha")); + } + } + + @Test + public void testPaginationFewerQueriesThanPageSize() throws Exception + { + String currentUser = "testUser"; + int pageSize = 4; + + store.createQuery(TestQueryBuilder.create("1", "query1", currentUser).withExplicitExecution().build(), currentUser); + store.createQuery(TestQueryBuilder.create("2", "query2", currentUser).withExplicitExecution().build(), currentUser); + + PaginatedResult result = store.searchQueries(createPaginatedSpec(new TestQuerySearchSpecificationBuilder().build(), null, pageSize), currentUser); + Assert.assertEquals(2, result.queries.size()); + Assert.assertFalse(result.hasNextPage); + Assert.assertNull(result.cursor); + } + + @Test + public void testPaginationWithNullPageSize() throws Exception + { + String currentUser = "testUser"; + + for (int i = 0; i < 5; i++) + { + store.createQuery(TestQueryBuilder.create(String.valueOf(i), "query" + i, currentUser).withExplicitExecution().build(), currentUser); + } + + PaginatedResult result = store.searchQueries(createPaginatedSpec(new TestQuerySearchSpecificationBuilder().build(), null, null), currentUser); + Assert.assertEquals(5, result.queries.size()); + Assert.assertFalse(result.hasNextPage); + } + + @Test + public void testPaginationWithNullSearchSpecification() throws Exception + { + String currentUser = "testUser"; + int pageSize = 4; + + for (int i = 0; i < 3; i++) + { + store.createQuery(TestQueryBuilder.create(String.valueOf(i), "query" + i, currentUser).withExplicitExecution().build(), currentUser); + } + + PaginatedQuerySearchSpecification paginatedSpec = new PaginatedQuerySearchSpecification(); + paginatedSpec.pageSize = pageSize; + + PaginatedResult result = store.searchQueries(paginatedSpec, currentUser); + Assert.assertEquals(3, result.queries.size()); + Assert.assertFalse(result.hasNextPage); + } + + @Test + public void testPaginationExactPageSize() throws Exception + { + String currentUser = "testUser"; + int pageSize = 4; + + for (int i = 0; i < pageSize; i++) + { + store.createQuery(TestQueryBuilder.create(String.valueOf(i), "query" + i, currentUser).withExplicitExecution().build(), currentUser); + } + + PaginatedResult result = store.searchQueries(createPaginatedSpec(new TestQuerySearchSpecificationBuilder().build(), null, pageSize), currentUser); + Assert.assertEquals(pageSize, result.queries.size()); + Assert.assertFalse(result.hasNextPage); + Assert.assertNull(result.cursor); + } + + @Test + public void testPaginationWithEmptyCursor() throws Exception + { + String currentUser = "testUser"; + int pageSize = 4; + + for (int i = 0; i < 6; i++) + { + store.createQuery(TestQueryBuilder.create(String.valueOf(i), "query" + i, currentUser).withExplicitExecution().build(), currentUser); + } + + PaginatedResult resultWithNullCursor = store.searchQueries(createPaginatedSpec(new TestQuerySearchSpecificationBuilder().build(), null, pageSize), currentUser); + PaginatedResult resultWithZeroCursor = store.searchQueries(createPaginatedSpec(new TestQuerySearchSpecificationBuilder().build(), 0, pageSize), currentUser); + + Assert.assertEquals(resultWithNullCursor.queries.size(), resultWithZeroCursor.queries.size()); + Assert.assertEquals(resultWithNullCursor.hasNextPage, resultWithZeroCursor.hasNextPage); + } }