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 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
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(goqu.RangeVal{Start:3,End: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(goqu.RangeVal{Start:3,End: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(goqu.RangeVal{Start:3,End: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(RangeVal{Start:1,End:2})))
assert.Equal(t, buf.String(), `("a" BETWEEN 1 AND 2)`)
assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").NotBetween(RangeVal{Start:1,End:2})))
assert.Equal(t, buf.String(), `("a" NOT BETWEEN 1 AND 2)`)
assert.NoError(t, ds.Literal(me.Truncate(buf), I("a").Between(RangeVal{Start:"aaa",End:"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(RangeVal{Start:1,End: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(RangeVal{Start:1,End: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(RangeVal{Start:"aaa",End:"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{Start:1,End:10}}}))
assert.Equal(t, buf.String(), `("a" BETWEEN 1 AND 10)`)
assert.NoError(t, ds.Literal(me.Truncate(buf), Ex{"a": Op{"notbetween": RangeVal{Start:1,End: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{Start:1,End: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{Start:1,End:10}}}))
assert.Equal(t, buf.args, []interface{}{1, 10})
assert.Equal(t, buf.String(), `("a" NOT BETWEEN ? AND ?)`)
}

func (me *datasetTest) TestLiteralExpressionOrMap() {
Expand Down
33 changes: 33 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,32 @@ func (me *DefaultAdapter) BooleanExpressionSql(buf *SqlBuilder, operator Boolean
return nil
}

//Generates SQL for a RangeExpresion (e.g. I("a").Between(RangeVal{Start:2,End: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)
}
rhs := operator.Rhs()
buf.WriteRune(space_rune)
if err := me.Literal(buf, rhs.Start); err != nil {
return err
}
buf.Write(default_and_fragment)
if err := me.Literal(buf, rhs.End); 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(goqu.RangeVal{Start:10,End:100})).ToSql()
fmt.Println(sql)

sql, _, _ = db.From("test").Where(goqu.L("(a + b)").NotBetween(goqu.RangeVal{Start:10,End: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
92 changes: 92 additions & 0 deletions expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ func mapToExpressionList(ex map[string]interface{}, eType ExpressionListType) (E
ored = lhs.ILike(op[opKey])
case "notilike":
ored = lhs.NotILike(op[opKey])
case "between":
rangeVal, ok := op[opKey].(RangeVal)
if ok {
ored = lhs.Between(rangeVal)
}
case "notbetween":
rangeVal, ok := op[opKey].(RangeVal)
if ok {
ored = lhs.NotBetween(rangeVal)
}
default:
return nil, NewGoquError("Unsupported expression type %s", op)
}
Expand Down Expand Up @@ -443,6 +453,14 @@ type (
// I("col").Lte(1) //("col" <= 1)
Lte(interface{}) BooleanExpression
}
RangeMethods interface {
//Creates a Range expression for between comparisons
// I("col").Between(RangeVal{Start:1, End:10}) //("col" BETWEEN 1 AND 10)
Between(RangeVal) RangeExpression
//Creates a Range expression for between comparisons
// I("col").NotBetween(RangeVal{Start:1, End:10}) //("col" NOT BETWEEN 1 AND 10)
NotBetween(RangeVal) RangeExpression
}
//Interface that an expression should implement if it can be used in an IN expression
InMethods interface {
//Creates a Boolean expression for IN clauses
Expand Down Expand Up @@ -525,6 +543,7 @@ type (
Expression
AliasMethods
ComparisonMethods
RangeMethods
InMethods
StringMethods
BooleanMethods
Expand Down Expand Up @@ -682,6 +701,12 @@ func (me identifier) Desc() OrderedExpression { return desc(
func (me identifier) Distinct() SqlFunctionExpression { return DISTINCT(me) }
func (me identifier) Cast(t string) CastExpression { return Cast(me, t) }

//Returns a RangeExpression for checking that a identifier is between two values (e.g "my_col" BETWEEN 1 AND 10)
func (me identifier) Between(val RangeVal) RangeExpression { return between(me, val) }

//Returns a RangeExpression for checking that a identifier is between two values (e.g "my_col" BETWEEN 1 AND 10)
func (me identifier) NotBetween(val RangeVal) RangeExpression { return notBetween(me, val) }

type (
//Expression for representing "literal" sql.
// L("col = 1") -> col = 1)
Expand All @@ -690,6 +715,7 @@ type (
Expression
AliasMethods
ComparisonMethods
RangeMethods
OrderedMethods
//Returns the literal sql
Literal() string
Expand Down Expand Up @@ -750,6 +776,8 @@ func (me literal) Lt(val interface{}) BooleanExpression { return lt(me, val) }
func (me literal) Lte(val interface{}) BooleanExpression { return lte(me, val) }
func (me literal) Asc() OrderedExpression { return asc(me) }
func (me literal) Desc() OrderedExpression { return desc(me) }
func (me literal) Between(val RangeVal) RangeExpression { return between(me, val) }
func (me literal) NotBetween(val RangeVal) RangeExpression { return notBetween(me, val) }

type (
UpdateExpression interface {
Expand Down Expand Up @@ -1003,6 +1031,66 @@ func checkBoolExpType(op BooleanOperation, lhs Expression, rhs interface{}, inve
return boolean{op: op, lhs: lhs, rhs: rhs}
}


type (
RangeOperation int
RangeExpression interface {
Expression
//Returns the operator for the expression
Op() RangeOperation
//The left hand side of the expression (e.g. I("a")
Lhs() Expression
//The right hand side of the expression could be a primitive value, dataset, or expression
Rhs() RangeVal
}
ranged struct {
lhs Expression
rhs RangeVal
op RangeOperation
}
RangeVal struct {
Start interface{}
End interface{}
}
)

const (
//BETWEEN
BETWEEN_OP RangeOperation = iota
//NOT BETWEEN
NBETWEEN_OP
)

func (me ranged) Clone() Expression {
return ranged{op: me.op, lhs: me.lhs.Clone(), rhs: me.rhs}
}

func (me ranged) Expression() Expression {
return me
}

func (me ranged) Rhs() RangeVal {
return me.rhs
}

func (me ranged) Lhs() Expression {
return me.lhs
}

func (me ranged) Op() RangeOperation {
return me.op
}

//used internally to create an BETWEEN comparison RangeExpression
func between(lhs Expression, rhs RangeVal) RangeExpression {
return ranged{op: BETWEEN_OP, lhs: lhs, rhs: rhs}
}

//used internally to create an NOT BETWEEN comparison RangeExpression
func notBetween(lhs Expression, rhs RangeVal) RangeExpression {
return ranged{op: NBETWEEN_OP, lhs: lhs, rhs: rhs}
}

type (
//Expression for Aliased expressions
// I("a").As("b") -> "a" AS "b"
Expand Down Expand Up @@ -1216,6 +1304,8 @@ func (me sqlFunctionExpression) Gt(val interface{}) BooleanExpression { return
func (me sqlFunctionExpression) Gte(val interface{}) BooleanExpression { return gte(me, val) }
func (me sqlFunctionExpression) Lt(val interface{}) BooleanExpression { return lt(me, val) }
func (me sqlFunctionExpression) Lte(val interface{}) BooleanExpression { return lte(me, val) }
func (me sqlFunctionExpression) Between(val RangeVal) RangeExpression { return between(me, val) }
func (me sqlFunctionExpression) NotBetween(val RangeVal) RangeExpression { return notBetween(me, val) }

type (
//An Expression that represents another Expression casted to a SQL type
Expand Down Expand Up @@ -1282,6 +1372,8 @@ func (me cast) IsNotTrue() BooleanExpression { return isNot(me, true
func (me cast) IsFalse() BooleanExpression { return is(me, false) }
func (me cast) IsNotFalse() BooleanExpression { return isNot(me, nil) }
func (me cast) Distinct() SqlFunctionExpression { return DISTINCT(me) }
func (me cast) Between(val RangeVal) RangeExpression { return between(me, val) }
func (me cast) NotBetween(val RangeVal) RangeExpression{ return notBetween(me, val) }

type (
compoundType int
Expand Down