Finish WHERE IN feature

* Fix bug with appendOr/And ordering
This commit is contained in:
Patrick O'brien 2016-08-19 03:21:53 +10:00
parent 3cdfc6237a
commit d28b3f4b62
5 changed files with 252 additions and 174 deletions

View file

@ -1 +1 @@
UPDATE thing happy, "fun", "stuff" SET ("col2", "fun"."col3", "col1") = ($1,$2,$3) WHERE (aa=$4 or bb=$5 or cc=$6) OR (dd=$7 or ee=$8 or ff=$9 and gg=$10) LIMIT 5; UPDATE thing happy, "fun", "stuff" SET ("col2", "fun"."col3", "col1") = ($1,$2,$3) WHERE (aa=$4 or bb=$5 or cc=$6) AND (dd=$7 or ee=$8 or ff=$9 and gg=$10) LIMIT 5;

View file

@ -63,8 +63,8 @@ func And(clause string, args ...interface{}) QueryMod {
// Or allows you to specify a where clause seperated by an OR for your statement // Or allows you to specify a where clause seperated by an OR for your statement
func Or(clause string, args ...interface{}) QueryMod { func Or(clause string, args ...interface{}) QueryMod {
return func(q *boil.Query) { return func(q *boil.Query) {
boil.SetLastWhereAsOr(q)
boil.AppendWhere(q, clause, args...) boil.AppendWhere(q, clause, args...)
boil.SetLastWhereAsOr(q)
} }
} }
@ -90,8 +90,8 @@ func AndIn(clause string, args ...interface{}) QueryMod {
// an OR for your where statement // an OR for your where statement
func OrIn(clause string, args ...interface{}) QueryMod { func OrIn(clause string, args ...interface{}) QueryMod {
return func(q *boil.Query) { return func(q *boil.Query) {
boil.SetLastInAsOr(q)
boil.AppendIn(q, clause, args...) boil.AppendIn(q, clause, args...)
boil.SetLastInAsOr(q)
} }
} }

View file

@ -12,6 +12,7 @@ import (
var ( var (
rgxIdentifier = regexp.MustCompile(`^(?i)"?[a-z_][_a-z0-9]*"?(?:\."?[_a-z][_a-z0-9]*"?)*$`) rgxIdentifier = regexp.MustCompile(`^(?i)"?[a-z_][_a-z0-9]*"?(?:\."?[_a-z][_a-z0-9]*"?)*$`)
rgxInClause = regexp.MustCompile(`^(?i)(.*[\s|\)|\?])IN([\s|\(|\?].*)$`)
) )
func buildQuery(q *Query) (string, []interface{}) { func buildQuery(q *Query) (string, []interface{}) {
@ -77,7 +78,8 @@ func buildSelectQuery(q *Query) (*bytes.Buffer, []interface{}) {
fmt.Fprintf(joinBuf, " INNER JOIN %s", j.clause) fmt.Fprintf(joinBuf, " INNER JOIN %s", j.clause)
args = append(args, j.args...) args = append(args, j.args...)
} }
fmt.Fprintf(buf, convertQuestionMarks(joinBuf.String(), argsLen+1)) resp, _ := convertQuestionMarks(joinBuf.String(), argsLen+1)
fmt.Fprintf(buf, resp)
strmangle.PutBuffer(joinBuf) strmangle.PutBuffer(joinBuf)
} }
@ -188,7 +190,8 @@ func writeModifiers(q *Query, buf *bytes.Buffer, args *[]interface{}) {
fmt.Fprintf(havingBuf, j.clause) fmt.Fprintf(havingBuf, j.clause)
*args = append(*args, j.args...) *args = append(*args, j.args...)
} }
fmt.Fprintf(buf, convertQuestionMarks(havingBuf.String(), argsLen+1)) resp, _ := convertQuestionMarks(havingBuf.String(), argsLen+1)
fmt.Fprintf(buf, resp)
strmangle.PutBuffer(havingBuf) strmangle.PutBuffer(havingBuf)
} }
@ -267,25 +270,26 @@ func whereClause(q *Query, startAt int) (string, []interface{}) {
var args []interface{} var args []interface{}
buf.WriteString(" WHERE ") buf.WriteString(" WHERE ")
for i := 0; i < len(q.where); i++ { for i, where := range q.where {
buf.WriteString(fmt.Sprintf("(%s)", q.where[i].clause)) if i != 0 {
args = append(args, q.where[i].args...) if where.orSeparator {
buf.WriteString(" OR ")
// break on the last loop } else {
if i == len(q.where)-1 { buf.WriteString(" AND ")
break }
} }
if q.where[i].orSeparator { buf.WriteString(fmt.Sprintf("(%s)", where.clause))
buf.WriteString(" OR ") args = append(args, where.args...)
} else {
buf.WriteString(" AND ")
}
} }
return convertQuestionMarks(buf.String(), startAt), args resp, _ := convertQuestionMarks(buf.String(), startAt)
return resp, args
} }
// inClause parses an in slice and converts it into a
// single IN clause, like:
// WHERE ("a", "b") IN (($1,$2),($3,$4)).
func inClause(q *Query, startAt int) (string, []interface{}) { func inClause(q *Query, startAt int) (string, []interface{}) {
if len(q.in) == 0 { if len(q.in) == 0 {
return "", nil return "", nil
@ -298,28 +302,93 @@ func inClause(q *Query, startAt int) (string, []interface{}) {
if len(q.where) == 0 { if len(q.where) == 0 {
buf.WriteString(" WHERE ") buf.WriteString(" WHERE ")
} }
for i := 0; i < len(q.in); i++ {
for i, in := range q.in {
ln := len(in.args)
// We only prefix the OR and AND separators after the first
// clause has been generated UNLESS there is already a where
// clause that we have to add on to.
if i != 0 || len(q.where) > 0 {
if in.orSeparator {
buf.WriteString(" OR ")
} else {
buf.WriteString(" AND ")
}
}
matches := rgxInClause.FindStringSubmatch(in.clause)
// If we can't find any matches attempt a simple replace with 1 group.
// Clauses that fit this criteria will not be able to contain ? in their
// column name side, however if this case is being hit then the regexp
// probably needs adjustment, or the user is passing in invalid clauses.
if matches == nil {
clause, count := convertInQuestionMarks(in.clause, startAt, 1, ln)
buf.WriteString(clause)
startAt = startAt + count
} else {
leftSide := strings.TrimSpace(matches[1])
rightSide := strings.TrimSpace(matches[2])
// If matches are found, we have to parse the left side (column side)
// of the clause to determine how many columns they are using.
// This number determines the groupAt for the convert function.
cols := strings.Split(leftSide, ",")
cols = strmangle.IdentQuoteSlice(cols)
groupAt := len(cols)
leftClause, leftCount := convertQuestionMarks(strings.Join(cols, ","), startAt)
rightClause, rightCount := convertInQuestionMarks(rightSide, startAt+leftCount, groupAt, ln-leftCount)
buf.WriteString(leftClause)
buf.WriteString(" IN ")
buf.WriteString(rightClause)
startAt = startAt + leftCount + rightCount
}
args = append(args, in.args...)
} }
// regexp split thing so we have left side and right side return buf.String(), args
// split on )IN( / \sIN\s, combine them
// buf.WriteString(convertQuestionMarks(leftSide, startAt))
// buf.WriteString(" IN ")
// buf.WriteString(convertInQuestionMarks(rightSide, total, group, startAt+offset))
return "", args
} }
func convertInQuestionMarks(clause string, total, groupAt, startAt int) string { // convertInQuestionMarks finds the first unescaped occurence of ? and swaps it
return "" // with a list of numbered placeholders, starting at startAt.
// It uses groupAt to determine how many placeholders should be in each group,
// for example, groupAt 2 would result in: (($1,$2),($3,$4))
// and groupAt 1 would result in ($1,$2,$3,$4)
func convertInQuestionMarks(clause string, startAt, groupAt, total int) (string, int) {
if startAt == 0 || len(clause) == 0 {
panic("Not a valid start number.")
}
paramBuf := strmangle.GetBuffer()
defer strmangle.PutBuffer(paramBuf)
foundAt := -1
for i := 0; i < len(clause); i++ {
if (clause[i] == '?' && i == 0) || (clause[i] == '?' && clause[i-1] != '\\') {
foundAt = i
break
}
}
if foundAt == -1 {
return strings.Replace(clause, `\?`, "?", -1), 0
}
paramBuf.WriteString(clause[:foundAt])
paramBuf.WriteByte('(')
paramBuf.WriteString(strmangle.Placeholders(total, startAt, groupAt))
paramBuf.WriteByte(')')
paramBuf.WriteString(clause[foundAt+1:])
// Remove all backslashes from escaped question-marks
ret := strings.Replace(paramBuf.String(), `\?`, "?", -1)
return ret, total
} }
// convertQuestionMarks converts each occurence of ? with $<number> // convertQuestionMarks converts each occurence of ? with $<number>
// where <number> is an incrementing digit starting at startAt. // where <number> is an incrementing digit starting at startAt.
// If question-mark (?) is escaped using back-slash (\), it will be ignored. // If question-mark (?) is escaped using back-slash (\), it will be ignored.
func convertQuestionMarks(clause string, startAt int) string { func convertQuestionMarks(clause string, startAt int) (string, int) {
if startAt == 0 { if startAt == 0 {
panic("Not a valid start number.") panic("Not a valid start number.")
} }
@ -327,6 +396,7 @@ func convertQuestionMarks(clause string, startAt int) string {
paramBuf := strmangle.GetBuffer() paramBuf := strmangle.GetBuffer()
defer strmangle.PutBuffer(paramBuf) defer strmangle.PutBuffer(paramBuf)
paramIndex := 0 paramIndex := 0
total := 0
for { for {
if paramIndex >= len(clause) { if paramIndex >= len(clause) {
@ -349,11 +419,12 @@ func convertQuestionMarks(clause string, startAt int) string {
} }
paramBuf.WriteString(clause[:paramIndex] + fmt.Sprintf("$%d", startAt)) paramBuf.WriteString(clause[:paramIndex] + fmt.Sprintf("$%d", startAt))
total++
startAt++ startAt++
paramIndex++ paramIndex++
} }
return paramBuf.String() return paramBuf.String(), total
} }
// identifierMapping creates a map of all identifiers to potential model names // identifierMapping creates a map of all identifiers to potential model names

View file

@ -247,8 +247,8 @@ func TestWhereClause(t *testing.T) {
{ {
q: Query{ q: Query{
where: []where{ where: []where{
where{clause: "(a=?)", orSeparator: true}, where{clause: "(a=?)"},
where{clause: "(b=?)"}, where{clause: "(b=?)", orSeparator: true},
}, },
}, },
expect: " WHERE ((a=$1)) OR ((b=$2))", expect: " WHERE ((a=$1)) OR ((b=$2))",
@ -318,11 +318,11 @@ func TestWhereClause(t *testing.T) {
q: Query{ q: Query{
where: []where{ where: []where{
where{clause: "a=? or b=?"}, where{clause: "a=? or b=?"},
where{clause: "c=? and d=?"}, where{clause: "c=? and d=?", orSeparator: true},
where{clause: "e=? or f=?"}, where{clause: "e=? or f=?"},
}, },
}, },
expect: " WHERE (a=$1 or b=$2) AND (c=$3 and d=$4) AND (e=$5 or f=$6)", expect: " WHERE (a=$1 or b=$2) OR (c=$3 and d=$4) AND (e=$5 or f=$6)",
}, },
} }
@ -340,129 +340,132 @@ func TestInClause(t *testing.T) {
tests := []struct { tests := []struct {
q Query q Query
expect string expect string
args []interface{}
}{ }{
// Or("a=?") {
q: Query{
in: []in{{clause: "a in ?", args: []interface{}{}, orSeparator: true}},
},
expect: ` WHERE "a" IN ()`,
},
{ {
q: Query{ q: Query{
in: []in{{clause: "a in ?", args: []interface{}{1}, orSeparator: true}}, in: []in{{clause: "a in ?", args: []interface{}{1}, orSeparator: true}},
}, },
expect: " WHERE a IN ($1)", expect: ` WHERE "a" IN ($1)`,
args: []interface{}{1},
}, },
{ {
q: Query{ q: Query{
in: []in{{clause: "a in ?", args: []interface{}{1, 2, 3}}}, in: []in{{clause: "a in ?", args: []interface{}{1, 2, 3}}},
}, },
expect: " WHERE a IN ($1,$2,$3)", expect: ` WHERE "a" IN ($1,$2,$3)`,
args: []interface{}{1, 2, 3},
},
{
q: Query{
in: []in{{clause: "? in ?", args: []interface{}{1, 2, 3}}},
},
expect: " WHERE $1 IN ($2,$3)",
args: []interface{}{1, 2, 3},
},
{
q: Query{
in: []in{{clause: "( ? , ? ) in ( ? )", orSeparator: true, args: []interface{}{"a", "b", 1, 2, 3, 4}}},
},
expect: " WHERE ( $1 , $2 ) IN ( (($3,$4),($5,$6)) )",
args: []interface{}{"a", "b", 1, 2, 3, 4},
},
{
q: Query{
in: []in{{clause: `("a")in(?)`, orSeparator: true, args: []interface{}{1, 2, 3}}},
},
expect: ` WHERE ("a") IN (($1,$2,$3))`,
args: []interface{}{1, 2, 3},
},
{
q: Query{
in: []in{{clause: `("a")in?`, args: []interface{}{1}}},
},
expect: ` WHERE ("a") IN ($1)`,
args: []interface{}{1},
},
{
q: Query{
where: []where{
{clause: "a=?", args: []interface{}{1}},
},
in: []in{
{clause: `?,?,"name" in ?`, orSeparator: true, args: []interface{}{"c", "d", 3, 4, 5, 6, 7, 8}},
{clause: `?,?,"name" in ?`, orSeparator: true, args: []interface{}{"e", "f", 9, 10, 11, 12, 13, 14}},
},
},
expect: ` OR $1,$2,"name" IN (($3,$4,$5),($6,$7,$8)) OR $9,$10,"name" IN (($11,$12,$13),($14,$15,$16))`,
args: []interface{}{"c", "d", 3, 4, 5, 6, 7, 8, "e", "f", 9, 10, 11, 12, 13, 14},
},
{
q: Query{
in: []in{
{clause: `("a")in`, args: []interface{}{1}},
{clause: `("a")in?`, orSeparator: true, args: []interface{}{1}},
},
},
expect: ` WHERE ("a")in OR ("a") IN ($1)`,
args: []interface{}{1, 1},
},
{
q: Query{
in: []in{
{clause: `\?,\? in \?`, args: []interface{}{1}},
{clause: `\?,\?in \?`, orSeparator: true, args: []interface{}{1}},
},
},
expect: ` WHERE ?,? IN ? OR ?,? IN ?`,
args: []interface{}{1, 1},
},
{
q: Query{
in: []in{
{clause: `("a")in`, args: []interface{}{1}},
{clause: `("a") in thing`, args: []interface{}{1, 2, 3}},
{clause: `("a")in?`, orSeparator: true, args: []interface{}{4, 5, 6}},
},
},
expect: ` WHERE ("a")in AND ("a") IN thing OR ("a") IN ($1,$2,$3)`,
args: []interface{}{1, 1, 2, 3, 4, 5, 6},
},
{
q: Query{
in: []in{
{clause: `("a")in?`, orSeparator: true, args: []interface{}{4, 5, 6}},
{clause: `("a") in thing`, args: []interface{}{1, 2, 3}},
{clause: `("a")in`, args: []interface{}{1}},
},
},
expect: ` WHERE ("a") IN ($1,$2,$3) AND ("a") IN thing AND ("a")in`,
args: []interface{}{4, 5, 6, 1, 2, 3, 1},
},
{
q: Query{
in: []in{
{clause: `("a")in?`, orSeparator: true, args: []interface{}{4, 5, 6}},
{clause: `("a")in`, args: []interface{}{1}},
{clause: `("a") in thing`, args: []interface{}{1, 2, 3}},
},
},
expect: ` WHERE ("a") IN ($1,$2,$3) AND ("a")in AND ("a") IN thing`,
args: []interface{}{4, 5, 6, 1, 1, 2, 3},
}, },
// // Where("a=?")
// {
// q: Query{
// where: []where{where{clause: "a=?"}},
// },
// expect: " WHERE (a=$1)",
// },
// // Where("(a=?)")
// {
// q: Query{
// where: []where{where{clause: "(a=?)"}},
// },
// expect: " WHERE ((a=$1))",
// },
// // Where("((a=? OR b=?))")
// {
// q: Query{
// where: []where{where{clause: "((a=? OR b=?))"}},
// },
// expect: " WHERE (((a=$1 OR b=$2)))",
// },
// // Where("(a=?)", Or("(b=?)")
// {
// q: Query{
// where: []where{
// where{clause: "(a=?)", orSeparator: true},
// where{clause: "(b=?)"},
// },
// },
// expect: " WHERE ((a=$1)) OR ((b=$2))",
// },
// // Where("a=? OR b=?")
// {
// q: Query{
// where: []where{where{clause: "a=? OR b=?"}},
// },
// expect: " WHERE (a=$1 OR b=$2)",
// },
// // 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=? OR b=?)"},
// where{clause: "(c=? OR d=? OR e=?)"},
// },
// },
// 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)",
// },
} }
for i, test := range tests { for i, test := range tests {
result, _ := inClause(&test.q, 1) result, args := inClause(&test.q, 1)
if result != test.expect { if result != test.expect {
t.Errorf("%d) Mismatch between expect and result:\n%s\n%s\n", i, test.expect, result) t.Errorf("%d) Mismatch between expect and result:\n%s\n%s\n", i, test.expect, result)
} }
if !reflect.DeepEqual(args, test.args) {
t.Errorf("%d) Mismatch between expected args:\n%#v\n%#v\n", i, test.args, args)
}
} }
} }
@ -473,35 +476,41 @@ func TestConvertQuestionMarks(t *testing.T) {
clause string clause string
start int start int
expect string expect string
count int
}{ }{
{clause: "hello friend", start: 1, expect: "hello friend"}, {clause: "hello friend", start: 1, expect: "hello friend", count: 0},
{clause: "thing=?", start: 2, expect: "thing=$2"}, {clause: "thing=?", start: 2, expect: "thing=$2", count: 1},
{clause: "thing=? and stuff=? and happy=?", start: 2, expect: "thing=$2 and stuff=$3 and happy=$4"}, {clause: "thing=? and stuff=? and happy=?", start: 2, expect: "thing=$2 and stuff=$3 and happy=$4", count: 3},
{clause: `thing \? stuff`, start: 2, expect: `thing ? stuff`}, {clause: `thing \? stuff`, start: 2, expect: `thing ? stuff`, count: 0},
{clause: `thing \? stuff and happy \? fun`, start: 2, expect: `thing ? stuff and happy ? fun`}, {clause: `thing \? stuff and happy \? fun`, start: 2, expect: `thing ? stuff and happy ? fun`, count: 0},
{ {
clause: `thing \? stuff ? happy \? and mad ? fun \? \? \?`, clause: `thing \? stuff ? happy \? and mad ? fun \? \? \?`,
start: 2, start: 2,
expect: `thing ? stuff $2 happy ? and mad $3 fun ? ? ?`, expect: `thing ? stuff $2 happy ? and mad $3 fun ? ? ?`,
count: 2,
}, },
{ {
clause: `thing ? stuff ? happy \? fun \? ? ?`, clause: `thing ? stuff ? happy \? fun \? ? ?`,
start: 1, start: 1,
expect: `thing $1 stuff $2 happy ? fun ? $3 $4`, expect: `thing $1 stuff $2 happy ? fun ? $3 $4`,
count: 4,
}, },
{clause: `?`, start: 1, expect: `$1`}, {clause: `?`, start: 1, expect: `$1`, count: 1},
{clause: `???`, start: 1, expect: `$1$2$3`}, {clause: `???`, start: 1, expect: `$1$2$3`, count: 3},
{clause: `\?`, start: 1, expect: `?`}, {clause: `\?`, start: 1, expect: `?`},
{clause: `\?\?\?`, start: 1, expect: `???`}, {clause: `\?\?\?`, start: 1, expect: `???`},
{clause: `\??\??\??`, start: 1, expect: `?$1?$2?$3`}, {clause: `\??\??\??`, start: 1, expect: `?$1?$2?$3`, count: 3},
{clause: `?\??\??\?`, start: 1, expect: `$1?$2?$3?`}, {clause: `?\??\??\?`, start: 1, expect: `$1?$2?$3?`, count: 3},
} }
for i, test := range tests { for i, test := range tests {
res := convertQuestionMarks(test.clause, test.start) res, count := convertQuestionMarks(test.clause, test.start)
if res != test.expect { if res != test.expect {
t.Errorf("%d) Mismatch between expect and result:\n%s\n%s\n", i, test.expect, res) t.Errorf("%d) Mismatch between expect and result:\n%s\n%s\n", i, test.expect, res)
} }
if count != test.count {
t.Errorf("%d) Expected count %d, got %d", i, test.count, count)
}
} }
} }
@ -511,36 +520,34 @@ func TestConvertInQuestionMarks(t *testing.T) {
tests := []struct { tests := []struct {
clause string clause string
start int start int
group int
total int
expect string expect string
}{ }{
{clause: "hello friend", start: 1, expect: "hello friend"}, {clause: "?", expect: "(($1,$2,$3),($4,$5,$6),($7,$8,$9))", start: 1, total: 9, group: 3},
{clause: "thing=?", start: 2, expect: "thing=$2"}, {clause: "?", expect: "(($2,$3),($4))", start: 2, total: 3, group: 2},
{clause: "thing=? and stuff=? and happy=?", start: 2, expect: "thing=$2 and stuff=$3 and happy=$4"}, {clause: "hello friend", start: 1, expect: "hello friend", total: 0, group: 1},
{clause: `thing \? stuff`, start: 2, expect: `thing ? stuff`}, {clause: "thing ? thing", start: 2, expect: "thing ($2,$3) thing", total: 2, group: 1},
{clause: `thing \? stuff and happy \? fun`, start: 2, expect: `thing ? stuff and happy ? fun`}, {clause: "thing?thing", start: 2, expect: "thing($2)thing", total: 1, group: 1},
{ {clause: `thing \? stuff`, start: 2, expect: `thing ? stuff`, total: 0, group: 1},
clause: `thing \? stuff ? happy \? and mad ? fun \? \? \?`, {clause: `thing \? stuff and happy \? fun`, start: 2, expect: `thing ? stuff and happy ? fun`, total: 0, group: 1},
start: 2, {clause: "thing ? thing ? thing", start: 1, expect: "thing ($1,$2,$3) thing ? thing", total: 3, group: 1},
expect: `thing ? stuff $2 happy ? and mad $3 fun ? ? ?`, {clause: `?`, start: 1, expect: `($1)`, total: 1, group: 1},
}, {clause: `???`, start: 1, expect: `($1,$2,$3)??`, total: 3, group: 1},
{ {clause: `\?`, start: 1, expect: `?`, total: 0, group: 1},
clause: `thing ? stuff ? happy \? fun \? ? ?`, {clause: `\?\?\?`, start: 1, expect: `???`, total: 0, group: 1},
start: 1, {clause: `\??\??\??`, start: 1, expect: `?($1,$2,$3)????`, total: 3, group: 1},
expect: `thing $1 stuff $2 happy ? fun ? $3 $4`, {clause: `?\??\??\?`, start: 1, expect: `($1,$2,$3)?????`, total: 3, group: 1},
},
{clause: `?`, start: 1, expect: `$1`},
{clause: `???`, start: 1, expect: `$1$2$3`},
{clause: `\?`, start: 1, expect: `?`},
{clause: `\?\?\?`, start: 1, expect: `???`},
{clause: `\??\??\??`, start: 1, expect: `?$1?$2?$3`},
{clause: `?\??\??\?`, start: 1, expect: `$1?$2?$3?`},
} }
for i, test := range tests { for i, test := range tests {
res := convertQuestionMarks(test.clause, test.start) res, count := convertInQuestionMarks(test.clause, test.start, test.group, test.total)
if res != test.expect { if res != test.expect {
t.Errorf("%d) Mismatch between expect and result:\n%s\n%s\n", i, test.expect, res) t.Errorf("%d) Mismatch between expect and result:\n%s\n%s\n", i, test.expect, res)
} }
if count != test.total {
t.Errorf("%d) Expected %d, got %d", i, test.total, count)
}
} }
} }

View file

@ -237,7 +237,7 @@ func PrefixStringSlice(str string, strs []string) []string {
} }
// Placeholders generates the SQL statement placeholders for in queries. // Placeholders generates the SQL statement placeholders for in queries.
// For example, ($1, $2, $3), ($4, $5, $6) etc. // For example, ($1,$2,$3),($4,$5,$6) etc.
// It will start counting placeholders at "start". // It will start counting placeholders at "start".
func Placeholders(count int, start int, group int) string { func Placeholders(count int, start int, group int) string {
buf := GetBuffer() buf := GetBuffer()