Skip to content

Commit 97208c9

Browse files
committed
chore(embedded/sql): Implement BETWEEN AND expressions
Signed-off-by: Stefano Scafiti <[email protected]>
1 parent 0578f93 commit 97208c9

File tree

5 files changed

+460
-437
lines changed

5 files changed

+460
-437
lines changed

embedded/sql/engine_test.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3182,6 +3182,24 @@ func TestQuery(t *testing.T) {
31823182
"SELECT * FROM (VALUES (1, true, 'test'))",
31833183
)
31843184
})
3185+
3186+
t.Run("should resolve rows equivalently for BETWEEN and >= AND <=", func(t *testing.T) {
3187+
betweenRows, err := engine.queryAll(
3188+
context.Background(),
3189+
nil,
3190+
"SELECT id, title FROM table1 WHERE id BETWEEN 1 AND 3 ORDER BY id",
3191+
nil,
3192+
)
3193+
require.NoError(t, err)
3194+
3195+
rangeRows, err := engine.queryAll(
3196+
context.Background(), nil,
3197+
"SELECT id, title FROM table1 WHERE id >= 1 AND id <= 3 ORDER BY id",
3198+
nil,
3199+
)
3200+
require.NoError(t, err)
3201+
require.Equal(t, betweenRows, rangeRows)
3202+
})
31853203
}
31863204

31873205
func TestJSON(t *testing.T) {
@@ -6499,7 +6517,7 @@ func TestInferParametersUnbounded(t *testing.T) {
64996517
require.Len(t, params, 1)
65006518
require.Equal(t, AnyType, params["param1"])
65016519

6502-
params, err = engine.InferParameters(context.Background(), nil, "SELECT * FROM mytable WHERE @param1 != NOT NULL")
6520+
params, err = engine.InferParameters(context.Background(), nil, "SELECT * FROM mytable WHERE @param1 OR TRUE")
65036521
require.NoError(t, err)
65046522
require.Len(t, params, 1)
65056523
require.Equal(t, BooleanType, params["param1"])

embedded/sql/parser.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ var reservedWords = map[string]int{
8888
"NOT": NOT,
8989
"LIKE": LIKE,
9090
"EXISTS": EXISTS,
91+
"BETWEEN": BETWEEN,
9192
"IN": IN,
9293
"AUTO_INCREMENT": AUTO_INCREMENT,
9394
"NULL": NULL,

embedded/sql/parser_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1859,6 +1859,20 @@ func TestParseExp(t *testing.T) {
18591859
},
18601860
},
18611861
},
1862+
{
1863+
input: "SELECT price FROM items WHERE price BETWEEN 1.5 and 3.9",
1864+
expectedOutput: []SQLStmt{
1865+
&SelectStmt{
1866+
targets: []TargetEntry{{Exp: &ColSelector{col: "price"}}},
1867+
ds: &tableRef{table: "items"},
1868+
where: &BinBoolExp{
1869+
op: And,
1870+
left: &CmpBoolExp{op: GE, left: &ColSelector{col: "price"}, right: &Float64{1.5}},
1871+
right: &CmpBoolExp{op: LE, left: &ColSelector{col: "price"}, right: &Float64{3.9}},
1872+
},
1873+
},
1874+
},
1875+
},
18621876
}
18631877

18641878
for i, tc := range testCases {

embedded/sql/sql_grammar.y

Lines changed: 94 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ func setResult(l yyLexer, stmts []SQLStmt) {
8484
%token NOT LIKE IF EXISTS IN IS
8585
%token AUTO_INCREMENT NULL CAST SCAST
8686
%token SHOW DATABASES TABLES USERS
87+
%token BETWEEN
8788
%token <id> NPARAM
8889
%token <pparam> PPARAM
8990
%token <joinType> JOINTYPE
@@ -105,19 +106,20 @@ func setResult(l yyLexer, stmts []SQLStmt) {
105106
%left ','
106107
%right AS
107108

109+
%nonassoc BETWEEN
110+
108111
%left OR
109112
%left AND
110113

111-
%right NOT_MATCHES_OP
112-
%right LIKE
113114
%right NOT
114115

115-
%left CMPOP
116+
%nonassoc CMPOP LIKE NOT_MATCHES_OP IS
117+
116118
%left '+' '-'
117119
%left '*' '/' '%'
118-
%left '.'
120+
%left '.'
121+
119122
%right STMT_SEPARATOR
120-
%left IS
121123

122124
%type <stmts> sql sqlstmts
123125
%type <stmt> sqlstmt ddlstmt dmlstmt dqlstmt select_stmt
@@ -144,8 +146,8 @@ func setResult(l yyLexer, stmts []SQLStmt) {
144146
%type <check> check
145147
%type <tableElem> tableElem
146148
%type <tableElems> tableElems
147-
%type <exp> exp opt_exp opt_where opt_having boundexp opt_else
148-
%type <binExp> binExp
149+
%type <exp> exp opt_exp opt_where opt_having boundexp opt_else orExp andExp cmpExp primaryBool addExp notExp
150+
mulExp unaryExp primary
149151
%type <cols> opt_groupby
150152
%type <exp> opt_limit opt_offset case_when_exp
151153
%type <targets> opt_targets targets
@@ -1140,63 +1142,6 @@ opt_exp:
11401142
}
11411143
;
11421144

1143-
exp:
1144-
boundexp
1145-
{
1146-
$$ = $1
1147-
}
1148-
|
1149-
binExp
1150-
{
1151-
$$ = $1
1152-
}
1153-
|
1154-
NOT exp
1155-
{
1156-
$$ = &NotBoolExp{exp: $2}
1157-
}
1158-
|
1159-
'-' exp
1160-
{
1161-
i, isInt := $2.(*Integer)
1162-
if isInt {
1163-
i.val = -i.val
1164-
$$ = i
1165-
} else {
1166-
$$ = &NumExp{left: &Integer{val: 0}, op: SUBSOP, right: $2}
1167-
}
1168-
}
1169-
|
1170-
boundexp opt_not LIKE exp
1171-
{
1172-
$$ = &LikeBoolExp{val: $1, notLike: $2, pattern: $4}
1173-
}
1174-
|
1175-
boundexp NOT_MATCHES_OP exp
1176-
{
1177-
$$ = &LikeBoolExp{val: $1, notLike: true, pattern: $3}
1178-
}
1179-
|
1180-
EXISTS '(' dqlstmt ')'
1181-
{
1182-
$$ = &ExistsBoolExp{q: ($3).(DataSource)}
1183-
}
1184-
|
1185-
boundexp opt_not IN '(' dqlstmt ')'
1186-
{
1187-
$$ = &InSubQueryExp{val: $1, notIn: $2, q: $5.(*SelectStmt)}
1188-
}
1189-
|
1190-
boundexp opt_not IN '(' values ')'
1191-
{
1192-
$$ = &InListExp{val: $1, notIn: $2, values: $5}
1193-
}
1194-
|
1195-
case_when_exp
1196-
{
1197-
$$ = $1
1198-
}
1199-
12001145
case_when_exp:
12011146
CASE opt_exp when_then_clauses opt_else END
12021147
{
@@ -1231,6 +1176,91 @@ opt_else:
12311176
}
12321177
;
12331178

1179+
exp
1180+
: orExp { $$ = $1 }
1181+
;
1182+
1183+
orExp
1184+
: orExp OR andExp { $$ = &BinBoolExp{left: $1, op: Or, right: $3} }
1185+
| andExp
1186+
;
1187+
1188+
andExp
1189+
: andExp AND notExp { $$ = &BinBoolExp{left: $1, op: And, right: $3} }
1190+
| notExp
1191+
;
1192+
1193+
notExp
1194+
: NOT notExp { $$ = &NotBoolExp{exp: $2} }
1195+
| cmpExp
1196+
;
1197+
1198+
cmpExp
1199+
: addExp CMPOP addExp { $$ = &CmpBoolExp{left: $1, op: $2, right: $3} }
1200+
| addExp IS NULL { $$ = &CmpBoolExp{left: $1, op: EQ, right: &NullValue{t: AnyType}} }
1201+
| addExp IS NOT NULL { $$ = &CmpBoolExp{left: $1, op: NE, right: &NullValue{t: AnyType}} }
1202+
| addExp BETWEEN addExp AND addExp
1203+
{
1204+
$$ = &BinBoolExp{
1205+
left: &CmpBoolExp{
1206+
left: $1,
1207+
op: GE,
1208+
right: $3,
1209+
},
1210+
op: And,
1211+
right: &CmpBoolExp{
1212+
left: $1,
1213+
op: LE,
1214+
right: $5,
1215+
},
1216+
}
1217+
}
1218+
| addExp opt_not LIKE addExp { $$ = &LikeBoolExp{val: $1, notLike: $2, pattern: $4} }
1219+
| addExp NOT_MATCHES_OP addExp { $$ = &LikeBoolExp{val: $1, notLike: true, pattern: $3} }
1220+
| primaryBool
1221+
;
1222+
1223+
primaryBool
1224+
: EXISTS '(' dqlstmt ')' { $$ = &ExistsBoolExp{q: ($3).(DataSource)} }
1225+
| addExp opt_not IN '(' dqlstmt ')' { $$ = &InSubQueryExp{val: $1, notIn: $2, q: $5.(*SelectStmt)} }
1226+
| addExp opt_not IN '(' values ')' { $$ = &InListExp{val: $1, notIn: $2, values: $5} }
1227+
| case_when_exp { $$ = $1 }
1228+
| addExp
1229+
;
1230+
1231+
addExp
1232+
: addExp '+' mulExp { $$ = &NumExp{left: $1, op: ADDOP, right: $3} }
1233+
| addExp '-' mulExp { $$ = &NumExp{left: $1, op: SUBSOP, right: $3} }
1234+
| mulExp
1235+
;
1236+
1237+
mulExp
1238+
: mulExp '*' unaryExp { $$ = &NumExp{left: $1, op: MULTOP, right: $3} }
1239+
| mulExp '/' unaryExp { $$ = &NumExp{left: $1, op: DIVOP, right: $3} }
1240+
| mulExp '%' unaryExp { $$ = &NumExp{left: $1, op: MODOP, right: $3} }
1241+
| unaryExp
1242+
;
1243+
1244+
unaryExp
1245+
: '-' unaryExp
1246+
{
1247+
i, isInt := $2.(*Integer)
1248+
if isInt {
1249+
i.val = -i.val
1250+
$$ = i
1251+
} else {
1252+
$$ = &NumExp{left: &Integer{val: 0}, op: SUBSOP, right: $2}
1253+
}
1254+
}
1255+
|
1256+
primary
1257+
;
1258+
1259+
primary
1260+
: '(' exp ')' { $$ = $2 }
1261+
| boundexp
1262+
;
1263+
12341264
boundexp:
12351265
selector
12361266
{
@@ -1241,11 +1271,6 @@ boundexp:
12411271
{
12421272
$$ = $1
12431273
}
1244-
|
1245-
'(' exp ')'
1246-
{
1247-
$$ = $2
1248-
}
12491274
|
12501275
boundexp SCAST TYPE
12511276
{
@@ -1262,53 +1287,3 @@ opt_not:
12621287
$$ = true
12631288
}
12641289

1265-
binExp:
1266-
exp '+' exp
1267-
{
1268-
$$ = &NumExp{left: $1, op: ADDOP, right: $3}
1269-
}
1270-
|
1271-
exp '-' exp
1272-
{
1273-
$$ = &NumExp{left: $1, op: SUBSOP, right: $3}
1274-
}
1275-
|
1276-
exp '/' exp
1277-
{
1278-
$$ = &NumExp{left: $1, op: DIVOP, right: $3}
1279-
}
1280-
|
1281-
exp '*' exp
1282-
{
1283-
$$ = &NumExp{left: $1, op: MULTOP, right: $3}
1284-
}
1285-
|
1286-
exp '%' exp
1287-
{
1288-
$$ = &NumExp{left: $1, op: MODOP, right: $3}
1289-
}
1290-
|
1291-
exp AND exp
1292-
{
1293-
$$ = &BinBoolExp{left: $1, op: And, right: $3}
1294-
}
1295-
|
1296-
exp OR exp
1297-
{
1298-
$$ = &BinBoolExp{left: $1, op: Or, right: $3}
1299-
}
1300-
|
1301-
exp CMPOP exp
1302-
{
1303-
$$ = &CmpBoolExp{left: $1, op: $2, right: $3}
1304-
}
1305-
|
1306-
exp IS NULL
1307-
{
1308-
$$ = &CmpBoolExp{left: $1, op: EQ, right: &NullValue{t: AnyType}}
1309-
}
1310-
|
1311-
exp IS NOT NULL
1312-
{
1313-
$$ = &CmpBoolExp{left: $1, op: NE, right: &NullValue{t: AnyType}}
1314-
}

0 commit comments

Comments
 (0)