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

jackie 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 03511e980e [feature] [null support # 7] Support null in least and 
greatest transform functions. (#10575)
03511e980e is described below

commit 03511e980ea7698fe8a95a19a3e78b2bda85998c
Author: Yao Liu <[email protected]>
AuthorDate: Fri Apr 21 23:50:07 2023 -0700

    [feature] [null support # 7] Support null in least and greatest transform 
functions. (#10575)
---
 .../function/GreatestTransformFunction.java        | 200 ++++++++++++++++++++-
 .../transform/function/LeastTransformFunction.java | 194 +++++++++++++++++++-
 .../SelectTupleElementTransformFunction.java       |  26 ++-
 .../TupleSelectionTransformFunctionsTest.java      | 120 +++++++++++++
 4 files changed, 536 insertions(+), 4 deletions(-)

diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/GreatestTransformFunction.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/GreatestTransformFunction.java
index 9a515d9ca8..32217bd40d 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/GreatestTransformFunction.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/GreatestTransformFunction.java
@@ -19,10 +19,22 @@
 package org.apache.pinot.core.operator.transform.function;
 
 import java.math.BigDecimal;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.pinot.common.function.TransformFunctionType;
 import org.apache.pinot.core.operator.blocks.ValueBlock;
+import org.roaringbitmap.RoaringBitmap;
 
-
+/**
+ * The <code>GreatestTransformFunction</code> implements the Greatest operator.
+ *
+ * Return the greatest results for the arguments
+ *
+ * Expected result:
+ * greatest(columnA, columnB, columnC): largest among columnA, columnB, columnC
+ *
+ * Note that null values will be ignored for evaluation. If all values are 
null, we return null.
+ */
 public class GreatestTransformFunction extends 
SelectTupleElementTransformFunction {
 
   public GreatestTransformFunction() {
@@ -44,6 +56,37 @@ public class GreatestTransformFunction extends 
SelectTupleElementTransformFuncti
     return _intValuesSV;
   }
 
+  @Override
+  public Pair<int[], RoaringBitmap> transformToIntValuesSVWithNull(ValueBlock 
valueBlock) {
+    int numDocs = valueBlock.getNumDocs();
+    initIntValuesSV(numDocs);
+    Pair<int[], RoaringBitmap> values = 
_arguments.get(0).transformToIntValuesSVWithNull(valueBlock);
+    int[] curValues = values.getLeft();
+    System.arraycopy(curValues, 0, _intValuesSV, 0, numDocs);
+    RoaringBitmap nullBitmap = values.getRight();
+    for (int i = 1; i < _arguments.size(); i++) {
+      values = _arguments.get(i).transformToIntValuesSVWithNull(valueBlock);
+      RoaringBitmap curNull = values.getRight();
+      for (int j = 0; j < numDocs & j < values.getLeft().length; j++) {
+        // If current value is not null, we process the data.
+        if (curNull == null || !curNull.contains(j)) {
+          // If existing maximum value is null, we set the value directly.
+          if (nullBitmap != null && nullBitmap.contains(j)) {
+            _intValuesSV[j] = values.getLeft()[j];
+          } else {
+            _intValuesSV[j] = Math.max(_intValuesSV[j], values.getLeft()[j]);
+          }
+        }
+      }
+      if (nullBitmap != null && curNull != null) {
+        nullBitmap.and(curNull);
+      } else {
+        nullBitmap = null;
+      }
+    }
+    return ImmutablePair.of(_intValuesSV, nullBitmap);
+  }
+
   @Override
   public long[] transformToLongValuesSV(ValueBlock valueBlock) {
     int numDocs = valueBlock.getNumDocs();
@@ -59,6 +102,37 @@ public class GreatestTransformFunction extends 
SelectTupleElementTransformFuncti
     return _longValuesSV;
   }
 
+  @Override
+  public Pair<long[], RoaringBitmap> 
transformToLongValuesSVWithNull(ValueBlock valueBlock) {
+    int numDocs = valueBlock.getNumDocs();
+    initLongValuesSV(numDocs);
+    Pair<long[], RoaringBitmap> values = 
_arguments.get(0).transformToLongValuesSVWithNull(valueBlock);
+    long[] curValues = values.getLeft();
+    System.arraycopy(curValues, 0, _longValuesSV, 0, numDocs);
+    RoaringBitmap nullBitmap = values.getRight();
+    for (int i = 1; i < _arguments.size(); i++) {
+      values = _arguments.get(i).transformToLongValuesSVWithNull(valueBlock);
+      RoaringBitmap curNull = values.getRight();
+      for (int j = 0; j < numDocs & j < values.getLeft().length; j++) {
+        // If current value is not null, we process the data.
+        if (curNull == null || !curNull.contains(j)) {
+          // If existing maximum value is null, we set the value directly.
+          if (nullBitmap != null && nullBitmap.contains(j)) {
+            _longValuesSV[j] = values.getLeft()[j];
+          } else {
+            _longValuesSV[j] = Math.max(_longValuesSV[j], values.getLeft()[j]);
+          }
+        }
+      }
+      if (nullBitmap != null && curNull != null) {
+        nullBitmap.and(curNull);
+      } else {
+        nullBitmap = null;
+      }
+    }
+    return ImmutablePair.of(_longValuesSV, nullBitmap);
+  }
+
   @Override
   public float[] transformToFloatValuesSV(ValueBlock valueBlock) {
     int numDocs = valueBlock.getNumDocs();
@@ -74,6 +148,37 @@ public class GreatestTransformFunction extends 
SelectTupleElementTransformFuncti
     return _floatValuesSV;
   }
 
+  @Override
+  public Pair<float[], RoaringBitmap> 
transformToFloatValuesSVWithNull(ValueBlock valueBlock) {
+    int numDocs = valueBlock.getNumDocs();
+    initFloatValuesSV(numDocs);
+    Pair<float[], RoaringBitmap> values = 
_arguments.get(0).transformToFloatValuesSVWithNull(valueBlock);
+    float[] curValues = values.getLeft();
+    System.arraycopy(curValues, 0, _floatValuesSV, 0, numDocs);
+    RoaringBitmap nullBitmap = values.getRight();
+    for (int i = 1; i < _arguments.size(); i++) {
+      values = _arguments.get(i).transformToFloatValuesSVWithNull(valueBlock);
+      RoaringBitmap curNull = values.getRight();
+      for (int j = 0; j < numDocs & j < values.getLeft().length; j++) {
+        // If current value is not null, we process the data.
+        if (curNull != null || !curNull.contains(j)) {
+          // If existing maximum value is null, we set the value directly.
+          if (nullBitmap != null && nullBitmap.contains(j)) {
+            _floatValuesSV[j] = values.getLeft()[j];
+          } else {
+            _floatValuesSV[j] = Math.max(_floatValuesSV[j], 
values.getLeft()[j]);
+          }
+        }
+      }
+      if (nullBitmap != null && curNull != null) {
+        nullBitmap.and(curNull);
+      } else {
+        nullBitmap = null;
+      }
+    }
+    return ImmutablePair.of(_floatValuesSV, nullBitmap);
+  }
+
   @Override
   public double[] transformToDoubleValuesSV(ValueBlock valueBlock) {
     int numDocs = valueBlock.getNumDocs();
@@ -89,6 +194,37 @@ public class GreatestTransformFunction extends 
SelectTupleElementTransformFuncti
     return _doubleValuesSV;
   }
 
+  @Override
+  public Pair<double[], RoaringBitmap> 
transformToDoubleValuesSVWithNull(ValueBlock valueBlock) {
+    int numDocs = valueBlock.getNumDocs();
+    initDoubleValuesSV(numDocs);
+    Pair<double[], RoaringBitmap> values = 
_arguments.get(0).transformToDoubleValuesSVWithNull(valueBlock);
+    double[] curValues = values.getLeft();
+    System.arraycopy(curValues, 0, _doubleValuesSV, 0, numDocs);
+    RoaringBitmap nullBitmap = values.getRight();
+    for (int i = 1; i < _arguments.size(); i++) {
+      values = _arguments.get(i).transformToDoubleValuesSVWithNull(valueBlock);
+      RoaringBitmap curNull = values.getRight();
+      for (int j = 0; j < numDocs & j < values.getLeft().length; j++) {
+        // If current value is not null, we process the data.
+        if (curNull == null || !curNull.contains(j)) {
+          // If existing maximum value is null, we set the value directly.
+          if (nullBitmap != null && nullBitmap.contains(j)) {
+            _doubleValuesSV[j] = values.getLeft()[j];
+          } else {
+            _doubleValuesSV[j] = Math.max(_doubleValuesSV[j], 
values.getLeft()[j]);
+          }
+        }
+      }
+      if (nullBitmap != null && curNull != null) {
+        nullBitmap.and(curNull);
+      } else {
+        nullBitmap = null;
+      }
+    }
+    return ImmutablePair.of(_doubleValuesSV, nullBitmap);
+  }
+
   @Override
   public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) {
     int numDocs = valueBlock.getNumDocs();
@@ -104,6 +240,37 @@ public class GreatestTransformFunction extends 
SelectTupleElementTransformFuncti
     return _bigDecimalValuesSV;
   }
 
+  @Override
+  public Pair<BigDecimal[], RoaringBitmap> 
transformToBigDecimalValuesSVWithNull(ValueBlock valueBlock) {
+    int numDocs = valueBlock.getNumDocs();
+    initBigDecimalValuesSV(numDocs);
+    Pair<BigDecimal[], RoaringBitmap> values = 
_arguments.get(0).transformToBigDecimalValuesSVWithNull(valueBlock);
+    BigDecimal[] curValues = values.getLeft();
+    System.arraycopy(curValues, 0, _bigDecimalValuesSV, 0, numDocs);
+    RoaringBitmap nullBitmap = values.getRight();
+    for (int i = 1; i < _arguments.size(); i++) {
+      values = 
_arguments.get(i).transformToBigDecimalValuesSVWithNull(valueBlock);
+      RoaringBitmap curNull = values.getRight();
+      for (int j = 0; j < numDocs & j < values.getLeft().length; j++) {
+        // If current value is not null, we process the data.
+        if (curNull == null || !curNull.contains(j)) {
+          // If existing maximum value is null, we set the value directly.
+          if (nullBitmap != null && nullBitmap.contains(j)) {
+            _bigDecimalValuesSV[j] = values.getLeft()[j];
+          } else {
+            _bigDecimalValuesSV[j] = 
_bigDecimalValuesSV[j].max(values.getLeft()[j]);
+          }
+        }
+      }
+      if (nullBitmap != null && curNull != null) {
+        nullBitmap.and(curNull);
+      } else {
+        nullBitmap = null;
+      }
+    }
+    return ImmutablePair.of(_bigDecimalValuesSV, nullBitmap);
+  }
+
   @Override
   public String[] transformToStringValuesSV(ValueBlock valueBlock) {
     int numDocs = valueBlock.getNumDocs();
@@ -120,4 +287,35 @@ public class GreatestTransformFunction extends 
SelectTupleElementTransformFuncti
     }
     return _stringValuesSV;
   }
+
+  @Override
+  public Pair<String[], RoaringBitmap> 
transformToStringValuesSVWithNull(ValueBlock valueBlock) {
+    int numDocs = valueBlock.getNumDocs();
+    initStringValuesSV(numDocs);
+    Pair<String[], RoaringBitmap> values = 
_arguments.get(0).transformToStringValuesSVWithNull(valueBlock);
+    String[] curValues = values.getLeft();
+    System.arraycopy(curValues, 0, _stringValuesSV, 0, numDocs);
+    RoaringBitmap nullBitmap = values.getRight();
+    for (int i = 1; i < _arguments.size(); i++) {
+      values = _arguments.get(i).transformToStringValuesSVWithNull(valueBlock);
+      RoaringBitmap curNull = values.getRight();
+      for (int j = 0; j < numDocs & j < values.getLeft().length; j++) {
+        // If current value is not null, we process the data.
+        if (curNull == null || !curNull.contains(j)) {
+          // If existing maximum value is null, we set the value directly.
+          if (nullBitmap != null && nullBitmap.contains(j)) {
+            _stringValuesSV[j] = values.getLeft()[j];
+          } else if (_stringValuesSV[j].compareTo(values.getLeft()[j]) < 0) {
+            _stringValuesSV[j] = values.getLeft()[j];
+          }
+        }
+      }
+      if (nullBitmap != null && curNull != null) {
+        nullBitmap.and(curNull);
+      } else {
+        nullBitmap = null;
+      }
+    }
+    return ImmutablePair.of(_stringValuesSV, nullBitmap);
+  }
 }
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LeastTransformFunction.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LeastTransformFunction.java
index 8891e2928b..4933266f06 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LeastTransformFunction.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LeastTransformFunction.java
@@ -19,10 +19,22 @@
 package org.apache.pinot.core.operator.transform.function;
 
 import java.math.BigDecimal;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.pinot.common.function.TransformFunctionType;
 import org.apache.pinot.core.operator.blocks.ValueBlock;
+import org.roaringbitmap.RoaringBitmap;
 
-
+/**
+ * The <code>LeastTransformFunction</code> implements the Least operator.
+ *
+ * Return the smallest results for the arguments
+ *
+ * Expected result:
+ * Least(columnA, columnB, columnC): smallest among columnA, columnB, columnC
+ *
+ * Note that null values will be ignored for evaluation. If all values are 
null, we return null.
+ */
 public class LeastTransformFunction extends 
SelectTupleElementTransformFunction {
 
   public LeastTransformFunction() {
@@ -44,6 +56,36 @@ public class LeastTransformFunction extends 
SelectTupleElementTransformFunction
     return _intValuesSV;
   }
 
+  @Override
+  public Pair<int[], RoaringBitmap> transformToIntValuesSVWithNull(ValueBlock 
valueBlock) {
+    int numDocs = valueBlock.getNumDocs();
+    initIntValuesSV(numDocs);
+    Pair<int[], RoaringBitmap> values = 
_arguments.get(0).transformToIntValuesSVWithNull(valueBlock);
+    System.arraycopy(values.getLeft(), 0, _intValuesSV, 0, numDocs);
+    RoaringBitmap nullBitmap = values.getRight();
+    for (int i = 1; i < _arguments.size(); i++) {
+      values = _arguments.get(i).transformToIntValuesSVWithNull(valueBlock);
+      RoaringBitmap curNull = values.getRight();
+      for (int j = 0; j < numDocs & j < values.getLeft().length; j++) {
+        // If current value is not null, we process the data.
+        if (curNull == null || !curNull.contains(j)) {
+          // If existing minimum value is null, we set the value directly.
+          if (nullBitmap != null && nullBitmap.contains(j)) {
+            _intValuesSV[j] = values.getLeft()[j];
+          } else {
+            _intValuesSV[j] = Math.min(_intValuesSV[j], values.getLeft()[j]);
+          }
+        }
+      }
+      if (nullBitmap != null && curNull != null) {
+        nullBitmap.and(curNull);
+      } else {
+        nullBitmap = null;
+      }
+    }
+    return ImmutablePair.of(_intValuesSV, nullBitmap);
+  }
+
   @Override
   public long[] transformToLongValuesSV(ValueBlock valueBlock) {
     int numDocs = valueBlock.getNumDocs();
@@ -59,6 +101,36 @@ public class LeastTransformFunction extends 
SelectTupleElementTransformFunction
     return _longValuesSV;
   }
 
+  @Override
+  public Pair<long[], RoaringBitmap> 
transformToLongValuesSVWithNull(ValueBlock valueBlock) {
+    int numDocs = valueBlock.getNumDocs();
+    initLongValuesSV(numDocs);
+    Pair<long[], RoaringBitmap> values = 
_arguments.get(0).transformToLongValuesSVWithNull(valueBlock);
+    System.arraycopy(values.getLeft(), 0, _longValuesSV, 0, numDocs);
+    RoaringBitmap nullBitmap = values.getRight();
+    for (int i = 1; i < _arguments.size(); i++) {
+      values = _arguments.get(i).transformToLongValuesSVWithNull(valueBlock);
+      RoaringBitmap curNull = values.getRight();
+      for (int j = 0; j < numDocs & j < values.getLeft().length; j++) {
+        // If current value is not null, we process the data.
+        if (curNull == null || !curNull.contains(j)) {
+          // If existing minimum value is null, we set the value directly.
+          if (nullBitmap != null && nullBitmap.contains(j)) {
+            _longValuesSV[j] = values.getLeft()[j];
+          } else {
+            _longValuesSV[j] = Math.min(_longValuesSV[j], values.getLeft()[j]);
+          }
+        }
+      }
+      if (nullBitmap != null && curNull != null) {
+        nullBitmap.and(curNull);
+      } else {
+        nullBitmap = null;
+      }
+    }
+    return ImmutablePair.of(_longValuesSV, nullBitmap);
+  }
+
   @Override
   public float[] transformToFloatValuesSV(ValueBlock valueBlock) {
     int numDocs = valueBlock.getNumDocs();
@@ -74,6 +146,36 @@ public class LeastTransformFunction extends 
SelectTupleElementTransformFunction
     return _floatValuesSV;
   }
 
+  @Override
+  public Pair<float[], RoaringBitmap> 
transformToFloatValuesSVWithNull(ValueBlock valueBlock) {
+    int numDocs = valueBlock.getNumDocs();
+    initFloatValuesSV(numDocs);
+    Pair<float[], RoaringBitmap> values = 
_arguments.get(0).transformToFloatValuesSVWithNull(valueBlock);
+    System.arraycopy(values.getLeft(), 0, _floatValuesSV, 0, numDocs);
+    RoaringBitmap nullBitmap = values.getRight();
+    for (int i = 1; i < _arguments.size(); i++) {
+      values = _arguments.get(i).transformToFloatValuesSVWithNull(valueBlock);
+      RoaringBitmap curNull = values.getRight();
+      for (int j = 0; j < numDocs & j < values.getLeft().length; j++) {
+        // If current value is not null, we process the data.
+        if (curNull != null || !curNull.contains(j)) {
+          // If existing minimum value is null, we set the value directly.
+          if (nullBitmap != null && nullBitmap.contains(j)) {
+            _floatValuesSV[j] = values.getLeft()[j];
+          } else {
+            _floatValuesSV[j] = Math.min(_floatValuesSV[j], 
values.getLeft()[j]);
+          }
+        }
+      }
+      if (nullBitmap != null && curNull != null) {
+        nullBitmap.and(curNull);
+      } else {
+        nullBitmap = null;
+      }
+    }
+    return ImmutablePair.of(_floatValuesSV, nullBitmap);
+  }
+
   @Override
   public double[] transformToDoubleValuesSV(ValueBlock valueBlock) {
     int numDocs = valueBlock.getNumDocs();
@@ -89,6 +191,36 @@ public class LeastTransformFunction extends 
SelectTupleElementTransformFunction
     return _doubleValuesSV;
   }
 
+  @Override
+  public Pair<double[], RoaringBitmap> 
transformToDoubleValuesSVWithNull(ValueBlock valueBlock) {
+    int numDocs = valueBlock.getNumDocs();
+    initDoubleValuesSV(numDocs);
+    Pair<double[], RoaringBitmap> values = 
_arguments.get(0).transformToDoubleValuesSVWithNull(valueBlock);
+    System.arraycopy(values.getLeft(), 0, _doubleValuesSV, 0, numDocs);
+    RoaringBitmap nullBitmap = values.getRight();
+    for (int i = 1; i < _arguments.size(); i++) {
+      values = _arguments.get(i).transformToDoubleValuesSVWithNull(valueBlock);
+      RoaringBitmap curNull = values.getRight();
+      for (int j = 0; j < numDocs & j < values.getLeft().length; j++) {
+        // If current value is not null, we process the data.
+        if (curNull == null || !curNull.contains(j)) {
+          // If existing minimum value is null, we set the value directly.
+          if (nullBitmap != null && nullBitmap.contains(j)) {
+            _doubleValuesSV[j] = values.getLeft()[j];
+          } else {
+            _doubleValuesSV[j] = Math.min(_doubleValuesSV[j], 
values.getLeft()[j]);
+          }
+        }
+      }
+      if (nullBitmap != null && curNull != null) {
+        nullBitmap.and(curNull);
+      } else {
+        nullBitmap = null;
+      }
+    }
+    return ImmutablePair.of(_doubleValuesSV, nullBitmap);
+  }
+
   @Override
   public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) {
     int numDocs = valueBlock.getNumDocs();
@@ -104,6 +236,36 @@ public class LeastTransformFunction extends 
SelectTupleElementTransformFunction
     return _bigDecimalValuesSV;
   }
 
+  @Override
+  public Pair<BigDecimal[], RoaringBitmap> 
transformToBigDecimalValuesSVWithNull(ValueBlock valueBlock) {
+    int numDocs = valueBlock.getNumDocs();
+    initBigDecimalValuesSV(numDocs);
+    Pair<BigDecimal[], RoaringBitmap> values = 
_arguments.get(0).transformToBigDecimalValuesSVWithNull(valueBlock);
+    System.arraycopy(values.getLeft(), 0, _bigDecimalValuesSV, 0, numDocs);
+    RoaringBitmap nullBitmap = values.getRight();
+    for (int i = 1; i < _arguments.size(); i++) {
+      values = 
_arguments.get(i).transformToBigDecimalValuesSVWithNull(valueBlock);
+      RoaringBitmap curNull = values.getRight();
+      for (int j = 0; j < numDocs & j < values.getLeft().length; j++) {
+        // If current value is not null, we process the data.
+        if (curNull == null || !curNull.contains(j)) {
+          // If existing minimum value is null, we set the value directly.
+          if (nullBitmap != null && nullBitmap.contains(j)) {
+            _bigDecimalValuesSV[j] = values.getLeft()[j];
+          } else {
+            _bigDecimalValuesSV[j] = 
_bigDecimalValuesSV[j].min(values.getLeft()[j]);
+          }
+        }
+      }
+      if (nullBitmap != null && curNull != null) {
+        nullBitmap.and(curNull);
+      } else {
+        nullBitmap = null;
+      }
+    }
+    return ImmutablePair.of(_bigDecimalValuesSV, nullBitmap);
+  }
+
   @Override
   public String[] transformToStringValuesSV(ValueBlock valueBlock) {
     int numDocs = valueBlock.getNumDocs();
@@ -120,4 +282,34 @@ public class LeastTransformFunction extends 
SelectTupleElementTransformFunction
     }
     return _stringValuesSV;
   }
+
+  @Override
+  public Pair<String[], RoaringBitmap> 
transformToStringValuesSVWithNull(ValueBlock valueBlock) {
+    int numDocs = valueBlock.getNumDocs();
+    initStringValuesSV(numDocs);
+    Pair<String[], RoaringBitmap> values = 
_arguments.get(0).transformToStringValuesSVWithNull(valueBlock);
+    System.arraycopy(values.getLeft(), 0, _stringValuesSV, 0, numDocs);
+    RoaringBitmap nullBitmap = values.getRight();
+    for (int i = 1; i < _arguments.size(); i++) {
+      values = _arguments.get(i).transformToStringValuesSVWithNull(valueBlock);
+      RoaringBitmap curNull = values.getRight();
+      for (int j = 0; j < numDocs & j < values.getLeft().length; j++) {
+        // If current value is not null, we process the data.
+        if (curNull == null || !curNull.contains(j)) {
+          // If existing minimum value is null, we set the value directly.
+          if (nullBitmap != null && nullBitmap.contains(j)) {
+            _stringValuesSV[j] = values.getLeft()[j];
+          } else if (_stringValuesSV[j].compareTo(values.getLeft()[j]) > 0) {
+            _stringValuesSV[j] = values.getLeft()[j];
+          }
+        }
+      }
+      if (nullBitmap != null && curNull != null) {
+        nullBitmap.and(curNull);
+      } else {
+        nullBitmap = null;
+      }
+    }
+    return ImmutablePair.of(_stringValuesSV, nullBitmap);
+  }
 }
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/SelectTupleElementTransformFunction.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/SelectTupleElementTransformFunction.java
index a4f29f8e9e..584efef5fd 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/SelectTupleElementTransformFunction.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/SelectTupleElementTransformFunction.java
@@ -23,15 +23,17 @@ import java.util.EnumSet;
 import java.util.List;
 import java.util.Map;
 import org.apache.pinot.core.operator.ColumnContext;
+import org.apache.pinot.core.operator.blocks.ValueBlock;
 import org.apache.pinot.core.operator.transform.TransformResultMetadata;
 import org.apache.pinot.spi.data.FieldSpec;
+import org.roaringbitmap.RoaringBitmap;
 
 
 public abstract class SelectTupleElementTransformFunction extends 
BaseTransformFunction {
 
   private static final EnumSet<FieldSpec.DataType> SUPPORTED_DATATYPES = 
EnumSet.of(FieldSpec.DataType.INT,
       FieldSpec.DataType.LONG, FieldSpec.DataType.FLOAT, 
FieldSpec.DataType.DOUBLE, FieldSpec.DataType.BIG_DECIMAL,
-      FieldSpec.DataType.TIMESTAMP, FieldSpec.DataType.STRING);
+      FieldSpec.DataType.TIMESTAMP, FieldSpec.DataType.STRING, 
FieldSpec.DataType.UNKNOWN);
 
   private static final EnumMap<FieldSpec.DataType, 
EnumSet<FieldSpec.DataType>> ACCEPTABLE_COMBINATIONS =
       createAcceptableCombinations();
@@ -63,7 +65,8 @@ public abstract class SelectTupleElementTransformFunction 
extends BaseTransformF
       }
       if (dataType == null) {
         dataType = argumentType;
-      } else if (ACCEPTABLE_COMBINATIONS.get(dataType).contains(argumentType)) 
{
+      } else if (dataType.isUnknown() || argumentType.isUnknown() || 
ACCEPTABLE_COMBINATIONS.get(dataType)
+          .contains(argumentType)) {
         dataType = getLowestCommonDenominatorType(dataType, argumentType);
       } else {
         throw new IllegalArgumentException(
@@ -84,6 +87,25 @@ public abstract class SelectTupleElementTransformFunction 
extends BaseTransformF
     return _name;
   }
 
+  @Override
+  public RoaringBitmap getNullBitmap(ValueBlock valueBlock) {
+    RoaringBitmap bitmap = _arguments.get(0).getNullBitmap(valueBlock);
+    if (bitmap == null || bitmap.isEmpty()) {
+      return bitmap;
+    }
+    for (int i = 1; i < _arguments.size(); i++) {
+      RoaringBitmap curBitmap = _arguments.get(i).getNullBitmap(valueBlock);
+      if (curBitmap == null || curBitmap.isEmpty()) {
+        return curBitmap;
+      }
+      bitmap.and(curBitmap);
+      if (bitmap.isEmpty()) {
+        return null;
+      }
+    }
+    return bitmap;
+  }
+
   private static FieldSpec.DataType 
getLowestCommonDenominatorType(FieldSpec.DataType left, FieldSpec.DataType 
right) {
     if (left == null || left == right) {
       return right;
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TupleSelectionTransformFunctionsTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TupleSelectionTransformFunctionsTest.java
index ac96cbf344..0ad118dbdd 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TupleSelectionTransformFunctionsTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TupleSelectionTransformFunctionsTest.java
@@ -19,11 +19,13 @@
 package org.apache.pinot.core.operator.transform.function;
 
 import java.math.BigDecimal;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.pinot.common.function.TransformFunctionType;
 import org.apache.pinot.common.request.context.ExpressionContext;
 import org.apache.pinot.common.request.context.RequestContextUtils;
 import org.apache.pinot.spi.data.FieldSpec;
 import org.apache.pinot.spi.exception.BadQueryRequestException;
+import org.roaringbitmap.RoaringBitmap;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
@@ -214,6 +216,65 @@ public class TupleSelectionTransformFunctionsTest extends 
BaseTransformFunctionT
     }
   }
 
+  @Test
+  public void testLeastTransformFunctionNullLiteral() {
+    TransformFunction transformFunction =
+        testLeastPreconditions(String.format("least(%s, null, %s)", 
INT_SV_COLUMN, DOUBLE_SV_COLUMN));
+    assertEquals(transformFunction.getResultMetadata().getDataType(), 
FieldSpec.DataType.DOUBLE);
+    Pair<double[], RoaringBitmap> doubleValues = 
transformFunction.transformToDoubleValuesSVWithNull(_projectionBlock);
+    for (int i = 0; i < NUM_ROWS; i++) {
+      assertEquals(doubleValues.getLeft()[i], Math.min(_intSVValues[i], 
_doubleSVValues[i]));
+    }
+    assertEquals(doubleValues.getRight(), null);
+    assertEquals(transformFunction.getNullBitmap(_projectionBlock), null);
+  }
+
+  @Test
+  public void testLeastTransformFunctionNullColumn() {
+    TransformFunction transformFunction =
+        testLeastPreconditions(String.format("least(%s, null, %s)", 
INT_SV_NULL_COLUMN, DOUBLE_SV_COLUMN));
+    assertEquals(transformFunction.getResultMetadata().getDataType(), 
FieldSpec.DataType.DOUBLE);
+    Pair<double[], RoaringBitmap> doubleValues = 
transformFunction.transformToDoubleValuesSVWithNull(_projectionBlock);
+    for (int i = 0; i < NUM_ROWS; i++) {
+      if (i % 2 == 0) {
+        assertEquals(doubleValues.getLeft()[i], Math.min(_intSVValues[i], 
_doubleSVValues[i]));
+      } else {
+        assertEquals(doubleValues.getLeft()[i], _doubleSVValues[i]);
+      }
+    }
+    assertEquals(doubleValues.getRight(), null);
+    assertEquals(transformFunction.getNullBitmap(_projectionBlock), null);
+  }
+
+  @Test
+  public void testLeastTransformFunctionAllNulls() {
+    TransformFunction transformFunction = 
testLeastPreconditions(String.format("least(null, null, null)"));
+    assertEquals(transformFunction.getResultMetadata().getDataType(), 
FieldSpec.DataType.UNKNOWN);
+    Pair<double[], RoaringBitmap> doubleValues = 
transformFunction.transformToDoubleValuesSVWithNull(_projectionBlock);
+    RoaringBitmap expectedNull = new RoaringBitmap();
+    expectedNull.add(0L, NUM_ROWS);
+    assertEquals(doubleValues.getRight(), expectedNull);
+    assertEquals(transformFunction.getNullBitmap(_projectionBlock), 
expectedNull);
+  }
+
+  @Test
+  public void testLeastTransformFunctionPartialAllNulls() {
+    TransformFunction transformFunction = testLeastPreconditions(
+        String.format("least(%s, %s, %s)", INT_SV_NULL_COLUMN, 
INT_SV_NULL_COLUMN, INT_SV_NULL_COLUMN));
+    assertEquals(transformFunction.getResultMetadata().getDataType(), 
FieldSpec.DataType.INT);
+    Pair<int[], RoaringBitmap> intValues = 
transformFunction.transformToIntValuesSVWithNull(_projectionBlock);
+    RoaringBitmap expectedNull = new RoaringBitmap();
+    for (int i = 0; i < NUM_ROWS; i++) {
+      if (i % 2 == 0) {
+        assertEquals(intValues.getLeft()[i], _intSVValues[i]);
+      } else {
+        expectedNull.add(i);
+      }
+    }
+    assertEquals(intValues.getRight(), expectedNull);
+    assertEquals(transformFunction.getNullBitmap(_projectionBlock), 
expectedNull);
+  }
+
   @Test(dataProvider = "rejectedParameters", expectedExceptions = 
BadQueryRequestException.class)
   public void testRejectLeast(String params) {
     testGreatestPreconditions("least" + params);
@@ -303,6 +364,65 @@ public class TupleSelectionTransformFunctionsTest extends 
BaseTransformFunctionT
     }
   }
 
+  @Test
+  public void testGreatestTransformFunctionNullLiteral() {
+    TransformFunction transformFunction =
+        testGreatestPreconditions(String.format("greatest(%s, null, %s)", 
INT_SV_COLUMN, DOUBLE_SV_COLUMN));
+    assertEquals(transformFunction.getResultMetadata().getDataType(), 
FieldSpec.DataType.DOUBLE);
+    Pair<double[], RoaringBitmap> doubleValues = 
transformFunction.transformToDoubleValuesSVWithNull(_projectionBlock);
+    for (int i = 0; i < NUM_ROWS; i++) {
+      assertEquals(doubleValues.getLeft()[i], Math.max(_intSVValues[i], 
_doubleSVValues[i]));
+    }
+    assertEquals(doubleValues.getRight(), null);
+    assertEquals(transformFunction.getNullBitmap(_projectionBlock), null);
+  }
+
+  @Test
+  public void testGreatestTransformFunctionNullColumn() {
+    TransformFunction transformFunction =
+        testGreatestPreconditions(String.format("greatest(%s, null, %s)", 
INT_SV_NULL_COLUMN, DOUBLE_SV_COLUMN));
+    assertEquals(transformFunction.getResultMetadata().getDataType(), 
FieldSpec.DataType.DOUBLE);
+    Pair<double[], RoaringBitmap> doubleValues = 
transformFunction.transformToDoubleValuesSVWithNull(_projectionBlock);
+    for (int i = 0; i < NUM_ROWS; i++) {
+      if (i % 2 == 0) {
+        assertEquals(doubleValues.getLeft()[i], Math.max(_intSVValues[i], 
_doubleSVValues[i]));
+      } else {
+        assertEquals(doubleValues.getLeft()[i], _doubleSVValues[i]);
+      }
+    }
+    assertEquals(doubleValues.getRight(), null);
+    assertEquals(transformFunction.getNullBitmap(_projectionBlock), null);
+  }
+
+  @Test
+  public void testGreatestTransformFunctionAllNulls() {
+    TransformFunction transformFunction = 
testGreatestPreconditions(String.format("greatest(null, null, null)"));
+    assertEquals(transformFunction.getResultMetadata().getDataType(), 
FieldSpec.DataType.UNKNOWN);
+    Pair<double[], RoaringBitmap> doubleValues = 
transformFunction.transformToDoubleValuesSVWithNull(_projectionBlock);
+    RoaringBitmap expectedNull = new RoaringBitmap();
+    expectedNull.add(0L, NUM_ROWS);
+    assertEquals(doubleValues.getRight(), expectedNull);
+    assertEquals(transformFunction.getNullBitmap(_projectionBlock), 
expectedNull);
+  }
+
+  @Test
+  public void testGreatestTransformFunctionPartialAllNulls() {
+    TransformFunction transformFunction = testGreatestPreconditions(
+        String.format("greatest(%s, %s, %s)", INT_SV_NULL_COLUMN, 
INT_SV_NULL_COLUMN, INT_SV_NULL_COLUMN));
+    assertEquals(transformFunction.getResultMetadata().getDataType(), 
FieldSpec.DataType.INT);
+    Pair<int[], RoaringBitmap> intValues = 
transformFunction.transformToIntValuesSVWithNull(_projectionBlock);
+    RoaringBitmap expectedNull = new RoaringBitmap();
+    for (int i = 0; i < NUM_ROWS; i++) {
+      if (i % 2 == 0) {
+        assertEquals(intValues.getLeft()[i], _intSVValues[i]);
+      } else {
+        expectedNull.add(i);
+      }
+    }
+    assertEquals(intValues.getRight(), expectedNull);
+    assertEquals(transformFunction.getNullBitmap(_projectionBlock), 
expectedNull);
+  }
+
   @Test
   public void testGreatestTransformFunctionString() {
     TransformFunction transformFunction = testGreatestPreconditions(


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

Reply via email to