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

Jackie-Jiang 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 8d140dcfba3 Fix rlsFiltersApplied reporting true for multi-stage 
queries when no RLS filter is applied (#20089)
8d140dcfba3 is described below

commit 8d140dcfba31efb69766ceb622e2f68005de5bf0
Author: Dylan Wylie <[email protected]>
AuthorDate: Sat Nov 7 00:00:47 2026 +0000

    Fix rlsFiltersApplied reporting true for multi-stage queries when no RLS 
filter is applied (#20089)
---
 .../MultiStageBrokerRequestHandler.java            | 45 +++++++++++++---------
 .../MultiStageBrokerRequestHandlerTest.java        | 38 +++++++++++++++++-
 2 files changed, 63 insertions(+), 20 deletions(-)

diff --git 
a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java
 
b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java
index 1d1f9fd9809..81dbfe2ddac 100644
--- 
a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java
+++ 
b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java
@@ -41,7 +41,6 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 import javax.ws.rs.WebApplicationException;
@@ -470,8 +469,7 @@ public class MultiStageBrokerRequestHandler extends 
BaseBrokerRequestHandler {
         QueryEnvironment.CompiledQuery compiledQuery = compileQuery(requestId, 
query, sqlNodeAndOptions, requestContext,
             httpHeaders, queryTimer)) {
       
validatePhysicalTablesWithMultiClusterRouting(compiledQuery.getTableNames(), 
compiledQuery.getOptions());
-      AtomicBoolean rlsFiltersApplied = new AtomicBoolean(false);
-      checkAuthorization(requesterIdentity, requestContext, httpHeaders, 
compiledQuery, rlsFiltersApplied);
+      boolean rlsFiltersApplied = checkAuthorization(requesterIdentity, 
requestContext, httpHeaders, compiledQuery);
 
       // Apply broker-default query options before branching to 
EXPLAIN/execute so both paths see the same options.
       applyBrokerDefaultQueryOptions(compiledQuery.getOptions());
@@ -481,7 +479,7 @@ public class MultiStageBrokerRequestHandler extends 
BaseBrokerRequestHandler {
         return explain(compiledQuery, requestId, requestContext, queryTimer, 
idSetSubqueries);
       } else {
         return query(compiledQuery, requestId, requesterIdentity, 
requestContext, httpHeaders, queryTimer,
-            rlsFiltersApplied.get(), queryWasLogged, workloadName);
+            rlsFiltersApplied, queryWasLogged, workloadName);
       }
     }
   }
@@ -518,8 +516,8 @@ public class MultiStageBrokerRequestHandler extends 
BaseBrokerRequestHandler {
     }
   }
 
-  private void checkAuthorization(RequesterIdentity requesterIdentity, 
RequestContext requestContext,
-      HttpHeaders httpHeaders, QueryEnvironment.CompiledQuery compiledQuery, 
AtomicBoolean rlsFiltersApplied) {
+  private boolean checkAuthorization(RequesterIdentity requesterIdentity, 
RequestContext requestContext,
+      HttpHeaders httpHeaders, QueryEnvironment.CompiledQuery compiledQuery) {
     Set<String> tables = compiledQuery.getTableNames();
     if (tables != null && !tables.isEmpty()) {
       TableAuthorizationResult tableAuthorizationResult =
@@ -527,21 +525,30 @@ public class MultiStageBrokerRequestHandler extends 
BaseBrokerRequestHandler {
       if (!tableAuthorizationResult.hasAccess()) {
         throwTableAccessError(tableAuthorizationResult);
       }
-      if (_enableRowColumnLevelAuth) {
-        AccessControl accessControl = _accessControlFactory.create();
-        for (String tableName : tables) {
-          accessControl.getRowColFilters(requesterIdentity, 
tableName).getRLSFilters()
-              .ifPresent(rowFilters -> {
-                rlsFiltersApplied.set(true);
-                String combinedFilters =
-                    rowFilters.stream().map(filter -> "( " + filter + " 
)").collect(Collectors.joining(" AND "));
-                String key = RlsUtils.buildRlsFilterKey(tableName);
-                compiledQuery.getOptions().put(key, combinedFilters);
-                _brokerMetrics.addMeteredTableValue(tableName, 
BrokerMeter.RLS_FILTERS_APPLIED, 1);
-              });
-        }
+      return _enableRowColumnLevelAuth && applyRlsFilters(requesterIdentity, 
tables, compiledQuery.getOptions());
+    }
+    return false;
+  }
+
+  /// Returns `true` if an RLS filter was added for at least one table.
+  @VisibleForTesting
+  boolean applyRlsFilters(@Nullable RequesterIdentity requesterIdentity, 
Set<String> tables,
+      Map<String, String> queryOptions) {
+    AccessControl accessControl = _accessControlFactory.create();
+    boolean applied = false;
+    for (String tableName : tables) {
+      List<String> rowFilters = 
accessControl.getRowColFilters(requesterIdentity, tableName).getRLSFilters()
+          .orElse(null);
+      if (rowFilters == null || rowFilters.isEmpty()) {
+        continue;
       }
+      String combinedFilters =
+          rowFilters.stream().map(filter -> "( " + filter + " 
)").collect(Collectors.joining(" AND "));
+      queryOptions.put(RlsUtils.buildRlsFilterKey(tableName), combinedFilters);
+      _brokerMetrics.addMeteredTableValue(tableName, 
BrokerMeter.RLS_FILTERS_APPLIED, 1);
+      applied = true;
     }
+    return applied;
   }
 
   private ImmutableQueryEnvironment.Config getQueryEnvConf(HttpHeaders 
httpHeaders, Map<String, String> queryOptions,
diff --git 
a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandlerTest.java
 
b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandlerTest.java
index b89ae2fc01b..a7cf7bc1699 100644
--- 
a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandlerTest.java
+++ 
b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandlerTest.java
@@ -27,6 +27,7 @@ import java.util.concurrent.atomic.AtomicReference;
 import javax.annotation.Nullable;
 import javax.ws.rs.core.HttpHeaders;
 import org.apache.pinot.broker.api.AccessControl;
+import org.apache.pinot.broker.broker.AccessControlFactory;
 import org.apache.pinot.broker.broker.AllowAllAccessControlFactory;
 import org.apache.pinot.broker.queryquota.QueryQuotaManager;
 import org.apache.pinot.common.config.provider.TableCache;
@@ -56,6 +57,7 @@ import 
org.apache.pinot.query.runtime.plan.MultiStageQueryStats;
 import org.apache.pinot.query.runtime.plan.pipeline.PipelineBreakerOperator;
 import org.apache.pinot.query.service.dispatch.QueryDispatcher;
 import org.apache.pinot.spi.accounting.ThreadAccountantUtils;
+import org.apache.pinot.spi.auth.TableRowColAccessResultImpl;
 import org.apache.pinot.spi.auth.broker.RequesterIdentity;
 import org.apache.pinot.spi.env.PinotConfiguration;
 import 
org.apache.pinot.spi.eventlistener.query.BrokerQueryEventListenerFactory;
@@ -73,6 +75,7 @@ import org.apache.pinot.spi.utils.JsonUtils;
 import org.apache.pinot.spi.utils.NetUtils;
 import org.apache.pinot.sql.parsers.CalciteSqlParser;
 import org.apache.pinot.sql.parsers.SqlNodeAndOptions;
+import org.apache.pinot.sql.parsers.rewriter.RlsUtils;
 import org.apache.pinot.util.TestUtils;
 import org.mockito.ArgumentCaptor;
 import org.mockito.MockedConstruction;
@@ -83,6 +86,7 @@ import org.testng.annotations.Test;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
@@ -470,6 +474,14 @@ public class MultiStageBrokerRequestHandlerTest extends 
QueryEnvironmentTestBase
   private static MultiStageBrokerRequestHandler newHandler(PinotConfiguration 
config, RoutingManager routingManager,
       FailureDetector failureDetector, @Nullable MultiClusterRoutingContext 
multiClusterRoutingContext)
       throws Exception {
+    return newHandler(config, routingManager, failureDetector, 
multiClusterRoutingContext,
+        new AllowAllAccessControlFactory());
+  }
+
+  private static MultiStageBrokerRequestHandler newHandler(PinotConfiguration 
config, RoutingManager routingManager,
+      FailureDetector failureDetector, @Nullable MultiClusterRoutingContext 
multiClusterRoutingContext,
+      AccessControlFactory accessControlFactory)
+      throws Exception {
     config.setProperty(MultiStageQueryRunner.KEY_OF_QUERY_RUNNER_HOSTNAME, 
"localhost");
     config.setProperty(MultiStageQueryRunner.KEY_OF_QUERY_RUNNER_PORT, 
Integer.toString(NetUtils.findOpenPort()));
     BrokerQueryEventListenerFactory.init(config);
@@ -481,7 +493,7 @@ public class MultiStageBrokerRequestHandlerTest extends 
QueryEnvironmentTestBase
     when(queryQuotaManager.acquireApplication(anyString())).thenReturn(true);
 
     return new MultiStageBrokerRequestHandler(config, "testBrokerId", new 
BrokerRequestIdGenerator(),
-        routingManager, new AllowAllAccessControlFactory(), queryQuotaManager,
+        routingManager, accessControlFactory, queryQuotaManager,
         mock(TableCache.class), mock(MultiStageQueryThrottler.class), 
failureDetector,
         ThreadAccountantUtils.getNoOpAccountant(), multiClusterRoutingContext, 
mock(WorkerManager.class),
         mock(WorkerManager.class)) {
@@ -496,6 +508,30 @@ public class MultiStageBrokerRequestHandlerTest extends 
QueryEnvironmentTestBase
     };
   }
 
+  /// An access control can return an empty RLS filter list for a table 
without row filters. No predicate is attached
+  /// for it, so the response must not report that RLS filters were applied.
+  @Test
+  public void testApplyRlsFiltersIgnoresEmptyFilterList()
+      throws Exception {
+    AccessControl accessControl = mock(AccessControl.class);
+    when(accessControl.getRowColFilters(any(), eq("empty"))).thenReturn(new 
TableRowColAccessResultImpl(List.of()));
+    when(accessControl.getRowColFilters(any(), 
eq("absent"))).thenReturn(TableRowColAccessResultImpl.unrestricted());
+    when(accessControl.getRowColFilters(any(), eq("filtered"))).thenReturn(
+        new TableRowColAccessResultImpl(List.of("col1 = 'a'", "col2 = 'b'")));
+    AccessControlFactory accessControlFactory = 
mock(AccessControlFactory.class);
+    when(accessControlFactory.create()).thenReturn(accessControl);
+    MultiStageBrokerRequestHandler handler = newHandler(new 
PinotConfiguration(), mock(RoutingManager.class),
+        mock(FailureDetector.class), null, accessControlFactory);
+
+    Map<String, String> queryOptions = new HashMap<>();
+    Assert.assertFalse(handler.applyRlsFilters(null, Set.of("empty", 
"absent"), queryOptions));
+    assertEquals(queryOptions, Map.of());
+
+    Assert.assertTrue(handler.applyRlsFilters(null, Set.of("empty", "absent", 
"filtered"), queryOptions));
+    assertEquals(queryOptions,
+        Map.of(RlsUtils.buildRlsFilterKey("filtered"), "( col1 = 'a' ) AND ( 
col2 = 'b' )"));
+  }
+
   /// The failure detector keeps an instance unhealthy while any retrier 
returns UNHEALTHY for it. A multi-stage worker
   /// that is not an enabled server of the routing manager, for example a 
server of a remote cluster, must recover
   /// once the dispatch channel to it is READY, with the retriers of all 
request handlers registered.


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

Reply via email to