Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature LIKE & NOT LIKE condition. #103

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion condition.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package dbr

import "reflect"
import (
"reflect"
)

func buildCond(d Dialect, buf Buffer, pred string, cond ...Builder) error {
for i, c := range cond {
Expand Down Expand Up @@ -117,3 +119,50 @@ func Lte(column string, value interface{}) Builder {
return buildCmp(d, buf, "<=", column, value)
})
}

func buildLikeCmp(d Dialect, buf Buffer, pred string, column string, value interface{}) error {
if value == nil {
return ErrInvalidValue
}

v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.String:
// pass as is
return buildCmp(d, buf, pred, column, value)
case reflect.Ptr, reflect.Interface: // pointer or interface
// for pointers & interfaces check
return buildLikeCmp(d, buf, pred, column, v.Elem().Interface())
case reflect.Slice:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems weird to me to support slice. Could you explain?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is an implementation that takes into consideration that byte slice or rune slice comes in

switch v.Type().Elem().Kind() {
case reflect.Uint8: // bytes
// interpolator will handle this case
return buildCmp(d, buf, pred, column, string(value.([]byte)))
case reflect.Int32: // rune
// need to convert into string
return buildCmp(d, buf, pred, column, string(value.([]rune)))
}
}

return ErrInvalidValue
}

// Like is `LIKE`.
// When value is nil, do nothing.
// When value is a slice, do nothing.
// Otherwise it will be translated to `LIKE`.
func Like(column string, value interface{}) Builder {
return BuildFunc(func(d Dialect, buf Buffer) error {
return buildLikeCmp(d, buf, "LIKE", column, value)
})
}

// NotLike is `NOT LIKE`.
// When value is nil, do nothing.
// When value is a slice, do nothing.
// Otherwise it will be translated to `NOT LIKE`.
func NotLike(column string, value interface{}) Builder {
return BuildFunc(func(d Dialect, buf Buffer) error {
return buildLikeCmp(d, buf, "NOT LIKE", column, value)
})
}
87 changes: 86 additions & 1 deletion condition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,61 +12,146 @@ func TestCondition(t *testing.T) {
cond Builder
query string
value []interface{}
isErr bool
}{
{
cond: Eq("col", 1),
query: "`col` = ?",
value: []interface{}{1},
isErr: false,
},
{
cond: Eq("col", nil),
query: "`col` IS NULL",
value: nil,
isErr: false,
},
{
cond: Eq("col", []int{}),
query: "0",
value: nil,
isErr: false,
},
{
cond: Neq("col", 1),
query: "`col` != ?",
value: []interface{}{1},
isErr: false,
},
{
cond: Neq("col", nil),
query: "`col` IS NOT NULL",
value: nil,
isErr: false,
},
{
cond: Gt("col", 1),
query: "`col` > ?",
value: []interface{}{1},
isErr: false,
},
{
cond: Gte("col", 1),
query: "`col` >= ?",
value: []interface{}{1},
isErr: false,
},
{
cond: Lt("col", 1),
query: "`col` < ?",
value: []interface{}{1},
isErr: false,
},
{
cond: Lte("col", 1),
query: "`col` <= ?",
value: []interface{}{1},
isErr: false,
},
{
cond: Like("col", 1),
query: "",
value: nil,
isErr: true,
},
{
cond: Like("col", "like"),
query: "`col` LIKE ?",
value: []interface{}{"like"},
isErr: false,
},
{
cond: Like("col", []rune{'l', 'i', 'k', 'e'}),
query: "`col` LIKE ?",
value: []interface{}{"like"},
isErr: false,
},
{
cond: Like("col", []byte("like")),
query: "`col` LIKE ?",
value: []interface{}{"like"},
isErr: false,
},
{
cond: Like("col", []int{}),
query: "",
value: nil,
isErr: true,
},
{
cond: Like("col", nil),
query: "",
value: nil,
isErr: true,
},
{
cond: NotLike("col", 1),
query: "",
value: nil,
isErr: true,
},
{
cond: NotLike("col", "not like"),
query: "`col` NOT LIKE ?",
value: []interface{}{"not like"},
isErr: false,
},
{
cond: NotLike("col", []rune{'n', 'o', 't', ' ', 'l', 'i', 'k', 'e'}),
query: "`col` NOT LIKE ?",
value: []interface{}{"not like"},
isErr: false,
},
{
cond: NotLike("col", []byte("not like")),
query: "`col` NOT LIKE ?",
value: []interface{}{"not like"},
isErr: false,
},
{
cond: NotLike("col", []int{}),
query: "",
value: nil,
isErr: true,
},
{
cond: NotLike("col", nil),
query: "",
value: nil,
isErr: true,
},
{
cond: And(Lt("a", 1), Or(Gt("b", 2), Neq("c", 3))),
query: "(`a` < ?) AND ((`b` > ?) OR (`c` != ?))",
value: []interface{}{1, 2, 3},
isErr: false,
},
} {
buf := NewBuffer()
err := test.cond.Build(dialect.MySQL, buf)
require.NoError(t, err)
if !test.isErr {
require.NoError(t, err)
}
require.Equal(t, test.query, buf.String())
require.Equal(t, test.value, buf.Value())
}
Expand Down
1 change: 1 addition & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ var (
ErrInvalidSliceLength = errors.New("dbr: length of slice is 0. length must be >= 1")
ErrCantConvertToTime = errors.New("dbr: can't convert to time.Time")
ErrInvalidTimestring = errors.New("dbr: invalid time string")
ErrInvalidValue = errors.New("dbr: invalid value")
)