Skip to content

Commit b078e29

Browse files
authored
SQL: Implement null safe equality operator <=> (#35873)
This operator handles nulls in different way than the normal `=`. If one of the operants is `null` and the other not it returns `false`. If both operants are `null` it returns `true`. Therefore in contrary to `=`, which returns `null` if at least one of the operants is `null`, this one never returns `null` as a result. Closes: #35871
1 parent 04ebc63 commit b078e29

File tree

19 files changed

+693
-482
lines changed

19 files changed

+693
-482
lines changed

docs/reference/sql/functions/operators.asciidoc

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,19 @@ Boolean operator for comparing against one or multiple expressions.
1212
include-tagged::{sql-specs}/filter.sql-spec[whereFieldEquality]
1313
--------------------------------------------------
1414

15-
* Inequality (`<>` or `!=` or `<=>`)
15+
* Null safe Equality (`<=>`)
16+
17+
["source","sql",subs="attributes,callouts,macros"]
18+
--------------------------------------------------
19+
include-tagged::{sql-specs}/docs.csv-spec[nullEqualsCompareWithNull]
20+
--------------------------------------------------
21+
22+
["source","sql",subs="attributes,callouts,macros"]
23+
--------------------------------------------------
24+
include-tagged::{sql-specs}/docs.csv-spec[nullEqualsCompareTwoNulls]
25+
--------------------------------------------------
26+
27+
* Inequality (`<>` or `!=`)
1628

1729
["source","sql",subs="attributes,callouts,macros"]
1830
--------------------------------------------------

x-pack/plugin/sql/qa/src/main/resources/agg.csv-spec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,4 @@ null |null |null |null |null |
136136
2 |2 |38 |2 |2.0 |100.0 |NaN |NaN
137137
3 |3 |51 |3 |3.0 |100.0 |NaN |NaN
138138
4 |4 |72 |4 |4.0 |0.0 |NaN |NaN
139-
;
139+
;

x-pack/plugin/sql/qa/src/main/resources/docs.csv-spec

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1619,3 +1619,23 @@ SELECT NULLIF('elastic', 'elastic') AS "nullif";
16191619
null
16201620
// end::nullIfReturnNull
16211621
;
1622+
1623+
nullEqualsCompareWithNull
1624+
// tag::nullEqualsCompareWithNull
1625+
SELECT 'elastic' <=> null AS "equals";
1626+
1627+
equals
1628+
---------------
1629+
false
1630+
// end::nullEqualsCompareWithNull
1631+
;
1632+
1633+
nullEqualsCompareTwoNulls
1634+
// tag::nullEqualsCompareTwoNulls
1635+
SELECT null <=> null AS "equals";
1636+
1637+
equals
1638+
---------------
1639+
true
1640+
// end::nullEqualsCompareTwoNulls
1641+
;

x-pack/plugin/sql/src/main/antlr/SqlBase.g4

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ constant
277277
;
278278

279279
comparisonOperator
280-
: EQ | NEQ | LT | LTE | GT | GTE
280+
: EQ | NULLEQ | NEQ | LT | LTE | GT | GTE
281281
;
282282

283283
booleanValue
@@ -452,7 +452,8 @@ GUID_ESC: '{GUID';
452452
ESC_END: '}';
453453

454454
EQ : '=';
455-
NEQ : '<>' | '!=' | '<=>';
455+
NULLEQ: '<=>';
456+
NEQ : '<>' | '!=';
456457
LT : '<';
457458
LTE : '<=';
458459
GT : '>';

x-pack/plugin/sql/src/main/antlr/SqlBase.tokens

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -96,32 +96,33 @@ TIMESTAMP_ESC=95
9696
GUID_ESC=96
9797
ESC_END=97
9898
EQ=98
99-
NEQ=99
100-
LT=100
101-
LTE=101
102-
GT=102
103-
GTE=103
104-
PLUS=104
105-
MINUS=105
106-
ASTERISK=106
107-
SLASH=107
108-
PERCENT=108
109-
CONCAT=109
110-
DOT=110
111-
PARAM=111
112-
STRING=112
113-
INTEGER_VALUE=113
114-
DECIMAL_VALUE=114
115-
IDENTIFIER=115
116-
DIGIT_IDENTIFIER=116
117-
TABLE_IDENTIFIER=117
118-
QUOTED_IDENTIFIER=118
119-
BACKQUOTED_IDENTIFIER=119
120-
SIMPLE_COMMENT=120
121-
BRACKETED_COMMENT=121
122-
WS=122
123-
UNRECOGNIZED=123
124-
DELIMITER=124
99+
NULLEQ=99
100+
NEQ=100
101+
LT=101
102+
LTE=102
103+
GT=103
104+
GTE=104
105+
PLUS=105
106+
MINUS=106
107+
ASTERISK=107
108+
SLASH=108
109+
PERCENT=109
110+
CONCAT=110
111+
DOT=111
112+
PARAM=112
113+
STRING=113
114+
INTEGER_VALUE=114
115+
DECIMAL_VALUE=115
116+
IDENTIFIER=116
117+
DIGIT_IDENTIFIER=117
118+
TABLE_IDENTIFIER=118
119+
QUOTED_IDENTIFIER=119
120+
BACKQUOTED_IDENTIFIER=120
121+
SIMPLE_COMMENT=121
122+
BRACKETED_COMMENT=122
123+
WS=123
124+
UNRECOGNIZED=124
125+
DELIMITER=125
125126
'('=1
126127
')'=2
127128
','=3
@@ -220,15 +221,16 @@ DELIMITER=124
220221
'{GUID'=96
221222
'}'=97
222223
'='=98
223-
'<'=100
224-
'<='=101
225-
'>'=102
226-
'>='=103
227-
'+'=104
228-
'-'=105
229-
'*'=106
230-
'/'=107
231-
'%'=108
232-
'||'=109
233-
'.'=110
234-
'?'=111
224+
'<=>'=99
225+
'<'=101
226+
'<='=102
227+
'>'=103
228+
'>='=104
229+
'+'=105
230+
'-'=106
231+
'*'=107
232+
'/'=108
233+
'%'=109
234+
'||'=110
235+
'.'=111
236+
'?'=112

x-pack/plugin/sql/src/main/antlr/SqlBaseLexer.tokens

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -96,31 +96,32 @@ TIMESTAMP_ESC=95
9696
GUID_ESC=96
9797
ESC_END=97
9898
EQ=98
99-
NEQ=99
100-
LT=100
101-
LTE=101
102-
GT=102
103-
GTE=103
104-
PLUS=104
105-
MINUS=105
106-
ASTERISK=106
107-
SLASH=107
108-
PERCENT=108
109-
CONCAT=109
110-
DOT=110
111-
PARAM=111
112-
STRING=112
113-
INTEGER_VALUE=113
114-
DECIMAL_VALUE=114
115-
IDENTIFIER=115
116-
DIGIT_IDENTIFIER=116
117-
TABLE_IDENTIFIER=117
118-
QUOTED_IDENTIFIER=118
119-
BACKQUOTED_IDENTIFIER=119
120-
SIMPLE_COMMENT=120
121-
BRACKETED_COMMENT=121
122-
WS=122
123-
UNRECOGNIZED=123
99+
NULLEQ=99
100+
NEQ=100
101+
LT=101
102+
LTE=102
103+
GT=103
104+
GTE=104
105+
PLUS=105
106+
MINUS=106
107+
ASTERISK=107
108+
SLASH=108
109+
PERCENT=109
110+
CONCAT=110
111+
DOT=111
112+
PARAM=112
113+
STRING=113
114+
INTEGER_VALUE=114
115+
DECIMAL_VALUE=115
116+
IDENTIFIER=116
117+
DIGIT_IDENTIFIER=117
118+
TABLE_IDENTIFIER=118
119+
QUOTED_IDENTIFIER=119
120+
BACKQUOTED_IDENTIFIER=120
121+
SIMPLE_COMMENT=121
122+
BRACKETED_COMMENT=122
123+
WS=123
124+
UNRECOGNIZED=124
124125
'('=1
125126
')'=2
126127
','=3
@@ -219,15 +220,16 @@ UNRECOGNIZED=123
219220
'{GUID'=96
220221
'}'=97
221222
'='=98
222-
'<'=100
223-
'<='=101
224-
'>'=102
225-
'>='=103
226-
'+'=104
227-
'-'=105
228-
'*'=106
229-
'/'=107
230-
'%'=108
231-
'||'=109
232-
'.'=110
233-
'?'=111
223+
'<=>'=99
224+
'<'=101
225+
'<='=102
226+
'>'=103
227+
'>='=104
228+
'+'=105
229+
'-'=106
230+
'*'=107
231+
'/'=108
232+
'%'=109
233+
'||'=110
234+
'.'=111
235+
'?'=112

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ public static Boolean eq(Object left, Object right) {
9292
return BinaryComparisonOperation.EQ.apply(left, right);
9393
}
9494

95+
public static Boolean nulleq(Object left, Object right) {
96+
return BinaryComparisonOperation.NULLEQ.apply(left, right);
97+
}
98+
9599
public static Boolean neq(Object left, Object right) {
96100
return BinaryComparisonOperation.NEQ.apply(left, right);
97101
}

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/operator/comparison/BinaryComparisonProcessor.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
import java.util.function.BiFunction;
1616

1717
public class BinaryComparisonProcessor extends FunctionalBinaryProcessor<Object, Object, Boolean, BinaryComparisonOperation> {
18-
18+
1919
public enum BinaryComparisonOperation implements PredicateBiFunction<Object, Object, Boolean> {
2020

2121
EQ(Comparisons::eq, "=="),
22+
NULLEQ(Comparisons::nulleq, "<=>"),
2223
NEQ(Comparisons::neq, "!="),
2324
GT(Comparisons::gt, ">"),
2425
GTE(Comparisons::gte, ">="),
@@ -38,6 +39,14 @@ public String symbol() {
3839
return symbol;
3940
}
4041

42+
@Override
43+
public Boolean apply(Object left, Object right) {
44+
if (this != NULLEQ && (left == null || right == null)) {
45+
return null;
46+
}
47+
return doApply(left, right);
48+
}
49+
4150
@Override
4251
public final Boolean doApply(Object left, Object right) {
4352
return process.apply(left, right);
@@ -48,7 +57,7 @@ public String toString() {
4857
return symbol;
4958
}
5059
}
51-
60+
5261
public static final String NAME = "cb";
5362

5463
public BinaryComparisonProcessor(Processor left, Processor right, BinaryComparisonOperation operation) {
@@ -63,4 +72,12 @@ public BinaryComparisonProcessor(StreamInput in) throws IOException {
6372
public String getWriteableName() {
6473
return NAME;
6574
}
75+
76+
@Override
77+
public Object process(Object input) {
78+
if (function() == BinaryComparisonOperation.NULLEQ) {
79+
return doProcess(left().process(input), right().process(input));
80+
}
81+
return super.process(input);
82+
}
6683
}

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/operator/comparison/Comparisons.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ public static Boolean eq(Object l, Object r) {
1919
return i == null ? null : i.intValue() == 0;
2020
}
2121

22+
public static boolean nulleq(Object l, Object r) {
23+
if (l == null && r == null) {
24+
return true;
25+
}
26+
Integer i = compare(l, r);
27+
return i == null ? false : i.intValue() == 0;
28+
}
29+
2230
static Boolean neq(Object l, Object r) {
2331
Integer i = compare(l, r);
2432
return i == null ? null : i.intValue() != 0;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.sql.expression.predicate.operator.comparison;
7+
8+
import org.elasticsearch.xpack.sql.expression.Expression;
9+
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.BinaryComparisonProcessor.BinaryComparisonOperation;
10+
import org.elasticsearch.xpack.sql.tree.Location;
11+
import org.elasticsearch.xpack.sql.tree.NodeInfo;
12+
13+
/**
14+
* Implements the MySQL {@code <=>} operator
15+
*/
16+
public class NullEquals extends BinaryComparison {
17+
18+
public NullEquals(Location location, Expression left, Expression right) {
19+
super(location, left, right, BinaryComparisonOperation.NULLEQ);
20+
}
21+
22+
@Override
23+
protected NodeInfo<NullEquals> info() {
24+
return NodeInfo.create(this, NullEquals::new, left(), right());
25+
}
26+
27+
@Override
28+
protected NullEquals replaceChildren(Expression newLeft, Expression newRight) {
29+
return new NullEquals(location(), newLeft, newRight);
30+
}
31+
32+
@Override
33+
public NullEquals swapLeftAndRight() {
34+
return new NullEquals(location(), right(), left());
35+
}
36+
37+
@Override
38+
public boolean nullable() {
39+
return false;
40+
}
41+
}

0 commit comments

Comments
 (0)