Skip to content

Commit

Permalink
[CALCITE-4980] Babel parser support MySQL NULL-safe equal operator '<…
Browse files Browse the repository at this point in the history
…=>' (xurenhe&&DuanXiong)
  • Loading branch information
wojustme authored and liyafan82 committed Mar 4, 2022
1 parent 909e134 commit 28f4195
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 2 deletions.
2 changes: 2 additions & 0 deletions babel/src/main/codegen/config.fmpp
Original file line number Diff line number Diff line change
Expand Up @@ -545,12 +545,14 @@ data: {
# Example: "< INFIX_CAST: \"::\" >".
binaryOperatorsTokens: [
"< INFIX_CAST: \"::\" >"
"< NULL_SAFE_EQUAL: \"<=>\" >"
]

# Binary operators initialization.
# Example: "InfixCast".
extraBinaryExpressions: [
"InfixCast"
"NullSafeEqual"
]

# List of files in @includes directory that have parser method
Expand Down
12 changes: 12 additions & 0 deletions babel/src/main/codegen/includes/parserImpls.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,15 @@ void InfixCast(List<Object> list, ExprContext exprContext, Span s) :
list.add(dt);
}
}

/** Parses the NULL-safe "<=>" equal operator used in MySQL. */
void NullSafeEqual(List<Object> list, ExprContext exprContext, Span s) :
{
}
{
<NULL_SAFE_EQUAL> {
checkNonQueryExpression(exprContext);
list.add(new SqlParserUtil.ToTreeListItem(SqlLibraryOperators.NULL_SAFE_EQUAL, getPos()));
}
Expression2b(ExprContext.ACCEPT_SUB_QUERY, list)
}
31 changes: 31 additions & 0 deletions babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,37 @@ private void checkParseInfixCast(String sqlType) {
sql(sql).ok(expected);
}

/** Tests parsing MySQL-style "<=>" equal operator. */
@Test void testParseNullSafeEqual() {
// x <=> y
final String projectSql = "SELECT x <=> 3 FROM (VALUES (1, 2)) as tbl(x,y)";
sql(projectSql).ok("SELECT (`X` <=> 3)\n"
+ "FROM (VALUES (ROW(1, 2))) AS `TBL` (`X`, `Y`)");
final String filterSql = "SELECT y FROM (VALUES (1, 2)) as tbl(x,y) WHERE x <=> null";
sql(filterSql).ok("SELECT `Y`\n"
+ "FROM (VALUES (ROW(1, 2))) AS `TBL` (`X`, `Y`)\n"
+ "WHERE (`X` <=> NULL)");
final String joinConditionSql = "SELECT tbl1.y FROM (VALUES (1, 2)) as tbl1(x,y)\n"
+ "LEFT JOIN (VALUES (null, 3)) as tbl2(x,y) ON tbl1.x <=> tbl2.x";
sql(joinConditionSql).ok("SELECT `TBL1`.`Y`\n"
+ "FROM (VALUES (ROW(1, 2))) AS `TBL1` (`X`, `Y`)\n"
+ "LEFT JOIN (VALUES (ROW(NULL, 3))) AS `TBL2` (`X`, `Y`) ON (`TBL1`.`X` <=> `TBL2`.`X`)");
// (a, b) <=> (x, y)
final String rowComparisonSql = "SELECT y\n"
+ "FROM (VALUES (1, 2)) as tbl(x,y) WHERE (x,y) <=> (null,2)";
sql(rowComparisonSql).ok("SELECT `Y`\n"
+ "FROM (VALUES (ROW(1, 2))) AS `TBL` (`X`, `Y`)\n"
+ "WHERE ((ROW(`X`, `Y`)) <=> (ROW(NULL, 2)))");
// the higher precedence
final String highPrecedenceSql = "SELECT x <=> 3 + 3 FROM (VALUES (1, 2)) as tbl(x,y)";
sql(highPrecedenceSql).ok("SELECT (`X` <=> (3 + 3))\n"
+ "FROM (VALUES (ROW(1, 2))) AS `TBL` (`X`, `Y`)");
// the lower precedence
final String lowPrecedenceSql = "SELECT NOT x <=> 3 FROM (VALUES (1, 2)) as tbl(x,y)";
sql(lowPrecedenceSql).ok("SELECT (NOT (`X` <=> 3))\n"
+ "FROM (VALUES (ROW(1, 2))) AS `TBL` (`X`, `Y`)");
}

@Test void testCreateTableWithNoCollectionTypeSpecified() {
final String sql = "create table foo (bar integer not null, baz varchar(30))";
final String expected = "CREATE TABLE `FOO` (`BAR` INTEGER NOT NULL, `BAZ` VARCHAR(30))";
Expand Down
31 changes: 31 additions & 0 deletions babel/src/test/java/org/apache/calcite/test/BabelTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,35 @@ private void checkInfixCast(Statement statement, String typeName, int sqlType)
p.sql("select 1 ^:^: integer as x")
.fails("(?s).*Encountered \":\" at .*");
}

@Test void testNullSafeEqual() {
// x <=> y
checkSqlResult("mysql", "SELECT 1 <=> NULL", "EXPR$0=false\n");
checkSqlResult("mysql", "SELECT NULL <=> NULL", "EXPR$0=true\n");
// (a, b) <=> (x, y)
checkSqlResult("mysql",
"SELECT (CAST(NULL AS Integer), 1) <=> (1, CAST(NULL AS Integer))",
"EXPR$0=false\n");
checkSqlResult("mysql",
"SELECT (CAST(NULL AS Integer), CAST(NULL AS Integer))\n"
+ "<=> (CAST(NULL AS Integer), CAST(NULL AS Integer))",
"EXPR$0=true\n");
// the higher precedence
checkSqlResult("mysql",
"SELECT x <=> 1 + 3 FROM (VALUES (1, 2)) as tbl(x,y)",
"EXPR$0=false\n");
// the lower precedence
checkSqlResult("mysql",
"SELECT NOT x <=> 1 FROM (VALUES (1, 2)) as tbl(x,y)",
"EXPR$0=false\n");
}

private void checkSqlResult(String funLibrary, String query, String result) {
CalciteAssert.that()
.with(CalciteConnectionProperty.PARSER_FACTORY,
SqlBabelParserImpl.class.getName() + "#FACTORY")
.with(CalciteConnectionProperty.FUN, funLibrary)
.query(query)
.returns(result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.sql.SqlAggFunction;
import org.apache.calcite.sql.SqlBinaryOperator;
import org.apache.calcite.sql.SqlFunction;
import org.apache.calcite.sql.SqlFunctionCategory;
import org.apache.calcite.sql.SqlKind;
Expand Down Expand Up @@ -706,4 +707,17 @@ private SqlLibraryOperators() {
@LibraryOperator(libraries = { POSTGRESQL })
public static final SqlOperator INFIX_CAST =
new SqlCastOperator();

/** NULL-safe "&lt;=&gt;" equal operator used by MySQL, for example
* {@code 1<=>NULL}. */
@LibraryOperator(libraries = { MYSQL })
public static final SqlOperator NULL_SAFE_EQUAL =
new SqlBinaryOperator(
"<=>",
SqlKind.IS_NOT_DISTINCT_FROM,
30,
true,
ReturnTypes.BOOLEAN,
InferTypes.FIRST_KNOWN,
OperandTypes.COMPARABLE_UNORDERED_COMPARABLE_UNORDERED);
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ private StandardConvertletTable() {
SqlStdOperatorTable.IS_NULL);
addAlias(SqlStdOperatorTable.IS_NOT_UNKNOWN,
SqlStdOperatorTable.IS_NOT_NULL);
addAlias(SqlLibraryOperators.NULL_SAFE_EQUAL, SqlStdOperatorTable.IS_NOT_DISTINCT_FROM);
addAlias(SqlStdOperatorTable.PERCENT_REMAINDER, SqlStdOperatorTable.MOD);

// Register convertlets for specific objects.
Expand Down
7 changes: 5 additions & 2 deletions site/_docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1203,13 +1203,13 @@ The operator precedence and associativity, highest to lowest.
| * / % &#124;&#124; | left
| + - | left
| BETWEEN, IN, LIKE, SIMILAR, OVERLAPS, CONTAINS etc. | -
| < > = <= >= <> != | left
| < > = <= >= <> != <=> | left
| IS NULL, IS FALSE, IS NOT TRUE etc. | -
| NOT | right
| AND | left
| OR | left

Note that `::` is dialect-specific, but is shown in this table for
Note that `::`,`<=>` is dialect-specific, but is shown in this table for
completeness.

### Comparison operators
Expand All @@ -1223,6 +1223,7 @@ completeness.
| value1 >= value2 | Greater than or equal
| value1 < value2 | Less than
| value1 <= value2 | Less than or equal
| value1 <=> value2 | Whether two values are equal, treating null values as the same
| value IS NULL | Whether *value* is null
| value IS NOT NULL | Whether *value* is not null
| value1 IS DISTINCT FROM value2 | Whether two values are not equal, treating null values as the same
Expand Down Expand Up @@ -1251,6 +1252,7 @@ comp:
| >=
| <
| <=
| <=>
{% endhighlight %}

### Logical operators
Expand Down Expand Up @@ -2513,6 +2515,7 @@ semantics.
| C | Operator syntax | Description
|:- |:-----------------------------------------------|:-----------
| p | expr :: type | Casts *expr* to *type*
| m | expr1 <=> expr2 | Whether two values are equal, treating null values as the same, and it's similar to `IS NOT DISTINCT FROM`
| b | ARRAY_CONCAT(array [, array ]*) | Concatenates one or more arrays. If any input argument is `NULL` the function returns `NULL`
| b | ARRAY_LENGTH(array) | Synonym for `CARDINALITY`
| b | ARRAY_REVERSE(array) | Reverses elements of *array*
Expand Down

0 comments on commit 28f4195

Please sign in to comment.