Add Or and And query mods, add ? placeholders

* Update whereClause builder to support Or and parentheses
* ? placeholders are now used instead of $1, $2 etc for where clauses.
* Update the whereClause test to add more tests and fix broken ones.
* Fix broken golden test files.
This commit is contained in:
Patrick O'brien 2016-08-11 16:44:15 +10:00
parent af1b647fb4
commit c3f8cff117
7 changed files with 177 additions and 45 deletions

View file

@ -1 +1 @@
DELETE FROM thing happy, upset as "sad", "fun", thing as stuff, "angry" as mad WHERE a=$1 AND b=$2 AND c=$3;
DELETE FROM thing happy, upset as "sad", "fun", thing as stuff, "angry" as mad WHERE (a=$1) AND (b=$2) AND (c=$3);

View file

@ -1 +1 @@
DELETE FROM thing happy, upset as "sad", "fun", thing as stuff, "angry" as mad WHERE (id=$1 and $thing=$2) or stuff=$3;
DELETE FROM thing happy, upset as "sad", "fun", thing as stuff, "angry" as mad WHERE ((id=$1 and thing=$2) or stuff=$3);

View file

@ -54,6 +54,23 @@ func Where(clause string, args ...interface{}) QueryMod {
}
}
// And allows you to specify a where clause seperated by an AND for your statement
// And is a duplicate of the Where function, but allows for more natural looking
// query mod chains, for example: (Where("a=?"), And("b=?"), Or("c=?")))
func And(clause string, args ...interface{}) QueryMod {
return func(q *boil.Query) {
boil.AppendWhere(q, clause, args...)
}
}
// Or allows you to specify a where clause seperated by an OR for your statement
func Or(clause string, args ...interface{}) QueryMod {
return func(q *boil.Query) {
boil.SetLastWhereAsOr(q)
boil.AppendWhere(q, clause, args...)
}
}
// GroupBy allows you to specify a group by clause for your statement
func GroupBy(clause string) QueryMod {
return func(q *boil.Query) {

View file

@ -35,8 +35,9 @@ type Query struct {
}
type where struct {
clause string
args []interface{}
clause string
orSeparator bool
args []interface{}
}
type plainSQL struct {
@ -179,6 +180,15 @@ func SetWhere(q *Query, clause string, args ...interface{}) {
q.where = append([]where(nil), where{clause: clause, args: args})
}
// SetLastWhereAsOr sets the or separator for the tail where in the slice
func SetLastWhereAsOr(q *Query) {
if len(q.where) == 0 {
return
}
q.where[len(q.where)-1].orSeparator = true
}
// ApplyGroupBy on the query.
func ApplyGroupBy(q *Query, clause string) {
q.groupBy = append(q.groupBy, clause)

View file

@ -175,14 +175,43 @@ func whereClause(q *Query) (string, []interface{}) {
buf.WriteString(" WHERE ")
for i := 0; i < len(q.where); i++ {
buf.WriteString(fmt.Sprintf("%s", q.where[i].clause))
buf.WriteString(fmt.Sprintf("(%s)", q.where[i].clause))
args = append(args, q.where[i].args...)
if i < len(q.where)-1 {
// break on the last loop
if i == len(q.where)-1 {
break
}
if q.where[i].orSeparator {
buf.WriteString(" OR ")
} else {
buf.WriteString(" AND ")
}
}
return buf.String(), args
whereStr := buf.String()
paramBuf := &bytes.Buffer{}
paramIndex := 0
for counter := 1; ; counter++ {
if paramIndex >= len(whereStr) {
break
}
whereStr = whereStr[paramIndex:]
paramIndex = strings.IndexByte(whereStr, '?')
if paramIndex == -1 {
paramBuf.WriteString(whereStr)
break
}
paramBuf.WriteString(whereStr[:paramIndex] + fmt.Sprintf("$%d", counter))
paramIndex++
}
return paramBuf.String(), args
}
// identifierMapping creates a map of all identifiers to potential model names

View file

@ -48,16 +48,16 @@ func TestBuildQuery(t *testing.T) {
delete: true,
from: []string{"thing happy", `upset as "sad"`, "fun", "thing as stuff", `"angry" as mad`},
where: []where{
where{clause: "a=$1", args: []interface{}{}},
where{clause: "b=$2", args: []interface{}{}},
where{clause: "c=$3", args: []interface{}{}},
where{clause: "a=?", args: []interface{}{}},
where{clause: "b=?", args: []interface{}{}},
where{clause: "c=?", args: []interface{}{}},
},
}, nil},
{&Query{
delete: true,
from: []string{"thing happy", `upset as "sad"`, "fun", "thing as stuff", `"angry" as mad`},
where: []where{
where{clause: "(id=$1 and $thing=$2) or stuff=$3", args: []interface{}{}},
where{clause: "(id=? and thing=?) or stuff=?", args: []interface{}{}},
},
}, nil},
}
@ -180,76 +180,119 @@ func TestWriteStars(t *testing.T) {
}
func TestWhereClause(t *testing.T) {
t.Parallel()
tests := []struct {
q Query
expect string
}{
// Where("a=$1")
// Or("a=?")
{
q: Query{
where: []where{
where{clause: "a=$1"},
},
where: []where{where{clause: "a=?", orSeparator: true}},
},
expect: " WHERE a=$1",
expect: " WHERE (a=$1)",
},
// Where("a=$1 OR b=$2")
// Where("a=?")
{
q: Query{
where: []where{
where{clause: "a=$1 OR b=$2"},
},
where: []where{where{clause: "a=?"}},
},
expect: " WHERE a=$1 OR b=$2",
expect: " WHERE (a=$1)",
},
// Where("a=$1", "b=$2")
// Where("(a=?)")
{
q: Query{
where: []where{
where{clause: "a=$1"},
where{clause: "b=$2"},
},
where: []where{where{clause: "(a=?)"}},
},
expect: " WHERE a=$1 AND b=$2",
expect: " WHERE ((a=$1))",
},
// Where("(a=$1 AND b=$2) OR c=$3")
// Where("((a=? OR b=?))")
{
q: Query{
where: []where{
where{clause: "(a=$1 AND b=$2) OR c=$3"},
},
where: []where{where{clause: "((a=? OR b=?))"}},
},
expect: " WHERE (a=$1 AND b=$2) OR c=$3",
expect: " WHERE (((a=$1 OR b=$2)))",
},
// Where("a=$1 OR b=$2", "c=$3 OR d=$4 OR e=$5")
// Where("(a=?)", Or("(b=?)")
{
q: Query{
where: []where{
where{clause: "(a=$1 OR b=$2)"},
where{clause: "(c=$3 OR d=$4 OR e=$5)"},
where{clause: "(a=?)", orSeparator: true},
where{clause: "(b=?)"},
},
},
expect: " WHERE (a=$1 OR b=$2) AND (c=$3 OR d=$4 OR e=$5)",
expect: " WHERE ((a=$1)) OR ((b=$2))",
},
// Where("(a=$1 AND b=$2) OR (c=$3 AND d=$4 AND e=$5) OR f=$6 OR f=$7")
// Where("a=? OR b=?")
{
q: Query{
where: []where{
where{clause: "(a=$1 AND b=$2) OR (c=$3 AND d=$4 AND e=$5) OR f=$6 OR g=$7"},
},
where: []where{where{clause: "a=? OR b=?"}},
},
expect: " WHERE (a=$1 AND b=$2) OR (c=$3 AND d=$4 AND e=$5) OR f=$6 OR g=$7",
expect: " WHERE (a=$1 OR b=$2)",
},
// Where("(a=$1 AND b=$2) OR (c=$3 AND d=$4 OR e=$5) OR f=$6 OR g=$7")
// Where("a=?"), Where("b=?")
{
q: Query{
where: []where{where{clause: "a=?"}, where{clause: "b=?"}},
},
expect: " WHERE (a=$1) AND (b=$2)",
},
// Where("(a=? AND b=?) OR c=?")
{
q: Query{
where: []where{where{clause: "(a=? AND b=?) OR c=?"}},
},
expect: " WHERE ((a=$1 AND b=$2) OR c=$3)",
},
// Where("a=? OR b=?"), Where("c=? OR d=? OR e=?")
{
q: Query{
where: []where{
where{clause: "(a=$1 AND b=$2) OR (c=$3 AND d=$4 OR e=$5) OR f=$6 OR g=$7"},
where{clause: "(a=? OR b=?)"},
where{clause: "(c=? OR d=? OR e=?)"},
},
},
expect: " WHERE (a=$1 AND b=$2) OR (c=$3 AND d=$4 OR e=$5) OR f=$6 OR g=$7",
expect: " WHERE ((a=$1 OR b=$2)) AND ((c=$3 OR d=$4 OR e=$5))",
},
// Where("(a=? AND b=?) OR (c=? AND d=? AND e=?) OR f=? OR f=?")
{
q: Query{
where: []where{
where{clause: "(a=? AND b=?) OR (c=? AND d=? AND e=?) OR f=? OR g=?"},
},
},
expect: " WHERE ((a=$1 AND b=$2) OR (c=$3 AND d=$4 AND e=$5) OR f=$6 OR g=$7)",
},
// Where("(a=? AND b=?) OR (c=? AND d=? OR e=?) OR f=? OR g=?")
{
q: Query{
where: []where{
where{clause: "(a=? AND b=?) OR (c=? AND d=? OR e=?) OR f=? OR g=?"},
},
},
expect: " WHERE ((a=$1 AND b=$2) OR (c=$3 AND d=$4 OR e=$5) OR f=$6 OR g=$7)",
},
// Where("a=? or b=?"), Or("c=? and d=?"), Or("e=? or f=?")
{
q: Query{
where: []where{
where{clause: "a=? or b=?", orSeparator: true},
where{clause: "c=? and d=?", orSeparator: true},
where{clause: "e=? or f=?", orSeparator: true},
},
},
expect: " WHERE (a=$1 or b=$2) OR (c=$3 and d=$4) OR (e=$5 or f=$6)",
},
// Where("a=? or b=?"), Or("c=? and d=?"), Or("e=? or f=?")
{
q: Query{
where: []where{
where{clause: "a=? or b=?"},
where{clause: "c=? and d=?"},
where{clause: "e=? or f=?"},
},
},
expect: " WHERE (a=$1 or b=$2) AND (c=$3 and d=$4) AND (e=$5 or f=$6)",
},
}

View file

@ -87,6 +87,39 @@ func TestWhere(t *testing.T) {
}
}
func TestSetLastWhereAsOr(t *testing.T) {
t.Parallel()
q := &Query{}
AppendWhere(q, "")
if q.where[0].orSeparator {
t.Errorf("Do not want or separator")
}
SetLastWhereAsOr(q)
if len(q.where) != 1 {
t.Errorf("Want len 1")
}
if !q.where[0].orSeparator {
t.Errorf("Want or separator")
}
AppendWhere(q, "")
SetLastWhereAsOr(q)
if len(q.where) != 2 {
t.Errorf("Want len 2")
}
if q.where[0].orSeparator != true {
t.Errorf("Expected true")
}
if q.where[1].orSeparator != true {
t.Errorf("Expected true")
}
}
func TestGroupBy(t *testing.T) {
t.Parallel()