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

zhenchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new ed71c9a705 [CALCITE-7079] MongoDB Adapter unable to translate multiple 
NOT EQUALS expressions combined with AND
ed71c9a705 is described below

commit ed71c9a70539a2401b0578b786366918abbaeebe
Author: Wang Zhao <[email protected]>
AuthorDate: Wed Jul 16 00:16:56 2025 +0800

    [CALCITE-7079] MongoDB Adapter unable to translate multiple NOT EQUALS 
expressions combined with AND
---
 .../calcite/adapter/mongodb/MongoFilter.java       | 37 +++++++++-
 .../calcite/adapter/mongodb/MongoAdapterTest.java  | 82 ++++++++++++++++++++++
 2 files changed, 118 insertions(+), 1 deletion(-)

diff --git 
a/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoFilter.java 
b/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoFilter.java
index 455877aa94..062623bcdd 100644
--- a/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoFilter.java
+++ b/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoFilter.java
@@ -24,12 +24,14 @@
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Filter;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexBuilder;
 import org.apache.calcite.rex.RexCall;
 import org.apache.calcite.rex.RexInputRef;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.util.JsonBuilder;
 import org.apache.calcite.util.Pair;
 
@@ -133,7 +135,26 @@ private Map<String, Object> translateAnd(RexNode node0) {
           : multimap.asMap().entrySet()) {
         Map<String, Object> map2 = builder.map();
         for (Pair<String, RexLiteral> s : entry.getValue()) {
-          addPredicate(map2, s.left, literalValue(s.right));
+          String op = s.left;
+          if ("$ne".equals(op))  {
+            if (map2.containsKey("$nin")) {
+              map2.computeIfPresent("$nin", (k, v) -> {
+                ((List<Object>) v).add(literalValue(s.right));
+                return v;
+              });
+            } else if (map2.containsKey(op)) {
+              // if two $ne conditions, translate to $nin op
+              List<Object> ninList = builder.list();
+              ninList.add(map2.remove(op));
+              ninList.add(literalValue(s.right));
+              map2.put("$nin", ninList);
+            } else {
+              // only one $ne condition
+              map2.put(op, literalValue(s.right));
+            }
+          } else {
+            addPredicate(map2, op, literalValue(s.right));
+          }
         }
         map.put(entry.getKey(), map2);
       }
@@ -197,6 +218,10 @@ private Void translateMatch2(RexNode node, 
List<Map<String, Object>> orMapList,
         return translateBinary("$gte", "$lte", (RexCall) node, multimap, 
eqMap);
       case OR:
         return translateOrAddToList(node, orMapList);
+      case IS_NOT_NULL:
+        return translateUnary("$ne", (RexCall) node, multimap, eqMap);
+      case IS_NULL:
+        return translateUnary("$eq", (RexCall) node, multimap, eqMap);
       default:
         throw new AssertionError("cannot translate " + node);
       }
@@ -267,5 +292,15 @@ private void translateOp2(String op, String name, 
RexLiteral right,
         multimap.put(name, Pair.of(op, right));
       }
     }
+
+    /** Translates is null/is not null to {$eq: null}/{$ne: null}. */
+    private Void translateUnary(String op, RexCall call,
+        Multimap<String, Pair<String, RexLiteral>> multimap, Map<String, 
RexLiteral> eqMap) {
+      final RexNode left = call.operands.get(0);
+      RelDataType nullType = 
rexBuilder.getTypeFactory().createSqlType(SqlTypeName.NULL);
+      final RexNode right = rexBuilder.makeNullLiteral(nullType);
+      translateBinary2(op, left, right, multimap, eqMap);
+      return null;
+    }
   }
 }
diff --git 
a/mongodb/src/test/java/org/apache/calcite/adapter/mongodb/MongoAdapterTest.java
 
b/mongodb/src/test/java/org/apache/calcite/adapter/mongodb/MongoAdapterTest.java
index 1876862d2e..8d7dff5fd9 100644
--- 
a/mongodb/src/test/java/org/apache/calcite/adapter/mongodb/MongoAdapterTest.java
+++ 
b/mongodb/src/test/java/org/apache/calcite/adapter/mongodb/MongoAdapterTest.java
@@ -919,4 +919,86 @@ private static Consumer<List> mongoChecker(final String... 
expected) {
                 "{$sort: {STATE: 1}}"))
         .returns("STATE=ME; CITY=LEWISTON\nSTATE=VT; CITY=BRATTLEBORO\n");
   }
+
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7079";>[CALCITE-7079]
+   * Mongo adapter: MongoDB Adapter unable to translate multiple NOT EQUALS 
expressions
+   * combined with AND  </a>. */
+  @Test void testMultiNeFilterContition() {
+    assertModel(MODEL)
+        .query("select city, state from zips where city <> 'ABERDEEN' and city 
<> 'AIKEN'  "
+            + "order by city")
+        .limit(3)
+        .queryContains(
+            mongoChecker(
+                "{$match: {city: {$nin: [\"ABERDEEN\", \"AIKEN\"]}}}",
+                "{$project: {CITY: '$city', STATE: '$state'}}",
+                "{$sort: {CITY: 1}}"))
+        .returnsOrdered("CITY=ALTON; STATE=TX",
+            "CITY=AMES; STATE=IA",
+            "CITY=ANCHORAGE; STATE=AK");
+
+    assertModel(MODEL)
+        .query("select city, state from zips where city <> 'ABERDEEN' and city 
<> 'AIKEN'  "
+            + "or (state <> 'IA' and state <> 'TX') order by city")
+        .limit(3)
+        .queryContains(
+            mongoChecker(
+                "{$match: {$or: [{city: {$nin: [\"ABERDEEN\", \"AIKEN\"]}}, 
{state: {$nin: [\"IA\", \"TX\"]}}]}}",
+                "{$project: {CITY: '$city', STATE: '$state'}}",
+                "{$sort: {CITY: 1}}"))
+        .returnsOrdered("CITY=ABERDEEN; STATE=SD",
+            "CITY=AIKEN; STATE=SC",
+            "CITY=ALTON; STATE=TX");
+
+    assertModel(MODEL)
+        .query("select city, state from zips where city <> 'ABERDEEN' and city 
<> 'AIKEN'  "
+            + "and state <> 'IA' order by city")
+        .limit(3)
+        .queryContains(
+            mongoChecker(
+                "{$match: {city: {$nin: [\"ABERDEEN\", \"AIKEN\"]}, state: 
{$ne: \"IA\"}}}",
+                "{$project: {CITY: '$city', STATE: '$state'}}",
+                "{$sort: {CITY: 1}}"))
+        .returnsOrdered("CITY=ALTON; STATE=TX",
+            "CITY=ANCHORAGE; STATE=AK",
+            "CITY=BALTIMORE; STATE=MD");
+
+    assertModel(MODEL)
+        .query("select city, state from zips where city <> 'ABERDEEN' and city 
<> 'AIKEN'  "
+            + "and (state <> 'IA' or state <> 'TX') order by city")
+        .limit(3)
+        .queryContains(
+            mongoChecker(
+                "{$match: {city: {$nin: [\"ABERDEEN\", \"AIKEN\"]}, state: 
{$ne: null}}}",
+                "{$project: {CITY: '$city', STATE: '$state'}}",
+                "{$sort: {CITY: 1}}"))
+        .returnsOrdered("CITY=ALTON; STATE=TX",
+            "CITY=AMES; STATE=IA",
+            "CITY=ANCHORAGE; STATE=AK");
+
+    assertModel(MODEL)
+        .query("select city, state from zips where city <> 'ABERDEEN' and city 
<> 'AIKEN'  "
+            + "and state IS NOT NULL order by city")
+        .limit(3)
+        .queryContains(
+            mongoChecker(
+                "{$match: {city: {$nin: [\"ABERDEEN\", \"AIKEN\"]}, state: 
{$ne: null}}}",
+                "{$project: {CITY: '$city', STATE: '$state'}}",
+                "{$sort: {CITY: 1}}"))
+        .returnsOrdered("CITY=ALTON; STATE=TX",
+            "CITY=AMES; STATE=IA",
+            "CITY=ANCHORAGE; STATE=AK");
+
+    assertModel(MODEL)
+        .query("select city, state from zips where city <> 'ABERDEEN' and city 
<> 'AIKEN'  "
+            + "and state IS NULL order by city")
+        .limit(3)
+        .queryContains(
+            mongoChecker(
+                "{$match: {city: {$nin: [\"ABERDEEN\", \"AIKEN\"]}, state: 
{$eq: null}}}",
+                "{$project: {CITY: '$city', STATE: '$state'}}",
+                "{$sort: {CITY: 1}}"))
+        .returnsOrdered("");
+  }
 }

Reply via email to