Skip to content

Commit

Permalink
Add support for LIKE and NOT LIKE pattern matching commands
Browse files Browse the repository at this point in the history
  • Loading branch information
nvanbenschoten committed Sep 17, 2015
1 parent a518d32 commit cfd6c40
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 11 deletions.
16 changes: 7 additions & 9 deletions sql/analyze_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,11 @@ func TestSimplifyExpr(t *testing.T) {
{`(a, b) = (1, 2) OR (a, b) = (3, 4)`, `(a, b) IN ((1, 2), (3, 4))`},
{`(a, b) IN ((2, 1), (1, 2), (1, 2), (2, 1))`, `(a, b) IN ((1, 2), (2, 1))`},

// TODO(pmattis): unsupported comparison operator: LIKE
// {`i LIKE '%foo'`, `true`},
// {`i LIKE 'foo'`, `i = 'foo'`},
// {`i LIKE 'foo%'`, `i >= 'foo' AND i < 'fop'`},
// {`i LIKE 'foo_'`, `i >= 'foo' AND i < 'fop'`},
// {`i LIKE 'bar_foo%'`, `i >= 'bar' AND i < 'bas'`},
{`i LIKE '%foo'`, `true`},
{`i LIKE 'foo'`, `i = 'foo'`},
{`i LIKE 'foo%'`, `i >= 'foo' AND i < 'fop'`},
{`i LIKE 'foo_'`, `i >= 'foo' AND i < 'fop'`},
{`i LIKE 'bar_foo%'`, `i >= 'bar' AND i < 'bas'`},
// TODO(pmattis): unsupported comparison operator: SIMILAR TO
// {`i SIMILAR TO '.*'`, `true`},
// {`i SIMILAR TO 'foo'`, `i = 'foo'`},
Expand Down Expand Up @@ -239,9 +238,8 @@ func TestSimplifyNotExpr(t *testing.T) {
{`NOT a <= 1`, `a > 1`, true},
{`NOT a IN (1, 2)`, `a NOT IN (1, 2)`, true},
{`NOT a NOT IN (1, 2)`, `a IN (1, 2)`, true},
// TODO(pmattis): unsupported comparison operator: LIKE
// {`NOT i LIKE 'foo'`, `true`, false},
// {`NOT i NOT LIKE 'foo'`, `i = 'foo'`, false},
{`NOT i LIKE 'foo'`, `true`, false},
{`NOT i NOT LIKE 'foo'`, `i = 'foo'`, false},
// TODO(pmattis): unsupported comparison operator: SIMILAR TO
// {`NOT i SIMILAR TO 'foo'`, `true`, false},
// {`NOT i NOT SIMILAR TO 'foo'`, `i = 'foo'`, false},
Expand Down
19 changes: 18 additions & 1 deletion sql/parser/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import (
"fmt"
"math"
"reflect"
"regexp"
"strconv"
"strings"
"time"
"unicode/utf8"

Expand Down Expand Up @@ -396,6 +398,17 @@ var cmpOps = map[cmpArgs]func(Datum, Datum) (DBool, error){
cmpArgs{LE, intervalType, intervalType}: func(left Datum, right Datum) (DBool, error) {
return DBool(left.(DInterval).Duration <= right.(DInterval).Duration), nil
},

cmpArgs{Like, stringType, stringType}: func(left Datum, right Datum) (DBool, error) {
pattern := regexp.QuoteMeta(string(right.(DString)))
pattern = strings.Replace(pattern, "%", ".*", -1)
pattern = strings.Replace(pattern, "_", ".", -1)
pattern = fmt.Sprintf("^%s$", pattern)

// TODO(pmattis): Can we cache this regexp compilation?
re := regexp.MustCompile(pattern)
return DBool(re.MatchString(string(left.(DString)))), nil
},
}

func init() {
Expand Down Expand Up @@ -727,6 +740,10 @@ func evalComparisonOp(op ComparisonOp, left, right Datum) (Datum, error) {
// NotIn(left, right) is implemented as !IN(left, right)
not = true
op = In
case NotLike:
// NotLike(left, right) is implemented as !Like(left, right)
not = true
op = Like
case IsDistinctFrom:
// IsDistinctFrom(left, right) is implemented as !EQ(left, right)
//
Expand All @@ -751,7 +768,7 @@ func evalComparisonOp(op ComparisonOp, left, right Datum) (Datum, error) {
}

switch op {
case Like, NotLike, SimilarTo, NotSimilarTo:
case SimilarTo, NotSimilarTo:
return DNull, util.Errorf("TODO(pmattis): unsupported comparison operator: %s", op)
}

Expand Down
11 changes: 11 additions & 0 deletions sql/parser/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,17 @@ func TestEvalExpr(t *testing.T) {
// Comparisons against NULL result in NULL.
{`0 = NULL`, `NULL`},
{`NULL = NULL`, `NULL`},
// LIKE and NOT LIKE
{`'TEST' LIKE 'TEST'`, `true`},
{`'TEST' LIKE 'TE%'`, `true`},
{`'TEST' LIKE '%E%'`, `true`},
{`'TEST' LIKE 'TES_'`, `true`},
{`'TEST' LIKE 'TE_'`, `false`},
{`'TEST' LIKE '%R'`, `false`},
{`'TEST' LIKE 'TESTER'`, `false`},
{`'TEST' NOT LIKE '%E%'`, `false`},
{`'TEST' NOT LIKE 'TES_'`, `false`},
{`'TEST' NOT LIKE 'TE_'`, `true`},
// IS DISTINCT FROM can be used to compare NULLs "safely".
{`0 IS DISTINCT FROM 0`, `false`},
{`0 IS DISTINCT FROM 1`, `true`},
Expand Down
5 changes: 4 additions & 1 deletion sql/parser/type_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ func typeCheckComparisonOp(op ComparisonOp, dummyLeft, dummyRight Datum) (Datum,
case NotIn:
// NotIn(left, right) is implemented as !IN(left, right)
op = In
case NotLike:
// NotLike(left, right) is implemented as !Like(left, right)
op = Like
}

lType := reflect.TypeOf(dummyLeft)
Expand All @@ -219,7 +222,7 @@ func typeCheckComparisonOp(op ComparisonOp, dummyLeft, dummyRight Datum) (Datum,
}

switch op {
case Like, NotLike, SimilarTo, NotSimilarTo:
case SimilarTo, NotSimilarTo:
return nil, util.Errorf("TODO(pmattis): unsupported comparison operator: %s", op)
}

Expand Down

0 comments on commit cfd6c40

Please sign in to comment.