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

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


The following commit(s) were added to refs/heads/master by this push:
     new 1d48614087f Don't let RLS filters clobber the leaf's 
serverReturnFinalResult flag (#19418)
1d48614087f is described below

commit 1d48614087f0ceee0fc3a118f9e5b8ce07e24a62
Author: Yash Mayya <[email protected]>
AuthorDate: Tue Sep 1 22:26:55 2026 -0400

    Don't let RLS filters clobber the leaf's serverReturnFinalResult flag 
(#19418)
---
 .../runtime/plan/server/ServerPlanRequestUtils.java      |  7 ++++---
 .../pinot/query/runtime/queries/QueryRunnerTest.java     | 16 ++++++++++++++++
 .../pinot/query/runtime/queries/QueryRunnerTestBase.java |  8 ++++++++
 3 files changed, 28 insertions(+), 3 deletions(-)

diff --git 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/server/ServerPlanRequestUtils.java
 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/server/ServerPlanRequestUtils.java
index 4cdd84594bf..8c35aa5442f 100644
--- 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/server/ServerPlanRequestUtils.java
+++ 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/server/ServerPlanRequestUtils.java
@@ -28,7 +28,6 @@ import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiConsumer;
 import javax.annotation.Nullable;
-import org.apache.commons.collections4.MapUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.pinot.common.metrics.ServerMetrics;
 import org.apache.pinot.common.request.BrokerRequest;
@@ -110,8 +109,10 @@ public class ServerPlanRequestUtils {
     PinotQuery pinotQuery = serverContext.getPinotQuery();
     pinotQuery.setExplain(explain);
 
-    if (MapUtils.isNotEmpty(rowFilters)) {
-      pinotQuery.setQueryOptions(rowFilters);
+    if (rowFilters != null) {
+      // Merge, never replace: the plan visitor may already have stamped 
SERVER_RETURN_FINAL_RESULT[_KEY_UNPARTITIONED]
+      // here, and updateQueryOptions() below does not restore them.
+      rowFilters.forEach(pinotQuery::putToQueryOptions);
     }
 
     List<InstanceRequest> instanceRequests;
diff --git 
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTest.java
 
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTest.java
index 951d7ae15ed..234c99cea74 100644
--- 
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTest.java
+++ 
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTest.java
@@ -48,6 +48,7 @@ import 
org.apache.pinot.spi.utils.CommonConstants.Broker.Request.QueryOptionKey;
 import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner;
 import org.apache.pinot.spi.utils.JsonUtils;
 import org.apache.pinot.spi.utils.builder.TableNameBuilder;
+import org.apache.pinot.sql.parsers.rewriter.RlsUtils;
 import org.assertj.core.api.Assertions;
 import org.intellij.lang.annotations.Language;
 import org.testng.Assert;
@@ -372,6 +373,21 @@ public class QueryRunnerTest extends QueryRunnerTestBase {
     }
   }
 
+  /// RLS filters are stamped onto the leaf's query options, which is also the 
only place the planner records that the
+  /// leaf must finalize its aggregates (`is_partitioned_by_group_by_keys` 
makes the aggregate `AggType.DIRECT`, so no
+  /// stage above it can finalize anything). Stamping must merge, not replace: 
dropping the flag makes the leaf emit a
+  /// raw `IntOpenHashSet` into a column typed `INT`. Table b lives on a 
single server, so one row per group.
+  @Test
+  public void testDirectAggregateWithRowLevelSecurityFilter() {
+    String sql = "SELECT /*+ 
aggOptions(is_partitioned_by_group_by_keys='true') */ col1, DISTINCTCOUNT(col3) 
FROM b "
+        + "GROUP BY col1 ORDER BY col1";
+    Map<String, String> rlsFilters = Map.of(RlsUtils.buildRlsFilterKey("b"), 
"col3 > 1");
+    QueryDispatcher.QueryResult queryResult = queryRunner(sql, false, 
rlsFilters);
+    Assert.assertNull(queryResult.getProcessingException(), "Query failed: " + 
queryResult.getProcessingException());
+    // The RLS filter keeps only the two rows with col3 = 42.
+    compareRowEquals(queryResult.getResultTable(), List.of(new Object[]{"bar", 
1}, new Object[]{"bob", 1}), true);
+  }
+
   @DataProvider(name = "testDataWithSqlToFinalRowCount")
   protected Object[][] provideTestSqlAndRowCount() {
     //@formatter:off
diff --git 
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTestBase.java
 
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTestBase.java
index 6d0b455402d..fc5db95f8f2 100644
--- 
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTestBase.java
+++ 
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTestBase.java
@@ -114,6 +114,13 @@ public abstract class QueryRunnerTestBase extends 
QueryTestSet {
   /// Dispatch query to each pinot-server. The logic should mimic 
QueryDispatcher.submit() but does not actually make
   /// ser/de dispatches.
   protected QueryDispatcher.QueryResult queryRunner(String sql, boolean trace) 
{
+    return queryRunner(sql, trace, Map.of());
+  }
+
+  /// Same as [#queryRunner(String, boolean)], but adds metadata the broker 
stamps outside the SQL text, e.g. the
+  /// `rlsFilters-<table>` entries carrying row-level-security filters.
+  protected QueryDispatcher.QueryResult queryRunner(String sql, boolean trace,
+      Map<String, String> extraRequestMetadata) {
     long startTimeMs = System.currentTimeMillis();
     long requestId = REQUEST_ID_GEN.getAndIncrement();
     SqlNodeAndOptions sqlNodeAndOptions = 
CalciteSqlParser.compileToSqlNodeAndOptions(sql);
@@ -141,6 +148,7 @@ public abstract class QueryRunnerTestBase extends 
QueryTestSet {
     requestMetadataMap.put(QueryOptionKey.TIMEOUT_MS, 
Long.toString(timeoutMs));
     requestMetadataMap.put(QueryOptionKey.EXTRA_PASSIVE_TIMEOUT_MS, 
Long.toString(extraPassiveTimeoutMs));
     requestMetadataMap.putIfAbsent(QueryOptionKey.ENABLE_NULL_HANDLING, 
"true");
+    requestMetadataMap.putAll(extraRequestMetadata);
 
     // Putting trace testing here as extra options as it doesn't go along with 
the rest of the items.
     if (trace) {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to