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 19002ab9613 Add support for unary +/- operators in SQL queries (#18444)
19002ab9613 is described below

commit 19002ab9613bb3fc41c61eff0e607c9dff6363ae
Author: Jinesh Parakh <[email protected]>
AuthorDate: Fri May 22 00:09:32 2026 +0530

    Add support for unary +/- operators in SQL queries (#18444)
---
 .../scalar/arithmetic/NegateScalarFunction.java    |   4 +-
 .../apache/pinot/sql/parsers/CalciteSqlParser.java |   9 +
 .../pinot/sql/parsers/CalciteSqlCompilerTest.java  | 231 ++++++++++++
 .../pinot/calcite/sql/fun/PinotOperatorTable.java  |  74 +++-
 .../query/planner/logical/RexExpressionUtils.java  |   9 +
 .../apache/pinot/query/QueryCompilationTest.java   |  77 ++++
 .../src/test/resources/queries/MathFuncs.json      | 387 +++++++++++++++++++++
 7 files changed, 771 insertions(+), 20 deletions(-)

diff --git 
a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/NegateScalarFunction.java
 
b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/NegateScalarFunction.java
index 8dca5edbb99..dd74b895a25 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/NegateScalarFunction.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/NegateScalarFunction.java
@@ -35,6 +35,8 @@ import org.apache.pinot.spi.annotations.ScalarFunction;
 @ScalarFunction
 public class NegateScalarFunction extends BaseUnaryArithmeticScalarFunction {
 
+  public static final String FUNCTION_NAME = "negate";
+
   private static final Map<ColumnDataType, FunctionInfo> 
TYPE_FUNCTION_INFO_MAP = new EnumMap<>(ColumnDataType.class);
 
   static {
@@ -62,7 +64,7 @@ public class NegateScalarFunction extends 
BaseUnaryArithmeticScalarFunction {
 
   @Override
   public String getName() {
-    return "negate";
+    return FUNCTION_NAME;
   }
 
   @Override
diff --git 
a/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java 
b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java
index 576906fd457..fe252e29bc6 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java
@@ -52,6 +52,7 @@ import org.apache.calcite.sql.fun.SqlLikeOperator;
 import org.apache.calcite.sql.parser.SqlAbstractParserImpl;
 import org.apache.calcite.sql.validate.SqlConformanceEnum;
 import org.apache.commons.collections4.CollectionUtils;
+import org.apache.pinot.common.function.scalar.arithmetic.NegateScalarFunction;
 import org.apache.pinot.common.request.DataSource;
 import org.apache.pinot.common.request.Expression;
 import org.apache.pinot.common.request.ExpressionType;
@@ -826,6 +827,14 @@ public class CalciteSqlParser {
         negated = ((SqlLikeOperator) functionNode.getOperator()).isNegated();
         canonicalName = SqlKind.LIKE.name();
         break;
+      case MINUS_PREFIX:
+        // SqlKind.MINUS_PREFIX.name() would canonicalize to "minusprefix", 
which has no matching entry in
+        // FunctionRegistry. Map directly to the registered 
NegateScalarFunction name instead.
+        canonicalName = NegateScalarFunction.FUNCTION_NAME;
+        break;
+      case PLUS_PREFIX:
+        // Unary plus is identity -- unwrap to the operand directly (no 
function node needed).
+        return toExpression(functionNode.getOperandList().get(0));
       case OTHER:
       case OTHER_FUNCTION:
       case DOT:
diff --git 
a/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java
 
b/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java
index 46bb7b09408..532b28ab0bd 100644
--- 
a/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java
+++ 
b/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java
@@ -1101,6 +1101,237 @@ public class CalciteSqlCompilerTest {
     Assert.assertEquals(selectList.get(4).getFunctionCall().getOperator(), 
"moduloorzero");
   }
 
+  @Test
+  public void testUnaryMinusSyntaxCompilesAsNegate() {
+    // -col should produce negate(col), not a binary subtraction
+    PinotQuery pinotQuery = compileToPinotQuery("SELECT -col1 FROM myTable");
+    Expression expr = pinotQuery.getSelectList().get(0);
+    Assert.assertEquals(expr.getFunctionCall().getOperator(), "negate");
+    Assert.assertEquals(expr.getFunctionCall().getOperandsSize(), 1);
+    
Assert.assertEquals(expr.getFunctionCall().getOperands().get(0).getIdentifier().getName(),
 "col1");
+
+    // unary minus in WHERE
+    pinotQuery = compileToPinotQuery("SELECT col1 FROM myTable WHERE -col1 > 
0");
+    Expression filter = pinotQuery.getFilterExpression();
+    Assert.assertEquals(filter.getFunctionCall().getOperator(), 
FilterKind.GREATER_THAN.name());
+    Expression negateExpr = filter.getFunctionCall().getOperands().get(0);
+    Assert.assertEquals(negateExpr.getFunctionCall().getOperator(), "negate");
+    
Assert.assertEquals(negateExpr.getFunctionCall().getOperands().get(0).getIdentifier().getName(),
 "col1");
+
+    // double negation
+    pinotQuery = compileToPinotQuery("SELECT -(-col1) FROM myTable");
+    Expression outer = pinotQuery.getSelectList().get(0);
+    Assert.assertEquals(outer.getFunctionCall().getOperator(), "negate");
+    Expression inner = outer.getFunctionCall().getOperands().get(0);
+    Assert.assertEquals(inner.getFunctionCall().getOperator(), "negate");
+    
Assert.assertEquals(inner.getFunctionCall().getOperands().get(0).getIdentifier().getName(),
 "col1");
+
+    // compound: -(col1 + col2)
+    pinotQuery = compileToPinotQuery("SELECT -(col1 + col2) FROM myTable");
+    Expression compound = pinotQuery.getSelectList().get(0);
+    Assert.assertEquals(compound.getFunctionCall().getOperator(), "negate");
+    
Assert.assertEquals(compound.getFunctionCall().getOperands().get(0).getFunctionCall().getOperator(),
 "plus");
+  }
+
+  @Test
+  public void testUnaryPlusSyntaxStrippedAsIdentity() {
+    // +col should be unwrapped to the bare identifier (no function wrapper)
+    PinotQuery pinotQuery = compileToPinotQuery("SELECT +col1 FROM myTable");
+    Expression expr = pinotQuery.getSelectList().get(0);
+    Assert.assertNull(expr.getFunctionCall());
+    Assert.assertEquals(expr.getIdentifier().getName(), "col1");
+
+    // +literal should resolve to the literal itself
+    pinotQuery = compileToPinotQuery("SELECT +5 FROM myTable");
+    Expression literal = pinotQuery.getSelectList().get(0);
+    Assert.assertNull(literal.getFunctionCall());
+    Assert.assertNotNull(literal.getLiteral());
+
+    // +col in arithmetic context: +col + col still strips the unary plus
+    pinotQuery = compileToPinotQuery("SELECT +col1 + col2 FROM myTable");
+    Function plus = pinotQuery.getSelectList().get(0).getFunctionCall();
+    Assert.assertEquals(plus.getOperator(), "plus");
+    Assert.assertEquals(plus.getOperands().get(0).getIdentifier().getName(), 
"col1");
+    Assert.assertEquals(plus.getOperands().get(1).getIdentifier().getName(), 
"col2");
+  }
+
+  @Test
+  public void testUnaryMinusInsideAggregations() {
+    // SUM(-col) -> sum(negate(col))
+    PinotQuery pinotQuery = compileToPinotQuery("SELECT SUM(-col1) FROM 
myTable");
+    Function sum = pinotQuery.getSelectList().get(0).getFunctionCall();
+    Assert.assertEquals(sum.getOperator(), "sum");
+    Function negate = sum.getOperands().get(0).getFunctionCall();
+    Assert.assertEquals(negate.getOperator(), "negate");
+    Assert.assertEquals(negate.getOperands().get(0).getIdentifier().getName(), 
"col1");
+
+    // MAX(-col), MIN(-col), AVG(-col)
+    pinotQuery = compileToPinotQuery("SELECT MAX(-col1), MIN(-col1), 
AVG(-col1) FROM myTable");
+    for (int i = 0; i < 3; i++) {
+      Function agg = pinotQuery.getSelectList().get(i).getFunctionCall();
+      
Assert.assertEquals(agg.getOperands().get(0).getFunctionCall().getOperator(), 
"negate");
+    }
+    
Assert.assertEquals(pinotQuery.getSelectList().get(0).getFunctionCall().getOperator(),
 "max");
+    
Assert.assertEquals(pinotQuery.getSelectList().get(1).getFunctionCall().getOperator(),
 "min");
+    
Assert.assertEquals(pinotQuery.getSelectList().get(2).getFunctionCall().getOperator(),
 "avg");
+  }
+
+  @Test
+  public void testUnaryMinusOutsideAggregations() {
+    // -SUM(col) -> negate(sum(col))
+    PinotQuery pinotQuery = compileToPinotQuery("SELECT -SUM(col1) FROM 
myTable");
+    Function negate = pinotQuery.getSelectList().get(0).getFunctionCall();
+    Assert.assertEquals(negate.getOperator(), "negate");
+    Function sum = negate.getOperands().get(0).getFunctionCall();
+    Assert.assertEquals(sum.getOperator(), "sum");
+    Assert.assertEquals(sum.getOperands().get(0).getIdentifier().getName(), 
"col1");
+
+    // -COUNT(*) -> negate(count(*))
+    pinotQuery = compileToPinotQuery("SELECT -COUNT(*) FROM myTable");
+    Function negCount = pinotQuery.getSelectList().get(0).getFunctionCall();
+    Assert.assertEquals(negCount.getOperator(), "negate");
+    
Assert.assertEquals(negCount.getOperands().get(0).getFunctionCall().getOperator(),
 "count");
+  }
+
+  @Test
+  public void testUnaryMinusInGroupBy() {
+    PinotQuery pinotQuery =
+        compileToPinotQuery("SELECT -col1, COUNT(*) FROM myTable GROUP BY 
-col1");
+    Assert.assertTrue(pinotQuery.isSetGroupByList());
+    Assert.assertEquals(pinotQuery.getGroupByList().size(), 1);
+    Function groupByNegate = 
pinotQuery.getGroupByList().get(0).getFunctionCall();
+    Assert.assertEquals(groupByNegate.getOperator(), "negate");
+    
Assert.assertEquals(groupByNegate.getOperands().get(0).getIdentifier().getName(),
 "col1");
+  }
+
+  @Test
+  public void testUnaryMinusInHaving() {
+    PinotQuery pinotQuery =
+        compileToPinotQuery("SELECT col1, SUM(col2) FROM myTable GROUP BY col1 
HAVING -SUM(col2) < 0");
+    Assert.assertTrue(pinotQuery.isSetHavingExpression());
+    Function lt = pinotQuery.getHavingExpression().getFunctionCall();
+    Assert.assertEquals(lt.getOperator(), FilterKind.LESS_THAN.name());
+    Function lhs = lt.getOperands().get(0).getFunctionCall();
+    Assert.assertEquals(lhs.getOperator(), "negate");
+    Function inner = lhs.getOperands().get(0).getFunctionCall();
+    Assert.assertEquals(inner.getOperator(), "sum");
+    Assert.assertEquals(inner.getOperands().get(0).getIdentifier().getName(), 
"col2");
+  }
+
+  @Test
+  public void testUnaryMinusWithAlias() {
+    // -col AS neg yields as(negate(col), neg)
+    PinotQuery pinotQuery = compileToPinotQuery("SELECT -col1 AS neg FROM 
myTable");
+    Function asFunc = pinotQuery.getSelectList().get(0).getFunctionCall();
+    Assert.assertEquals(asFunc.getOperator(), "as");
+    Function negate = asFunc.getOperands().get(0).getFunctionCall();
+    Assert.assertEquals(negate.getOperator(), "negate");
+    Assert.assertEquals(negate.getOperands().get(0).getIdentifier().getName(), 
"col1");
+    // Alias side is the literal identifier
+    Assert.assertEquals(asFunc.getOperands().get(1).getIdentifier().getName(), 
"neg");
+  }
+
+  @Test
+  public void testUnaryMinusInsideCast() {
+    // CAST(-col AS DOUBLE) -> cast(negate(col), 'DOUBLE')
+    PinotQuery pinotQuery = compileToPinotQuery("SELECT CAST(-col1 AS DOUBLE) 
FROM myTable");
+    Function cast = pinotQuery.getSelectList().get(0).getFunctionCall();
+    Assert.assertEquals(cast.getOperator(), "cast");
+    Function negate = cast.getOperands().get(0).getFunctionCall();
+    Assert.assertEquals(negate.getOperator(), "negate");
+    Assert.assertEquals(negate.getOperands().get(0).getIdentifier().getName(), 
"col1");
+  }
+
+  @Test
+  public void testUnaryMinusInsideCase() {
+    // CASE WHEN col1 > 0 THEN -col1 ELSE col1 END
+    PinotQuery pinotQuery = compileToPinotQuery(
+        "SELECT CASE WHEN col1 > 0 THEN -col1 ELSE col1 END FROM myTable");
+    Function caseFunc = pinotQuery.getSelectList().get(0).getFunctionCall();
+    Assert.assertEquals(caseFunc.getOperator(), "case");
+    // Operands: [predicate, then-value, else-value]
+    Function thenNegate = caseFunc.getOperands().get(1).getFunctionCall();
+    Assert.assertEquals(thenNegate.getOperator(), "negate");
+    
Assert.assertEquals(thenNegate.getOperands().get(0).getIdentifier().getName(), 
"col1");
+    
Assert.assertEquals(caseFunc.getOperands().get(2).getIdentifier().getName(), 
"col1");
+  }
+
+  @Test
+  public void testUnaryMinusInDistinct() {
+    // SELECT DISTINCT -col1 -> distinct(negate(col1))
+    PinotQuery pinotQuery = compileToPinotQuery("SELECT DISTINCT -col1 FROM 
myTable");
+    Function distinct = pinotQuery.getSelectList().get(0).getFunctionCall();
+    Assert.assertEquals(distinct.getOperator(), "distinct");
+    Function negate = distinct.getOperands().get(0).getFunctionCall();
+    Assert.assertEquals(negate.getOperator(), "negate");
+    Assert.assertEquals(negate.getOperands().get(0).getIdentifier().getName(), 
"col1");
+  }
+
+  @Test
+  public void testUnaryMinusInJoinCondition() {
+    // JOIN ON T1.key = -T2.key  (no aliases -- matches the style used by the 
existing testJoin())
+    PinotQuery pinotQuery = compileToPinotQuery(
+        "SELECT T1.col1, T2.col1 FROM T1 INNER JOIN T2 ON T1.col1 = -T2.col1");
+    Assert.assertNotNull(pinotQuery.getDataSource().getJoin());
+    Expression cond = pinotQuery.getDataSource().getJoin().getCondition();
+    Function eq = cond.getFunctionCall();
+    Assert.assertEquals(eq.getOperator(), FilterKind.EQUALS.name());
+    Function negate = eq.getOperands().get(1).getFunctionCall();
+    Assert.assertEquals(negate.getOperator(), "negate");
+  }
+
+  @Test
+  public void testUnaryMinusInOrderBy() {
+    // ORDER BY -col is wrapped as asc(negate(col))
+    PinotQuery pinotQuery = compileToPinotQuery("SELECT col1 FROM myTable 
ORDER BY -col1");
+    Assert.assertTrue(pinotQuery.isSetOrderByList());
+    Expression orderExpr = pinotQuery.getOrderByList().get(0);
+    // Outer wrapper is asc/desc; inner is negate(col1)
+    Function inner = 
orderExpr.getFunctionCall().getOperands().get(0).getFunctionCall();
+    Assert.assertEquals(inner.getOperator(), "negate");
+    Assert.assertEquals(inner.getOperands().get(0).getIdentifier().getName(), 
"col1");
+  }
+
+  @Test
+  public void testUnaryMinusBinaryArithmeticMix() {
+    // 1 - -col  -> minus(1, negate(col))   (binary minus then unary minus)
+    PinotQuery pinotQuery = compileToPinotQuery("SELECT 1 - -col1 FROM 
myTable");
+    Function minus = pinotQuery.getSelectList().get(0).getFunctionCall();
+    Assert.assertEquals(minus.getOperator(), "minus");
+    Assert.assertEquals(minus.getOperands().get(0).getLiteral().getIntValue(), 
1);
+    Function negate = minus.getOperands().get(1).getFunctionCall();
+    Assert.assertEquals(negate.getOperator(), "negate");
+    Assert.assertEquals(negate.getOperands().get(0).getIdentifier().getName(), 
"col1");
+  }
+
+  @Test
+  public void testNegativeLiteralCompiles() {
+    // Calcite typically folds "-5" to a negative literal at parse time. 
Either form is acceptable;
+    // we just verify the compiled form makes sense (literal -5, or negate(5)).
+    PinotQuery pinotQuery = compileToPinotQuery("SELECT -5 FROM myTable");
+    Expression expr = pinotQuery.getSelectList().get(0);
+    if (expr.isSetLiteral()) {
+      Assert.assertEquals(expr.getLiteral().getIntValue(), -5);
+    } else {
+      Assert.assertEquals(expr.getFunctionCall().getOperator(), "negate");
+      
Assert.assertEquals(expr.getFunctionCall().getOperands().get(0).getLiteral().getIntValue(),
 5);
+    }
+  }
+
+  @Test
+  public void testUnaryMinusInInList() {
+    PinotQuery pinotQuery = compileToPinotQuery("SELECT col1 FROM myTable 
WHERE col1 IN (-1, -2, -3)");
+    Assert.assertTrue(pinotQuery.isSetFilterExpression());
+    Function inFunc = pinotQuery.getFilterExpression().getFunctionCall();
+    Assert.assertEquals(inFunc.getOperator(), FilterKind.IN.name());
+    // First operand is the column, remaining operands are the IN-list values.
+    Assert.assertEquals(inFunc.getOperands().get(0).getIdentifier().getName(), 
"col1");
+    // Calcite folds negative literals directly, so each IN-list entry should 
be a negative int literal.
+    
Assert.assertEquals(inFunc.getOperands().get(1).getLiteral().getIntValue(), -1);
+    
Assert.assertEquals(inFunc.getOperands().get(2).getLiteral().getIntValue(), -2);
+    
Assert.assertEquals(inFunc.getOperands().get(3).getLiteral().getIntValue(), -3);
+  }
+
   @Test
   public void testSelectionTransformFunction() {
     PinotQuery pinotQuery =
diff --git 
a/pinot-query-planner/src/main/java/org/apache/pinot/calcite/sql/fun/PinotOperatorTable.java
 
b/pinot-query-planner/src/main/java/org/apache/pinot/calcite/sql/fun/PinotOperatorTable.java
index 7206eb87412..6d073d2949e 100644
--- 
a/pinot-query-planner/src/main/java/org/apache/pinot/calcite/sql/fun/PinotOperatorTable.java
+++ 
b/pinot-query-planner/src/main/java/org/apache/pinot/calcite/sql/fun/PinotOperatorTable.java
@@ -20,6 +20,7 @@ package org.apache.pinot.calcite.sql.fun;
 
 import com.google.common.base.Preconditions;
 import com.google.common.base.Suppliers;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -186,6 +187,8 @@ public class PinotOperatorTable implements SqlOperatorTable 
{
       // PREFIX OPERATORS
       SqlStdOperatorTable.EXISTS,
       SqlStdOperatorTable.NOT,
+      SqlStdOperatorTable.UNARY_MINUS,
+      SqlStdOperatorTable.UNARY_PLUS,
 
       // AGGREGATE OPERATORS
       SqlStdOperatorTable.COUNT,
@@ -332,12 +335,12 @@ public class PinotOperatorTable implements 
SqlOperatorTable {
   );
   //@formatter:on
 
-  // Key is canonical name
-  private final Map<String, SqlOperator> _operatorMap;
+  // Key is canonical name. Multiple operators can share the same name (e.g. 
binary "-" and unary "-").
+  private final Map<String, List<SqlOperator>> _operatorMap;
   private final List<SqlOperator> _operatorList;
 
   private PinotOperatorTable(boolean nullHandlingEnabled) {
-    Map<String, SqlOperator> operatorMap = new HashMap<>();
+    Map<String, List<SqlOperator>> operatorMap = new HashMap<>();
 
     // Register standard operators
     for (SqlOperator operator : STANDARD_OPERATORS) {
@@ -354,8 +357,8 @@ public class PinotOperatorTable implements SqlOperatorTable 
{
     // `col IS NOT NULL AND col <> 0` is simplified to `col <> 0` because `col 
IS NOT NULL` is always true if
     // `col <> 0` is true. However, in Pinot, `IS NOT NULL` has a special 
meaning when using basic null handling
     if (!nullHandlingEnabled) {
-      operatorMap.put(FunctionRegistry.canonicalize(PINOT_IS_NULL.getName()), 
PINOT_IS_NULL);
-      
operatorMap.put(FunctionRegistry.canonicalize(PINOT_IS_NOT_NULL.getName()), 
PINOT_IS_NOT_NULL);
+      registerOverride(FunctionRegistry.canonicalize(PINOT_IS_NULL.getName()), 
PINOT_IS_NULL, operatorMap);
+      
registerOverride(FunctionRegistry.canonicalize(PINOT_IS_NOT_NULL.getName()), 
PINOT_IS_NOT_NULL, operatorMap);
     }
 
     // Register Pinot operators
@@ -373,16 +376,45 @@ public class PinotOperatorTable implements 
SqlOperatorTable {
     registerTransformFunctions(operatorMap);
     registerScalarFunctions(operatorMap);
 
-    _operatorMap = Map.copyOf(operatorMap);
-    _operatorList = List.copyOf(operatorMap.values());
+    Map<String, List<SqlOperator>> immutableMap = new 
HashMap<>(operatorMap.size());
+    List<SqlOperator> allOperators = new ArrayList<>();
+    for (Map.Entry<String, List<SqlOperator>> entry : operatorMap.entrySet()) {
+      List<SqlOperator> immutableList = List.copyOf(entry.getValue());
+      immutableMap.put(entry.getKey(), immutableList);
+      allOperators.addAll(immutableList);
+    }
+    _operatorMap = Map.copyOf(immutableMap);
+    _operatorList = List.copyOf(allOperators);
   }
 
-  private void register(String name, SqlOperator sqlOperator, Map<String, 
SqlOperator> operatorMap) {
-    
Preconditions.checkState(operatorMap.put(FunctionRegistry.canonicalize(name), 
sqlOperator) == null,
-        "SqlOperator: %s is already registered", name);
+  /**
+   * Registers an operator under the given name. Multiple operators with 
different syntax (e.g. binary "-" and
+   * prefix unary "-") are allowed to share the same canonical name; all other 
duplicates are rejected.
+   */
+  private void register(String name, SqlOperator sqlOperator, Map<String, 
List<SqlOperator>> operatorMap) {
+    String canonicalName = FunctionRegistry.canonicalize(name);
+    List<SqlOperator> existing = operatorMap.get(canonicalName);
+    if (existing != null) {
+      for (SqlOperator op : existing) {
+        Preconditions.checkState(op.getSyntax() != sqlOperator.getSyntax(),
+            "SqlOperator: %s with syntax %s is already registered", name, 
sqlOperator.getSyntax());
+      }
+      existing.add(sqlOperator);
+    } else {
+      operatorMap.put(canonicalName, new ArrayList<>(List.of(sqlOperator)));
+    }
+  }
+
+  /**
+   * Forcibly registers an operator under the given canonical name, replacing 
any existing entry.
+   * Used to override standard Calcite operators (e.g. PINOT_IS_NULL / 
PINOT_IS_NOT_NULL).
+   */
+  private void registerOverride(String canonicalName, SqlOperator sqlOperator,
+      Map<String, List<SqlOperator>> operatorMap) {
+    operatorMap.put(canonicalName, new ArrayList<>(List.of(sqlOperator)));
   }
 
-  private void registerAggregateFunctions(Map<String, SqlOperator> 
operatorMap) {
+  private void registerAggregateFunctions(Map<String, List<SqlOperator>> 
operatorMap) {
     for (AggregationFunctionType functionType : 
AggregationFunctionType.values()) {
       if (functionType.getReturnTypeInference() != null) {
         String functionName = functionType.getName();
@@ -396,26 +428,30 @@ public class PinotOperatorTable implements 
SqlOperatorTable {
               functionType.getOperandTypeChecker());
         }
 
-        
Preconditions.checkState(operatorMap.put(FunctionRegistry.canonicalize(functionName),
 function) == null,
+        String canonicalName = FunctionRegistry.canonicalize(functionName);
+        Preconditions.checkState(!operatorMap.containsKey(canonicalName),
             "Aggregate function: %s is already registered", functionName);
+        operatorMap.put(canonicalName, new ArrayList<>(List.of(function)));
       }
     }
   }
 
-  private void registerTransformFunctions(Map<String, SqlOperator> 
operatorMap) {
+  private void registerTransformFunctions(Map<String, List<SqlOperator>> 
operatorMap) {
     for (TransformFunctionType functionType : TransformFunctionType.values()) {
       if (functionType.getReturnTypeInference() != null) {
         PinotSqlFunction function = new 
PinotSqlFunction(functionType.getName(), functionType.getReturnTypeInference(),
             functionType.getOperandTypeChecker());
         for (String name : functionType.getNames()) {
-          
Preconditions.checkState(operatorMap.put(FunctionRegistry.canonicalize(name), 
function) == null,
+          String canonicalName = FunctionRegistry.canonicalize(name);
+          Preconditions.checkState(!operatorMap.containsKey(canonicalName),
               "Transform function: %s is already registered", name);
+          operatorMap.put(canonicalName, new ArrayList<>(List.of(function)));
         }
       }
     }
   }
 
-  private void registerScalarFunctions(Map<String, SqlOperator> operatorMap) {
+  private void registerScalarFunctions(Map<String, List<SqlOperator>> 
operatorMap) {
     for (Map.Entry<String, PinotScalarFunction> entry : 
FunctionRegistry.FUNCTION_MAP.entrySet()) {
       String canonicalName = entry.getKey();
       PinotScalarFunction scalarFunction = entry.getValue();
@@ -429,7 +465,7 @@ public class PinotOperatorTable implements SqlOperatorTable 
{
             "Scalar function: %s is already registered", canonicalName);
         continue;
       }
-      operatorMap.put(canonicalName, sqlFunction);
+      operatorMap.put(canonicalName, new ArrayList<>(List.of(sqlFunction)));
     }
   }
 
@@ -440,9 +476,9 @@ public class PinotOperatorTable implements SqlOperatorTable 
{
       return;
     }
     String canonicalName = FunctionRegistry.canonicalize(opName.getSimple());
-    SqlOperator operator = _operatorMap.get(canonicalName);
-    if (operator != null) {
-      operatorList.add(operator);
+    List<SqlOperator> operators = _operatorMap.get(canonicalName);
+    if (operators != null) {
+      operatorList.addAll(operators);
     }
   }
 
diff --git 
a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/logical/RexExpressionUtils.java
 
b/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/logical/RexExpressionUtils.java
index 6e16892a34f..9122cb6a36e 100644
--- 
a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/logical/RexExpressionUtils.java
+++ 
b/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/logical/RexExpressionUtils.java
@@ -48,6 +48,7 @@ import org.apache.calcite.tools.RelBuilder;
 import org.apache.calcite.util.NlsString;
 import org.apache.calcite.util.Sarg;
 import org.apache.calcite.util.TimestampString;
+import org.apache.pinot.common.function.scalar.arithmetic.NegateScalarFunction;
 import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
 import org.apache.pinot.spi.utils.BooleanUtils;
 import org.apache.pinot.spi.utils.ByteArray;
@@ -276,6 +277,14 @@ public class RexExpressionUtils {
         return handleReinterpret(rexCall);
       case SEARCH:
         return handleSearch(rexCall);
+      case MINUS_PREFIX:
+        // Without this explicit case the default branch calls 
getFunctionName(), which returns
+        // SqlKind.MINUS_PREFIX.name() = "MINUS_PREFIX". That canonicalizes to 
"minusprefix", which is
+        // not registered in FunctionRegistry. Map directly to 
NegateScalarFunction's registered name.
+        // Note: PLUS_PREFIX is intentionally not handled here. Calcite's 
StandardConvertletTable strips
+        // UNARY_PLUS during SqlNode -> RexNode conversion, so it never 
reaches this switch.
+        return new 
RexExpression.FunctionCall(RelToPlanNodeConverter.convertToColumnDataType(rexCall.type),
+            NegateScalarFunction.FUNCTION_NAME, 
fromRexNodes(rexCall.operands));
       default:
         return new 
RexExpression.FunctionCall(RelToPlanNodeConverter.convertToColumnDataType(rexCall.type),
             getFunctionName(rexCall.op), fromRexNodes(rexCall.operands));
diff --git 
a/pinot-query-planner/src/test/java/org/apache/pinot/query/QueryCompilationTest.java
 
b/pinot-query-planner/src/test/java/org/apache/pinot/query/QueryCompilationTest.java
index 842bbf97c15..2dd2f79335b 100644
--- 
a/pinot-query-planner/src/test/java/org/apache/pinot/query/QueryCompilationTest.java
+++ 
b/pinot-query-planner/src/test/java/org/apache/pinot/query/QueryCompilationTest.java
@@ -73,6 +73,83 @@ public class QueryCompilationTest extends 
QueryEnvironmentTestBase {
     assertNotNull(dispatchableSubPlan);
   }
 
+  @Test(dataProvider = "testUnaryOperatorQueries")
+  public void testUnaryPrefixOperatorsPlanQuery(String query) {
+    DispatchableSubPlan dispatchableSubPlan = 
_queryEnvironment.planQuery(query);
+    assertNotNull(dispatchableSubPlan);
+  }
+
+  @DataProvider(name = "testUnaryOperatorQueries")
+  public Object[][] provideUnaryOperatorQueries() {
+    return new Object[][]{
+        // INT (col3, col6)
+        {"SELECT -col3 FROM a"},
+        {"SELECT col3 FROM a ORDER BY -col3"},
+        {"SELECT col1, col3, DENSE_RANK() OVER (PARTITION BY col1 ORDER BY "
+            + "CASE WHEN col5 = true THEN -col3 ELSE col3 END) FROM a"},
+        {"SELECT col1, RANK() OVER (ORDER BY -col3) FROM a"},
+        {"SELECT +col3 FROM a"},
+        {"SELECT col3 FROM a ORDER BY +col3"},
+        {"SELECT -(col3 + col6) FROM a"},
+        {"SELECT col3 FROM a WHERE -col3 < 0"},
+        {"SELECT -col3, -col6 FROM a ORDER BY -col3"},
+        {"SELECT col3 - col6, -col3 FROM a"},
+        // LONG (col7)
+        {"SELECT -col7 FROM a"},
+        {"SELECT +col7 FROM a"},
+        {"SELECT -col7, col7 FROM a ORDER BY -col7"},
+        {"SELECT col7 FROM a WHERE -col7 < 0"},
+        {"SELECT -(col3 + col7) FROM a"},
+        // BIG_DECIMAL (col4)
+        {"SELECT -col4 FROM a"},
+        {"SELECT +col4 FROM a"},
+        {"SELECT col4 FROM a WHERE -col4 < 0"},
+        // double negation and compound
+        {"SELECT -(-col3) FROM a"},
+        {"SELECT -(-col7) FROM a"},
+        {"SELECT col3 - (-col6) FROM a"},
+        // GROUP BY with negation
+        {"SELECT -col3, COUNT(*) FROM a GROUP BY -col3"},
+        {"SELECT col1, COUNT(*) FROM a GROUP BY col1, -col3"},
+        // Aggregations with negation
+        {"SELECT SUM(-col3), MAX(-col3), MIN(-col3), AVG(-col3) FROM a"},
+        {"SELECT SUM(-col7), MAX(-col7), MIN(-col7), AVG(-col7) FROM a"},
+        {"SELECT SUM(-col4) FROM a"},
+        {"SELECT -SUM(col3), -MAX(col3), -MIN(col3), -AVG(col3) FROM a"},
+        {"SELECT -COUNT(*) FROM a"},
+        {"SELECT -COUNT(col3) FROM a"},
+        // HAVING with negation
+        {"SELECT col1, SUM(col3) FROM a GROUP BY col1 HAVING -SUM(col3) < 0"},
+        {"SELECT col1, SUM(col3) FROM a GROUP BY col1 HAVING SUM(-col3) > 
-100"},
+        // alias-then-orderby
+        {"SELECT -col3 AS neg FROM a ORDER BY neg"},
+        {"SELECT -col3 AS neg, col1 FROM a ORDER BY neg DESC"},
+        // CAST involving negation
+        {"SELECT CAST(-col3 AS DOUBLE) FROM a"},
+        {"SELECT CAST(-col7 AS BIGINT) FROM a"},
+        {"SELECT -CAST(col3 AS DOUBLE) FROM a"},
+        // JOIN with negation
+        {"SELECT a.col1 FROM a JOIN b ON a.col3 = -b.col3"},
+        {"SELECT a.col1, b.col1 FROM a JOIN b ON -a.col3 = b.col3"},
+        // window: PARTITION BY with negation
+        {"SELECT col1, RANK() OVER (PARTITION BY -col3 ORDER BY col6) FROM a"},
+        {"SELECT col1, ROW_NUMBER() OVER (PARTITION BY -col3 ORDER BY -col6) 
FROM a"},
+        // DISTINCT with negation
+        {"SELECT DISTINCT -col3 FROM a"},
+        {"SELECT COUNT(DISTINCT -col3) FROM a"},
+        // IN with negative literals
+        {"SELECT col3 FROM a WHERE col3 IN (-1, -2, -3)"},
+        {"SELECT col3 FROM a WHERE -col3 IN (-1, -2)"},
+        // negation in BETWEEN bounds
+        {"SELECT col3 FROM a WHERE col3 BETWEEN -10 AND 10"},
+        {"SELECT col3 FROM a WHERE -col3 BETWEEN -100 AND 0"},
+        // negation in subquery
+        {"SELECT col1 FROM a WHERE col3 > (SELECT AVG(-col3) FROM b)"},
+        // FLOAT (none in schema, skip) -- verify mixed double/big_decimal 
negation
+        {"SELECT CAST(-col3 AS DOUBLE) + col4 FROM a"},
+    };
+  }
+
   @Test(dataProvider = "testQueryExceptionDataProvider")
   public void testQueryWithException(String query, String exceptionSnippet) {
     try {
diff --git a/pinot-query-runtime/src/test/resources/queries/MathFuncs.json 
b/pinot-query-runtime/src/test/resources/queries/MathFuncs.json
index 76dcc727ec9..72c032e5db8 100644
--- a/pinot-query-runtime/src/test/resources/queries/MathFuncs.json
+++ b/pinot-query-runtime/src/test/resources/queries/MathFuncs.json
@@ -511,5 +511,392 @@
         "sql": "SELECT longCol / 1e20 FROM {numTbl}"
       }
     ]
+  },
+  "unary_minus": {
+    "tables": {
+      "numTbl": {
+        "schema": [
+          {"name": "intCol", "type": "INT"},
+          {"name": "longCol", "type": "LONG"},
+          {"name": "floatCol", "type": "FLOAT"},
+          {"name": "doubleCol", "type": "DOUBLE"}
+        ],
+        "inputs": [
+          [0, 0, 0.0, 0.0],
+          [1, 1, 1.5, 1.5],
+          [-1, -1, -1.5, -1.5],
+          [123, 321, 4.2, 4.2],
+          [-456, -456, -7.7, -7.7]
+        ]
+      },
+      "bigDecTbl": {
+        "schema": [
+          {"name": "bigDecCol", "type": "BIG_DECIMAL"}
+        ],
+        "inputs": [
+          ["0"],
+          ["1"],
+          ["-1"],
+          ["123456789012345678901234567890"],
+          ["-123456789012345678901234567890"]
+        ]
+      }
+    },
+    "queries": [
+      {
+        "description": "unary minus on INT column preserves INT type",
+        "sql": "SELECT -intCol FROM {numTbl}"
+      },
+      {
+        "description": "unary minus on LONG column preserves LONG type",
+        "sql": "SELECT -longCol FROM {numTbl}"
+      },
+      {
+        "description": "unary minus on FLOAT column",
+        "sql": "SELECT -floatCol FROM {numTbl}"
+      },
+      {
+        "description": "unary minus on DOUBLE column",
+        "sql": "SELECT -doubleCol FROM {numTbl}"
+      },
+      {
+        "description": "unary minus on BIG_DECIMAL column",
+        "sql": "SELECT -bigDecCol FROM {bigDecTbl}"
+      },
+      {
+        "description": "unary minus in WHERE clause on INT",
+        "sql": "SELECT intCol FROM {numTbl} WHERE -intCol < 0"
+      },
+      {
+        "description": "unary minus in WHERE clause on DOUBLE",
+        "sql": "SELECT doubleCol FROM {numTbl} WHERE -doubleCol < 0"
+      },
+      {
+        "description": "double negation --col returns original value",
+        "sql": "SELECT -(-intCol) FROM {numTbl}"
+      },
+      {
+        "description": "double negation on LONG",
+        "sql": "SELECT -(-longCol) FROM {numTbl}"
+      },
+      {
+        "description": "unary minus on compound expression",
+        "sql": "SELECT -(intCol + longCol) FROM {numTbl}"
+      },
+      {
+        "description": "unary minus mixed with binary arithmetic",
+        "sql": "SELECT intCol - (-longCol) FROM {numTbl}"
+      },
+      {
+        "description": "unary minus on zero returns zero",
+        "sql": "SELECT -intCol FROM {numTbl} WHERE intCol = 0"
+      }
+    ]
+  },
+  "unary_minus_null_handling": {
+    "extraProps": {
+      "enableColumnBasedNullHandling": true
+    },
+    "tables": {
+      "nullTbl": {
+        "schema": [
+          {"name": "id", "type": "INT"},
+          {"name": "intCol", "type": "INT", "notNull": false},
+          {"name": "longCol", "type": "LONG", "notNull": false},
+          {"name": "doubleCol", "type": "DOUBLE", "notNull": false}
+        ],
+        "inputs": [
+          [1, 10, 100, 1.5],
+          [2, null, null, null],
+          [3, -20, -200, -2.5]
+        ]
+      }
+    },
+    "queries": [
+      {
+        "description": "unary minus on DOUBLE column with null rows produces 
NULL output",
+        "sql": "SET enableNullHandling=true; SELECT id, -doubleCol FROM 
{nullTbl} ORDER BY id"
+      },
+      {
+        "description": "IsNullTransformFunction calls getNullBitmap() on the 
inner negate transform at runtime; BaseTransformFunction.getNullBitmap() ORs 
the argument bitmaps without invoking the scalar, so Math.negateExact is never 
called and no overflow occurs",
+        "sql": "SET enableNullHandling=true; SELECT id FROM {nullTbl} WHERE 
-intCol IS NULL ORDER BY id"
+      },
+      {
+        "description": "IS NOT NULL takes the same bitmap-only path through 
BaseTransformFunction.getNullBitmap(); the scalar is never invoked",
+        "sql": "SET enableNullHandling=true; SELECT id FROM {nullTbl} WHERE 
-intCol IS NOT NULL ORDER BY id"
+      },
+      {
+        "description": "SELECT projection invokes negate on every row; null 
INT rows are stored as Integer.MIN_VALUE, and 
Math.negateExact(Integer.MIN_VALUE) overflows",
+        "sql": "SET enableNullHandling=true; SELECT id, -intCol FROM {nullTbl} 
ORDER BY id",
+        "expectedException": ".*integer overflow.*"
+      },
+      {
+        "description": "SELECT projection invokes negate on every row; null 
LONG rows are stored as Long.MIN_VALUE, and Math.negateExact(Long.MIN_VALUE) 
overflows",
+        "sql": "SET enableNullHandling=true; SELECT id, -longCol FROM 
{nullTbl} ORDER BY id",
+        "expectedException": ".*long overflow.*|.*integer overflow.*"
+      },
+      {
+        "description": "SumAggregationFunction.aggregateSV() calls 
blockValSet.getIntValuesSV() to materialize the negated column; that triggers 
Math.negateExact on the null-row sentinel and overflows before foldNotNull gets 
a chance to skip null rows",
+        "sql": "SET enableNullHandling=true; SELECT SUM(-intCol) FROM 
{nullTbl}",
+        "expectedException": ".*integer overflow.*"
+      }
+    ]
+  },
+  "unary_minus_overflow": {
+    "tables": {
+      "overflowTbl": {
+        "schema": [
+          {"name": "intCol", "type": "INT"},
+          {"name": "longCol", "type": "LONG"}
+        ],
+        "inputs": [
+          [-2147483648, -9223372036854775808]
+        ]
+      }
+    },
+    "queries": [
+      {
+        "description": "negating INT_MIN throws ArithmeticException via 
Math.negateExact",
+        "sql": "SELECT -intCol FROM {overflowTbl}",
+        "expectedException": ".*integer overflow.*"
+      },
+      {
+        "description": "negating LONG_MIN throws ArithmeticException via 
Math.negateExact",
+        "sql": "SELECT -longCol FROM {overflowTbl}",
+        "expectedException": ".*long overflow.*|.*integer overflow.*"
+      }
+    ]
+  },
+  "unary_minus_null_handling_disabled": {
+    "tables": {
+      "nullTbl": {
+        "schema": [
+          {"name": "id", "type": "INT"},
+          {"name": "intCol", "type": "INT", "notNull": false},
+          {"name": "longCol", "type": "LONG", "notNull": false},
+          {"name": "doubleCol", "type": "DOUBLE", "notNull": false}
+        ],
+        "inputs": [
+          [1, 10, 100, 1.5],
+          [2, null, null, null],
+          [3, -20, -200, -2.5]
+        ]
+      }
+    },
+    "queries": [
+      {
+        "description": "with null handling disabled there is no null concept; 
every row is its raw stored value, and rows storing Integer.MIN_VALUE overflow 
Math.negateExact",
+        "sql": "SET enableNullHandling=false; SELECT -intCol FROM {nullTbl}",
+        "expectedException": ".*integer overflow.*"
+      },
+      {
+        "description": "with null handling disabled, rows storing 
Long.MIN_VALUE overflow Math.negateExact",
+        "sql": "SET enableNullHandling=false; SELECT -longCol FROM {nullTbl}",
+        "expectedException": ".*long overflow.*|.*integer overflow.*"
+      },
+      {
+        "description": "with null handling disabled, negation of finite 
doubles succeeds (no value in the DOUBLE column overflows under negation)",
+        "sql": "SET enableNullHandling=false; SELECT -doubleCol FROM {nullTbl} 
WHERE id IN (1, 3) ORDER BY id"
+      }
+    ]
+  },
+  "unary_plus": {
+    "tables": {
+      "numTbl": {
+        "schema": [
+          {"name": "intCol", "type": "INT"},
+          {"name": "longCol", "type": "LONG"},
+          {"name": "floatCol", "type": "FLOAT"},
+          {"name": "doubleCol", "type": "DOUBLE"}
+        ],
+        "inputs": [
+          [0, 0, 0.0, 0.0],
+          [1, 1, 1.5, 1.5],
+          [-1, -1, -1.5, -1.5],
+          [123, 321, 4.2, 4.2]
+        ]
+      }
+    },
+    "queries": [
+      {
+        "description": "unary plus on INT is identity",
+        "sql": "SELECT +intCol FROM {numTbl}"
+      },
+      {
+        "description": "unary plus on LONG is identity",
+        "sql": "SELECT +longCol FROM {numTbl}"
+      },
+      {
+        "description": "unary plus on FLOAT is identity",
+        "sql": "SELECT +floatCol FROM {numTbl}"
+      },
+      {
+        "description": "unary plus on DOUBLE is identity",
+        "sql": "SELECT +doubleCol FROM {numTbl}"
+      },
+      {
+        "description": "unary plus over negated column is still negation",
+        "sql": "SELECT +(-intCol) FROM {numTbl}"
+      },
+      {
+        "description": "unary plus in ORDER BY is identity",
+        "sql": "SELECT intCol FROM {numTbl} ORDER BY +intCol"
+      }
+    ]
+  },
+  "unary_minus_aggregations": {
+    "tables": {
+      "numTbl": {
+        "schema": [
+          {"name": "groupKey", "type": "STRING"},
+          {"name": "intCol", "type": "INT"},
+          {"name": "longCol", "type": "LONG"},
+          {"name": "doubleCol", "type": "DOUBLE"}
+        ],
+        "inputs": [
+          ["a", 1, 10, 1.5],
+          ["a", 2, 20, 2.5],
+          ["a", 3, 30, 3.5],
+          ["b", 4, 40, 4.5],
+          ["b", 5, 50, 5.5],
+          ["c", 6, 60, 6.5],
+          ["c", -7, -70, -7.5]
+        ]
+      }
+    },
+    "queries": [
+      {
+        "description": "SUM(-col) equals -SUM(col)",
+        "sql": "SELECT SUM(-intCol), -SUM(intCol) FROM {numTbl}"
+      },
+      {
+        "description": "MAX(-col) equals -MIN(col)",
+        "sql": "SELECT MAX(-intCol), -MIN(intCol) FROM {numTbl}"
+      },
+      {
+        "description": "MIN(-col) equals -MAX(col)",
+        "sql": "SELECT MIN(-intCol), -MAX(intCol) FROM {numTbl}"
+      },
+      {
+        "description": "AVG(-col) equals -AVG(col)",
+        "sql": "SELECT AVG(-doubleCol), -AVG(doubleCol) FROM {numTbl}"
+      },
+      {
+        "description": "COUNT is unaffected by negation of argument",
+        "sql": "SELECT COUNT(-intCol) FROM {numTbl}"
+      },
+      {
+        "description": "negation of aggregate result",
+        "sql": "SELECT -SUM(intCol), -MAX(intCol), -MIN(intCol) FROM {numTbl}"
+      },
+      {
+        "description": "aggregate of compound negation",
+        "sql": "SELECT SUM(-(intCol + longCol)) FROM {numTbl}"
+      },
+      {
+        "description": "long type aggregations with negation",
+        "sql": "SELECT SUM(-longCol), MAX(-longCol), MIN(-longCol) FROM 
{numTbl}"
+      },
+      {
+        "description": "double type aggregations with negation",
+        "sql": "SELECT SUM(-doubleCol), MAX(-doubleCol), MIN(-doubleCol) FROM 
{numTbl}"
+      }
+    ]
+  },
+  "unary_minus_group_by": {
+    "tables": {
+      "numTbl": {
+        "schema": [
+          {"name": "groupKey", "type": "STRING"},
+          {"name": "intCol", "type": "INT"},
+          {"name": "longCol", "type": "LONG"}
+        ],
+        "inputs": [
+          ["a", 1, 10],
+          ["a", 2, 20],
+          ["b", 3, 30],
+          ["b", 4, 40],
+          ["c", 5, 50]
+        ]
+      }
+    },
+    "queries": [
+      {
+        "description": "GROUP BY -col -- negation as grouping key",
+        "sql": "SELECT -intCol, COUNT(*) FROM {numTbl} GROUP BY -intCol ORDER 
BY -intCol"
+      },
+      {
+        "description": "GROUP BY normal key, aggregate over negation",
+        "sql": "SELECT groupKey, SUM(-intCol) FROM {numTbl} GROUP BY groupKey 
ORDER BY groupKey"
+      },
+      {
+        "description": "GROUP BY normal key, negation of aggregate",
+        "sql": "SELECT groupKey, -SUM(intCol) FROM {numTbl} GROUP BY groupKey 
ORDER BY groupKey"
+      },
+      {
+        "description": "HAVING -SUM(col) > threshold",
+        "sql": "SELECT groupKey, SUM(intCol) FROM {numTbl} GROUP BY groupKey 
HAVING -SUM(intCol) > -7 ORDER BY groupKey"
+      },
+      {
+        "description": "HAVING SUM(-col) < threshold excludes a group",
+        "sql": "SELECT groupKey, SUM(longCol) FROM {numTbl} GROUP BY groupKey 
HAVING SUM(-longCol) < -40 ORDER BY groupKey"
+      },
+      {
+        "description": "DISTINCT -col",
+        "sql": "SELECT DISTINCT -intCol FROM {numTbl} ORDER BY -intCol"
+      },
+      {
+        "description": "COUNT(DISTINCT -col)",
+        "sql": "SELECT COUNT(DISTINCT -intCol) FROM {numTbl}"
+      }
+    ]
+  },
+  "unary_minus_misc": {
+    "tables": {
+      "numTbl": {
+        "schema": [
+          {"name": "intCol", "type": "INT"},
+          {"name": "longCol", "type": "LONG"},
+          {"name": "doubleCol", "type": "DOUBLE"}
+        ],
+        "inputs": [
+          [1, 10, 1.5],
+          [2, 20, 2.5],
+          [3, 30, 3.5],
+          [4, 40, 4.5],
+          [5, 50, 5.5]
+        ]
+      }
+    },
+    "queries": [
+      {
+        "description": "unary minus inside CAST",
+        "sql": "SELECT CAST(-intCol AS DOUBLE) FROM {numTbl}"
+      },
+      {
+        "description": "negation of CAST result",
+        "sql": "SELECT -CAST(intCol AS DOUBLE) FROM {numTbl}"
+      },
+      {
+        "description": "alias on negated column, ORDER BY alias",
+        "sql": "SELECT -intCol AS neg FROM {numTbl} ORDER BY neg"
+      },
+      {
+        "description": "IN with negative literals",
+        "sql": "SELECT -intCol FROM {numTbl} WHERE -intCol IN (-1, -2, -3) 
ORDER BY -intCol"
+      },
+      {
+        "description": "BETWEEN with negative bounds on negated column",
+        "sql": "SELECT -intCol FROM {numTbl} WHERE -intCol BETWEEN -4 AND -2 
ORDER BY -intCol"
+      },
+      {
+        "description": "negation in CASE expression",
+        "sql": "SELECT CASE WHEN intCol > 2 THEN -intCol ELSE intCol END FROM 
{numTbl} ORDER BY intCol"
+      },
+      {
+        "description": "negation of mixed-type expression preserves correct 
semantics",
+        "sql": "SELECT -(intCol + longCol), intCol, longCol FROM {numTbl} 
ORDER BY intCol"
+      }
+    ]
   }
 }


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


Reply via email to