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. #75

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ One common reason to use this is to prevent string concatenation in a loop.
* Gte
* Lt
* Lte
* Like
* NotLike

```go
dbr.And(
Expand Down
59 changes: 58 additions & 1 deletion condition.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package dbr

import "reflect"
import (
"fmt"
"reflect"
)

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

// Lk is `LIKE`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use the correct function name for golint.

// 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 {
if value == nil {

Choose a reason for hiding this comment

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

IMO: it would be better avoid some logic here and pass arguments as is to underlayed level. if aguments is not valid, user will be notified about this via query builder error or via sql error.

return fmt.Errorf("Column %s LIKE nil is Not Supported", column)
}

v := reflect.ValueOf(value)
switch {
case v.Kind() == reflect.TypeOf([]rune{}).Kind() && v.Len() != 0:
return buildCmp(d, buf, "LIKE", column, string(value.([]rune)))

case v.Kind() == reflect.TypeOf([0]rune{}).Kind() && v.Len() != 0:
return buildCmp(d, buf, "LIKE", column, string(value.([]rune)[:]))

case v.Kind() == reflect.Slice || v.Kind() == reflect.Array:
return fmt.Errorf("Column %s LIKE Multiple Value is Not Supported", column)

default:
return buildCmp(d, buf, "LIKE", column, value)
}
})
}

// Nlk is `NOT LIKE`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use the correct function name for golint.

// 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 {
if value == nil {
return fmt.Errorf("Column %s NOT LIKE nil is Not Supported", column)
}

v := reflect.ValueOf(value)

Choose a reason for hiding this comment

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

I think the common part of this 2 functions can be moved to separate function.
something like this:
func Like(column string, value interface{}) Builder { return BuildFunc(func(d Dialect, buf Buffer) error { return buildLikeCmp(d, buf, "NOT LIKE", column, value) }

switch {
case v.Kind() == reflect.TypeOf([]rune{}).Kind() && v.Len() != 0:

Choose a reason for hiding this comment

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

I think it is not needed to check that v.Len() != 0, becuase 'LIKE ""' is valid expression.

return buildCmp(d, buf, "NOT LIKE", column, string(value.([]rune)))

case v.Kind() == reflect.TypeOf([0]rune{}).Kind() && v.Len() != 0:

Choose a reason for hiding this comment

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

ditto

return buildCmp(d, buf, "NOT LIKE", column, string(value.([]rune)[:]))

case v.Kind() == reflect.Slice || v.Kind() == reflect.Array:
return fmt.Errorf("Column %s NOT LIKE Multiple Value is Not Supported", column)

default:
return buildCmp(d, buf, "NOT LIKE", column, value)
}
})
}
75 changes: 74 additions & 1 deletion condition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,61 +12,134 @@ 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,
},
{

Choose a reason for hiding this comment

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

LGTM.
IMO: it would be great also add tests case for []byte

cond: Like("col", 1),
query: "`col` LIKE ?",
value: []interface{}{1},
isErr: false,
},
{
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", []int{}),
query: "",
value: nil,
isErr: true,
},
{
cond: Like("col", nil),
query: "",
value: nil,
isErr: true,
},

Choose a reason for hiding this comment

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

please add test with array of runs.

{
cond: NotLike("col", 1),
query: "`col` NOT LIKE ?",
value: []interface{}{1},
isErr: false,
},
{
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", []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)
assert.NoError(t, err)
if !test.isErr {
assert.NoError(t, err)
}
assert.Equal(t, test.query, buf.String())
assert.Equal(t, test.value, buf.Value())
}
Expand Down