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 range clauses ([NOT] BETWEEN) support #25

Merged
merged 6 commits into from
Sep 14, 2016
Merged
Show file tree
Hide file tree
Changes from 5 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
4 changes: 4 additions & 0 deletions adapters.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ type (
//
//buf: The current SqlBuilder to write the sql to
BooleanExpressionSql(buf *SqlBuilder, operator BooleanExpression) error
//Generates SQL value for a RangeExpression
//
//buf: The current SqlBuilder to write the sql to
RangeExpressionSql(buf *SqlBuilder, operator RangeExpression) error
//Generates SQL value for an OrderedExpression
//
//buf: The current SqlBuilder to write the sql to
Expand Down
9 changes: 9 additions & 0 deletions adapters/mysql/mysql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,15 @@ func (me *mysqlTest) TestQuery() {
assert.True(t, entry.Int <= 4)
}

entries = entries[0:0]
assert.NoError(t, ds.Where(goqu.I("int").Between(3,6)).Order(goqu.I("id").Asc()).ScanStructs(&entries))
assert.Len(t, entries, 4)
assert.NoError(t, err)
for _, entry := range entries {
assert.True(t, entry.Int >= 3)
assert.True(t, entry.Int <= 6)
}

entries = entries[0:0]
assert.NoError(t, ds.Where(goqu.I("string").Eq("0.100000")).Order(goqu.I("id").Asc()).ScanStructs(&entries))
assert.Len(t, entries, 1)
Expand Down
9 changes: 9 additions & 0 deletions adapters/postgres/postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,15 @@ func (me *postgresTest) TestQuery() {
assert.True(t, entry.Int <= 4)
}

entries = entries[0:0]
assert.NoError(t, ds.Where(goqu.I("int").Between(3,6)).Order(goqu.I("id").Asc()).ScanStructs(&entries))
assert.Len(t, entries, 4)
assert.NoError(t, err)
for _, entry := range entries {
assert.True(t, entry.Int >= 3)
assert.True(t, entry.Int <= 6)
}

entries = entries[0:0]
assert.NoError(t, ds.Where(goqu.I("string").Eq("0.100000")).Order(goqu.I("id").Asc()).ScanStructs(&entries))
assert.Len(t, entries, 1)
Expand Down
9 changes: 9 additions & 0 deletions adapters/sqlite3/sqlite3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,15 @@ func (me *sqlite3Test) TestQuery() {
assert.True(t, entry.Int <= 4)
}

entries = entries[0:0]
assert.NoError(t, ds.Where(goqu.I("int").Between(3,6)).Order(goqu.I("id").Asc()).ScanStructs(&entries))
assert.Len(t, entries, 4)
assert.NoError(t, err)
for _, entry := range entries {
assert.True(t, entry.Int >= 3)
assert.True(t, entry.Int <= 6)
}

entries = entries[0:0]
assert.NoError(t, ds.Where(goqu.I("string").Eq("0.100000")).Order(goqu.I("id").Asc()).ScanStructs(&entries))
assert.Len(t, entries, 1)
Expand Down
2 changes: 2 additions & 0 deletions dataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ func (me *Dataset) expressionSql(buf *SqlBuilder, expression Expression) error {
return me.adapter.AliasedExpressionSql(buf, e)
} else if e, ok := expression.(BooleanExpression); ok {
return me.adapter.BooleanExpressionSql(buf, e)
} else if e, ok := expression.(RangeExpression); ok {
return me.adapter.RangeExpressionSql(buf, e)
} else if e, ok := expression.(OrderedExpression); ok {
return me.adapter.OrderedExpressionSql(buf, e)
} else if e, ok := expression.(UpdateExpression); ok {
Expand Down
32 changes: 32 additions & 0 deletions dataset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,29 @@ func (me *datasetTest) TestBooleanExpression() {
assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").NotILike(regexp.MustCompile("(a|b)"))))
assert.Equal(t, buf.args, []interface{}{"(a|b)"})
assert.Equal(t, buf.String(), `("a" !~* ?)`)
}

func (me *datasetTest) TestRangeExpression() {
t := me.T()
buf := NewSqlBuilder(false)
ds := From("test")
assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Between(1,2)))
assert.Equal(t, buf.String(), `("a" BETWEEN 1 AND 2)`)
assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").NotBetween(1,2)))
assert.Equal(t, buf.String(), `("a" NOT BETWEEN 1 AND 2)`)
assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Between("aaa","zzz")))
assert.Equal(t, buf.String(), `("a" BETWEEN 'aaa' AND 'zzz')`)

buf = NewSqlBuilder(true)
assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Between(1,2)))
assert.Equal(t, buf.args, []interface{}{1, 2})
assert.Equal(t, buf.String(), `("a" BETWEEN ? AND ?)`)
assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").NotBetween(1,2)))
assert.Equal(t, buf.args, []interface{}{1, 2})
assert.Equal(t, buf.String(), `("a" NOT BETWEEN ? AND ?)`)
assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Between("aaa","zzz")))
assert.Equal(t, buf.args, []interface{}{"aaa", "zzz"})
assert.Equal(t, buf.String(), `("a" BETWEEN ? AND ?)`)
}

func (me *datasetTest) TestLiteralOrderedExpression() {
Expand Down Expand Up @@ -748,6 +770,10 @@ func (me *datasetTest) TestLiteralExpressionMap() {
assert.Equal(t, buf.String(), `("a" NOT IN ('a', 'b', 'c'))`)
assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"is": nil, "eq": 10}}))
assert.Equal(t, buf.String(), `(("a" = 10) OR ("a" IS NULL))`)
assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"between": RangeVal{1,10}}}))
Copy link
Contributor

Choose a reason for hiding this comment

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

golint gripes about using RangeVal{1,10} this way. I would specify the Start and End like you do in https://github.com/doug-martin/goqu/pull/25/files#diff-7101a3ea121adf8d5b0acb5a2c520ca3R1524

assert.Equal(t, buf.String(), `("a" BETWEEN 1 AND 10)`)
assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"notbetween": RangeVal{1,10}}}))
assert.Equal(t, buf.String(), `("a" NOT BETWEEN 1 AND 10)`)

buf = NewSqlBuilder(true)
assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": 1}))
Expand Down Expand Up @@ -796,6 +822,12 @@ func (me *datasetTest) TestLiteralExpressionMap() {
assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"is": nil, "eq": 10}}))
assert.Equal(t, buf.args, []interface{}{10, nil})
assert.Equal(t, buf.String(), `(("a" = ?) OR ("a" IS ?))`)
assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"between": RangeVal{1,10}}}))
assert.Equal(t, buf.args, []interface{}{1, 10})
assert.Equal(t, buf.String(), `("a" BETWEEN ? AND ?)`)
assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"notbetween": RangeVal{1,10}}}))
assert.Equal(t, buf.args, []interface{}{1, 10})
assert.Equal(t, buf.String(), `("a" NOT BETWEEN ? AND ?)`)
}

func (me *datasetTest) TestLiteralExpressionOrMap() {
Expand Down
34 changes: 34 additions & 0 deletions default_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ var (
REGEXP_I_LIKE_OP: []byte("~*"),
REGEXP_NOT_I_LIKE_OP: []byte("!~*"),
}
default_rangeop_lookup = map[RangeOperation][]byte{
BETWEEN_OP: []byte("BETWEEN"),
NBETWEEN_OP: []byte("NOT BETWEEN"),
}
default_join_lookup = map[JoinType][]byte{
INNER_JOIN: []byte(" INNER JOIN "),
FULL_OUTER_JOIN: []byte(" FULL OUTER JOIN "),
Expand Down Expand Up @@ -182,6 +186,8 @@ type (
TimeFormat string
//A map used to look up BooleanOperations and their SQL equivalents
BooleanOperatorLookup map[BooleanOperation][]byte
//A map used to look up RangeOperations and their SQL equivalents
RangeOperatorLookup map[RangeOperation][]byte
//A map used to look up JoinTypes and their SQL equivalents
JoinTypeLookup map[JoinType][]byte
//Whether or not to use literal TRUE or FALSE for IS statements (e.g. IS TRUE or IS 0)
Expand Down Expand Up @@ -233,6 +239,7 @@ func NewDefaultAdapter(ds *Dataset) Adapter {
IntersectAllFragment: default_intersect_all_fragment,
PlaceHolderRune: default_place_holder_rune,
BooleanOperatorLookup: default_operator_lookup,
RangeOperatorLookup: default_rangeop_lookup,
JoinTypeLookup: default_join_lookup,
TimeFormat: time.RFC3339Nano,
UseLiteralIsBools: true,
Expand Down Expand Up @@ -726,6 +733,33 @@ func (me *DefaultAdapter) BooleanExpressionSql(buf *SqlBuilder, operator Boolean
return nil
}

//Generates SQL for a RangeExpresion (e.g. I("a").Between(2,5) -> "a" BETWEEN 2 AND 5)
func (me *DefaultAdapter) RangeExpressionSql(buf *SqlBuilder, operator RangeExpression) error {
buf.WriteRune(left_paren_rune)
if err := me.Literal(buf, operator.Lhs()); err != nil {
return err
}
buf.WriteRune(space_rune)
operatorOp := operator.Op()
if val, ok := me.RangeOperatorLookup[operatorOp]; ok {
buf.Write(val)
} else {
return NewGoquError("Range operator %+v not supported", operatorOp)
}
rhs1 := operator.Rhs1()
rhs2 := operator.Rhs2()
buf.WriteRune(space_rune)
if err := me.Literal(buf, rhs1); err != nil {
return err
}
buf.Write(default_and_fragment)
if err := me.Literal(buf, rhs2); err != nil {
return err
}
buf.WriteRune(right_paren_rune)
return nil
}

//Generates SQL for an OrderedExpression (e.g. I("a").Asc() -> "a" ASC)
func (me *DefaultAdapter) OrderedExpressionSql(buf *SqlBuilder, order OrderedExpression) error {
if err := me.Literal(buf, order.SortExpression()); err != nil {
Expand Down
23 changes: 22 additions & 1 deletion example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ func ExampleComparisonMethods() {
sql, _, _ = db.From("test").Where(goqu.L("(a + b)").Lte(10)).ToSql()
fmt.Println(sql)

sql, _, _ = db.From("test").Where(goqu.L("(a + b)").Between(10,100)).ToSql()
fmt.Println(sql)

sql, _, _ = db.From("test").Where(goqu.L("(a + b)").NotBetween(10,100)).ToSql()
fmt.Println(sql)

//used with Ex expression map
sql, _, _ = db.From("test").Where(goqu.Ex{
"a": 10,
Expand All @@ -208,6 +214,8 @@ func ExampleComparisonMethods() {
// SELECT * FROM "test" WHERE ((a + b) >= 10)
// SELECT * FROM "test" WHERE ((a + b) < 10)
// SELECT * FROM "test" WHERE ((a + b) <= 10)
// SELECT * FROM "test" WHERE ((a + b) BETWEEN 10 AND 100)
// SELECT * FROM "test" WHERE ((a + b) NOT BETWEEN 10 AND 100)
// SELECT * FROM "test" WHERE (("a" = 10) AND ("b" != 10) AND ("c" >= 10) AND ("d" < 10) AND ("e" <= 10))
}

Expand Down Expand Up @@ -1511,12 +1519,18 @@ func ExampleEx_withOpPrepared() {
}).ToSql()
fmt.Println(sql, args)

sql, args, _ = db.From("items").Prepared(true).Where(goqu.Ex{
"col1": goqu.Op{"between": goqu.RangeVal{Start:1,End:10}},
"col2": goqu.Op{"notbetween": goqu.RangeVal{Start:1,End:10}},
}).ToSql()
fmt.Println(sql, args)

// Output:
// SELECT * FROM "items" WHERE (("col1" != ?) AND ("col3" IS NOT TRUE) AND ("col6" NOT IN (?, ?, ?))) [a a b c]
// SELECT * FROM "items" WHERE (("col1" > ?) AND ("col2" >= ?) AND ("col3" < ?) AND ("col4" <= ?)) [1 1 1 1]
// SELECT * FROM "items" WHERE (("col1" LIKE ?) AND ("col2" NOT LIKE ?) AND ("col3" ILIKE ?) AND ("col4" NOT ILIKE ?)) [a% a% a% a%]
// SELECT * FROM "items" WHERE (("col1" ~ ?) AND ("col2" !~ ?) AND ("col3" ~* ?) AND ("col4" !~* ?)) [^(a|b) ^(a|b) ^(a|b) ^(a|b)]

// SELECT * FROM "items" WHERE (("col1" BETWEEN ? AND ?) AND ("col2" NOT BETWEEN ? AND ?)) [1 10 1 10]
}

func ExampleOp() {
Expand Down Expand Up @@ -1552,11 +1566,18 @@ func ExampleOp() {
}).ToSql()
fmt.Println(sql)

sql, _, _ = db.From("items").Where(goqu.Ex{
"col1": goqu.Op{"between": goqu.RangeVal{Start:1,End:10}},
"col2": goqu.Op{"notbetween": goqu.RangeVal{Start:1,End:10}},
}).ToSql()
fmt.Println(sql)

// Output:
// SELECT * FROM "items" WHERE (("col1" != 'a') AND ("col3" IS NOT TRUE) AND ("col6" NOT IN ('a', 'b', 'c')))
// SELECT * FROM "items" WHERE (("col1" > 1) AND ("col2" >= 1) AND ("col3" < 1) AND ("col4" <= 1))
// SELECT * FROM "items" WHERE (("col1" LIKE 'a%') AND ("col2" NOT LIKE 'a%') AND ("col3" ILIKE 'a%') AND ("col4" NOT ILIKE 'a%'))
// SELECT * FROM "items" WHERE (("col1" ~ '^(a|b)') AND ("col2" !~ '^(a|b)') AND ("col3" ~* '^(a|b)') AND ("col4" !~* '^(a|b)'))
// SELECT * FROM "items" WHERE (("col1" BETWEEN 1 AND 10) AND ("col2" NOT BETWEEN 1 AND 10))
}

func ExampleOp_withMultipleKeys() {
Expand Down
Loading