This is an automated email from the ASF dual-hosted git repository.

sergehuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/unomi.git


The following commit(s) were added to refs/heads/master by this push:
     new 71b073534 UNOMI-1014: Return aggregate results and keep them in the 
current tenant (#922)
71b073534 is described below

commit 71b0735344ec0e7dcd9c5ce15360efab290e0f9b
Author: Serge Huber <[email protected]>
AuthorDate: Fri Oct 23 16:36:35 2026 +0200

    UNOMI-1014: Return aggregate results and keep them in the current tenant 
(#922)
    
    Co-authored-by: Claude Opus 5.5 <[email protected]>
---
 .../apache/unomi/itests/PersistenceServiceIT.java  | 119 +++++++++++++++++++++
 .../ElasticSearchPersistenceServiceImpl.java       |  75 ++++++++-----
 .../OpenSearchPersistenceServiceImpl.java          |  84 +++++++++------
 3 files changed, 217 insertions(+), 61 deletions(-)

diff --git 
a/itests/src/test/java/org/apache/unomi/itests/PersistenceServiceIT.java 
b/itests/src/test/java/org/apache/unomi/itests/PersistenceServiceIT.java
index 05d00aef4..fdbd2b296 100644
--- a/itests/src/test/java/org/apache/unomi/itests/PersistenceServiceIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/PersistenceServiceIT.java
@@ -19,7 +19,9 @@ package org.apache.unomi.itests;
 import org.apache.unomi.api.Event;
 import org.apache.unomi.api.PartialList;
 import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.conditions.Condition;
 import org.apache.unomi.api.tenants.Tenant;
+import org.apache.unomi.persistence.spi.aggregate.TermsAggregate;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Test;
@@ -32,6 +34,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 /**
@@ -213,6 +216,122 @@ public class PersistenceServiceIT extends BaseIT {
         }
     }
 
+    /**
+     * UNOMI-1014: a terms aggregate with a condition must return buckets 
again (non-optimized and optimized).
+     */
+    @Test
+    public void testAggregateWithConditionReturnsBuckets() throws 
InterruptedException {
+        String suffix = UUID.randomUUID().toString();
+        saveProfileWithFirstName("agg-alice-" + suffix, "Alice");
+        saveProfileWithFirstName("agg-alice-2-" + suffix, "Alice");
+        saveProfileWithFirstName("agg-bob-" + suffix, "Bob");
+        refreshPersistence(Profile.class);
+
+        Condition matchAll = matchAllCondition();
+        TermsAggregate byFirstName = new 
TermsAggregate("properties.firstName");
+
+        Map<String, Long> withCondition = keepTrying(
+                "Aggregate with a condition should return first-name buckets",
+                () -> persistenceService.aggregateQuery(matchAll, byFirstName, 
Profile.ITEM_TYPE),
+                r -> r != null && Long.valueOf(2L).equals(r.get("Alice")) && 
Long.valueOf(1L).equals(r.get("Bob")),
+                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
+        Assert.assertEquals(Long.valueOf(2L), withCondition.get("Alice"));
+        Assert.assertEquals(Long.valueOf(1L), withCondition.get("Bob"));
+        Assert.assertTrue("Non-optimized aggregate should report a 
tenant-scoped _all count",
+                withCondition.get("_all") != null && withCondition.get("_all") 
>= 3L);
+        Assert.assertTrue("Non-optimized aggregate with a condition should 
report _filtered",
+                withCondition.get("_filtered") != null && 
withCondition.get("_filtered") >= 3L);
+
+        Map<String, Long> optimized = keepTrying(
+                "Optimized aggregate with a condition should return first-name 
buckets",
+                () -> persistenceService.aggregateWithOptimizedQuery(matchAll, 
byFirstName, Profile.ITEM_TYPE),
+                r -> r != null && Long.valueOf(2L).equals(r.get("Alice")) && 
Long.valueOf(1L).equals(r.get("Bob")),
+                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
+        Assert.assertEquals(Long.valueOf(2L), optimized.get("Alice"));
+        Assert.assertEquals(Long.valueOf(1L), optimized.get("Bob"));
+    }
+
+    /**
+     * Aggregate and metric queries must only see the current tenant's 
documents.
+     */
+    @Test
+    public void testAggregateAndMetricsStayInTheCurrentTenant() throws 
Exception {
+        String suffix = UUID.randomUUID().toString().substring(0, 8);
+        String localAlice = "agg-tenant-local-alice-" + suffix;
+        String otherAlice = "agg-tenant-other-alice-" + suffix;
+        String otherBob = "agg-tenant-other-bob-" + suffix;
+        Tenant otherTenant = tenantService.createTenant("agg-other-" + suffix, 
Collections.emptyMap());
+        try {
+            saveProfileWithFirstName(localAlice, "Alice");
+            Profile local = persistenceService.load(localAlice, Profile.class);
+            local.setProperty("age", 7);
+            persistenceService.save(local);
+            executionContextManager.executeAsTenant(otherTenant.getItemId(), 
() -> {
+                Profile otherAliceProfile = new Profile(otherAlice);
+                otherAliceProfile.setProperty("firstName", "Alice");
+                otherAliceProfile.setProperty("age", 10);
+                persistenceService.save(otherAliceProfile);
+                Profile otherBobProfile = new Profile(otherBob);
+                otherBobProfile.setProperty("firstName", "Bob");
+                otherBobProfile.setProperty("age", 5);
+                persistenceService.save(otherBobProfile);
+            });
+            refreshPersistence(Profile.class);
+
+            Condition matchAll = matchAllCondition();
+            TermsAggregate byFirstName = new 
TermsAggregate("properties.firstName");
+
+            Map<String, Long> termsNoCondition = keepTrying(
+                    "Terms aggregate without a condition should only count the 
current tenant",
+                    () -> persistenceService.aggregateWithOptimizedQuery(null, 
byFirstName, Profile.ITEM_TYPE),
+                    r -> r != null && Long.valueOf(1L).equals(r.get("Alice")) 
&& !r.containsKey("Bob"),
+                    DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
+            Assert.assertEquals(Long.valueOf(1L), 
termsNoCondition.get("Alice"));
+            Assert.assertNull("Another tenant's first names must not appear", 
termsNoCondition.get("Bob"));
+
+            Map<String, Long> termsWithCondition = keepTrying(
+                    "Terms aggregate with a condition should only count the 
current tenant",
+                    () -> persistenceService.aggregateQuery(matchAll, 
byFirstName, Profile.ITEM_TYPE),
+                    r -> r != null && Long.valueOf(1L).equals(r.get("Alice")) 
&& !r.containsKey("Bob"),
+                    DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
+            Assert.assertEquals(Long.valueOf(1L), 
termsWithCondition.get("Alice"));
+            Assert.assertNull(termsWithCondition.get("Bob"));
+            Assert.assertNotNull("Non-optimized aggregate should report _all 
for the current tenant",
+                    termsWithCondition.get("_all"));
+            Assert.assertEquals("With match-all, _all and _filtered must match 
for the current tenant",
+                    termsWithCondition.get("_all"), 
termsWithCondition.get("_filtered"));
+
+            Map<String, Double> metrics = keepTrying(
+                    "Sum metric should only include the current tenant",
+                    () -> persistenceService.getSingleValuesMetrics(matchAll, 
new String[]{"sum", "count"},
+                            AGE_PROPERTY, Profile.ITEM_TYPE),
+                    r -> r != null && r.get("_sum") != null && r.get("_sum") 
== 7.0,
+                    DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
+            Assert.assertEquals(7.0, metrics.get("_sum"), 0.001);
+            Assert.assertEquals(1.0, metrics.get("_count"), 0.001);
+        } finally {
+            try {
+                
executionContextManager.executeAsTenant(otherTenant.getItemId(), () -> {
+                    persistenceService.remove(otherAlice, Profile.class);
+                    persistenceService.remove(otherBob, Profile.class);
+                });
+            } finally {
+                tenantService.deleteTenant(otherTenant.getItemId());
+            }
+        }
+    }
+
+    private Condition matchAllCondition() {
+        return new 
Condition(definitionsService.getConditionType("matchAllCondition"));
+    }
+
+    private void saveProfileWithFirstName(String itemId, String firstName) {
+        Profile profile = new Profile(itemId);
+        profile.setProperty("firstName", firstName);
+        persistenceService.save(profile);
+        profileIds.add(itemId);
+    }
+
     private Profile saveProfile(String itemId) {
         Profile profile = new Profile(itemId);
         persistenceService.save(profile);
diff --git 
a/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
 
b/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
index 46cdad5ba..f5980a7ac 100644
--- 
a/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
+++ 
b/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
@@ -2501,28 +2501,35 @@ public class ElasticSearchPersistenceServiceImpl 
implements PersistenceService,
                 SearchRequest.Builder searchSourceBuilder = new 
SearchRequest.Builder();
                 searchSourceBuilder.index(getIndexNameForQuery(itemType));
                 searchSourceBuilder.size(0);
-                searchSourceBuilder.query(
-                        isItemTypeSharingIndex(itemType) ? 
getItemTypeQuery(itemType) : Query.of(q -> q.matchAll(m -> m)));
 
-                // If the request is optimized then we don't need a global 
aggregation which is very slow and we can put the query with a
-                // filter on range items in the query block so we don't 
retrieve all the document before filtering the whole
-                if (optimizedQuery) {
-                    searchSourceBuilder.aggregations(aggregationsByType);
+                Query tenantScopedQuery = 
wrapWithTenantAndItemTypeQuery(itemType, Query.of(q -> q.matchAll(m -> m)), 
finalTenantId);
 
+                // Optimized: put the optional condition in the query and skip 
the slow global aggregation.
+                // Non-optimized: keep a filter aggregation for _filtered, but 
always scope the query to the
+                // current tenant. A global aggregation ignores the query and 
would count every tenant's documents.
+                // Also register aggregations under stable names ("filter", 
"buckets", "missing"): the previous
+                // code used String.valueOf(Kind) ("Global") while reading 
Kind.jsonValue() ("global"), so POST
+                // aggregates with a condition returned nothing (UNOMI-1014).
+                if (optimizedQuery) {
                     if (filter != null) {
-                        
searchSourceBuilder.query(wrapWithTenantAndItemTypeQuery(itemType, 
conditionESQueryBuilderDispatcher.buildFilter(filter), finalTenantId));
+                        
searchSourceBuilder.query(wrapWithTenantAndItemTypeQuery(itemType,
+                                
conditionESQueryBuilderDispatcher.buildFilter(filter), finalTenantId));
+                    } else {
+                        searchSourceBuilder.query(tenantScopedQuery);
                     }
+                    searchSourceBuilder.aggregations(aggregationsByType);
                 } else {
+                    searchSourceBuilder.query(tenantScopedQuery);
+                    // _all comes from hits.total, which is capped at 10,000 
unless hits are tracked exactly.
+                    searchSourceBuilder.trackTotalHits(t -> t.enabled(true));
                     if (filter != null) {
-                        Aggregation.Builder aggBuilder = new 
Aggregation.Builder();
-                        
aggBuilder.filter(wrapWithTenantAndItemTypeQuery(itemType, 
conditionESQueryBuilderDispatcher.buildFilter(filter), finalTenantId))
-                                .aggregations(aggregationsByType);
-
-                        aggregationsByType = Map.of("filter", 
aggBuilder.build());
+                        Aggregation filterAggregation = new 
Aggregation.Builder().filter(wrapWithTenantAndItemTypeQuery(
+                                        itemType, 
conditionESQueryBuilderDispatcher.buildFilter(filter), finalTenantId))
+                                .aggregations(aggregationsByType).build();
+                        searchSourceBuilder.aggregations("filter", 
filterAggregation);
+                    } else {
+                        searchSourceBuilder.aggregations(aggregationsByType);
                     }
-                    Aggregation globalAgg = new 
Aggregation.Builder().global(new GlobalAggregation.Builder().build())
-                            .aggregations(aggregationsByType).build();
-                    
searchSourceBuilder.aggregations(String.valueOf(globalAgg._kind()), globalAgg);
                 }
 
                 RequestOptions.Builder builder = 
RequestOptions.DEFAULT.toBuilder();
@@ -2550,16 +2557,17 @@ public class ElasticSearchPersistenceServiceImpl 
implements PersistenceService,
 
                 if (aggregations != null) {
                     if (optimizedQuery) {
-                        if (response.hits() != null) {
+                        if (response.hits() != null && response.hits().total() 
!= null) {
                             results.put("_filtered", 
response.hits().total().value());
                         }
                     } else {
-                        GlobalAggregate globalAggregate = 
aggregations.get(Aggregation.Kind.Global.jsonValue()).global();
-                        results.put("_all", globalAggregate.docCount());
-                        aggregations = globalAggregate.aggregations();
+                        if (response.hits() != null && response.hits().total() 
!= null) {
+                            results.put("_all", 
response.hits().total().value());
+                        }
 
-                        if 
(aggregations.get(Aggregate.Kind.Filter.jsonValue()) != null) {
-                            FilterAggregate filterAggregate = 
aggregations.get(Aggregation.Kind.Filter.jsonValue()).filter();
+                        Aggregate filterAggResult = aggregations.get("filter");
+                        if (filterAggResult != null && 
filterAggResult.isFilter()) {
+                            FilterAggregate filterAggregate = 
filterAggResult.filter();
                             results.put("_filtered", 
filterAggregate.docCount());
                             aggregations = filterAggregate.aggregations();
                         }
@@ -2605,12 +2613,16 @@ public class ElasticSearchPersistenceServiceImpl 
implements PersistenceService,
                             }
                         }
 
-                        MissingAggregate missing = 
aggregations.get("missing").missing();
-                        if (missing.docCount() > 0) {
-                            results.put("_missing", missing.docCount());
-                            totalDocCount += missing.docCount();
+                        Aggregate missingAgg = aggregations.get("missing");
+                        if (missingAgg != null && missingAgg.isMissing()) {
+                            MissingAggregate missing = missingAgg.missing();
+                            if (missing.docCount() > 0) {
+                                results.put("_missing", missing.docCount());
+                                totalDocCount += missing.docCount();
+                            }
                         }
-                        if (response.hits() != null && 
TotalHitsRelation.Gte.equals(response.hits().total().relation())) {
+                        if (response.hits() != null && response.hits().total() 
!= null
+                                && 
TotalHitsRelation.Gte.equals(response.hits().total().relation())) {
                             results.put("_filtered", totalDocCount);
                         }
                     }
@@ -2802,6 +2814,7 @@ public class ElasticSearchPersistenceServiceImpl 
implements PersistenceService,
 
     @Override public Map<String, Double> getSingleValuesMetrics(final 
Condition condition, final String[] metrics, final String field,
             final String itemType) {
+        String finalTenantId = 
validateTenantAndGetId(SecurityServiceConfiguration.PERMISSION_AGGREGATE);
         return new InClassLoaderExecute<Map<String, Double>>(metricsService, 
this.getClass().getName() + ".getSingleValuesMetrics",
                 this.bundleContext, this.fatalIllegalStateErrors, 
throwExceptions) {
 
@@ -2834,11 +2847,15 @@ public class ElasticSearchPersistenceServiceImpl 
implements PersistenceService,
                     }
                 }
 
-                Aggregation filterAggregation = Aggregation.of(
-                        a -> 
a.filter(conditionESQueryBuilderDispatcher.buildFilter(condition)).aggregations(subAggs));
+                Query conditionQuery = condition != null ?
+                        
conditionESQueryBuilderDispatcher.buildFilter(condition) :
+                        Query.of(q -> q.matchAll(m -> m));
+                Aggregation filterAggregation = Aggregation.of(a -> a.filter(
+                                wrapWithTenantAndItemTypeQuery(itemType, 
conditionQuery, finalTenantId))
+                        .aggregations(subAggs));
                 SearchRequest searchRequest = SearchRequest.of(
                         s -> 
s.index(getIndexNameForQuery(itemType)).size(0).source(builder -> 
builder.fetch(true))
-                                .query(isItemTypeSharingIndex(itemType) ? 
getItemTypeQuery(itemType) : Query.of(q -> q.matchAll(m -> m)))
+                                
.query(wrapWithTenantAndItemTypeQuery(itemType, Query.of(q -> q.matchAll(m -> 
m)), finalTenantId))
                                 .aggregations("metrics", filterAggregation));
 
                 SearchResponse<Void> searchResponse = 
esClient.search(searchRequest);
diff --git 
a/persistence-opensearch/core/src/main/java/org/apache/unomi/persistence/opensearch/OpenSearchPersistenceServiceImpl.java
 
b/persistence-opensearch/core/src/main/java/org/apache/unomi/persistence/opensearch/OpenSearchPersistenceServiceImpl.java
index a83d6a7b6..0e2be7a7e 100644
--- 
a/persistence-opensearch/core/src/main/java/org/apache/unomi/persistence/opensearch/OpenSearchPersistenceServiceImpl.java
+++ 
b/persistence-opensearch/core/src/main/java/org/apache/unomi/persistence/opensearch/OpenSearchPersistenceServiceImpl.java
@@ -2404,8 +2404,8 @@ public class OpenSearchPersistenceServiceImpl implements 
PersistenceService, Syn
                 SearchRequest.Builder searchRequestBuilder = new 
SearchRequest.Builder().index(getIndexNameForQuery(itemType));
                 searchRequestBuilder.size(0);
                 Query matchAll = Query.of(q->q.matchAll(m->m));
-                boolean isItemTypeSharingIndex = 
isItemTypeSharingIndex(itemType);
-                
searchRequestBuilder.query(wrapWithTenantAndItemTypeQuery(itemType,matchAll, 
finalTenantId));
+                Query tenantScopedQuery = 
wrapWithTenantAndItemTypeQuery(itemType, matchAll, finalTenantId);
+                searchRequestBuilder.query(tenantScopedQuery);
                 Map<String, Aggregation.Builder.ContainerBuilder> 
lastAggregation = new LinkedHashMap<>();
 
                 if (aggregate != null) {
@@ -2495,31 +2495,41 @@ public class OpenSearchPersistenceServiceImpl 
implements PersistenceService, Syn
                     }
                 }
 
-                // If the request is optimized then we don't need a global 
aggregation which is very slow and we can put the query with a
-                // filter on range items in the query block so we don't 
retrieve all the document before filtering the whole
+                // Optimized: put the optional condition in the query and skip 
the slow global aggregation.
+                // Non-optimized: keep a filter aggregation for _filtered, 
scoped to the current tenant.
+                // Register aggregations under stable names ("filter", 
"buckets", "missing"). The previous
+                // non-optimized path stored the global aggregation as 
"buckets" while reading "global", and
+                // nested the condition filter under "buckets" while reading 
"filter", so POST aggregates with
+                // a condition returned nothing (UNOMI-1014). A global 
aggregation also ignores the query and
+                // would count every tenant's documents.
                 if (optimizedQuery) {
+                    if (filter != null) {
+                        
searchRequestBuilder.query(wrapWithTenantAndItemTypeQuery(itemType,
+                                
conditionOSQueryBuilderDispatcher.buildFilter(filter), finalTenantId));
+                    } else {
+                        searchRequestBuilder.query(tenantScopedQuery);
+                    }
                     for (Map.Entry<String, 
Aggregation.Builder.ContainerBuilder> aggregationBuilder : 
lastAggregation.entrySet()) {
                         
searchRequestBuilder.aggregations(aggregationBuilder.getKey(), 
aggregationBuilder.getValue().build());
                     }
-
-                    if (filter != null) {
-                        
searchRequestBuilder.query(wrapWithTenantAndItemTypeQuery(itemType, 
conditionOSQueryBuilderDispatcher.buildFilter(filter), finalTenantId));
-                    }
                 } else {
+                    searchRequestBuilder.query(tenantScopedQuery);
+                    // _all comes from hits.total, which is capped at 10,000 
unless hits are tracked exactly.
+                    searchRequestBuilder.trackTotalHits(t -> t.enabled(true));
                     if (filter != null) {
-                        Aggregation.Builder.ContainerBuilder 
filterAggregationContainerBuilder = new 
Aggregation.Builder().filter(wrapWithTenantAndItemTypeQuery(itemType, 
conditionOSQueryBuilderDispatcher.buildFilter(filter), finalTenantId));
-                        for 
(Map.Entry<String,Aggregation.Builder.ContainerBuilder> aggregationBuilder : 
lastAggregation.entrySet()) {
-                            
filterAggregationContainerBuilder.aggregations(aggregationBuilder.getKey(), 
aggregationBuilder.getValue().build());
+                        Aggregation.Builder.ContainerBuilder 
filterAggregationContainerBuilder = new Aggregation.Builder().filter(
+                                wrapWithTenantAndItemTypeQuery(itemType, 
conditionOSQueryBuilderDispatcher.buildFilter(filter),
+                                        finalTenantId));
+                        for (Map.Entry<String, 
Aggregation.Builder.ContainerBuilder> aggregationBuilder : 
lastAggregation.entrySet()) {
+                            
filterAggregationContainerBuilder.aggregations(aggregationBuilder.getKey(),
+                                    aggregationBuilder.getValue().build());
+                        }
+                        searchRequestBuilder.aggregations("filter", 
filterAggregationContainerBuilder.build());
+                    } else {
+                        for (Map.Entry<String, 
Aggregation.Builder.ContainerBuilder> aggregationBuilder : 
lastAggregation.entrySet()) {
+                            
searchRequestBuilder.aggregations(aggregationBuilder.getKey(), 
aggregationBuilder.getValue().build());
                         }
-                        lastAggregation = Map.of("buckets", 
filterAggregationContainerBuilder);
-                    }
-
-                    Aggregation.Builder.ContainerBuilder 
globalAggregationContainerBuilder = new Aggregation.Builder().global(g -> 
g.name("global"));
-                    for (Map.Entry<String, 
Aggregation.Builder.ContainerBuilder> aggregationBuilder : 
lastAggregation.entrySet()) {
-                        
globalAggregationContainerBuilder.aggregations(aggregationBuilder.getKey(), 
aggregationBuilder.getValue().build());
                     }
-
-                    searchRequestBuilder.aggregations("buckets", 
globalAggregationContainerBuilder.build());
                 }
 
                 /*
@@ -2540,16 +2550,17 @@ public class OpenSearchPersistenceServiceImpl 
implements PersistenceService, Syn
 
                 if (aggregates != null) {
                     if (optimizedQuery) {
-                        if (response.hits() != null) {
+                        if (response.hits() != null && response.hits().total() 
!= null) {
                             results.put("_filtered", 
response.hits().total().value());
                         }
                     } else {
-                        GlobalAggregate globalAgg = 
aggregates.get("global").global();
-                        results.put("_all", globalAgg.docCount());
-                        aggregates = globalAgg.aggregations();
+                        if (response.hits() != null && response.hits().total() 
!= null) {
+                            results.put("_all", 
response.hits().total().value());
+                        }
 
-                        if (aggregates.get("filter") != null) {
-                            FilterAggregate filterAgg = 
aggregates.get("filter").filter();
+                        Aggregate filterAggResult = aggregates.get("filter");
+                        if (filterAggResult != null && 
filterAggResult.isFilter()) {
+                            FilterAggregate filterAgg = 
filterAggResult.filter();
                             results.put("_filtered", filterAgg.docCount());
                             aggregates = filterAgg.aggregations();
                         }
@@ -2574,12 +2585,16 @@ public class OpenSearchPersistenceServiceImpl 
implements PersistenceService, Syn
                                 totalDocCount += bucket.docCount();
                             }
                         }
-                        MissingAggregate missing = 
aggregates.get("missing").missing();
-                        if (missing.docCount() > 0) {
-                            results.put("_missing", missing.docCount());
-                            totalDocCount += missing.docCount();
+                        Aggregate missingAgg = aggregates.get("missing");
+                        if (missingAgg != null && missingAgg.isMissing()) {
+                            MissingAggregate missing = missingAgg.missing();
+                            if (missing.docCount() > 0) {
+                                results.put("_missing", missing.docCount());
+                                totalDocCount += missing.docCount();
+                            }
                         }
-                        if (response.hits() != null && 
TotalHitsRelation.Gte.equals(response.hits().total().relation())) {
+                        if (response.hits() != null && response.hits().total() 
!= null
+                                && 
TotalHitsRelation.Gte.equals(response.hits().total().relation())) {
                             results.put("_filtered", totalDocCount);
                         }
                     }
@@ -2742,13 +2757,18 @@ public class OpenSearchPersistenceServiceImpl 
implements PersistenceService, Syn
 
     @Override
     public Map<String, Double> getSingleValuesMetrics(final Condition 
condition, final String[] metrics, final String field, final String itemType) {
+        String finalTenantId = 
validateTenantAndGetId(SecurityServiceConfiguration.PERMISSION_AGGREGATE);
         return new InClassLoaderExecute<Map<String, Double>>(metricsService, 
this.getClass().getName() + ".getSingleValuesMetrics", this.bundleContext, 
this.fatalIllegalStateErrors, throwExceptions) {
 
             @Override
             protected Map<String, Double> execute(Object... args) throws 
IOException {
                 Map<String, Double> results = new LinkedHashMap<>();
 
-                Aggregation.Builder.ContainerBuilder 
filterAggregationContainerBuilder = new 
Aggregation.Builder().filter(conditionOSQueryBuilderDispatcher.buildFilter(condition));
+                Query conditionQuery = condition != null ?
+                        
conditionOSQueryBuilderDispatcher.buildFilter(condition) :
+                        Query.of(q -> q.matchAll(i -> i));
+                Aggregation.Builder.ContainerBuilder 
filterAggregationContainerBuilder = new Aggregation.Builder().filter(
+                        wrapWithTenantAndItemTypeQuery(itemType, 
conditionQuery, finalTenantId));
 
                 if (metrics != null) {
                     for (String metric : metrics) {
@@ -2778,7 +2798,7 @@ public class OpenSearchPersistenceServiceImpl implements 
PersistenceService, Syn
                         .index(getIndexNameForQuery(itemType))
                         .size(0)
                         .aggregations("metrics", 
filterAggregationContainerBuilder.build())
-                        .query(isItemTypeSharingIndex(itemType) ? 
getItemTypeQueryBuilder(itemType) : Query.of(q -> q.matchAll(i -> i)))
+                        .query(wrapWithTenantAndItemTypeQuery(itemType, 
Query.of(q -> q.matchAll(i -> i)), finalTenantId))
                         , Item.class
                 );
 

Reply via email to