Finish UpdateAll query builder
* Add modifiers to delete builder * Update golden file tests * Add startAt to whereClause
This commit is contained in:
parent
c3f8cff117
commit
e3f319346f
10 changed files with 240 additions and 40 deletions
|
@ -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) LIMIT 5;
|
1
boil/_fixtures/10.sql
Normal file
1
boil/_fixtures/10.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
UPDATE thing happy, "fun", "stuff" SET ("col1", "col2", "fun"."col3") VALUES ($1, $2, $3) WHERE (aa=$4 or bb=$5) OR (cc=$6) LIMIT 5;
|
|
@ -70,9 +70,65 @@ func buildSelectQuery(q *Query) (*bytes.Buffer, []interface{}) {
|
||||||
fmt.Fprintf(buf, " INNER JOIN %s", j.clause)
|
fmt.Fprintf(buf, " INNER JOIN %s", j.clause)
|
||||||
}
|
}
|
||||||
|
|
||||||
where, args := whereClause(q)
|
where, args := whereClause(q, 1)
|
||||||
buf.WriteString(where)
|
buf.WriteString(where)
|
||||||
|
|
||||||
|
writeModifiers(q, buf)
|
||||||
|
|
||||||
|
buf.WriteByte(';')
|
||||||
|
return buf, args
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDeleteQuery(q *Query) (*bytes.Buffer, []interface{}) {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
|
buf.WriteString("DELETE FROM ")
|
||||||
|
buf.WriteString(strings.Join(strmangle.IdentQuoteSlice(q.from), ", "))
|
||||||
|
|
||||||
|
where, args := whereClause(q, 1)
|
||||||
|
buf.WriteString(where)
|
||||||
|
|
||||||
|
writeModifiers(q, buf)
|
||||||
|
|
||||||
|
buf.WriteByte(';')
|
||||||
|
|
||||||
|
return buf, args
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildUpdateQuery(q *Query) (*bytes.Buffer, []interface{}) {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
|
buf.WriteString("UPDATE ")
|
||||||
|
buf.WriteString(strings.Join(strmangle.IdentQuoteSlice(q.from), ", "))
|
||||||
|
|
||||||
|
cols := make([]string, len(q.update))
|
||||||
|
args := make([]interface{}, len(q.update))
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
for name, value := range q.update {
|
||||||
|
cols[count] = strmangle.IdentQuote(name)
|
||||||
|
args[count] = value
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteString(fmt.Sprintf(
|
||||||
|
" SET (%s) VALUES (%s)",
|
||||||
|
strings.Join(cols, ", "),
|
||||||
|
strmangle.Placeholders(len(cols), 1, 1)),
|
||||||
|
)
|
||||||
|
|
||||||
|
where, whereArgs := whereClause(q, len(args)+1)
|
||||||
|
buf.WriteString(where)
|
||||||
|
args = append(args, whereArgs...)
|
||||||
|
|
||||||
|
writeModifiers(q, buf)
|
||||||
|
|
||||||
|
buf.WriteByte(';')
|
||||||
|
|
||||||
|
return buf, args
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeModifiers(q *Query, buf *bytes.Buffer) {
|
||||||
if len(q.groupBy) != 0 {
|
if len(q.groupBy) != 0 {
|
||||||
fmt.Fprintf(buf, " GROUP BY %s", strings.Join(q.groupBy, ", "))
|
fmt.Fprintf(buf, " GROUP BY %s", strings.Join(q.groupBy, ", "))
|
||||||
}
|
}
|
||||||
|
@ -92,9 +148,6 @@ func buildSelectQuery(q *Query) (*bytes.Buffer, []interface{}) {
|
||||||
if q.offset != 0 {
|
if q.offset != 0 {
|
||||||
fmt.Fprintf(buf, " OFFSET %d", q.offset)
|
fmt.Fprintf(buf, " OFFSET %d", q.offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.WriteByte(';')
|
|
||||||
return buf, args
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeStars(q *Query) []string {
|
func writeStars(q *Query) []string {
|
||||||
|
@ -144,28 +197,12 @@ func writeAsStatements(q *Query) []string {
|
||||||
return cols
|
return cols
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildDeleteQuery(q *Query) (*bytes.Buffer, []interface{}) {
|
// whereClause parses a where slice and converts it into a
|
||||||
buf := &bytes.Buffer{}
|
// single WHERE clause like:
|
||||||
|
// WHERE (a=$1) AND (b=$2)
|
||||||
buf.WriteString("DELETE FROM ")
|
//
|
||||||
buf.WriteString(strings.Join(strmangle.IdentQuoteSlice(q.from), ", "))
|
// startAt specifies what number placeholders start at
|
||||||
|
func whereClause(q *Query, startAt int) (string, []interface{}) {
|
||||||
where, args := whereClause(q)
|
|
||||||
buf.WriteString(where)
|
|
||||||
|
|
||||||
buf.WriteByte(';')
|
|
||||||
|
|
||||||
return buf, args
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildUpdateQuery(q *Query) (*bytes.Buffer, []interface{}) {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
|
|
||||||
buf.WriteByte(';')
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func whereClause(q *Query) (string, []interface{}) {
|
|
||||||
if len(q.where) == 0 {
|
if len(q.where) == 0 {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
@ -211,7 +248,35 @@ func whereClause(q *Query) (string, []interface{}) {
|
||||||
paramIndex++
|
paramIndex++
|
||||||
}
|
}
|
||||||
|
|
||||||
return paramBuf.String(), args
|
return convertQuestionMarks(buf.String(), startAt), args
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertQuestionMarks(clause string, startAt int) string {
|
||||||
|
if startAt == 0 {
|
||||||
|
panic("Not a valid start number.")
|
||||||
|
}
|
||||||
|
|
||||||
|
paramBuf := &bytes.Buffer{}
|
||||||
|
paramIndex := 0
|
||||||
|
|
||||||
|
for ; ; startAt++ {
|
||||||
|
if paramIndex >= len(clause) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
clause = clause[paramIndex:]
|
||||||
|
paramIndex = strings.IndexByte(clause, '?')
|
||||||
|
|
||||||
|
if paramIndex == -1 {
|
||||||
|
paramBuf.WriteString(clause)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
paramBuf.WriteString(clause[:paramIndex] + fmt.Sprintf("$%d", startAt))
|
||||||
|
paramIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
return paramBuf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// identifierMapping creates a map of all identifiers to potential model names
|
// identifierMapping creates a map of all identifiers to potential model names
|
||||||
|
|
|
@ -59,7 +59,21 @@ func TestBuildQuery(t *testing.T) {
|
||||||
where: []where{
|
where: []where{
|
||||||
where{clause: "(id=? and thing=?) or stuff=?", args: []interface{}{}},
|
where{clause: "(id=? and thing=?) or stuff=?", args: []interface{}{}},
|
||||||
},
|
},
|
||||||
|
limit: 5,
|
||||||
}, nil},
|
}, nil},
|
||||||
|
{&Query{
|
||||||
|
from: []string{"thing happy", `"fun"`, `stuff`},
|
||||||
|
update: map[string]interface{}{
|
||||||
|
"col1": 1,
|
||||||
|
`"col2"`: 2,
|
||||||
|
`"fun".col3`: 3,
|
||||||
|
},
|
||||||
|
where: []where{
|
||||||
|
where{clause: "aa=? or bb=?", orSeparator: true, args: []interface{}{4, 5}},
|
||||||
|
where{clause: "cc=?", args: []interface{}{6}},
|
||||||
|
},
|
||||||
|
limit: 5,
|
||||||
|
}, []interface{}{1, 2, 3, 4, 5, 6}},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
|
@ -297,7 +311,7 @@ func TestWhereClause(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
result, _ := whereClause(&test.q)
|
result, _ := whereClause(&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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,15 +213,19 @@ func PrefixStringSlice(str string, strs []string) []string {
|
||||||
func Placeholders(count int, start int, group int) string {
|
func Placeholders(count int, start int, group int) string {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
if start == 0 || group == 0 {
|
||||||
|
panic("Invalid start or group numbers supplied.")
|
||||||
|
}
|
||||||
|
|
||||||
if group > 1 {
|
if group > 1 {
|
||||||
buf.WriteByte('(')
|
buf.WriteByte('(')
|
||||||
}
|
}
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
if group > 1 && i%group == 0 {
|
if group > 1 && i%group == 0 {
|
||||||
buf.WriteString(`),(`)
|
buf.WriteString("), (")
|
||||||
} else {
|
} else {
|
||||||
buf.WriteByte(',')
|
buf.WriteString(", ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buf.WriteString(fmt.Sprintf("$%d", start+i))
|
buf.WriteString(fmt.Sprintf("$%d", start+i))
|
||||||
|
|
|
@ -68,7 +68,14 @@ func (o *{{$tableNameSingular}}) Update(exec boil.Executor, whitelist ... string
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateAll updates all rows with matching column names.
|
// UpdateAllP updates all rows with matching column names, and panics on error.
|
||||||
|
func (q {{$varNameSingular}}Query) UpdateAllP(cols M) {
|
||||||
|
if err := q.UpdateAll(cols); err != nil {
|
||||||
|
panic(boil.WrapErr(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAll updates all rows with the specified column values.
|
||||||
func (q {{$varNameSingular}}Query) UpdateAll(cols M) error {
|
func (q {{$varNameSingular}}Query) UpdateAll(cols M) error {
|
||||||
boil.SetUpdate(q.Query, cols)
|
boil.SetUpdate(q.Query, cols)
|
||||||
|
|
||||||
|
@ -80,13 +87,72 @@ func (q {{$varNameSingular}}Query) UpdateAll(cols M) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateAllP updates all rows with matching column names, and panics on error.
|
// UpdateAllG updates all rows with the specified column values.
|
||||||
func (q {{$varNameSingular}}Query) UpdateAllP(cols M) {
|
func (o {{$tableNameSingular}}Slice) UpdateAllG(cols M) error {
|
||||||
if err := q.UpdateAll(cols); err != nil {
|
return o.UpdateAll(boil.GetDB(), cols)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAllGP updates all rows with the specified column values, and panics on error.
|
||||||
|
func (o {{$tableNameSingular}}Slice) UpdateAllGP(cols M) {
|
||||||
|
if err := o.UpdateAll(boil.GetDB(), cols); err != nil {
|
||||||
panic(boil.WrapErr(err))
|
panic(boil.WrapErr(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateAllP updates all rows with the specified column values, and panics on error.
|
||||||
|
func (o {{$tableNameSingular}}Slice) UpdateAllP(exec boil.Executor, cols M) {
|
||||||
|
if err := o.UpdateAll(exec, cols); err != nil {
|
||||||
|
panic(boil.WrapErr(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAll updates all rows with the specified column values, using an executor.
|
||||||
|
func (o {{$tableNameSingular}}Slice) UpdateAll(exec boil.Executor, cols M) error {
|
||||||
|
if o == nil {
|
||||||
|
return errors.New("{{.PkgName}}: no {{$tableNameSingular}} slice provided for update all")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(o) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
colNames := make([]string, len(cols))
|
||||||
|
var args []interface{}
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
for name, value := range cols {
|
||||||
|
colNames[count] = name
|
||||||
|
args = append(args, value)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append all of the primary key values for each column
|
||||||
|
args = append(args, o.inPrimaryKeyArgs())
|
||||||
|
|
||||||
|
sql := fmt.Sprintf(
|
||||||
|
`UPDATE {{.Table.Name}} SET (%s) VALUES (%s) WHERE (%s) IN (%s)`,
|
||||||
|
strings.Join(colNames, ", "),
|
||||||
|
strmangle.Placeholders(len(args), 1, 1),
|
||||||
|
strings.Join({{$varNameSingular}}PrimaryKeyColumns, ","),
|
||||||
|
strmangle.Placeholders(len(o) * len({{$varNameSingular}}PrimaryKeyColumns), len(args)+1, len({{$varNameSingular}}PrimaryKeyColumns)),
|
||||||
|
)
|
||||||
|
|
||||||
|
q := boil.SQL(sql, args...)
|
||||||
|
boil.SetExecutor(q, exec)
|
||||||
|
|
||||||
|
_, err := boil.ExecQuery(q)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("{{.PkgName}}: unable to update all in {{$varNameSingular}} slice: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if boil.DebugMode {
|
||||||
|
fmt.Fprintln(boil.DebugWriter, sql)
|
||||||
|
fmt.Fprintln(boil.DebugWriter, )
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// generateUpdateColumns generates the whitelist columns for an update statement
|
// generateUpdateColumns generates the whitelist columns for an update statement
|
||||||
// if a whitelist is supplied, it's returned
|
// if a whitelist is supplied, it's returned
|
||||||
// if a whitelist is missing then we begin with all columns
|
// if a whitelist is missing then we begin with all columns
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
// M type is for providing where filters to Where helpers.
|
|
||||||
type M map[string]interface{}
|
|
||||||
|
|
||||||
// NewQueryG initializes a new Query using the passed in QueryMods
|
// NewQueryG initializes a new Query using the passed in QueryMods
|
||||||
func NewQueryG(mods ...qm.QueryMod) *boil.Query {
|
func NewQueryG(mods ...qm.QueryMod) *boil.Query {
|
||||||
return NewQuery(boil.GetDB(), mods...)
|
return NewQuery(boil.GetDB(), mods...)
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// M type is for providing columns and column values to UpdateAll.
|
||||||
|
type M map[string]interface{}
|
||||||
|
|
||||||
type upsertData struct {
|
type upsertData struct {
|
||||||
conflict []string
|
conflict []string
|
||||||
update []string
|
update []string
|
||||||
|
|
|
@ -28,8 +28,11 @@ func Test{{$tableNamePlural}}Insert(t *testing.T) {
|
||||||
|
|
||||||
j := make({{$tableNameSingular}}Slice, 3)
|
j := make({{$tableNameSingular}}Slice, 3)
|
||||||
// Perform all Find queries and assign result objects to slice for comparison
|
// Perform all Find queries and assign result objects to slice for comparison
|
||||||
for i := 0; i < len(j); i++ {
|
for i := 0; i < len(o); i++ {
|
||||||
j[i], err = {{$tableNameSingular}}FindG({{.Table.PKey.Columns | stringMap .StringFuncs.titleCase | prefixStringSlice "o[i]." | join ", "}})
|
j[i], err = {{$tableNameSingular}}FindG({{.Table.PKey.Columns | stringMap .StringFuncs.titleCase | prefixStringSlice "o[i]." | join ", "}})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unable to find {{$tableNameSingular}} row: %s", err)
|
||||||
|
}
|
||||||
{{$varNameSingular}}CompareVals(o[i], j[i], t)
|
{{$varNameSingular}}CompareVals(o[i], j[i], t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,3 +41,50 @@ func Test{{$tableNamePlural}}Update(t *testing.T) {
|
||||||
|
|
||||||
{{$varNamePlural}}DeleteAllRows(t)
|
{{$varNamePlural}}DeleteAllRows(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test{{$tableNamePlural}}SliceUpdateAll(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// insert random columns to test UpdateAll
|
||||||
|
o := make({{$tableNameSingular}}Slice, 3)
|
||||||
|
j := make({{$tableNameSingular}}Slice, 3)
|
||||||
|
|
||||||
|
if err = boil.RandomizeSlice(&o, {{$varNameSingular}}DBTypes, false); err != nil {
|
||||||
|
t.Errorf("Unable to randomize {{$tableNameSingular}} slice: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(o); i++ {
|
||||||
|
if err = o[i].InsertG(); err != nil {
|
||||||
|
t.Errorf("Unable to insert {{$tableNameSingular}}:\n%#v\nErr: %s", o[i], err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vals := M{}
|
||||||
|
|
||||||
|
tmp := {{$tableNameSingular}}{}
|
||||||
|
if err = boil.RandomizeStruct(&tmp, {{$varNameSingular}}DBTypes, false, {{$varNameSingular}}PrimaryKeyColumns...); err != nil {
|
||||||
|
t.Errorf("Unable to randomize struct {{$tableNameSingular}}: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the columns and column values from the randomized struct
|
||||||
|
tmpVal := reflect.Indirect(reflect.ValueOf(tmp))
|
||||||
|
nonPrimKeys := boil.SetComplement({{$varNameSingular}}Columns, {{$varNameSingular}}PrimaryKeyColumns)
|
||||||
|
for _, col := range nonPrimKeys {
|
||||||
|
vals[col] = tmpVal.FieldByName(strmangle.TitleCase(col)).Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = o.UpdateAllG(vals)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to update all for {{$tableNameSingular}}: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(o); i++ {
|
||||||
|
j[i], err = {{$tableNameSingular}}FindG({{.Table.PKey.Columns | stringMap .StringFuncs.titleCase | prefixStringSlice "o[i]." | join ", "}})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unable to find {{$tableNameSingular}} row: %s", err)
|
||||||
|
}
|
||||||
|
{{$varNameSingular}}CompareVals(o[i], &tmp, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
{{$varNamePlural}}DeleteAllRows(t)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue