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 4 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
22 changes: 22 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
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
8 changes: 8 additions & 0 deletions 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
88 changes: 88 additions & 0 deletions expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ func mapToExpressionList(ex map[string]interface{}, eType ExpressionListType) (E
ored = lhs.Lt(op[opKey])
case "lte":
ored = lhs.Lte(op[opKey])
// case "between":
// ored = lhs.Between(op[opKey])
// case "notbetween":
// ored = lhs.NotBetween(op[opKey])
case "in":
ored = lhs.In(op[opKey])
case "notin":
Expand Down Expand Up @@ -443,6 +447,14 @@ type (
// I("col").Lte(1) //("col" <= 1)
Lte(interface{}) BooleanExpression
}
RangeMethods interface {
//Creates a Range expression for between comparisons
// I("col").Between(1, 10) //("col" BETWEEN 1 AND 10)
Between(interface{}, interface{}) RangeExpression
//Creates a Range expression for between comparisons
// I("col").NotBetween(1, 10) //("col" NOT BETWEEN 1 AND 10)
NotBetween(interface{}, interface{}) 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 +537,7 @@ type (
Expression
AliasMethods
ComparisonMethods
RangeMethods
InMethods
StringMethods
BooleanMethods
Expand Down Expand Up @@ -682,6 +695,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(val1 interface{}, val2 interface{}) RangeExpression { return between(me, val1, val2) }

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

type (
//Expression for representing "literal" sql.
// L("col = 1") -> col = 1)
Expand All @@ -690,6 +709,7 @@ type (
Expression
AliasMethods
ComparisonMethods
RangeMethods
OrderedMethods
//Returns the literal sql
Literal() string
Expand Down Expand Up @@ -750,6 +770,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(val1 interface{}, val2 interface{}) RangeExpression { return between(me, val1, val2) }
func (me literal) NotBetween(val1 interface{}, val2 interface{}) RangeExpression { return notBetween(me, val1, val2) }

type (
UpdateExpression interface {
Expand Down Expand Up @@ -1003,6 +1025,68 @@ 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
Rhs1() interface{}
Copy link
Owner

Choose a reason for hiding this comment

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

So Im torn on this you could go a couple of different ways

  1. I think Rhs1 and 2 could be joined into a single slice of what would always be two values here and you could keep with the pattern on Lhs and Rhs and change the between methods to take variadic args but that could get messy.
  2. Create a new Range type I("a").Between(Range(1,2)) which could still allow you to get the single Rhs and allow you to include between and notbetween in the goqu.Ex code which was commented out above.

What do you think? I lean towards option 2.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, option 2 looks cleaner and less error prone :D

Rhs2() interface{}
}
ranged struct {
lhs Expression
rhs1 interface{}
Copy link
Owner

Choose a reason for hiding this comment

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

I think we can replace the rhs1, rhs2 with a single RangeVal

rhs2 interface{}
op RangeOperation
}
)

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

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

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

func (me ranged) Rhs1() interface{} {
return me.rhs1
}

func (me ranged) Rhs2() interface{} {
return me.rhs2
}

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, rhs1 interface{}, rhs2 interface{}) RangeExpression {
return ranged{op: BETWEEN_OP, lhs: lhs, rhs1: rhs1, rhs2: rhs2}
}

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

type (
//Expression for Aliased expressions
// I("a").As("b") -> "a" AS "b"
Expand Down Expand Up @@ -1216,6 +1300,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(val1 interface{}, val2 interface{}) RangeExpression { return between(me, val1, val2) }
func (me sqlFunctionExpression) NotBetween(val1 interface{}, val2 interface{}) RangeExpression { return notBetween(me, val1, val2) }

type (
//An Expression that represents another Expression casted to a SQL type
Expand Down Expand Up @@ -1282,6 +1368,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(val1 interface{}, val2 interface{}) RangeExpression { return between(me, val1, val2) }
func (me cast) NotBetween(val1 interface{}, val2 interface{}) RangeExpression{ return notBetween(me, val1, val2) }

type (
compoundType int
Expand Down