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 98969168f4e Adding logical functions for Apache Pinot (#17189)
98969168f4e is described below

commit 98969168f4e0768a71e32bc6fac4469d8461a34e
Author: Akanksha kedia <[email protected]>
AuthorDate: Mon Dec 8 06:01:30 2025 +0530

    Adding logical functions for Apache Pinot (#17189)
---
 .../common/function/scalar/LogicalFunctions.java   | 136 ++++++-
 .../function/scalar/LogicalFunctionsTest.java      | 445 +++++++++++++++++++++
 .../pinot/core/function/FunctionRegistryTest.java  |   4 +-
 3 files changed, 578 insertions(+), 7 deletions(-)

diff --git 
a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/LogicalFunctions.java
 
b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/LogicalFunctions.java
index af6bbd95490..cd1d7fa2454 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/LogicalFunctions.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/LogicalFunctions.java
@@ -18,20 +18,146 @@
  */
 package org.apache.pinot.common.function.scalar;
 
+import javax.annotation.Nullable;
 import org.apache.pinot.spi.annotations.ScalarFunction;
 
+
 /**
- * Logical transformation on boolean values. Currently, only not is supported.
+ * Inbuilt logical transform functions with Trino-compatible NULL handling
+ *
+ * <p>These functions implement logical operators (AND, OR, NOT) with proper 
three-valued logic
+ * support, following the SQL standard and Trino's behavior for NULL handling.
+ *
+ * <p>Truth tables for NULL handling:
+ * <ul>
+ *   <li>AND: Returns NULL if one side is NULL and the other is not FALSE.
+ *       Returns FALSE if at least one side is FALSE.</li>
+ *   <li>OR: Returns NULL if one side is NULL and the other is not TRUE.
+ *       Returns TRUE if at least one side is TRUE.</li>
+ *   <li>NOT: Returns NULL if the input is NULL.</li>
+ * </ul>
+ *
+ * <p>Examples:
+ * <pre>
+ * and(true, true) → true
+ * and(true, false) → false
+ * and(true, null) → null
+ * and(false, null) → false
+ *
+ * or(true, false) → true
+ * or(false, false) → false
+ * or(false, null) → null
+ * or(true, null) → true
+ *
+ * not(true) → false
+ * not(false) → true
+ * not(null) → null
+ * </pre>
  */
 public class LogicalFunctions {
+
   private LogicalFunctions() {
   }
 
   /**
-   * Returns logical negate of value
+   * Logical AND operator with Trino-compatible NULL handling.
+   *
+   * <p>Truth table:
+   * <pre>
+   * a     | b     | result
+   * ------|-------|-------
+   * TRUE  | TRUE  | TRUE
+   * TRUE  | FALSE | FALSE
+   * TRUE  | NULL  | NULL
+   * FALSE | TRUE  | FALSE
+   * FALSE | FALSE | FALSE
+   * FALSE | NULL  | FALSE
+   * NULL  | TRUE  | NULL
+   * NULL  | FALSE | FALSE
+   * NULL  | NULL  | NULL
+   * </pre>
+   *
+   * @param a First boolean value (nullable)
+   * @param b Second boolean value (nullable)
+   * @return The logical AND result, or NULL if result cannot be determined
+   */
+  @Nullable
+  @ScalarFunction(nullableParameters = true)
+  public static Boolean and(@Nullable Boolean a, @Nullable Boolean b) {
+    // If either value is FALSE, the result is FALSE
+    if (Boolean.FALSE.equals(a) || Boolean.FALSE.equals(b)) {
+      return false;
+    }
+
+    // If both values are TRUE, the result is TRUE
+    if (Boolean.TRUE.equals(a) && Boolean.TRUE.equals(b)) {
+      return true;
+    }
+
+    // Otherwise, at least one value is NULL and neither is FALSE, so result 
is NULL
+    return null;
+  }
+
+  /**
+   * Logical OR operator with Trino-compatible NULL handling.
+   *
+   * <p>Truth table:
+   * <pre>
+   * a     | b     | result
+   * ------|-------|-------
+   * TRUE  | TRUE  | TRUE
+   * TRUE  | FALSE | TRUE
+   * TRUE  | NULL  | TRUE
+   * FALSE | TRUE  | TRUE
+   * FALSE | FALSE | FALSE
+   * FALSE | NULL  | NULL
+   * NULL  | TRUE  | TRUE
+   * NULL  | FALSE | NULL
+   * NULL  | NULL  | NULL
+   * </pre>
+   *
+   * @param a First boolean value (nullable)
+   * @param b Second boolean value (nullable)
+   * @return The logical OR result, or NULL if the result cannot be determined
+   */
+  @Nullable
+  @ScalarFunction(nullableParameters = true)
+  public static Boolean or(@Nullable Boolean a, @Nullable Boolean b) {
+    // If either value is TRUE, the result is TRUE
+    if (Boolean.TRUE.equals(a) || Boolean.TRUE.equals(b)) {
+      return true;
+    }
+
+    // If both values are FALSE, the result is FALSE
+    if (Boolean.FALSE.equals(a) && Boolean.FALSE.equals(b)) {
+      return false;
+    }
+
+    // Otherwise, at least one value is NULL and neither is TRUE, so result is 
NULL
+    return null;
+  }
+
+  /**
+   * Logical NOT operator with Trino-compatible NULL handling.
+   *
+   * <p>Truth table:
+   * <pre>
+   * a     | result
+   * ------|-------
+   * TRUE  | FALSE
+   * FALSE | TRUE
+   * NULL  | NULL
+   * </pre>
+   *
+   * @param a Boolean value to negate (nullable)
+   * @return The logical negation of the input, or NULL if the input is NULL
    */
-  @ScalarFunction
-  public static boolean not(boolean value) {
-    return !value;
+  @Nullable
+  @ScalarFunction(nullableParameters = true)
+  public static Boolean not(@Nullable Boolean a) {
+    if (a == null) {
+      return null;
+    }
+    return !a;
   }
 }
diff --git 
a/pinot-common/src/test/java/org/apache/pinot/common/function/scalar/LogicalFunctionsTest.java
 
b/pinot-common/src/test/java/org/apache/pinot/common/function/scalar/LogicalFunctionsTest.java
new file mode 100644
index 00000000000..8e307340c90
--- /dev/null
+++ 
b/pinot-common/src/test/java/org/apache/pinot/common/function/scalar/LogicalFunctionsTest.java
@@ -0,0 +1,445 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.common.function.scalar;
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+
+/**
+ * Tests for Logical functions with Trino-compatible NULL handling
+ */
+public class LogicalFunctionsTest {
+
+  // ==================== Tests for and ====================
+
+  @Test
+  public void testAndTrueTrue() {
+    Boolean result = LogicalFunctions.and(true, true);
+    assertTrue(result);
+  }
+
+  @Test
+  public void testAndTrueFalse() {
+    Boolean result = LogicalFunctions.and(true, false);
+    assertFalse(result);
+  }
+
+  @Test
+  public void testAndFalseTrue() {
+    Boolean result = LogicalFunctions.and(false, true);
+    assertFalse(result);
+  }
+
+  @Test
+  public void testAndFalseFalse() {
+    Boolean result = LogicalFunctions.and(false, false);
+    assertFalse(result);
+  }
+
+  @Test
+  public void testAndTrueNull() {
+    Boolean result = LogicalFunctions.and(true, null);
+    assertNull(result, "AND with TRUE and NULL should return NULL");
+  }
+
+  @Test
+  public void testAndNullTrue() {
+    Boolean result = LogicalFunctions.and(null, true);
+    assertNull(result, "AND with NULL and TRUE should return NULL");
+  }
+
+  @Test
+  public void testAndFalseNull() {
+    Boolean result = LogicalFunctions.and(false, null);
+    assertFalse(result, "AND with FALSE and NULL should return FALSE");
+  }
+
+  @Test
+  public void testAndNullFalse() {
+    Boolean result = LogicalFunctions.and(null, false);
+    assertFalse(result, "AND with NULL and FALSE should return FALSE");
+  }
+
+  @Test
+  public void testAndNullNull() {
+    Boolean result = LogicalFunctions.and(null, null);
+    assertNull(result, "AND with NULL and NULL should return NULL");
+  }
+
+  // ==================== Tests for or ====================
+
+  @Test
+  public void testOrTrueTrue() {
+    Boolean result = LogicalFunctions.or(true, true);
+    assertTrue(result);
+  }
+
+  @Test
+  public void testOrTrueFalse() {
+    Boolean result = LogicalFunctions.or(true, false);
+    assertTrue(result);
+  }
+
+  @Test
+  public void testOrFalseTrue() {
+    Boolean result = LogicalFunctions.or(false, true);
+    assertTrue(result);
+  }
+
+  @Test
+  public void testOrFalseFalse() {
+    Boolean result = LogicalFunctions.or(false, false);
+    assertFalse(result);
+  }
+
+  @Test
+  public void testOrTrueNull() {
+    Boolean result = LogicalFunctions.or(true, null);
+    assertTrue(result, "OR with TRUE and NULL should return TRUE");
+  }
+
+  @Test
+  public void testOrNullTrue() {
+    Boolean result = LogicalFunctions.or(null, true);
+    assertTrue(result, "OR with NULL and TRUE should return TRUE");
+  }
+
+  @Test
+  public void testOrFalseNull() {
+    Boolean result = LogicalFunctions.or(false, null);
+    assertNull(result, "OR with FALSE and NULL should return NULL");
+  }
+
+  @Test
+  public void testOrNullFalse() {
+    Boolean result = LogicalFunctions.or(null, false);
+    assertNull(result, "OR with NULL and FALSE should return NULL");
+  }
+
+  @Test
+  public void testOrNullNull() {
+    Boolean result = LogicalFunctions.or(null, null);
+    assertNull(result, "OR with NULL and NULL should return NULL");
+  }
+
+  // ==================== Tests for not ====================
+
+  @Test
+  public void testNotTrue() {
+    Boolean result = LogicalFunctions.not(true);
+    assertFalse(result);
+  }
+
+  @Test
+  public void testNotFalse() {
+    Boolean result = LogicalFunctions.not(false);
+    assertTrue(result);
+  }
+
+  @Test
+  public void testNotNull() {
+    Boolean result = LogicalFunctions.not(null);
+    assertNull(result, "NOT with NULL should return NULL");
+  }
+
+  // ==================== Complex Combination Tests ====================
+
+  @Test
+  public void testComplexAndOrCombinations() {
+    // (TRUE AND FALSE) OR TRUE = FALSE OR TRUE = TRUE
+    Boolean andResult = LogicalFunctions.and(true, false);
+    Boolean finalResult = LogicalFunctions.or(andResult, true);
+    assertTrue(finalResult);
+
+    // (TRUE OR FALSE) AND FALSE = TRUE AND FALSE = FALSE
+    Boolean orResult = LogicalFunctions.or(true, false);
+    finalResult = LogicalFunctions.and(orResult, false);
+    assertFalse(finalResult);
+
+    // (NULL AND TRUE) OR FALSE = NULL OR FALSE = NULL
+    andResult = LogicalFunctions.and(null, true);
+    finalResult = LogicalFunctions.or(andResult, false);
+    assertNull(finalResult);
+
+    // (NULL OR TRUE) AND TRUE = TRUE AND TRUE = TRUE
+    orResult = LogicalFunctions.or(null, true);
+    finalResult = LogicalFunctions.and(orResult, true);
+    assertTrue(finalResult);
+  }
+
+  @Test
+  public void testComplexNotCombinations() {
+    // NOT(TRUE AND FALSE) = NOT(FALSE) = TRUE
+    Boolean andResult = LogicalFunctions.and(true, false);
+    Boolean notResult = LogicalFunctions.not(andResult);
+    assertTrue(notResult);
+
+    // NOT(TRUE OR FALSE) = NOT(TRUE) = FALSE
+    Boolean orResult = LogicalFunctions.or(true, false);
+    notResult = LogicalFunctions.not(orResult);
+    assertFalse(notResult);
+
+    // NOT(NULL AND TRUE) = NOT(NULL) = NULL
+    andResult = LogicalFunctions.and(null, true);
+    notResult = LogicalFunctions.not(andResult);
+    assertNull(notResult);
+
+    // NOT(NULL OR FALSE) = NOT(NULL) = NULL
+    orResult = LogicalFunctions.or(null, false);
+    notResult = LogicalFunctions.not(orResult);
+    assertNull(notResult);
+  }
+
+  @Test
+  public void testDeMorgansLaws() {
+    // De Morgan's Law: NOT(A AND B) = NOT(A) OR NOT(B)
+    // Test with TRUE and FALSE
+    Boolean notAndResult = LogicalFunctions.not(LogicalFunctions.and(true, 
false));
+    Boolean notAOrNotB = LogicalFunctions.or(
+        LogicalFunctions.not(true),
+        LogicalFunctions.not(false)
+    );
+    assertEquals(notAndResult, notAOrNotB);
+
+    // De Morgan's Law: NOT(A OR B) = NOT(A) AND NOT(B)
+    // Test with TRUE and FALSE
+    Boolean notOrResult = LogicalFunctions.not(LogicalFunctions.or(true, 
false));
+    Boolean notAAndNotB = LogicalFunctions.and(
+        LogicalFunctions.not(true),
+        LogicalFunctions.not(false)
+    );
+    assertEquals(notOrResult, notAAndNotB);
+  }
+
+  // ==================== Edge Cases and Truth Table Verification 
====================
+
+  @Test
+  public void testAndTruthTableCompleteness() {
+    // Verify all 9 combinations in the AND truth table
+    assertTrue(LogicalFunctions.and(true, true));
+    assertFalse(LogicalFunctions.and(true, false));
+    assertNull(LogicalFunctions.and(true, null));
+
+    assertFalse(LogicalFunctions.and(false, true));
+    assertFalse(LogicalFunctions.and(false, false));
+    assertFalse(LogicalFunctions.and(false, null));
+
+    assertNull(LogicalFunctions.and(null, true));
+    assertFalse(LogicalFunctions.and(null, false));
+    assertNull(LogicalFunctions.and(null, null));
+  }
+
+  @Test
+  public void testOrTruthTableCompleteness() {
+    // Verify all 9 combinations in the OR truth table
+    assertTrue(LogicalFunctions.or(true, true));
+    assertTrue(LogicalFunctions.or(true, false));
+    assertTrue(LogicalFunctions.or(true, null));
+
+    assertTrue(LogicalFunctions.or(false, true));
+    assertFalse(LogicalFunctions.or(false, false));
+    assertNull(LogicalFunctions.or(false, null));
+
+    assertTrue(LogicalFunctions.or(null, true));
+    assertNull(LogicalFunctions.or(null, false));
+    assertNull(LogicalFunctions.or(null, null));
+  }
+
+  @Test
+  public void testNotTruthTableCompleteness() {
+    // Verify all 3 combinations in the NOT truth table
+    assertFalse(LogicalFunctions.not(true));
+    assertTrue(LogicalFunctions.not(false));
+    assertNull(LogicalFunctions.not(null));
+  }
+
+  // ==================== Commutative Property Tests ====================
+
+  @Test
+  public void testAndCommutativeProperty() {
+    // AND should be commutative: A AND B = B AND A
+    assertEquals(
+        LogicalFunctions.and(true, false),
+        LogicalFunctions.and(false, true)
+    );
+
+    assertEquals(
+        LogicalFunctions.and(true, null),
+        LogicalFunctions.and(null, true)
+    );
+
+    assertEquals(
+        LogicalFunctions.and(false, null),
+        LogicalFunctions.and(null, false)
+    );
+  }
+
+  @Test
+  public void testOrCommutativeProperty() {
+    // OR should be commutative: A OR B = B OR A
+    assertEquals(
+        LogicalFunctions.or(true, false),
+        LogicalFunctions.or(false, true)
+    );
+
+    assertEquals(
+        LogicalFunctions.or(true, null),
+        LogicalFunctions.or(null, true)
+    );
+
+    assertEquals(
+        LogicalFunctions.or(false, null),
+        LogicalFunctions.or(null, false)
+    );
+  }
+
+  // ==================== Associative Property Tests ====================
+
+  @Test
+  public void testAndAssociativeProperty() {
+    // AND should be associative: (A AND B) AND C = A AND (B AND C)
+    Boolean left = LogicalFunctions.and(
+        LogicalFunctions.and(true, false),
+        true
+    );
+    Boolean right = LogicalFunctions.and(
+        true,
+        LogicalFunctions.and(false, true)
+    );
+    assertEquals(left, right);
+
+    // Test with NULL
+    left = LogicalFunctions.and(
+        LogicalFunctions.and(true, null),
+        false
+    );
+    right = LogicalFunctions.and(
+        true,
+        LogicalFunctions.and(null, false)
+    );
+    assertEquals(left, right);
+  }
+
+  @Test
+  public void testOrAssociativeProperty() {
+    // OR should be associative: (A OR B) OR C = A OR (B OR C)
+    Boolean left = LogicalFunctions.or(
+        LogicalFunctions.or(false, false),
+        true
+    );
+    Boolean right = LogicalFunctions.or(
+        false,
+        LogicalFunctions.or(false, true)
+    );
+    assertEquals(left, right);
+
+    // Test with NULL
+    left = LogicalFunctions.or(
+        LogicalFunctions.or(false, null),
+        true
+    );
+    right = LogicalFunctions.or(
+        false,
+        LogicalFunctions.or(null, true)
+    );
+    assertEquals(left, right);
+  }
+
+  // ==================== Identity and Absorption Tests ====================
+
+  @Test
+  public void testAndIdentityElement() {
+    // TRUE is the identity element for AND: A AND TRUE = A
+    assertEquals(LogicalFunctions.and(true, true), true);
+    assertEquals(LogicalFunctions.and(false, true), false);
+    assertEquals(LogicalFunctions.and(null, true), null);
+  }
+
+  @Test
+  public void testOrIdentityElement() {
+    // FALSE is the identity element for OR: A OR FALSE = A
+    assertEquals(LogicalFunctions.or(true, false), true);
+    assertEquals(LogicalFunctions.or(false, false), false);
+    assertEquals(LogicalFunctions.or(null, false), null);
+  }
+
+  @Test
+  public void testAndAbsorptionElement() {
+    // FALSE is the absorption element for AND: A AND FALSE = FALSE
+    assertFalse(LogicalFunctions.and(true, false));
+    assertFalse(LogicalFunctions.and(false, false));
+    assertFalse(LogicalFunctions.and(null, false));
+  }
+
+  @Test
+  public void testOrAbsorptionElement() {
+    // TRUE is the absorption element for OR: A OR TRUE = TRUE
+    assertTrue(LogicalFunctions.or(true, true));
+    assertTrue(LogicalFunctions.or(false, true));
+    assertTrue(LogicalFunctions.or(null, true));
+  }
+
+  // ==================== Double Negation Tests ====================
+
+  @Test
+  public void testDoubleNegation() {
+    // NOT(NOT(A)) = A
+    assertEquals(
+        LogicalFunctions.not(LogicalFunctions.not(true)),
+        true
+    );
+    assertEquals(
+        LogicalFunctions.not(LogicalFunctions.not(false)),
+        false
+    );
+    assertNull(LogicalFunctions.not(LogicalFunctions.not(null)));
+  }
+
+  // ==================== Trino Compatibility Examples ====================
+
+  @Test
+  public void testTrinoExampleFromDocumentation() {
+    // Examples from Trino documentation:
+    // CAST(null AS boolean) AND true → null
+    assertNull(LogicalFunctions.and(null, true));
+
+    // CAST(null AS boolean) AND false → false
+    assertFalse(LogicalFunctions.and(null, false));
+
+    // CAST(null AS boolean) AND CAST(null AS boolean) → null
+    assertNull(LogicalFunctions.and(null, null));
+
+    // CAST(null AS boolean) OR CAST(null AS boolean) → null
+    assertNull(LogicalFunctions.or(null, null));
+
+    // CAST(null AS boolean) OR false → null
+    assertNull(LogicalFunctions.or(null, false));
+
+    // CAST(null AS boolean) OR true → true
+    assertTrue(LogicalFunctions.or(null, true));
+
+    // NOT CAST(null AS boolean) → null
+    assertNull(LogicalFunctions.not(null));
+  }
+}
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/function/FunctionRegistryTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/function/FunctionRegistryTest.java
index 78600325be4..82c6524ccc8 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/function/FunctionRegistryTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/function/FunctionRegistryTest.java
@@ -42,7 +42,7 @@ public class FunctionRegistryTest {
       // TODO: Support these functions
       TransformFunctionType.IN, TransformFunctionType.NOT_IN, 
TransformFunctionType.IS_TRUE,
       TransformFunctionType.IS_NOT_TRUE, TransformFunctionType.IS_FALSE, 
TransformFunctionType.IS_NOT_FALSE,
-      TransformFunctionType.AND, TransformFunctionType.OR, 
TransformFunctionType.JSON_EXTRACT_SCALAR,
+      TransformFunctionType.JSON_EXTRACT_SCALAR,
       TransformFunctionType.JSON_EXTRACT_KEY, 
TransformFunctionType.TIME_CONVERT,
       TransformFunctionType.DATE_TIME_CONVERT_WINDOW_HOP, 
TransformFunctionType.ARRAY_LENGTH,
       TransformFunctionType.ARRAY_AVERAGE, TransformFunctionType.ARRAY_MIN, 
TransformFunctionType.ARRAY_MAX,
@@ -54,7 +54,7 @@ public class FunctionRegistryTest {
       // Special filter functions without implementation
       FilterKind.TEXT_MATCH, FilterKind.TEXT_CONTAINS, FilterKind.JSON_MATCH, 
FilterKind.VECTOR_SIMILARITY,
       // TODO: Support these functions
-      FilterKind.AND, FilterKind.OR, FilterKind.RANGE, FilterKind.IN, 
FilterKind.NOT_IN);
+      FilterKind.RANGE, FilterKind.IN, FilterKind.NOT_IN);
 
   @Test
   public void testTransformAndFilterFunctionsRegistered() {


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

Reply via email to