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

mhubail pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/asterixdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 4d5476e  [ASTERIXDB-2951][COMP] Support IS DISTINCT FROM operator
4d5476e is described below

commit 4d5476e6672e6bb749cc2a0de69f4887ff5dce9b
Author: Dmitry Lychagin <dmitry.lycha...@couchbase.com>
AuthorDate: Wed Aug 18 19:43:22 2021 -0700

    [ASTERIXDB-2951][COMP] Support IS DISTINCT FROM operator
    
    - user model changes: no
    - storage format changes: no
    - interface changes: no
    
    Details:
    - Add support for IS [NOT] DISTINCT FROM comparison operator
    - Add testcases and update documentation
    
    Change-Id: Ifad404fa7c613771f60bd12f3aa2bd5fea77ba34
    Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/12884
    Integration-Tests: Jenkins <jenk...@fulliautomatix.ics.uci.edu>
    Tested-by: Jenkins <jenk...@fulliautomatix.ics.uci.edu>
    Reviewed-by: Dmitry Lychagin <dmitry.lycha...@couchbase.com>
    Reviewed-by: Ali Alsuliman <ali.al.solai...@gmail.com>
---
 .../is_distinct_01/is_distinct_01.1.query.sqlpp    | 39 +++++++++
 .../is_distinct_01/is_distinct_01.2.query.sqlpp    | 42 +++++++++
 .../comparison/is_distinct_01/is_distinct_01.1.adm | 81 ++++++++++++++++++
 .../comparison/is_distinct_01/is_distinct_01.2.adm |  1 +
 .../test/resources/runtimets/testsuite_sqlpp.xml   |  5 ++
 .../asterix-doc/src/main/markdown/sqlpp/2_expr.md  |  4 +-
 .../asterix/lang/common/struct/OperatorType.java   |  4 +-
 .../visitor/OperatorExpressionVisitor.java         | 99 ++++++++++++++++++----
 .../asterix-lang-sqlpp/src/main/javacc/SQLPP.jj    | 13 ++-
 9 files changed, 268 insertions(+), 20 deletions(-)

diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/comparison/is_distinct_01/is_distinct_01.1.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/comparison/is_distinct_01/is_distinct_01.1.query.sqlpp
new file mode 100644
index 0000000..c0ed568
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/comparison/is_distinct_01/is_distinct_01.1.query.sqlpp
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+from
+  [ int64(2), float(2.0), double(2.5), "str1", "str2", true, false, null, 
missing ] arg1,
+  [ int32(2), double(2.0), float(2.5), "str1", "str2", true, false, null, 
missing ] arg2
+let
+  is_distinct = arg1 is distinct from arg2,
+  is_not_distinct = arg1 is not distinct from arg2
+select
+  case
+    when arg1 is missing then "MISSING"
+    when arg1 is null then "NULL"
+    else arg1
+  end as arg1,
+  case
+    when arg2 is missing then "MISSING"
+    when arg2 is null then "NULL"
+    else arg2
+  end as arg2,
+  is_distinct as `!!==`,
+  is_not_distinct as `==`
+order by is_distinct, arg1, arg2
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/comparison/is_distinct_01/is_distinct_01.2.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/comparison/is_distinct_01/is_distinct_01.2.query.sqlpp
new file mode 100644
index 0000000..fcc1dc2
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/comparison/is_distinct_01/is_distinct_01.2.query.sqlpp
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+let
+  x = 2, y = 3
+select
+  /* test expression */
+  x between 1 and 3 is not distinct from x between 0 and 4 as t1,
+  /* expected operator precedence */
+  (x between 1 and 3) is not distinct from (x between 0 and 4) as t1_expected,
+  /* unexpected operator precedence */
+  (((x between 1 and 3) is not distinct from x) between 0 and 4) as 
t1_unexpected,
+
+  /* test expression */
+  x is unknown is not distinct from y is unknown as t2,
+  /* expected operator precedence */
+  (x is unknown) is not distinct from (y is unknown) as t2_expected,
+  /* unexpected operator precedence */
+  (((x is unknown) is not distinct from y) is unknown) as t2_unexpected,
+
+  /* test expression */
+  x is not unknown is distinct from y is unknown as t3,
+  /* expected operator precedence */
+  (x is not unknown) is distinct from (y is unknown) as t3_expected,
+  /* unexpected operator precedence */
+  (((x is not unknown) is distinct from y) is unknown) as t3_unexpected;
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/comparison/is_distinct_01/is_distinct_01.1.adm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/comparison/is_distinct_01/is_distinct_01.1.adm
new file mode 100644
index 0000000..6b0764d
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/comparison/is_distinct_01/is_distinct_01.1.adm
@@ -0,0 +1,81 @@
+{ "!!==": false, "==": true, "arg1": "MISSING", "arg2": "MISSING" }
+{ "!!==": false, "==": true, "arg1": "NULL", "arg2": "NULL" }
+{ "!!==": false, "==": true, "arg1": 2, "arg2": 2 }
+{ "!!==": false, "==": true, "arg1": 2, "arg2": 2.0 }
+{ "!!==": false, "==": true, "arg1": 2.0, "arg2": 2 }
+{ "!!==": false, "==": true, "arg1": 2.0, "arg2": 2.0 }
+{ "!!==": false, "==": true, "arg1": 2.5, "arg2": 2.5 }
+{ "!!==": false, "==": true, "arg1": "str1", "arg2": "str1" }
+{ "!!==": false, "==": true, "arg1": "str2", "arg2": "str2" }
+{ "!!==": false, "==": true, "arg1": false, "arg2": false }
+{ "!!==": false, "==": true, "arg1": true, "arg2": true }
+{ "!!==": true, "==": false, "arg1": "MISSING", "arg2": "NULL" }
+{ "!!==": true, "==": false, "arg1": "MISSING", "arg2": 2 }
+{ "!!==": true, "==": false, "arg1": "MISSING", "arg2": 2.0 }
+{ "!!==": true, "==": false, "arg1": "MISSING", "arg2": 2.5 }
+{ "!!==": true, "==": false, "arg1": "MISSING", "arg2": "str1" }
+{ "!!==": true, "==": false, "arg1": "MISSING", "arg2": "str2" }
+{ "!!==": true, "==": false, "arg1": "MISSING", "arg2": false }
+{ "!!==": true, "==": false, "arg1": "MISSING", "arg2": true }
+{ "!!==": true, "==": false, "arg1": "NULL", "arg2": "MISSING" }
+{ "!!==": true, "==": false, "arg1": "NULL", "arg2": 2 }
+{ "!!==": true, "==": false, "arg1": "NULL", "arg2": 2.0 }
+{ "!!==": true, "==": false, "arg1": "NULL", "arg2": 2.5 }
+{ "!!==": true, "==": false, "arg1": "NULL", "arg2": "str1" }
+{ "!!==": true, "==": false, "arg1": "NULL", "arg2": "str2" }
+{ "!!==": true, "==": false, "arg1": "NULL", "arg2": false }
+{ "!!==": true, "==": false, "arg1": "NULL", "arg2": true }
+{ "!!==": true, "==": false, "arg1": 2, "arg2": "MISSING" }
+{ "!!==": true, "==": false, "arg1": 2.0, "arg2": "MISSING" }
+{ "!!==": true, "==": false, "arg1": 2, "arg2": "NULL" }
+{ "!!==": true, "==": false, "arg1": 2.0, "arg2": "NULL" }
+{ "!!==": true, "==": false, "arg1": 2, "arg2": 2.5 }
+{ "!!==": true, "==": false, "arg1": 2.0, "arg2": 2.5 }
+{ "!!==": true, "==": false, "arg1": 2, "arg2": "str1" }
+{ "!!==": true, "==": false, "arg1": 2.0, "arg2": "str1" }
+{ "!!==": true, "==": false, "arg1": 2, "arg2": "str2" }
+{ "!!==": true, "==": false, "arg1": 2.0, "arg2": "str2" }
+{ "!!==": true, "==": false, "arg1": 2, "arg2": false }
+{ "!!==": true, "==": false, "arg1": 2.0, "arg2": false }
+{ "!!==": true, "==": false, "arg1": 2, "arg2": true }
+{ "!!==": true, "==": false, "arg1": 2.0, "arg2": true }
+{ "!!==": true, "==": false, "arg1": 2.5, "arg2": "MISSING" }
+{ "!!==": true, "==": false, "arg1": 2.5, "arg2": "NULL" }
+{ "!!==": true, "==": false, "arg1": 2.5, "arg2": 2 }
+{ "!!==": true, "==": false, "arg1": 2.5, "arg2": 2.0 }
+{ "!!==": true, "==": false, "arg1": 2.5, "arg2": "str1" }
+{ "!!==": true, "==": false, "arg1": 2.5, "arg2": "str2" }
+{ "!!==": true, "==": false, "arg1": 2.5, "arg2": false }
+{ "!!==": true, "==": false, "arg1": 2.5, "arg2": true }
+{ "!!==": true, "==": false, "arg1": "str1", "arg2": "MISSING" }
+{ "!!==": true, "==": false, "arg1": "str1", "arg2": "NULL" }
+{ "!!==": true, "==": false, "arg1": "str1", "arg2": 2 }
+{ "!!==": true, "==": false, "arg1": "str1", "arg2": 2.0 }
+{ "!!==": true, "==": false, "arg1": "str1", "arg2": 2.5 }
+{ "!!==": true, "==": false, "arg1": "str1", "arg2": "str2" }
+{ "!!==": true, "==": false, "arg1": "str1", "arg2": false }
+{ "!!==": true, "==": false, "arg1": "str1", "arg2": true }
+{ "!!==": true, "==": false, "arg1": "str2", "arg2": "MISSING" }
+{ "!!==": true, "==": false, "arg1": "str2", "arg2": "NULL" }
+{ "!!==": true, "==": false, "arg1": "str2", "arg2": 2 }
+{ "!!==": true, "==": false, "arg1": "str2", "arg2": 2.0 }
+{ "!!==": true, "==": false, "arg1": "str2", "arg2": 2.5 }
+{ "!!==": true, "==": false, "arg1": "str2", "arg2": "str1" }
+{ "!!==": true, "==": false, "arg1": "str2", "arg2": false }
+{ "!!==": true, "==": false, "arg1": "str2", "arg2": true }
+{ "!!==": true, "==": false, "arg1": false, "arg2": "MISSING" }
+{ "!!==": true, "==": false, "arg1": false, "arg2": "NULL" }
+{ "!!==": true, "==": false, "arg1": false, "arg2": 2 }
+{ "!!==": true, "==": false, "arg1": false, "arg2": 2.0 }
+{ "!!==": true, "==": false, "arg1": false, "arg2": 2.5 }
+{ "!!==": true, "==": false, "arg1": false, "arg2": "str1" }
+{ "!!==": true, "==": false, "arg1": false, "arg2": "str2" }
+{ "!!==": true, "==": false, "arg1": false, "arg2": true }
+{ "!!==": true, "==": false, "arg1": true, "arg2": "MISSING" }
+{ "!!==": true, "==": false, "arg1": true, "arg2": "NULL" }
+{ "!!==": true, "==": false, "arg1": true, "arg2": 2 }
+{ "!!==": true, "==": false, "arg1": true, "arg2": 2.0 }
+{ "!!==": true, "==": false, "arg1": true, "arg2": 2.5 }
+{ "!!==": true, "==": false, "arg1": true, "arg2": "str1" }
+{ "!!==": true, "==": false, "arg1": true, "arg2": "str2" }
+{ "!!==": true, "==": false, "arg1": true, "arg2": false }
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/comparison/is_distinct_01/is_distinct_01.2.adm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/comparison/is_distinct_01/is_distinct_01.2.adm
new file mode 100644
index 0000000..bd83c66
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/comparison/is_distinct_01/is_distinct_01.2.adm
@@ -0,0 +1 @@
+{ "t1": true, "t1_expected": true, "t1_unexpected": null, "t2": true, 
"t2_expected": true, "t2_unexpected": false, "t3": true, "t3_expected": true, 
"t3_unexpected": false }
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml 
b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
index 5937278..cb4c5ad 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -3431,6 +3431,11 @@
       </compilation-unit>
     </test-case>
     <test-case FilePath="comparison">
+      <compilation-unit name="is_distinct_01">
+        <output-dir compare="Text">is_distinct_01</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="comparison">
       <compilation-unit name="like">
         <output-dir compare="Text">like</output-dir>
       </compilation-unit>
diff --git a/asterixdb/asterix-doc/src/main/markdown/sqlpp/2_expr.md 
b/asterixdb/asterix-doc/src/main/markdown/sqlpp/2_expr.md
index 9ecc29b..37bd2f0 100644
--- a/asterixdb/asterix-doc/src/main/markdown/sqlpp/2_expr.md
+++ b/asterixdb/asterix-doc/src/main/markdown/sqlpp/2_expr.md
@@ -53,7 +53,7 @@ The following table summarizes the precedence order (from 
higher to lower) of th
 | &#124;&#124;                                                                
|  String concatenation |
 | IS NULL, IS NOT NULL, IS MISSING, IS NOT MISSING, <br/>IS UNKNOWN, IS NOT 
UNKNOWN, IS VALUED, IS NOT VALUED | Unknown value comparison |
 | BETWEEN, NOT BETWEEN                                                        
| Range comparison (inclusive on both sides) |
-| =, !=, <>, <, >, <=, >=, LIKE, NOT LIKE, IN, NOT IN                          
   | Comparison  |
+| =, !=, <>, <, >, <=, >=, LIKE, NOT LIKE, IN, NOT IN, IS DISTINCT FROM, IS 
NOT DISTINCT FROM | Comparison  |
 | NOT                                                                         
| Logical negation |
 | AND                                                                         
| Conjunction |
 | OR                                                                          
| Disjunction |
@@ -130,6 +130,8 @@ The following table enumerates all of the comparison 
operators available in SQL+
 | >=             |  Greater than or equal to                      | FROM 
customers AS c <br/> WHERE c.rating >= 640 <br/> SELECT *; |
 | LIKE           |  Test if the left side matches a pattern defined on the 
right side; in the pattern,  "%" matches any string while "&#95;" matches any 
character. | FROM customers AS c WHERE c.name LIKE "%Dodge%" SELECT *;|
 | NOT LIKE       |  Test if the left side does not match a pattern defined on 
the right side; in the pattern, "%" matches any string while "&#95;" matches 
any character. | FROM customers AS c WHERE c.name NOT LIKE "%Dodge%" SELECT *;|
+| IS DISTINCT FROM | Inequality test that that treats NULL values as equal to 
each other and MISSING values as equal to each other | FROM orders AS o <br/> 
WHERE o.order_date IS DISTINCT FROM o.ship_date <br/> SELECT *; | |
+| IS NOT DISTINCT FROM | Equality test that treats NULL values as equal to 
each other and MISSING values as equal to each other | FROM orders AS o <br/> 
WHERE o.order_date IS NOT DISTINCT FROM o.ship_date <br/> SELECT *;  |
 
 The following table summarizes how the missing value comparison operators work.
 
diff --git 
a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/OperatorType.java
 
b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/OperatorType.java
index f4f2ae1..e207e75 100644
--- 
a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/OperatorType.java
+++ 
b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/OperatorType.java
@@ -41,7 +41,9 @@ public enum OperatorType {
     IN("in"),
     NOT_IN("not_in"),
     BETWEEN("between"),
-    NOT_BETWEEN("not_between");
+    NOT_BETWEEN("not_between"),
+    DISTINCT("distinct"),
+    NOT_DISTINCT("not_distinct");
 
     private static final OperatorType[] VALUES = values();
 
diff --git 
a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/OperatorExpressionVisitor.java
 
b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/OperatorExpressionVisitor.java
index c47634e..93a14ca 100644
--- 
a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/OperatorExpressionVisitor.java
+++ 
b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/OperatorExpressionVisitor.java
@@ -29,13 +29,17 @@ import 
org.apache.asterix.common.functions.FunctionSignature;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
 import org.apache.asterix.lang.common.expression.CallExpr;
+import org.apache.asterix.lang.common.expression.LiteralExpr;
 import org.apache.asterix.lang.common.expression.OperatorExpr;
 import org.apache.asterix.lang.common.expression.QuantifiedExpression;
 import 
org.apache.asterix.lang.common.expression.QuantifiedExpression.Quantifier;
 import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.literal.FalseLiteral;
+import org.apache.asterix.lang.common.literal.TrueLiteral;
 import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
 import org.apache.asterix.lang.common.struct.OperatorType;
 import org.apache.asterix.lang.common.struct.QuantifiedPair;
+import org.apache.asterix.lang.sqlpp.expression.CaseExpression;
 import org.apache.asterix.lang.sqlpp.util.FunctionMapUtil;
 import org.apache.asterix.lang.sqlpp.util.SqlppRewriteUtil;
 import 
org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppExpressionScopingVisitor;
@@ -71,8 +75,9 @@ public class OperatorExpressionVisitor extends 
AbstractSqlppExpressionScopingVis
             case BETWEEN:
             case NOT_BETWEEN:
                 return processBetweenOperator(operatorExpr, opType);
-            default:
-                break;
+            case DISTINCT:
+            case NOT_DISTINCT:
+                return processDistinctOperator(operatorExpr, opType);
         }
         return operatorExpr;
     }
@@ -144,18 +149,83 @@ public class OperatorExpressionVisitor extends 
AbstractSqlppExpressionScopingVis
         Expression targetCopy = (Expression) SqlppRewriteUtil.deepCopy(target);
         Expression rightComparison = createOperatorExpression(OperatorType.LE, 
targetCopy, right,
                 operatorExpr.getHints(), operatorExpr.getSourceLocation());
-        OperatorExpr andExpr = new OperatorExpr();
-        andExpr.addOperand(leftComparison);
-        andExpr.addOperand(rightComparison);
-        andExpr.addOperator(OperatorType.AND);
-        andExpr.setSourceLocation(operatorExpr.getSourceLocation());
-        if (opType == OperatorType.BETWEEN) {
-            return andExpr;
-        } else {
-            CallExpr callExpr = new CallExpr(new 
FunctionSignature(BuiltinFunctions.NOT),
-                    new ArrayList<>(Collections.singletonList(andExpr)));
-            callExpr.setSourceLocation(operatorExpr.getSourceLocation());
-            return callExpr;
+        Expression andExpr = createOperatorExpression(OperatorType.AND, 
leftComparison, rightComparison, null,
+                operatorExpr.getSourceLocation());
+        switch (opType) {
+            case BETWEEN:
+                return andExpr;
+            case NOT_BETWEEN:
+                CallExpr callExpr = new CallExpr(new 
FunctionSignature(BuiltinFunctions.NOT),
+                        new ArrayList<>(Collections.singletonList(andExpr)));
+                callExpr.setSourceLocation(operatorExpr.getSourceLocation());
+                return callExpr;
+            default:
+                throw new IllegalArgumentException(String.valueOf(opType));
+        }
+    }
+
+    private Expression processDistinctOperator(OperatorExpr operatorExpr, 
OperatorType opType)
+            throws CompilationException {
+
+        // lhs IS NOT DISTINCT FROM rhs =>
+        //   CASE
+        //      WHEN (lhs = rhs) OR (lhs IS NULL AND rhs IS NULL) OR (lhs IS 
MISSING AND rhs IS MISSING)
+        //      THEN TRUE
+        //      ELSE FALSE
+        //   END
+        //
+        // lhs IS DISTINCT FROM rhs => NOT ( lhs IS NOT DISTINCT FROM rhs )
+
+        Expression lhs = operatorExpr.getExprList().get(0);
+        Expression rhs = operatorExpr.getExprList().get(1);
+
+        Expression lhsEqRhs = createOperatorExpression(OperatorType.EQ, lhs, 
rhs, operatorExpr.getHints(),
+                operatorExpr.getSourceLocation());
+
+        CallExpr lhsIsNull = new CallExpr(new 
FunctionSignature(BuiltinFunctions.IS_NULL),
+                new ArrayList<>(Collections.singletonList((Expression) 
SqlppRewriteUtil.deepCopy(lhs))));
+        lhsIsNull.setSourceLocation(operatorExpr.getSourceLocation());
+
+        CallExpr rhsIsNull = new CallExpr(new 
FunctionSignature(BuiltinFunctions.IS_NULL),
+                new ArrayList<>(Collections.singletonList((Expression) 
SqlppRewriteUtil.deepCopy(rhs))));
+        rhsIsNull.setSourceLocation(operatorExpr.getSourceLocation());
+
+        CallExpr lhsIsMissing = new CallExpr(new 
FunctionSignature(BuiltinFunctions.IS_MISSING),
+                new ArrayList<>(Collections.singletonList((Expression) 
SqlppRewriteUtil.deepCopy(lhs))));
+        lhsIsMissing.setSourceLocation(operatorExpr.getSourceLocation());
+
+        CallExpr rhsIsMissing = new CallExpr(new 
FunctionSignature(BuiltinFunctions.IS_MISSING),
+                new ArrayList<>(Collections.singletonList((Expression) 
SqlppRewriteUtil.deepCopy(rhs))));
+        rhsIsMissing.setSourceLocation(operatorExpr.getSourceLocation());
+
+        Expression bothAreNull = createOperatorExpression(OperatorType.AND, 
lhsIsNull, rhsIsNull, null,
+                operatorExpr.getSourceLocation());
+
+        Expression bothAreMissing = createOperatorExpression(OperatorType.AND, 
lhsIsMissing, rhsIsMissing, null,
+                operatorExpr.getSourceLocation());
+
+        Expression bothAreNullOrMissing = 
createOperatorExpression(OperatorType.OR, bothAreNull, bothAreMissing, null,
+                operatorExpr.getSourceLocation());
+
+        Expression eqOrNullOrMissing = 
createOperatorExpression(OperatorType.OR, lhsEqRhs, bothAreNullOrMissing, null,
+                operatorExpr.getSourceLocation());
+
+        CaseExpression caseExpr = new CaseExpression(new 
LiteralExpr(TrueLiteral.INSTANCE),
+                new ArrayList<>(Collections.singletonList(eqOrNullOrMissing)),
+                new ArrayList<>(Collections.singletonList(new 
LiteralExpr(TrueLiteral.INSTANCE))),
+                new LiteralExpr(FalseLiteral.INSTANCE));
+        caseExpr.setSourceLocation(operatorExpr.getSourceLocation());
+
+        switch (opType) {
+            case NOT_DISTINCT:
+                return caseExpr;
+            case DISTINCT:
+                CallExpr callExpr = new CallExpr(new 
FunctionSignature(BuiltinFunctions.NOT),
+                        new ArrayList<>(Collections.singletonList(caseExpr)));
+                callExpr.setSourceLocation(operatorExpr.getSourceLocation());
+                return callExpr;
+            default:
+                throw new IllegalArgumentException(String.valueOf(opType));
         }
     }
 
@@ -173,5 +243,4 @@ public class OperatorExpressionVisitor extends 
AbstractSqlppExpressionScopingVis
         }
         return comparison;
     }
-
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj 
b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index b240779..ca4d903 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -3264,6 +3264,7 @@ Expression RelExpr() throws ParseException:
 {
   boolean not = false;
   OperatorExpr op = null;
+  Token opToken = null;
   Expression operand = null;
   IExpressionAnnotation annotation = null;
 }
@@ -3271,8 +3272,12 @@ Expression RelExpr() throws ParseException:
     operand = BetweenExpr()
 
     (
-      LOOKAHEAD(2)( <LT> | <GT> | <LE> | <GE> | <EQ> | <NE> | <LG> |<SIMILAR> 
| (<NOT> { not = true; })? <IN>)
+      LOOKAHEAD(3)( <LT> | <GT> | <LE> | <GE> | <EQ> | <NE> | <LG> |<SIMILAR> 
| (<NOT> { not = true; })? <IN> |
+                    <IS> (<NOT> { not = true; })? <DISTINCT> { opToken = 
token; } <FROM> )
         {
+          if (opToken == null) {
+            opToken = token;
+          }
           Token hintToken = fetchHint(token,
             SqlppHint.HASH_BROADCAST_JOIN_HINT, 
SqlppHint.INDEXED_NESTED_LOOP_JOIN_HINT,
             SqlppHint.SKIP_SECONDARY_INDEX_SEARCH_HINT, 
SqlppHint.USE_SECONDARY_INDEX_SEARCH_HINT
@@ -3280,7 +3285,7 @@ Expression RelExpr() throws ParseException:
           if (hintToken != null) {
             annotation = parseExpressionAnnotation(hintToken);
           }
-          String operator = token.image.toLowerCase();
+          String operator = opToken.image.toLowerCase();
           if (operator.equals("<>")){
               operator = "!=";
           }
@@ -3382,7 +3387,9 @@ Expression IsExpr() throws ParseException:
 }
 {
     operand = LikeExpr()
-    ( <IS>
+    (
+      LOOKAHEAD(3)
+      <IS>
         (<NOT> { not = true; notToken = token; })?
         (
             <NULL> { fn = BuiltinFunctions.IS_NULL; } |

Reply via email to