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

gortiz 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 faa74c30178 Fail fast on EXPR_MIN/EXPR_MAX queries that the servers 
cannot execute (#19883)
faa74c30178 is described below

commit faa74c301783366f53fd1f29fc173c7310e4863d
Author: Gonzalo Ortiz Jaureguizar <[email protected]>
AuthorDate: Fri Oct 16 13:14:11 2026 +0200

    Fail fast on EXPR_MIN/EXPR_MAX queries that the servers cannot execute 
(#19883)
    
    - InstanceRequestHandler: when building the ServerQueryRequest fails after 
Thrift
      deserialization succeeded, send an error response with the known request 
id instead
      of no response. The broker no longer waits for the timeout and reports 
427.
    - BaseSingleStageBrokerRequestHandler#validateRequest: reject 
EXPR_MIN/EXPR_MAX with
      QUERY_VALIDATION when ExprMinMaxRewriter is not enabled, when the call 
has an alias,
      is nested or is outside the select list, or when 
ParentAggregationResultRewriter is
      not enabled. The message names the missing rewriter and its config key.
    - Compile failures on the server now count in QUERY_EXECUTION_EXCEPTIONS 
instead of
      REQUEST_DESERIALIZATION_EXCEPTIONS.
---
 .../BaseSingleStageBrokerRequestHandler.java       | 97 ++++++++++++++++++++++
 .../broker/requesthandler/QueryValidationTest.java | 57 +++++++++++++
 .../core/transport/InstanceRequestHandler.java     | 36 ++++++--
 .../core/transport/InstanceRequestHandlerTest.java | 46 ++++++++++
 4 files changed, 230 insertions(+), 6 deletions(-)

diff --git 
a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseSingleStageBrokerRequestHandler.java
 
b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseSingleStageBrokerRequestHandler.java
index f1039aaf4f2..38bd566f434 100644
--- 
a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseSingleStageBrokerRequestHandler.java
+++ 
b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseSingleStageBrokerRequestHandler.java
@@ -90,6 +90,8 @@ import 
org.apache.pinot.core.query.reduce.BaseGapfillProcessor;
 import org.apache.pinot.core.query.reduce.GapfillProcessorFactory;
 import org.apache.pinot.core.query.request.context.QueryContext;
 import 
org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils;
+import 
org.apache.pinot.core.query.utils.rewriter.ParentAggregationResultRewriter;
+import org.apache.pinot.core.query.utils.rewriter.ResultRewriterFactory;
 import org.apache.pinot.core.routing.ImplicitHybridTableRouteInfo;
 import org.apache.pinot.core.routing.ImplicitHybridTableRouteProvider;
 import org.apache.pinot.core.routing.LogicalTableRouteProvider;
@@ -137,6 +139,7 @@ import org.apache.pinot.sql.FilterKind;
 import org.apache.pinot.sql.parsers.CalciteSqlCompiler;
 import org.apache.pinot.sql.parsers.CalciteSqlParser;
 import org.apache.pinot.sql.parsers.SqlNodeAndOptions;
+import org.apache.pinot.sql.parsers.rewriter.ExprMinMaxRewriter;
 import org.apache.pinot.sql.parsers.rewriter.RlsFiltersRewriter;
 import org.apache.pinot.sql.parsers.rewriter.RlsUtils;
 import org.slf4j.Logger;
@@ -155,6 +158,12 @@ public abstract class BaseSingleStageBrokerRequestHandler 
extends BaseBrokerRequ
   /// Canonical name (see [RequestUtils#canonicalizeFunctionName]) of the 
`lookup()` transform function, which reads a
   /// dimension table named by a string literal argument instead of by the 
FROM clause.
   private static final String LOOKUP_FUNCTION = "lookup";
+  private static final String EXPR_MIN = "exprmin";
+  private static final String EXPR_MAX = "exprmax";
+  private static final Set<String> EXPR_MIN_MAX_FUNCTIONS = Set.of(EXPR_MIN, 
EXPR_MAX);
+  private static final Set<String> CHILD_EXPR_MIN_MAX_FUNCTIONS =
+      Set.of(CommonConstants.RewriterConstants.CHILD_AGGREGATION_NAME_PREFIX + 
EXPR_MIN,
+          CommonConstants.RewriterConstants.CHILD_AGGREGATION_NAME_PREFIX + 
EXPR_MAX);
   private static final int 
MAX_UNAVAILABLE_SEGMENTS_TO_PRINT_IN_QUERY_EXCEPTION = 10;
 
   protected final QueryOptimizer _queryOptimizer = new QueryOptimizer();
@@ -2455,6 +2464,7 @@ public abstract class BaseSingleStageBrokerRequestHandler 
extends BaseBrokerRequ
   /// - Value for 'LIMIT' <= configured value
   /// - Query options must be set to SQL mode
   /// - Check if numReplicaGroupsToQuery option provided is valid
+  /// - EXPR_MIN/EXPR_MAX can be executed with the configured rewriters, see 
[#validateExprMinMax(PinotQuery)]
   @VisibleForTesting
   static void validateRequest(PinotQuery pinotQuery, int queryResponseLimit) {
     // Verify LIMIT
@@ -2464,11 +2474,98 @@ public abstract class 
BaseSingleStageBrokerRequestHandler extends BaseBrokerRequ
           "Value for 'LIMIT' (" + limit + ") exceeds maximum allowed value of 
" + queryResponseLimit);
     }
     QueryOptionsUtils.getNumReplicaGroupsToQuery(pinotQuery.getQueryOptions());
+    validateExprMinMax(pinotQuery);
     if (pinotQuery.getDataSource().getSubquery() != null) {
       validateRequest(pinotQuery.getDataSource().getSubquery(), 
queryResponseLimit);
     }
   }
 
+  /// Servers cannot execute EXPR_MIN/EXPR_MAX directly. The broker must 
rewrite them into parent and child
+  /// aggregations with [ExprMinMaxRewriter], and merge the parent result back 
into the child columns with
+  /// [ParentAggregationResultRewriter]. Neither rewriter is enabled by 
default.
+  ///
+  /// Without this check, a query that still has EXPR_MIN/EXPR_MAX fails on 
the servers, and a query without the
+  /// result rewriter returns the placeholder values of the child aggregations 
instead of the expected values.
+  @VisibleForTesting
+  static void validateExprMinMax(PinotQuery pinotQuery) {
+    String exprMinMax = findFunction(pinotQuery, EXPR_MIN_MAX_FUNCTIONS);
+    if (exprMinMax != null) {
+      String functionName = exprMinMax.equals(EXPR_MIN) ? "EXPR_MIN" : 
"EXPR_MAX";
+      boolean queryRewriterEnabled =
+          
CalciteSqlParser.QUERY_REWRITERS.stream().anyMatch(ExprMinMaxRewriter.class::isInstance);
+      if (!queryRewriterEnabled) {
+        throw new BadQueryRequestException(
+            functionName + " is not enabled on this broker. To enable it, add 
'" + ExprMinMaxRewriter.class.getName()
+                + "' to '" + 
Broker.CONFIG_OF_BROKER_QUERY_REWRITER_CLASS_NAMES + "' and '"
+                + ParentAggregationResultRewriter.class.getName() + "' to '"
+                + Broker.CONFIG_OF_BROKER_RESULT_REWRITER_CLASS_NAMES + "'");
+      }
+      // The rewriter only handles the top-level expressions of the select 
list, so the function has an alias, is nested
+      // in another expression, or is in another clause.
+      throw new BadQueryRequestException(functionName + " is only supported as 
a top-level expression in the select "
+          + "list, without an alias. It is not supported in other expressions 
or in the WHERE, GROUP BY, HAVING or "
+          + "ORDER BY clauses");
+    }
+    if (findFunction(pinotQuery, CHILD_EXPR_MIN_MAX_FUNCTIONS) != null) {
+      boolean resultRewriterEnabled = 
ResultRewriterFactory.getResultRewriter().stream()
+          .anyMatch(ParentAggregationResultRewriter.class::isInstance);
+      if (!resultRewriterEnabled) {
+        throw new BadQueryRequestException(
+            "EXPR_MIN and EXPR_MAX require the result rewriter '" + 
ParentAggregationResultRewriter.class.getName()
+                + "'. To enable it, add it to '" + 
Broker.CONFIG_OF_BROKER_RESULT_REWRITER_CLASS_NAMES + "'");
+      }
+    }
+  }
+
+  /// Returns the first function of the given names in the select, filter, 
group-by, having or order-by expressions, or
+  /// `null` if there is none.
+  @Nullable
+  private static String findFunction(PinotQuery pinotQuery, Set<String> 
functionNames) {
+    String functionName = findFunction(pinotQuery.getSelectList(), 
functionNames);
+    if (functionName == null) {
+      functionName = findFunction(pinotQuery.getFilterExpression(), 
functionNames);
+    }
+    if (functionName == null) {
+      functionName = findFunction(pinotQuery.getGroupByList(), functionNames);
+    }
+    if (functionName == null) {
+      functionName = findFunction(pinotQuery.getHavingExpression(), 
functionNames);
+    }
+    if (functionName == null) {
+      functionName = findFunction(pinotQuery.getOrderByList(), functionNames);
+    }
+    return functionName;
+  }
+
+  @Nullable
+  private static String findFunction(@Nullable List<Expression> expressions, 
Set<String> functionNames) {
+    if (expressions == null) {
+      return null;
+    }
+    for (Expression expression : expressions) {
+      String functionName = findFunction(expression, functionNames);
+      if (functionName != null) {
+        return functionName;
+      }
+    }
+    return null;
+  }
+
+  @Nullable
+  private static String findFunction(@Nullable Expression expression, 
Set<String> functionNames) {
+    if (expression == null) {
+      return null;
+    }
+    Function function = expression.getFunctionCall();
+    if (function == null) {
+      return null;
+    }
+    if (functionNames.contains(function.getOperator())) {
+      return function.getOperator();
+    }
+    return findFunction(function.getOperands(), functionNames);
+  }
+
   /// Broker-side helper invoked by the MV handler's split-dispatcher 
callback. Owns the generic
   /// Execute a SPLIT_REWRITE plan via the configured MV handler.  Returns a 
finalized
   /// `BrokerResponseNative` on success, or `null` to signal the caller should 
fall through to
diff --git 
a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/QueryValidationTest.java
 
b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/QueryValidationTest.java
index 9512ebf8e15..7650cebe545 100644
--- 
a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/QueryValidationTest.java
+++ 
b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/QueryValidationTest.java
@@ -24,8 +24,13 @@ import java.util.Map;
 import org.apache.pinot.common.evaluator.GroovyFunctionEvaluator;
 import org.apache.pinot.common.evaluator.GroovyStaticAnalyzerConfig;
 import org.apache.pinot.common.request.PinotQuery;
+import 
org.apache.pinot.core.query.utils.rewriter.ParentAggregationResultRewriter;
+import org.apache.pinot.core.query.utils.rewriter.ResultRewriterFactory;
 import org.apache.pinot.sql.parsers.CalciteSqlParser;
+import org.apache.pinot.sql.parsers.rewriter.ExprMinMaxRewriter;
+import org.apache.pinot.sql.parsers.rewriter.QueryRewriterFactory;
 import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
 import org.testng.annotations.Test;
 
 import static org.testng.Assert.assertTrue;
@@ -167,6 +172,58 @@ public class QueryValidationTest {
         () -> BaseSingleStageBrokerRequestHandler.validateRequest(pinotQuery, 
10));
   }
 
+  /// Restores the default query and result rewriters, which the 
EXPR_MIN/EXPR_MAX tests change.
+  @AfterMethod
+  public void resetRewriters() {
+    QueryRewriterFactory.init(null);
+    ResultRewriterFactory.init(null);
+  }
+
+  @Test
+  public void testExprMinMaxWithoutRewriters() {
+    testUnsupportedQuery("SELECT EXPR_MIN(a, b) FROM testTable",
+        "EXPR_MIN is not enabled on this broker. To enable it, add '" + 
ExprMinMaxRewriter.class.getName()
+            + "' to 'pinot.broker.query.rewriter.class.names' and '" + 
ParentAggregationResultRewriter.class.getName()
+            + "' to 'pinot.broker.result.rewriter.class.names'");
+  }
+
+  @Test
+  public void testExprMinMaxWithRewriters() {
+    enableExprMinMaxQueryRewriter();
+    
ResultRewriterFactory.init(ParentAggregationResultRewriter.class.getName());
+    validQuery("SELECT EXPR_MIN(a, b), EXPR_MAX(c, b, d), COUNT(*) FROM 
testTable");
+    testUnsupportedExprMinMaxPosition("SELECT COUNT(*), EXPR_MIN(a, b) AS minA 
FROM testTable", "EXPR_MIN");
+    testUnsupportedExprMinMaxPosition("SELECT EXPR_MAX(a, b) + 1 FROM 
testTable", "EXPR_MAX");
+    testUnsupportedExprMinMaxPosition("SELECT COUNT(*) FROM testTable WHERE 
EXPR_MIN(a, b) > 1", "EXPR_MIN");
+    testUnsupportedExprMinMaxPosition("SELECT c, COUNT(*) FROM testTable GROUP 
BY c HAVING EXPR_MAX(a, b) > 1",
+        "EXPR_MAX");
+    testUnsupportedExprMinMaxPosition("SELECT c, COUNT(*) FROM testTable GROUP 
BY c ORDER BY EXPR_MIN(a, b)",
+        "EXPR_MIN");
+  }
+
+  @Test
+  public void testExprMinMaxWithoutResultRewriter() {
+    enableExprMinMaxQueryRewriter();
+    testUnsupportedQuery("SELECT EXPR_MIN(a, b) FROM testTable",
+        "EXPR_MIN and EXPR_MAX require the result rewriter '" + 
ParentAggregationResultRewriter.class.getName()
+            + "'. To enable it, add it to 
'pinot.broker.result.rewriter.class.names'");
+  }
+
+  private void testUnsupportedExprMinMaxPosition(String query, String 
functionName) {
+    testUnsupportedQuery(query, functionName + " is only supported as a 
top-level expression in the select list, "
+        + "without an alias. It is not supported in other expressions or in 
the WHERE, GROUP BY, HAVING or ORDER BY "
+        + "clauses");
+  }
+
+  private static void enableExprMinMaxQueryRewriter() {
+    QueryRewriterFactory.init(String.join(",", 
QueryRewriterFactory.DEFAULT_QUERY_REWRITERS_CLASS_NAMES) + ","
+        + ExprMinMaxRewriter.class.getName());
+  }
+
+  private static void validQuery(String query) {
+    
BaseSingleStageBrokerRequestHandler.validateRequest(CalciteSqlParser.compileToPinotQuery(query),
 1000);
+  }
+
   private void testValidateGroovyQuery(String query, boolean 
queryContainsGroovy) {
     try {
       PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(query);
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/transport/InstanceRequestHandler.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/transport/InstanceRequestHandler.java
index 2068e31dbc1..0c4e239c626 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/transport/InstanceRequestHandler.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/transport/InstanceRequestHandler.java
@@ -43,6 +43,7 @@ import org.apache.pinot.common.metrics.ServerMeter;
 import org.apache.pinot.common.metrics.ServerMetrics;
 import org.apache.pinot.common.metrics.ServerQueryPhase;
 import org.apache.pinot.common.metrics.ServerTimer;
+import org.apache.pinot.common.request.BrokerRequest;
 import org.apache.pinot.common.request.InstanceRequest;
 import org.apache.pinot.core.common.datatable.DataTableBuilderFactory;
 import org.apache.pinot.core.query.request.ServerQueryRequest;
@@ -123,20 +124,31 @@ public class InstanceRequestHandler extends 
SimpleChannelInboundHandler<ByteBuf>
     
_serverMetrics.addMeteredGlobalValue(ServerMeter.NETTY_CONNECTION_BYTES_RECEIVED,
 requestSize);
     byte[] requestBytes = new byte[requestSize];
     msg.readBytes(requestBytes);
-    ServerQueryRequest queryRequest;
+    InstanceRequest instanceRequest = new InstanceRequest();
     try {
-      InstanceRequest instanceRequest = new InstanceRequest();
       THREAD_LOCAL_T_DESERIALIZER.get().deserialize(instanceRequest, 
requestBytes);
-      queryRequest = new ServerQueryRequest(instanceRequest, _serverMetrics, 
queryArrivalTimeMs);
-      _serverMetrics.addMeteredTableValue(queryRequest.getTableNameWithType(), 
ServerMeter.QUERIES_ON_TABLE, 1);
-      
queryRequest.getTimerContext().startNewPhaseTimer(ServerQueryPhase.REQUEST_DESERIALIZATION,
 queryArrivalTimeMs)
-          .stopAndRecord();
     } catch (Exception e) {
       // Do not send error response because request id is unknown.
       LOGGER.error("Failed to deserialize instance request: {}", 
BytesUtils.toHexString(requestBytes), e);
       
_serverMetrics.addMeteredGlobalValue(ServerMeter.REQUEST_DESERIALIZATION_EXCEPTIONS,
 1);
       return;
     }
+    ServerQueryRequest queryRequest;
+    try {
+      queryRequest = new ServerQueryRequest(instanceRequest, _serverMetrics, 
queryArrivalTimeMs);
+    } catch (Exception e) {
+      // The request id is known, so reply with the error. Without a response, 
the broker waits until the query times
+      // out and reports the server as not responded, which hides the actual 
error (e.g. an unsupported function).
+      long requestId = instanceRequest.getRequestId();
+      String tableNameWithType = getTableNameWithType(instanceRequest);
+      LOGGER.error("Failed to compile query request: {} on table: {}", 
requestId, tableNameWithType, e);
+      sendErrorResponse(ctx, requestId, tableNameWithType, queryArrivalTimeMs,
+          DataTableBuilderFactory.getEmptyDataTable(), e);
+      return;
+    }
+    _serverMetrics.addMeteredTableValue(queryRequest.getTableNameWithType(), 
ServerMeter.QUERIES_ON_TABLE, 1);
+    
queryRequest.getTimerContext().startNewPhaseTimer(ServerQueryPhase.REQUEST_DESERIALIZATION,
 queryArrivalTimeMs)
+        .stopAndRecord();
     try {
       submitQuery(queryRequest, ctx, queryArrivalTimeMs);
     } catch (Exception e) {
@@ -258,6 +270,18 @@ public class InstanceRequestHandler extends 
SimpleChannelInboundHandler<ByteBuf>
     return new HashSet<>(_executionContexts.keySet());
   }
 
+  /// Returns the table name of a deserialized request that could not be 
compiled, or `null` if the request does not
+  /// carry one.
+  @Nullable
+  private static String getTableNameWithType(InstanceRequest instanceRequest) {
+    BrokerRequest brokerRequest = instanceRequest.getQuery();
+    if (brokerRequest == null || brokerRequest.getPinotQuery() == null
+        || brokerRequest.getPinotQuery().getDataSource() == null) {
+      return null;
+    }
+    return brokerRequest.getPinotQuery().getDataSource().getTableName();
+  }
+
   /// Send an exception back to broker as response to the query request.
   private void sendErrorResponse(ChannelHandlerContext ctx, long requestId, 
String tableNameWithType,
       long queryArrivalTimeMs, DataTable dataTable, Exception e) {
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/transport/InstanceRequestHandlerTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/transport/InstanceRequestHandlerTest.java
index a9298e3ba95..b83f19e6e80 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/transport/InstanceRequestHandlerTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/transport/InstanceRequestHandlerTest.java
@@ -20,15 +20,23 @@ package org.apache.pinot.core.transport;
 
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListenableFutureTask;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
 import io.netty.channel.ChannelFuture;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.util.concurrent.Future;
 import io.netty.util.concurrent.GenericFutureListener;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.LongAccumulator;
+import org.apache.pinot.common.datatable.DataTable;
+import org.apache.pinot.common.datatable.DataTable.MetadataKey;
+import org.apache.pinot.common.datatable.DataTableFactory;
 import org.apache.pinot.common.metrics.ServerMetrics;
+import org.apache.pinot.common.request.BrokerRequest;
+import org.apache.pinot.common.request.InstanceRequest;
 import org.apache.pinot.core.query.executor.QueryExecutor;
 import org.apache.pinot.core.query.request.ServerQueryRequest;
 import org.apache.pinot.core.query.scheduler.QueryScheduler;
@@ -37,10 +45,14 @@ import 
org.apache.pinot.core.query.scheduler.resources.UnboundedResourceManager;
 import org.apache.pinot.server.access.AccessControl;
 import org.apache.pinot.spi.accounting.ThreadAccountantUtils;
 import org.apache.pinot.spi.env.PinotConfiguration;
+import org.apache.pinot.spi.exception.QueryErrorCode;
 import org.apache.pinot.spi.metrics.PinotMetricUtils;
 import org.apache.pinot.spi.metrics.PinotMetricsRegistry;
 import org.apache.pinot.spi.query.QueryExecutionContext;
+import org.apache.pinot.sql.parsers.CalciteSqlParser;
 import org.apache.pinot.util.TestUtils;
+import org.apache.thrift.TSerializer;
+import org.apache.thrift.protocol.TCompactProtocol;
 import org.mockito.ArgumentCaptor;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -144,6 +156,40 @@ public class InstanceRequestHandlerTest {
     verify(ctx).close();
   }
 
+  @Test
+  public void testRepliesWithErrorWhenQueryCannotBeCompiled()
+      throws Exception {
+    PinotConfiguration config = new PinotConfiguration();
+    QueryScheduler queryScheduler = createQueryScheduler(config, new 
CountDownLatch(0));
+    InstanceRequestHandler handler =
+        new InstanceRequestHandler("server01", config, queryScheduler, 
mock(AccessControl.class),
+            ThreadAccountantUtils.getNoOpAccountant());
+
+    // EXPR_MIN cannot be executed without the broker-side ExprMinMaxRewriter, 
so the server fails to build the
+    // aggregation function while it compiles the request.
+    BrokerRequest brokerRequest = new BrokerRequest();
+    brokerRequest.setPinotQuery(CalciteSqlParser.compileToPinotQuery("SELECT 
EXPR_MIN(a, b) FROM testTable_OFFLINE"));
+    InstanceRequest instanceRequest = new InstanceRequest(123L, brokerRequest);
+    byte[] requestBytes = new TSerializer(new 
TCompactProtocol.Factory()).serialize(instanceRequest);
+
+    ChannelHandlerContext ctx = mock(ChannelHandlerContext.class);
+    when(ctx.writeAndFlush(any())).thenReturn(mock(ChannelFuture.class));
+    handler.channelRead0(ctx, Unpooled.wrappedBuffer(requestBytes));
+
+    ArgumentCaptor<ByteBuf> responseCaptor = 
ArgumentCaptor.forClass(ByteBuf.class);
+    verify(ctx).writeAndFlush(responseCaptor.capture());
+    ByteBuf response = responseCaptor.getValue();
+    byte[] responseBytes = new byte[response.readableBytes()];
+    response.readBytes(responseBytes);
+    DataTable dataTable = DataTableFactory.getDataTable(responseBytes);
+    
assertEquals(dataTable.getMetadata().get(MetadataKey.REQUEST_ID.getName()), 
"123");
+    Map<Integer, String> exceptions = dataTable.getExceptions();
+    assertEquals(exceptions.size(), 1);
+    String message = exceptions.get(QueryErrorCode.QUERY_EXECUTION.getId());
+    // Thrown by AggregationFunctionFactory for an EXPR_MIN that the broker 
did not rewrite
+    assertTrue(message.contains("Aggregation function: EXPRMIN is only 
supported in selection without alias"), message);
+  }
+
   private QueryScheduler createQueryScheduler(PinotConfiguration config, 
CountDownLatch queryFinishLatch) {
     ResourceManager resourceManager = new UnboundedResourceManager(config);
     return new QueryScheduler(config, "serverId", mock(QueryExecutor.class), 
ThreadAccountantUtils.getNoOpAccountant(),


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

Reply via email to