Repository: gisaia/ARLAS-server (language: python)

Issue:
Update documentation of partition-filters
Based on the new implementation in #964


Discussion / hints:

Fix patch:
diff --git a/arlas-commons/src/main/java/io/arlas/filter/impl/AbstractPolicyEnforcer.java b/arlas-commons/src/main/java/io/arlas/filter/impl/AbstractPolicyEnforcer.java
index 74580c409..d954f3b32 100644
--- a/arlas-commons/src/main/java/io/arlas/filter/impl/AbstractPolicyEnforcer.java
+++ b/arlas-commons/src/main/java/io/arlas/filter/impl/AbstractPolicyEnforcer.java
@@ -235,7 +235,9 @@ public void filter(ContainerRequestContext ctx, String method, String path, Stri
                 Object token = getObjectToken(accessToken, orgFilter);
                 Set<String> permissions = getPermissionsClaim(token);
                 Optional<String> org = Optional.ofNullable(new ArlasClaims(permissions.stream().toList()).getVariables().get(VAR_ORG));
-
+                if(ctx.getHeaders().get(PARTITION_FILTER) != null){
+                    ctx.getHeaders().remove(PARTITION_FILTER); // remove it in case it's been set manually
+                }
                 ctx.getHeaders().remove(authConf.headerUser); // remove it in case it's been set manually
                 String userId = getSubject(token);
                 if (!StringUtil.isNullOrEmpty(userId)) {
diff --git a/arlas-core/src/main/java/io/arlas/server/core/impl/elastic/services/ElasticFluidSearch.java b/arlas-core/src/main/java/io/arlas/server/core/impl/elastic/services/ElasticFluidSearch.java
index 5028659bc..a94695b2b 100644
--- a/arlas-core/src/main/java/io/arlas/server/core/impl/elastic/services/ElasticFluidSearch.java
+++ b/arlas-core/src/main/java/io/arlas/server/core/impl/elastic/services/ElasticFluidSearch.java
@@ -64,6 +64,7 @@ public class ElasticFluidSearch extends FluidSearchService {
     private int elasticMaxPrecisionThreshold;
     private SearchRequest.Builder requestBuilder;
     private BoolQuery.Builder boolQueryBuilder;
+    private BoolQuery.Builder boolPartitionQueryBuilder;
 
 
     public ElasticFluidSearch(CollectionReference collectionReference, int elasticMaxPrecisionThreshold) {
@@ -73,6 +74,8 @@ public ElasticFluidSearch(CollectionReference collectionReference, int elasticMa
                 .index(collectionReference.params.indexName)
                 .trackTotalHits(b -> b.enabled(true));
         boolQueryBuilder = new BoolQuery.Builder();
+        boolPartitionQueryBuilder = new BoolQuery.Builder();
+
     }
 
     public ElasticClient getClient() {
@@ -87,6 +90,10 @@ public ElasticFluidSearch setClient(ElasticClient client) {
     public BoolQuery.Builder getBoolQueryBuilder() {
         return boolQueryBuilder;
     }
+    public BoolQuery.Builder getBoolPartitionQueryBuilder() {
+        return boolPartitionQueryBuilder;
+    }
+
 
     public SearchResponse<Map> exec() throws ArlasException {
         Pair<String[], String[]> includeExclude = computeIncludeExclude(false);
@@ -98,7 +105,7 @@ public SearchResponse<Map> exec() throws ArlasException {
                                 .excludes(Arrays.asList(includeExclude.getRight()))
                         )
                 )
-                .query(boolQueryBuilder.build()._toQuery())
+                .query(boolQueryBuilder.must(boolPartitionQueryBuilder.build()._toQuery()).build()._toQuery())
                 .build();
 
         // https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/reading.html#_reading_raw_json
@@ -106,7 +113,7 @@ public SearchResponse<Map> exec() throws ArlasException {
     }
 
     @Override
-    public FluidSearchService filter(MultiValueFilter<Expression> f, String dateFormat, Boolean rightHand )throws ArlasException {
+    public FluidSearchService filter(MultiValueFilter<Expression> f, String dateFormat, Boolean rightHand) throws ArlasException {
         List<Query> queries = new ArrayList<>();
         for (Expression fFilter : f) {
             queries.add(filter(fFilter, dateFormat, rightHand));
@@ -115,6 +122,37 @@ public FluidSearchService filter(MultiValueFilter<Expression> f, String dateForm
         return this;
     }
 
+    @Override
+    public FluidSearchService partitionFilter(List<Filter> filters) throws ArlasException {
+        List<Query> finalQueries = new ArrayList<>();
+        for (Filter filter : filters){
+            // OR LEVEL
+            BoolQuery.Builder builder = new BoolQuery.Builder();
+            if(filter.f != null){
+                for (MultiValueFilter<Expression> f : filter.f) {
+                    // AND LEVEL
+                    List<Query> queries = new ArrayList<>();
+                    for (Expression fFilter : f) {
+                        //OR LEVEL
+                        queries.add(filter(fFilter, filter.dateformat, filter.righthand));
+                    }
+                    builder = builder.filter(QueryBuilders.bool().should(queries).minimumShouldMatch("1").build()._toQuery());
+                }
+            }
+            if(filter.q != null){
+                for (MultiValueFilter<String> q : filter.q) {
+                    BoolQuery.Builder orBoolQueryBuilder = getFilterQBuilder(q);
+                    builder = builder.filter(orBoolQueryBuilder.build()._toQuery());
+                }
+            }
+            //AND OF OR
+            finalQueries.add(builder.build()._toQuery());
+        }
+        // OR OF (AND OF OR)
+        boolPartitionQueryBuilder = boolPartitionQueryBuilder.should(finalQueries).minimumShouldMatch("1");
+        return this;
+    }
+
     private Query filter(Expression expression, String dateFormat, Boolean rightHand) throws ArlasException {
         BoolQuery.Builder ret = new BoolQuery.Builder();
         if (StringUtil.isNullOrEmpty(expression.field) || expression.op == null || StringUtil.isNullOrEmpty(expression.value)) {
@@ -287,6 +325,12 @@ protected RangeQuery.Builder getRangeQueryBuilder(String field, String value, St
 
     @Override
     public FluidSearchService filterQ(MultiValueFilter<String> q) throws ArlasException {
+        BoolQuery.Builder orBoolQueryBuilder = getFilterQBuilder(q);
+        boolQueryBuilder = boolQueryBuilder.filter(orBoolQueryBuilder.build()._toQuery());
+        return this;
+    }
+
+    private BoolQuery.Builder getFilterQBuilder(MultiValueFilter<String> q) throws InvalidParameterException {
         BoolQuery.Builder orBoolQueryBuilder = new BoolQuery.Builder().minimumShouldMatch("1");
         for (String qFilter : q) {
             String[] operands = qFilter.split(":",2);
@@ -300,8 +344,7 @@ public FluidSearchService filterQ(MultiValueFilter<String> q) throws ArlasExcept
                 throw new InvalidParameterException(INVALID_Q_FILTER);
             }
         }
-        boolQueryBuilder = boolQueryBuilder.filter(orBoolQueryBuilder.build()._toQuery());
-        return this;
+        return orBoolQueryBuilder;
     }
 
     public List<Query> filterPWithin(String field, String pwithinFilter) throws ArlasException {
diff --git a/arlas-core/src/main/java/io/arlas/server/core/model/request/Request.java b/arlas-core/src/main/java/io/arlas/server/core/model/request/Request.java
index db4e81b2a..b172f057a 100644
--- a/arlas-core/src/main/java/io/arlas/server/core/model/request/Request.java
+++ b/arlas-core/src/main/java/io/arlas/server/core/model/request/Request.java
@@ -20,7 +20,10 @@
 package io.arlas.server.core.model.request;
 
 
+import java.util.List;
+
 public class Request {
+    public List<Filter> partitionFilter;
     public Filter filter;
     public Form form;
 }
diff --git a/arlas-core/src/main/java/io/arlas/server/core/services/ExploreService.java b/arlas-core/src/main/java/io/arlas/server/core/services/ExploreService.java
index 10fb2a137..115e4a8dc 100644
--- a/arlas-core/src/main/java/io/arlas/server/core/services/ExploreService.java
+++ b/arlas-core/src/main/java/io/arlas/server/core/services/ExploreService.java
@@ -79,6 +79,13 @@ public void setValidGeoFilters(CollectionReference collectionReference, Request
         if (request != null && request.filter != null) {
             request.filter = ParamsParser.getFilterWithValidGeos(collectionReference, request.filter);
         }
+        if (request != null && request.partitionFilter != null) {
+            List<Filter> newFilters = new ArrayList<>();
+            for (Filter f : request.partitionFilter){
+                newFilters.add(ParamsParser.getFilterWithValidGeos(collectionReference, f));
+            }
+            request.partitionFilter = newFilters;
+        }
     }
 
     public Map<String, Object> flat(AggregationResponse element,
@@ -154,6 +161,23 @@ public void applyFilter(Filter filter, FluidSearchService fluidSearch) throws Ar
         }
     }
 
+    public void applyPartitionFilter(List<Filter> filters, FluidSearchService fluidSearch) throws ArlasException {
+        if (filters != null) {
+            for(Filter f:filters){
+                if (f != null) {
+                    CheckParams.checkFilter(f);
+                    if (f.f != null && !f.f.isEmpty()) {
+                        CollectionReference collectionReference = fluidSearch.getCollectionReference();
+                        if (!filterFHasDateQuery(f, collectionReference) && !StringUtil.isNullOrEmpty(f.dateformat)) {
+                            throw new BadRequestException("dateformat is specified but no date field is queried in f filter (gt, lt, gte, lte or range operations)");
+                        }
+                    }
+                }
+            }
+            fluidSearch = fluidSearch.partitionFilter(filters);
+        }
+    }
+
     /**
      * This method checks whether in all the expressions of the filter `f`, a date field has been queried using `lte`, `gte`, `lt`, `gt` or `range` operations
      * **/
@@ -333,7 +357,7 @@ public AggregationResponse aggregate(MixedRequest request,
         FluidSearchService fluidSearch = getFluidSearch(collectionReference);
         applyFilter(collectionReference.params.filter, fluidSearch);
         applyFilter(request.basicRequest.filter, fluidSearch);
-        applyFilter(request.headerRequest.filter, fluidSearch);
+        applyPartitionFilter(request.headerRequest.partitionFilter, fluidSearch);
         List<Aggregation> aggregations = ((AggregationsRequest) request.basicRequest).aggregations;
         if (aggregations != null && !aggregations.isEmpty()) {
             fluidSearch.aggregate(aggregations, isGeoAggregation);
@@ -347,7 +371,7 @@ public ComputationResponse compute(MixedRequest request,
         FluidSearchService fluidSearch = getFluidSearch(collectionReference);
         applyFilter(collectionReference.params.filter, fluidSearch);
         applyFilter(request.basicRequest.filter, fluidSearch);
-        applyFilter(request.headerRequest.filter, fluidSearch);
+        applyPartitionFilter(request.headerRequest.partitionFilter, fluidSearch);
         String field = ((ComputationRequest)request.basicRequest).field;
         ComputationEnum metric = ((ComputationRequest)request.basicRequest).metric;
         int precisionThreshold = ((ComputationRequest)request.basicRequest).precisionThreshold;
@@ -360,7 +384,7 @@ public Hits count(MixedRequest request,
         FluidSearchService fluidSearch = getFluidSearch(collectionReference);
         applyFilter(collectionReference.params.filter, fluidSearch);
         applyFilter(request.basicRequest.filter, fluidSearch);
-        applyFilter(request.headerRequest.filter, fluidSearch);
+        applyPartitionFilter(request.headerRequest.partitionFilter, fluidSearch);
         return count(collectionReference, fluidSearch);
     }
 
@@ -374,7 +398,7 @@ protected FluidSearchService getSearchRequest(MixedRequest request, CollectionRe
         FluidSearchService fluidSearch = getFluidSearch(collectionReference);
         applyFilter(collectionReference.params.filter, fluidSearch);
         applyFilter(request.basicRequest.filter, fluidSearch);
-        applyFilter(request.headerRequest.filter, fluidSearch);
+        applyPartitionFilter(request.headerRequest.partitionFilter, fluidSearch);
         paginate(((Search) request.basicRequest).page, collectionReference, fluidSearch);
         applyProjection(((Search) request.basicRequest).projection, fluidSearch, request.columnFilter, collectionReference);
         return fluidSearch;
diff --git a/arlas-core/src/main/java/io/arlas/server/core/services/FluidSearchService.java b/arlas-core/src/main/java/io/arlas/server/core/services/FluidSearchService.java
index 25a3f2de7..0640e2be8 100644
--- a/arlas-core/src/main/java/io/arlas/server/core/services/FluidSearchService.java
+++ b/arlas-core/src/main/java/io/arlas/server/core/services/FluidSearchService.java
@@ -25,10 +25,7 @@
 import io.arlas.server.core.model.CollectionReference;
 import io.arlas.server.core.model.enumerations.AggregationTypeEnum;
 import io.arlas.server.core.model.enumerations.ComputationEnum;
-import io.arlas.server.core.model.request.Aggregation;
-import io.arlas.server.core.model.request.Expression;
-import io.arlas.server.core.model.request.MultiValueFilter;
-import io.arlas.server.core.model.request.Page;
+import io.arlas.server.core.model.request.*;
 import org.apache.commons.lang3.tuple.Pair;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -193,6 +190,8 @@ public static String getAggregationName(String aggName) {
 
     abstract public FluidSearchService filter(MultiValueFilter<Expression> f, String dateFormat, Boolean rightHand) throws ArlasException;
 
+    public abstract FluidSearchService partitionFilter(List<Filter> filters)throws ArlasException;
+
     abstract public FluidSearchService filterQ(MultiValueFilter<String> q) throws ArlasException;
 
     abstract public FluidSearchService sort(String sort) throws ArlasException;
diff --git a/arlas-core/src/main/java/io/arlas/server/core/utils/ParamsParser.java b/arlas-core/src/main/java/io/arlas/server/core/utils/ParamsParser.java
index 33e40f11a..3f86b1a60 100644
--- a/arlas-core/src/main/java/io/arlas/server/core/utils/ParamsParser.java
+++ b/arlas-core/src/main/java/io/arlas/server/core/utils/ParamsParser.java
@@ -260,7 +260,7 @@ public static String getValidAggregationFormat(String aggFormat) {
         return Objects.requireNonNullElse(aggFormat, "yyyy-MM-dd-HH:mm:ss");
     }
 
-    public static Filter getFilter(CollectionReference collectionReference, String serializedFilter) throws InvalidParameterException {
+    public static List<Filter> getPartitionFilter(CollectionReference collectionReference, String serializedFilter) throws InvalidParameterException {
         if (serializedFilter != null) {
             List<Filter> fList;
             String sf = "[" + serializedFilter + "]";
@@ -278,19 +278,7 @@ public static Filter getFilter(CollectionReference collectionReference, String s
                     throw new InvalidParameterException(INVALID_FILTER + ": '" + sf + "'", ex);
                 }
             }
-            // if not null and parsing ok then we have at least one filter
-            if (fList != null && fList.size() > 0) {
-                Filter retFilter = fList.get(0);
-                if (retFilter.righthand == null) {
-                    retFilter.righthand = Boolean.TRUE;
-                }
-
-                for (int i = 1; i < fList.size(); i++) {
-                    // for now, a list of partition filters is combined with OR. TODO: support more complex combination
-                    retFilter.f.get(0).addAll(fList.get(i).f.get(0));
-                }
-                return retFilter;
-            }
+            return fList;
         }
         return null;
     }
diff --git a/arlas-rest/src/main/java/io/arlas/server/rest/explore/aggregate/AggregateRESTService.java b/arlas-rest/src/main/java/io/arlas/server/rest/explore/aggregate/AggregateRESTService.java
index 18df4b7e9..9ffa80dd2 100644
--- a/arlas-rest/src/main/java/io/arlas/server/rest/explore/aggregate/AggregateRESTService.java
+++ b/arlas-rest/src/main/java/io/arlas/server/rest/explore/aggregate/AggregateRESTService.java
@@ -156,7 +156,7 @@ public Response aggregate(
         ColumnFilterUtil.assertRequestAllowed(Optional.ofNullable(columnFilter), collectionReference, aggregationsRequest);
 
         AggregationsRequest aggregationsRequestHeader = new AggregationsRequest();
-        aggregationsRequestHeader.filter = ParamsParser.getFilter(collectionReference, partitionFilter);
+        aggregationsRequestHeader.partitionFilter = ParamsParser.getPartitionFilter(collectionReference, partitionFilter);
         MixedRequest request = new MixedRequest();
         request.basicRequest = aggregationsRequest;
         exploreService.setValidGeoFilters(collectionReference, aggregationsRequestHeader);
@@ -235,7 +235,7 @@ public Response aggregatePost(
         }
 
         AggregationsRequest aggregationsRequestHeader = new AggregationsRequest();
-        aggregationsRequestHeader.filter = ParamsParser.getFilter(collectionReference, partitionFilter);
+        aggregationsRequestHeader.partitionFilter = ParamsParser.getPartitionFilter(collectionReference, partitionFilter);
         MixedRequest request = new MixedRequest();
         exploreService.setValidGeoFilters(collectionReference, aggregationsRequest);
         exploreService.setValidGeoFilters(collectionReference, aggregationsRequestHeader);
diff --git a/arlas-rest/src/main/java/io/arlas/server/rest/explore/aggregate/GeoAggregateRESTService.java b/arlas-rest/src/main/java/io/arlas/server/rest/explore/aggregate/GeoAggregateRESTService.java
index 72e6b3d16..f5bfe16fc 100644
--- a/arlas-rest/src/main/java/io/arlas/server/rest/explore/aggregate/GeoAggregateRESTService.java
+++ b/arlas-rest/src/main/java/io/arlas/server/rest/explore/aggregate/GeoAggregateRESTService.java
@@ -683,7 +683,7 @@ public Response geoaggregatePost(
         }
 
         AggregationsRequest aggregationsRequestHeader = new AggregationsRequest();
-        aggregationsRequestHeader.filter = ParamsParser.getFilter(collectionReference, partitionFilter);
+        aggregationsRequestHeader.partitionFilter = ParamsParser.getPartitionFilter(collectionReference, partitionFilter);
         MixedRequest request = new MixedRequest();
         exploreService.setValidGeoFilters(collectionReference, aggregationRequest);
         exploreService.setValidGeoFilters(collectionReference, aggregationsRequestHeader);
@@ -765,7 +765,7 @@ public Response shapeaggregatePost(
         }
 
         AggregationsRequest aggregationsRequestHeader = new AggregationsRequest();
-        aggregationsRequestHeader.filter = ParamsParser.getFilter(collectionReference, partitionFilter);
+        aggregationsRequestHeader.partitionFilter = ParamsParser.getPartitionFilter(collectionReference, partitionFilter);
         MixedRequest request = new MixedRequest();
         exploreService.setValidGeoFilters(collectionReference, aggregationRequest);
         exploreService.setValidGeoFilters(collectionReference, aggregationsRequestHeader);
@@ -799,7 +799,7 @@ private MixedRequest getGeoaggregateRequest(CollectionReference collectionRefere
         ColumnFilterUtil.assertRequestAllowed(columnFilter, collectionReference, aggregationsRequest);
 
         AggregationsRequest aggregationsRequestHeader = new AggregationsRequest();
-        aggregationsRequestHeader.filter = ParamsParser.getFilter(collectionReference, partitionFilter);
+        aggregationsRequestHeader.partitionFilter = ParamsParser.getPartitionFilter(collectionReference, partitionFilter);
         MixedRequest request = new MixedRequest();
         request.basicRequest = aggregationsRequest;
         exploreService.setValidGeoFilters(collectionReference, aggregationsRequestHeader);
diff --git a/arlas-rest/src/main/java/io/arlas/server/rest/explore/compute/ComputeRESTService.java b/arlas-rest/src/main/java/io/arlas/server/rest/explore/compute/ComputeRESTService.java
index b1895d900..c479cb0fd 100644
--- a/arlas-rest/src/main/java/io/arlas/server/rest/explore/compute/ComputeRESTService.java
+++ b/arlas-rest/src/main/java/io/arlas/server/rest/explore/compute/ComputeRESTService.java
@@ -159,7 +159,7 @@ public Response compute(
         ColumnFilterUtil.assertRequestAllowed(Optional.ofNullable(columnFilter), collectionReference, computationRequest);
 
         ComputationRequest computationRequestHeader = new ComputationRequest();
-        computationRequestHeader.filter = ParamsParser.getFilter(collectionReference, partitionFilter);
+        computationRequestHeader.partitionFilter = ParamsParser.getPartitionFilter(collectionReference, partitionFilter);
         exploreService.setValidGeoFilters(collectionReference, computationRequestHeader);
         MixedRequest request = new MixedRequest();
         request.basicRequest = computationRequest;
@@ -233,7 +233,7 @@ public Response computePost(
             throw new NotFoundException(collection);
         }
         ComputationRequest computationRequestHeader = new ComputationRequest();
-        computationRequestHeader.filter = ParamsParser.getFilter(collectionReference, partitionFilter);
+        computationRequestHeader.partitionFilter = ParamsParser.getPartitionFilter(collectionReference, partitionFilter);
         MixedRequest request = new MixedRequest();
         exploreService.setValidGeoFilters(collectionReference, computationRequest);
         exploreService.setValidGeoFilters(collectionReference, computationRequestHeader);
diff --git a/arlas-rest/src/main/java/io/arlas/server/rest/explore/count/CountRESTService.java b/arlas-rest/src/main/java/io/arlas/server/rest/explore/count/CountRESTService.java
index b863a93fe..27994942a 100644
--- a/arlas-rest/src/main/java/io/arlas/server/rest/explore/count/CountRESTService.java
+++ b/arlas-rest/src/main/java/io/arlas/server/rest/explore/count/CountRESTService.java
@@ -139,7 +139,7 @@ public Response count(
         MixedRequest request = new MixedRequest();
         request.basicRequest = count;
         Count countHeader = new Count();
-        countHeader.filter = ParamsParser.getFilter(collectionReference, partitionfilter);
+        countHeader.partitionFilter = ParamsParser.getPartitionFilter(collectionReference, partitionfilter);
         exploreService.setValidGeoFilters(collectionReference, countHeader);
         request.headerRequest = countHeader;
         request.columnFilter = ColumnFilterUtil.getCollectionRelatedColumnFilter(Optional.ofNullable(columnFilter), collectionReference);
@@ -213,7 +213,7 @@ public Response countPost(
 
         request.basicRequest = count;
         Count countHeader = new Count();
-        countHeader.filter = ParamsParser.getFilter(collectionReference, partitionfilter);
+        countHeader.partitionFilter = ParamsParser.getPartitionFilter(collectionReference, partitionfilter);
         exploreService.setValidGeoFilters(collectionReference, countHeader);
         request.headerRequest = countHeader;
         request.columnFilter = ColumnFilterUtil.getCollectionRelatedColumnFilter(Optional.ofNullable(columnFilter), collectionReference);
diff --git a/arlas-rest/src/main/java/io/arlas/server/rest/explore/search/GeoSearchRESTService.java b/arlas-rest/src/main/java/io/arlas/server/rest/explore/search/GeoSearchRESTService.java
index e1ce560e7..a4cbb67ff 100644
--- a/arlas-rest/src/main/java/io/arlas/server/rest/explore/search/GeoSearchRESTService.java
+++ b/arlas-rest/src/main/java/io/arlas/server/rest/explore/search/GeoSearchRESTService.java
@@ -560,7 +560,7 @@ public Response geosearchPost(
         CheckParams.checkReturnedGeometries(collectionReference, includes, excludes, search.returned_geometries);
 
         Search searchHeader = new Search();
-        searchHeader.filter = ParamsParser.getFilter(collectionReference, partitionFilter);
+        searchHeader.partitionFilter = ParamsParser.getPartitionFilter(collectionReference, partitionFilter);
         exploreService.setValidGeoFilters(collectionReference, search);
         exploreService.setValidGeoFilters(collectionReference, searchHeader);
 
@@ -644,7 +644,7 @@ public Response shapesearchPost(
         CheckParams.checkReturnedGeometries(collectionReference, includes, excludes, search.returned_geometries);
 
         Search searchHeader = new Search();
-        searchHeader.filter = ParamsParser.getFilter(collectionReference, partitionFilter);
+        searchHeader.partitionFilter = ParamsParser.getPartitionFilter(collectionReference, partitionFilter);
 
         exploreService.setValidGeoFilters(collectionReference, search);
         exploreService.setValidGeoFilters(collectionReference, searchHeader);
@@ -689,7 +689,7 @@ private Response geosearch(CollectionReference collectionReference, Filter filte
         search.projection = ParamsParser.enrichIncludes(search.projection, returned_geometries);
 
         Search searchHeader = new Search();
-        searchHeader.filter = ParamsParser.getFilter(collectionReference, partitionFilter);
+        searchHeader.partitionFilter = ParamsParser.getPartitionFilter(collectionReference, partitionFilter);
         MixedRequest request = new MixedRequest();
         request.basicRequest = search;
         exploreService.setValidGeoFilters(collectionReference, searchHeader);
diff --git a/arlas-rest/src/main/java/io/arlas/server/rest/explore/search/SearchRESTService.java b/arlas-rest/src/main/java/io/arlas/server/rest/explore/search/SearchRESTService.java
index 83b4e0118..c1b80e70c 100644
--- a/arlas-rest/src/main/java/io/arlas/server/rest/explore/search/SearchRESTService.java
+++ b/arlas-rest/src/main/java/io/arlas/server/rest/explore/search/SearchRESTService.java
@@ -203,7 +203,7 @@ public Response search(
         search.projection = ParamsParser.enrichIncludes(search.projection, returned_geometries);
 
         Search searchHeader = new Search();
-        searchHeader.filter = ParamsParser.getFilter(collectionReference, partitionFilter);
+        searchHeader.partitionFilter = ParamsParser.getPartitionFilter(collectionReference, partitionFilter);
         MixedRequest request = new MixedRequest();
         request.basicRequest = search;
         exploreService.setValidGeoFilters(collectionReference, searchHeader);
@@ -282,7 +282,7 @@ public Response searchPost(
         String excludes = search.projection != null ? search.projection.excludes : null;
 
         Search searchHeader = new Search();
-        searchHeader.filter = ParamsParser.getFilter(collectionReference, partitionFilter);
+        searchHeader.partitionFilter = ParamsParser.getPartitionFilter(collectionReference, partitionFilter);
 
         exploreService.setValidGeoFilters(collectionReference, search);
         exploreService.setValidGeoFilters(collectionReference, searchHeader);
diff --git a/arlas-rest/src/main/java/io/arlas/server/rest/plugins/eo/TileRESTService.java b/arlas-rest/src/main/java/io/arlas/server/rest/plugins/eo/TileRESTService.java
index 68ef7fc73..0dcd69633 100644
--- a/arlas-rest/src/main/java/io/arlas/server/rest/plugins/eo/TileRESTService.java
+++ b/arlas-rest/src/main/java/io/arlas/server/rest/plugins/eo/TileRESTService.java
@@ -207,7 +207,7 @@ public Response tiledgeosearch(
         ColumnFilterUtil.assertRequestAllowed(Optional.ofNullable(columnFilter), collectionReference, search);
 
         Search searchHeader = new Search();
-        searchHeader.filter = ParamsParser.getFilter(collectionReference, partitionFilter);
+        searchHeader.partitionFilter = ParamsParser.getPartitionFilter(collectionReference, partitionFilter);
         MixedRequest request = new MixedRequest();
         request.basicRequest = search;
         request.headerRequest = searchHeader;
diff --git a/docs/arlas-api-exploration.md b/docs/arlas-api-exploration.md
index 8b66e1e23..00def9d63 100644
--- a/docs/arlas-api-exploration.md
+++ b/docs/arlas-api-exploration.md
@@ -283,6 +283,12 @@ In the case of `lt`, `gt`, `lte`, `gte`, `range` operations that are applied on
 When dealing with multi collections, filter can also be specified in a JSON map <collection name, filter>, e.g.:
 > Example: `curl --header "partition-filter: {"mycollection1": {"f":[[{"field":"city","op":"eq","value":"Bordeaux"}]]}}" https://api.gisaia.com/demo/arlas/explore/cities/_count`
 
+You can pass several filter in `partition-filter`, separated by comma :
+
+> Example: `curl --header "partition-filter: {"f":[[{"field":"city","op":"eq","value":"Bordeaux"}]]},{"f":[[{"field":"city","op":"eq","value":"Paris"}]]}" https://api.gisaia.com/demo/arlas/explore/cities/_count`
+
+In this case a OR combination is done.
+
 #### Column filtering
 
 A comma-separated list of columns can be passed in request header `column-filter`. Wildcards are supported.
diff --git a/ogc-wfs/src/main/java/io/arlas/server/ogc/wfs/services/ElasticWFSToolService.java b/ogc-wfs/src/main/java/io/arlas/server/ogc/wfs/services/ElasticWFSToolService.java
index 04b8888c4..124607030 100644
--- a/ogc-wfs/src/main/java/io/arlas/server/ogc/wfs/services/ElasticWFSToolService.java
+++ b/ogc-wfs/src/main/java/io/arlas/server/ogc/wfs/services/ElasticWFSToolService.java
@@ -22,7 +22,6 @@
 import co.elastic.clients.elasticsearch._types.GeoBounds;
 import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
 import co.elastic.clients.elasticsearch._types.query_dsl.GeoBoundingBoxQuery;
-import co.elastic.clients.elasticsearch._types.query_dsl.GeoShapeQuery;
 import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery;
 import co.elastic.clients.elasticsearch.core.SearchRequest;
 import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
@@ -218,20 +217,25 @@ private void buildStoredQueryIdQuery(String id, String storedquery_id, WFSReques
     }
 
     private void addPartitionFilter(CollectionReference collectionReference, ElasticFluidSearch fluidSearch, String partitionFilter) throws ArlasException {
-        Filter headerFilter = ParamsParser.getFilter(collectionReference, partitionFilter);
+        List<Filter> headerFilter = ParamsParser.getPartitionFilter(collectionReference, partitionFilter);
         Filter collectionFilter = collectionReference.params.filter;
-        Boolean applyFilter = false;
+        Boolean applyCollectionFilter = false;
+        Boolean applyPartitionFilter = false;
+
         if(collectionFilter != null){
             exploreServices.applyFilter(collectionFilter, fluidSearch);
-            applyFilter = true;
+            applyCollectionFilter = true;
         }
-        if(headerFilter.f != null || headerFilter.q != null){
-            exploreServices.applyFilter(headerFilter, fluidSearch);
-            applyFilter = true;
+        if(headerFilter != null){
+            exploreServices.applyPartitionFilter(headerFilter, fluidSearch);
+            applyPartitionFilter = true;
         }
-        if(applyFilter){
+        if(applyCollectionFilter){
             wfsQuery.filter(fluidSearch.getBoolQueryBuilder().build()._toQuery());
         }
+        if(applyPartitionFilter){
+            wfsQuery.filter(fluidSearch.getBoolPartitionQueryBuilder().build()._toQuery());
+        }
     }
 
     private void addCollectionFilter(ElasticFluidSearch fluidSearch, CollectionReference collectionReference) throws ArlasException {
diff --git a/stac-api/src/main/java/io/arlas/server/stac/api/StacRESTService.java b/stac-api/src/main/java/io/arlas/server/stac/api/StacRESTService.java
index 0e90e82a2..9fbb9beaf 100644
--- a/stac-api/src/main/java/io/arlas/server/stac/api/StacRESTService.java
+++ b/stac-api/src/main/java/io/arlas/server/stac/api/StacRESTService.java
@@ -245,7 +245,7 @@ protected StacFeatureCollection getStacFeatureCollection(CollectionReference col
         ColumnFilterUtil.assertRequestAllowed(columnFilter, collectionReference, search);
 
         Search searchHeader = new Search();
-        searchHeader.filter = ParamsParser.getFilter(collectionReference, partitionFilter);
+        searchHeader.partitionFilter = ParamsParser.getPartitionFilter(collectionReference, partitionFilter);
         MixedRequest request = new MixedRequest();
         request.basicRequest = search;
         request.headerRequest = searchHeader;


Test patch:
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/ATOMSearchServiceIT.java b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/ATOMSearchServiceIT.java
index 5a81ccfc5..f82f4f48f 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/ATOMSearchServiceIT.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/ATOMSearchServiceIT.java
@@ -99,6 +99,17 @@ public void handleComplexFilter(ValidatableResponse then) throws Exception {
         }
     }
 
+    @Override
+    public void handleMultiPartitionFilter(ValidatableResponse then) throws Exception {
+        if (then.extract().contentType().equals(ATOM.APPLICATION_ATOM_XML)) {
+            then.statusCode(200)
+                    .body(ATOM.XML_PREFIX + ":feed.totalResults", equalTo("33"));
+        } else {
+            then.statusCode(200)
+                    .body("totalnb", equalTo(33));
+        }
+    }
+
     @Override
     protected void handleFieldFilter(ValidatableResponse then, int nbResults, String... values) throws Exception {
         if (then.extract().contentType().equals(ATOM.APPLICATION_ATOM_XML)) {
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractFilteredTest.java b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractFilteredTest.java
index df091e19a..41c5eee0d 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractFilteredTest.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractFilteredTest.java
@@ -1636,6 +1636,19 @@ public void testComplexFilter() throws Exception {
 
     }
 
+    @Test
+    public void testMultiPartitionFilter() throws Exception {
+        List<MultiValueFilter<Expression>> fGroup1Filter = Arrays.asList(new MultiValueFilter<>(new Expression("params.job", OperatorEnum.like, "Architect")),//"job:eq:Architect"
+                new MultiValueFilter<>(new Expression("geo_params.geometry", OperatorEnum.intersects, "POLYGON((-20 20, -20 -20, 20 -20, 20 20, -20 20))")));
+        List<MultiValueFilter<Expression>> fGroup2Filter = Arrays.asList(new MultiValueFilter<>(new Expression("geo_params.geometry", OperatorEnum.intersects, "POLYGON((30 20, 30 -20, 70 -20, 70 20, 30 20))")));
+        Filter group1Filter = new Filter();
+        group1Filter.f = fGroup1Filter;
+        Filter group2Filter = new Filter();
+        group2Filter.f = fGroup2Filter;
+        String serializedFilter =  objectMapper.writeValueAsString(group1Filter) +"," +  objectMapper.writeValueAsString(group2Filter) ;
+        handleMultiPartitionFilter(header(serializedFilter));
+    }
+
 
     @Test
     public void testCollectionFilter() throws Exception {
@@ -2206,6 +2219,8 @@ protected abstract void handleMatchingStringRangeFilter(ValidatableResponse then
 
     protected abstract void handleComplexFilter(ValidatableResponse then) throws Exception;
 
+    protected abstract void handleMultiPartitionFilter(ValidatableResponse then) throws Exception;
+
 
     //----------------------------------------------------------------
     //---------------------- NOT MATCHING RESPONSES ------------------
@@ -2296,6 +2311,12 @@ private ValidatableResponse get(List<Pair<String, Object>> params) {
                 .then();
     }
 
+    private ValidatableResponse header(String serializedFilter) throws JsonProcessingException {
+        return givenFilterableRequestParams().header(PARTITION_FILTER, serializedFilter)
+                .when().get(getUrlPath("geodata"))
+                .then();
+    }
+
     private ValidatableResponse header(Filter filter) throws JsonProcessingException {
         return givenFilterableRequestParams().header(PARTITION_FILTER, objectMapper.writeValueAsString(filter))
                 .when().get(getUrlPath("geodata"))
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AggregateServiceIT.java b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AggregateServiceIT.java
index 9510de285..3e76815a0 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AggregateServiceIT.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AggregateServiceIT.java
@@ -346,6 +346,12 @@ public void handleComplexFilter(ValidatableResponse then) throws Exception {
                 .body("elements.size()", equalTo(1));
     }
 
+    @Override
+    protected void handleMultiPartitionFilter(ValidatableResponse then) throws Exception {
+        then.statusCode(200)
+                .body("elements.size()", equalTo(33));
+    }
+
     @Override
     protected void handleFieldFilter(ValidatableResponse then, int nbResults, String... values) throws Exception {
         handleFieldFilter(then, nbResults);
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/ComputeServiceIT.java b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/ComputeServiceIT.java
index fefef62e7..c13699727 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/ComputeServiceIT.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/ComputeServiceIT.java
@@ -97,6 +97,11 @@ public void handleComplexFilter(ValidatableResponse then) throws Exception {
         handleMatchingFilter(then, 1);
     }
 
+    @Override
+    protected void handleMultiPartitionFilter(ValidatableResponse then) throws Exception {
+        handleMatchingFilter(then, 33);
+    }
+
     @Override
     protected void handleFieldFilter(ValidatableResponse then, int nbResults, String... values) throws Exception {
         handleFieldFilter(then, nbResults);
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/CountServiceIT.java b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/CountServiceIT.java
index a049c5e6b..f5af070d6 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/CountServiceIT.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/CountServiceIT.java
@@ -61,6 +61,11 @@ public void handleComplexFilter(ValidatableResponse then) throws Exception {
         handleMatchingFilter(then, 1);
     }
 
+    @Override
+    public void handleMultiPartitionFilter(ValidatableResponse then) throws Exception {
+        handleMatchingFilter(then, 33);
+    }
+
     @Override
     protected void handleFieldFilter(ValidatableResponse then, int nbResults, String... values) throws Exception {
         handleFieldFilter(then, nbResults);
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/GeoAggregateServiceIT.java b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/GeoAggregateServiceIT.java
index 6d3ce53f8..6e0fcaf63 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/GeoAggregateServiceIT.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/GeoAggregateServiceIT.java
@@ -339,6 +339,11 @@ public void handleComplexFilter(ValidatableResponse then) throws Exception {
                 .body("features.size()", equalTo(1));
     }
 
+    @Override
+    protected void handleMultiPartitionFilter(ValidatableResponse then) throws Exception {
+        then.statusCode(200).body("features.size()", equalTo(33));;
+    }
+
     @Override
     protected void handleFieldFilter(ValidatableResponse then, int nbResults, String... values) throws Exception {
         handleFieldFilter(then, nbResults);
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/GeoSearchServiceIT.java b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/GeoSearchServiceIT.java
index 332029c64..5ffde1ff2 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/GeoSearchServiceIT.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/GeoSearchServiceIT.java
@@ -60,7 +60,7 @@ protected void handleNotMatchingRequest(ValidatableResponse then) {
     //----------------------------------------------------------------
     @Override
     protected RequestSpecification givenFilterableRequestParams() {
-        return given();
+        return  given();
     }
 
     @Override
@@ -81,6 +81,11 @@ public void handleComplexFilter(ValidatableResponse then) throws Exception {
                 .body("features[0].properties.md.geometry", isEmptyOrNullString());
     }
 
+    @Override
+    protected void handleMultiPartitionFilter(ValidatableResponse then) throws Exception {
+        then.statusCode(200).body("features[0].properties.params.job", equalTo("Architect"));
+    }
+
     @Override
     protected void handleFieldFilter(ValidatableResponse then, int nbResults, String... values) throws Exception {
         then.statusCode(200)
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/SearchServiceIT.java b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/SearchServiceIT.java
index a23884140..477f8f8d6 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/SearchServiceIT.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/SearchServiceIT.java
@@ -78,6 +78,13 @@ public void handleComplexFilter(ValidatableResponse then) throws Exception {
                 .body("hits[0].md.geometry", hasKey("coordinates"));
     }
 
+    @Override
+    public void handleMultiPartitionFilter(ValidatableResponse then) throws Exception {
+        then.statusCode(200)
+                .body("totalnb", equalTo(33));
+
+    }
+
     @Override
     protected void handleFieldFilter(ValidatableResponse then, int nbResults, String... values) throws Exception {
         then.statusCode(200)