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("");
+ }
}