Skip to content

Commit 929fcd7

Browse files
Add BETWEEN SQL operator (#3332)
1 parent 87cba00 commit 929fcd7

File tree

10 files changed

+265
-6
lines changed

10 files changed

+265
-6
lines changed

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/typing/Type.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -510,16 +510,16 @@ private static Descriptors.GenericDescriptor getTypeSpecificDescriptor(@Nonnull
510510
@Nullable
511511
@SuppressWarnings("PMD.CompareObjectsWithEquals")
512512
static Type maximumType(@Nonnull final Type t1, @Nonnull final Type t2) {
513-
Verify.verify(!t1.isUnresolved());
514-
Verify.verify(!t2.isUnresolved());
515-
516513
if (t1.getTypeCode() == TypeCode.NULL && PromoteValue.isPromotable(t1, t2)) {
517514
return t2.withNullability(true);
518515
}
519516
if (t2.getTypeCode() == TypeCode.NULL && PromoteValue.isPromotable(t2, t1)) {
520517
return t1.withNullability(true);
521518
}
522519

520+
Verify.verify(!t1.isUnresolved());
521+
Verify.verify(!t2.isUnresolved());
522+
523523
boolean isResultNullable = t1.isNullable() || t2.isNullable();
524524

525525
if (t1.isEnum() && t2.isEnum()) {

fdb-relational-core/src/main/antlr/RelationalParser.g4

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,9 +1125,10 @@ expression
11251125
;
11261126

11271127
predicate
1128-
: expressionAtom IN inList #inPredicate // done
1129-
| left=predicate comparisonOperator right=predicate #binaryComparisonPredicate // done
1130-
| expressionAtom #expressionAtomPredicate // done
1128+
: expressionAtom IN inList #inPredicate // done
1129+
| left=predicate comparisonOperator right=predicate #binaryComparisonPredicate // done
1130+
| operand=predicate NOT? BETWEEN left=predicate AND right=predicate #betweenComparisonPredicate // done
1131+
| expressionAtom #expressionAtomPredicate // done
11311132
;
11321133

11331134
inList

fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,6 +1548,12 @@ public Expression visitBinaryComparisonPredicate(@Nonnull RelationalParser.Binar
15481548
return expressionVisitor.visitBinaryComparisonPredicate(ctx);
15491549
}
15501550

1551+
@Nonnull
1552+
@Override
1553+
public Expression visitBetweenComparisonPredicate(@Nonnull RelationalParser.BetweenComparisonPredicateContext ctx) {
1554+
return expressionVisitor.visitBetweenComparisonPredicate(ctx);
1555+
}
1556+
15511557
@Nonnull
15521558
@Override
15531559
public Expression visitInPredicate(@Nonnull RelationalParser.InPredicateContext ctx) {

fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DelegatingVisitor.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,6 +1391,12 @@ public Expression visitBinaryComparisonPredicate(@Nonnull RelationalParser.Binar
13911391
return getDelegate().visitBinaryComparisonPredicate(ctx);
13921392
}
13931393

1394+
@Nonnull
1395+
@Override
1396+
public Expression visitBetweenComparisonPredicate(@Nonnull RelationalParser.BetweenComparisonPredicateContext ctx) {
1397+
return getDelegate().visitBetweenComparisonPredicate(ctx);
1398+
}
1399+
13941400
@Nonnull
13951401
@Override
13961402
public Expression visitInPredicate(@Nonnull RelationalParser.InPredicateContext ctx) {

fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,23 @@ public Expression visitBinaryComparisonPredicate(@Nonnull RelationalParser.Binar
549549
return getDelegate().resolveFunction(ctx.comparisonOperator().getText(), left, right);
550550
}
551551

552+
@Nonnull
553+
@Override
554+
public Expression visitBetweenComparisonPredicate(@Nonnull RelationalParser.BetweenComparisonPredicateContext ctx) {
555+
final var operand = Assert.castUnchecked(ctx.operand.accept(this), Expression.class);
556+
final var left = Assert.castUnchecked(ctx.left.accept(this), Expression.class);
557+
final var right = Assert.castUnchecked(ctx.right.accept(this), Expression.class);
558+
if (ctx.NOT() == null) {
559+
return getDelegate().resolveFunction("and",
560+
getDelegate().resolveFunction("<=", left, operand),
561+
getDelegate().resolveFunction("<=", operand, right));
562+
} else {
563+
return getDelegate().resolveFunction("or",
564+
getDelegate().resolveFunction("<", operand, left),
565+
getDelegate().resolveFunction(">", operand, right));
566+
}
567+
}
568+
552569
@Nonnull
553570
@Override
554571
public Expression visitMathExpressionAtom(@Nonnull RelationalParser.MathExpressionAtomContext ctx) {

fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,10 @@ public interface TypedVisitor extends RelationalParserVisitor<Object> {
884884
@Override
885885
Expression visitBinaryComparisonPredicate(@Nonnull RelationalParser.BinaryComparisonPredicateContext ctx);
886886

887+
@Nonnull
888+
@Override
889+
Expression visitBetweenComparisonPredicate(@Nonnull RelationalParser.BetweenComparisonPredicateContext ctx);
890+
887891
@Nonnull
888892
@Override
889893
Expression visitInPredicate(@Nonnull RelationalParser.InPredicateContext ctx);

yaml-tests/src/test/java/YamlIntegrationTests.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,11 @@ public void tableFunctionsTest(YamlTest.Runner runner) throws Exception {
264264
runner.runYamsql("table-functions.yamsql");
265265
}
266266

267+
@TestTemplate
268+
public void betweenTest(YamlTest.Runner runner) throws Exception {
269+
runner.runYamsql("between.yamsql");
270+
}
271+
267272
@TestTemplate
268273
public void sqlFunctionsTest(YamlTest.Runner runner) throws Exception {
269274
runner.runYamsql("sql-functions.yamsql");

yaml-tests/src/test/resources/between.metrics.binpb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
�
2+
L
3+
between-index-tests5EXPLAIN select * from t1 WHERE col1 BETWEEN 10 AND 10�
4+
����} �ü($0���8-@cISCAN(I1 [[GREATER_THAN_OR_EQUALS promote(@c8 AS INT) && LESS_THAN_OR_EQUALS promote(@c8 AS INT)]])�digraph G {
5+
fontname=courier;
6+
rankdir=BT;
7+
splines=polyline;
8+
1 [ label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="8"><tr><td align="left">Index Scan</td></tr><tr><td align="left">comparisons: [[GREATER_THAN_OR_EQUALS promote(@c8 AS INT) &amp;&amp; LESS_THAN_OR_EQUALS promote(@c8 AS INT)]]</td></tr></table>> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ];
9+
2 [ label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="8"><tr><td align="left">Index</td></tr><tr><td align="left">I1</td></tr></table>> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ];
10+
2 -> 1 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ];
11+
}�
12+
P
13+
between-index-tests9EXPLAIN select * from t1 WHERE col1 + 5 BETWEEN 10 AND 20�
14+
����u ��(0���8+@�ISCAN(I1 <,>) | FILTER _.COL1 + @c8 GREATER_THAN_OR_EQUALS promote(@c10 AS INT) AND _.COL1 + @c8 LESS_THAN_OR_EQUALS promote(@c12 AS INT)�
15+
digraph G {
16+
fontname=courier;
17+
rankdir=BT;
18+
splines=polyline;
19+
1 [ label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="8"><tr><td align="left">Predicate Filter</td></tr><tr><td align="left">WHERE q2.COL1 + @c8 GREATER_THAN_OR_EQUALS promote(@c10 AS INT) AND q2.COL1 + @c8 LESS_THAN_OR_EQUALS promote(@c12 AS INT)</td></tr></table>> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ];
20+
2 [ label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="8"><tr><td align="left">Index Scan</td></tr><tr><td align="left">range: &lt;-∞, ∞&gt;</td></tr></table>> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ];
21+
3 [ label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="8"><tr><td align="left">Index</td></tr><tr><td align="left">I1</td></tr></table>> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ];
22+
3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ];
23+
2 -> 1 [ label=<&nbsp;q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ];
24+
}�
25+
T
26+
between-index-tests=EXPLAIN select * from t1 WHERE col1 + 5 NOT BETWEEN 10 AND 20�
27+
����v ���(0��^8+@tISCAN(I1 <,>) | FILTER _.COL1 + @c8 LESS_THAN promote(@c11 AS INT) OR _.COL1 + @c8 GREATER_THAN promote(@c13 AS INT)�
28+
digraph G {
29+
fontname=courier;
30+
rankdir=BT;
31+
splines=polyline;
32+
1 [ label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="8"><tr><td align="left">Predicate Filter</td></tr><tr><td align="left">WHERE q2.COL1 + @c8 LESS_THAN promote(@c11 AS INT) OR q2.COL1 + @c8 GREATER_THAN promote(@c13 AS INT)</td></tr></table>> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ];
33+
2 [ label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="8"><tr><td align="left">Index Scan</td></tr><tr><td align="left">range: &lt;-∞, ∞&gt;</td></tr></table>> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ];
34+
3 [ label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="8"><tr><td align="left">Index</td></tr><tr><td align="left">I1</td></tr></table>> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(INT AS ID, )" ];
35+
3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ];
36+
2 -> 1 [ label=<&nbsp;q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ];
37+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
between-index-tests:
2+
- query: EXPLAIN select * from t1 WHERE col1 BETWEEN 10 AND 10
3+
explain: ISCAN(I1 [[GREATER_THAN_OR_EQUALS promote(@c8 AS INT) && LESS_THAN_OR_EQUALS
4+
promote(@c8 AS INT)]])
5+
task_count: 470
6+
task_total_time_ms: 29
7+
transform_count: 125
8+
transform_time_ms: 13
9+
transform_yield_count: 36
10+
insert_time_ms: 2
11+
insert_new_count: 45
12+
insert_reused_count: 6
13+
- query: EXPLAIN select * from t1 WHERE col1 + 5 BETWEEN 10 AND 20
14+
explain: ISCAN(I1 <,>) | FILTER _.COL1 + @c8 GREATER_THAN_OR_EQUALS promote(@c10
15+
AS INT) AND _.COL1 + @c8 LESS_THAN_OR_EQUALS promote(@c12 AS INT)
16+
task_count: 417
17+
task_total_time_ms: 32
18+
transform_count: 117
19+
transform_time_ms: 12
20+
transform_yield_count: 31
21+
insert_time_ms: 3
22+
insert_new_count: 43
23+
insert_reused_count: 5
24+
- query: EXPLAIN select * from t1 WHERE col1 + 5 NOT BETWEEN 10 AND 20
25+
explain: ISCAN(I1 <,>) | FILTER _.COL1 + @c8 LESS_THAN promote(@c11 AS INT) OR
26+
_.COL1 + @c8 GREATER_THAN promote(@c13 AS INT)
27+
task_count: 417
28+
task_total_time_ms: 18
29+
transform_count: 118
30+
transform_time_ms: 7
31+
transform_yield_count: 31
32+
insert_time_ms: 1
33+
insert_new_count: 43
34+
insert_reused_count: 5
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#
2+
# between.yamsql
3+
#
4+
# This source file is part of the FoundationDB open source project
5+
#
6+
# Copyright 2021-2024 Apple Inc. and the FoundationDB project authors
7+
#
8+
# Licensed under the Apache License, Version 2.0 (the "License");
9+
# you may not use this file except in compliance with the License.
10+
# You may obtain a copy of the License at
11+
#
12+
# http://www.apache.org/licenses/LICENSE-2.0
13+
#
14+
# Unless required by applicable law or agreed to in writing, software
15+
# distributed under the License is distributed on an "AS IS" BASIS,
16+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
# See the License for the specific language governing permissions and
18+
# limitations under the License.
19+
20+
21+
---
22+
options:
23+
supported_version: !current_version
24+
---
25+
schema_template:
26+
create table t1(id integer, col1 integer, col2 integer, primary key(id))
27+
create index i1 as select col1 from t1
28+
---
29+
setup:
30+
steps:
31+
- query: INSERT INTO T1
32+
VALUES (1, 10, 1),
33+
(2, 10, 2),
34+
(3, 10, 3),
35+
(4, 10, 4),
36+
(5, 10, 5),
37+
(6, 20, 6),
38+
(7, 20, 7),
39+
(8, 20, 8),
40+
(9, 20, 9),
41+
(10, 20, 10),
42+
(11, 30, 11),
43+
(12, 30, 12),
44+
(13, 30, 13)
45+
---
46+
test_block:
47+
name: between-pk-tests
48+
tests:
49+
-
50+
- query: select * from t1 WHERE col2 BETWEEN 4 AND 6
51+
- result: [{ID: 4, 10, 4}, {ID: 5, 10, 5}, {ID: 6, 20, 6}]
52+
-
53+
- query: select * from t1 WHERE col2 BETWEEN 4 AND 4
54+
- result: [{ID: 4, 10, 4}]
55+
-
56+
- query: select * from t1 WHERE col2 BETWEEN 4 AND 3
57+
- result: []
58+
-
59+
- query: select * from t1 WHERE col2 NOT BETWEEN 2 AND 12
60+
- result: [{ID: 1, 10, 1}, {ID: 13, 30, 13}]
61+
-
62+
- query: select * from t1 WHERE col2 NOT BETWEEN 12 AND 2
63+
- result: [
64+
{ID: 1, 10, 1}, {ID: 2, 10, 2}, {ID: 3, 10, 3}, {ID: 4, 10, 4}, {ID: 5, 10, 5},
65+
{ID: 6, 20, 6}, {ID: 7, 20, 7}, {ID: 8, 20, 8}, {ID: 9, 20, 9}, {ID: 10, 20, 10},
66+
{ID: 11, 30, 11}, {ID: 12, 30, 12}, {ID: 13, 30, 13}]
67+
-
68+
- query: select * from t1 WHERE col2 BETWEEN 2 AND 4 OR col2 BETWEEN 6 AND 7
69+
- result: [{ID: 2, 10, 2}, {ID: 3, 10, 3}, {ID: 4, 10, 4}, {ID: 6, 20, 6}, {ID: 7, 20, 7}]
70+
71+
---
72+
test_block:
73+
name: between-index-tests
74+
tests:
75+
-
76+
- query: select * from t1 WHERE col1 BETWEEN 10 AND 10
77+
- explain: "ISCAN(I1 [[GREATER_THAN_OR_EQUALS promote(@c8 AS INT) && LESS_THAN_OR_EQUALS promote(@c8 AS INT)]])"
78+
- result: [
79+
{ID: 1, 10, 1}, {ID: 2, 10, 2}, {ID: 3, 10, 3}, {ID: 4, 10, 4}, {ID: 5, 10, 5}
80+
]
81+
-
82+
- query: select * from t1 WHERE col1 + 5 BETWEEN 10 AND 20
83+
- explain: "ISCAN(I1 <,>) | FILTER _.COL1 + @c8 GREATER_THAN_OR_EQUALS promote(@c10 AS INT) AND _.COL1 + @c8 LESS_THAN_OR_EQUALS promote(@c12 AS INT)"
84+
- result: [
85+
{ID: 1, 10, 1}, {ID: 2, 10, 2}, {ID: 3, 10, 3}, {ID: 4, 10, 4}, {ID: 5, 10, 5}
86+
]
87+
-
88+
- query: select * from t1 WHERE col1 + 5 NOT BETWEEN 10 AND 20
89+
- explain: "ISCAN(I1 <,>) | FILTER _.COL1 + @c8 LESS_THAN promote(@c11 AS INT) OR _.COL1 + @c8 GREATER_THAN promote(@c13 AS INT)"
90+
- result: [
91+
{ID: 6, 20, 6}, {ID: 7, 20, 7}, {ID: 8, 20, 8}, {ID: 9, 20, 9}, {ID: 10, 20, 10},
92+
{ID: 11, 30, 11}, {ID: 12, 30, 12}, {ID: 13, 30, 13}
93+
]
94+
---
95+
test_block:
96+
name: between-incompatible-types
97+
tests:
98+
-
99+
- query: select * from t1 WHERE col1 BETWEEN 10 AND 'a'
100+
- error: "XX000"
101+
-
102+
- query: select * from t1 WHERE 'a' BETWEEN 10 AND 20
103+
- error: "XX000"
104+
---
105+
test_block:
106+
name: between-compatible-types
107+
tests:
108+
-
109+
- query: select * from t1 WHERE col2 BETWEEN 4 AND 6.2
110+
- result: [{ID: 4, 10, 4}, {ID: 5, 10, 5}, {ID: 6, 20, 6}]
111+
-
112+
- query: select * from t1 WHERE col2 BETWEEN 4.0 AND 6
113+
- result: [{ID: 4, 10, 4}, {ID: 5, 10, 5}, {ID: 6, 20, 6}]
114+
-
115+
- query: select * from t1 WHERE col2 BETWEEN 4.1 AND 6
116+
- result: [{ID: 5, 10, 5}, {ID: 6, 20, 6}]
117+
-
118+
- query: select count(*) from t1 WHERE 4.5 BETWEEN 4 AND 6
119+
- result: [{13}]
120+
---
121+
test_block:
122+
name: between-complex-operands
123+
tests:
124+
-
125+
- query: select count(*) from t1 WHERE 2+2 BETWEEN 1+1 AND 3+3
126+
- result: [{13}]
127+
---
128+
test_block:
129+
name: between-nulls
130+
tests:
131+
-
132+
- query: select count(*) from t1 WHERE null BETWEEN 1 AND 2
133+
- result: [{0}]
134+
-
135+
- query: select count(*) from t1 WHERE col2 BETWEEN null AND 2
136+
- result: [{0}]
137+
-
138+
- query: select count(*) from t1 WHERE col2 BETWEEN 1 AND null
139+
- result: [{0}]
140+
-
141+
- query: select count(*) from t1 WHERE null NOT BETWEEN 1 AND 2
142+
- result: [{0}]
143+
-
144+
- query: select count(*) from t1 WHERE col2 NOT BETWEEN null AND 2
145+
- result: [{11}]
146+
-
147+
- query: select count(*) from t1 WHERE col2 NOT BETWEEN 1 AND null
148+
- result: [{0}]
149+
...

0 commit comments

Comments
 (0)