From 557674a88649f4dd72f14f362212d15359aca5b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Matczuk?= Date: Fri, 22 Sep 2017 09:33:46 +0200 Subject: [PATCH] qb: update builder advanced assignments --- README.md | 33 +---------------- example_test.go | 23 ++++++++++-- qb/cmp.go | 8 ++--- qb/update.go | 91 ++++++++++++++++++++++++++++++++++++++++------- qb/update_test.go | 30 ++++++++++++++++ 5 files changed, 134 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 85b5100..64d87e1 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ hood it uses `sqlx/reflectx` package so `sqlx` models will also work with `gocql * Builders for `SELECT`, `INSERT`, `UPDATE` `DELETE` and `BATCH` * Queries with named parameters (:identifier) support +* Functions support * Binding parameters form struct or map * Scanning results into structs and slices * Automatic query releasing @@ -85,38 +86,6 @@ type Person struct { t.Log(people) // [{Patricia Citizen [patricia.citzen@gocqlx_test.com patricia1.citzen@gocqlx_test.com]} {Igy Citizen [igy.citzen@gocqlx_test.com]} {Ian Citizen [ian.citzen@gocqlx_test.com]}] } - -// Batch insert two rows in a single query, advanced struct binding. -{ - i := qb.Insert("gocqlx_test.person").Columns("first_name", "last_name", "email") - - stmt, names := qb.Batch(). - AddWithPrefix("a", i). - AddWithPrefix("b", i). - ToCql() - - batch := struct { - A Person - B Person - }{ - A: Person{ - "Igy", - "Citizen", - []string{"igy.citzen@gocqlx_test.com"}, - }, - B: Person{ - "Ian", - "Citizen", - []string{"ian.citzen@gocqlx_test.com"}, - }, - } - - q := gocqlx.Query(session.Query(stmt), names).BindStruct(&batch) - - if err := q.ExecRelease(); err != nil { - t.Fatal(err) - } -} ``` See more examples in [example_test.go](https://github.com/scylladb/gocqlx/blob/master/example_test.go). diff --git a/example_test.go b/example_test.go index 2da97c5..6ebfb4d 100644 --- a/example_test.go +++ b/example_test.go @@ -89,6 +89,22 @@ func TestExample(t *testing.T) { } } + // Advanced update, adding and removing elements to collections and counters. + { + stmt, names := qb.Update("gocqlx_test.person"). + AddNamed("email", "new_email"). + Where(qb.Eq("first_name"), qb.Eq("last_name")). + ToCql() + + q := gocqlx.Query(session.Query(stmt), names).BindStructMap(p, qb.M{ + "new_email": []string{"patricia2.citzen@gocqlx_test.com", "patricia3.citzen@gocqlx_test.com"}, + }) + + if err := q.ExecRelease(); err != nil { + t.Fatal(err) + } + } + // Batch insert two rows in a single query, advanced struct binding. { i := qb.Insert("gocqlx_test.person").Columns("first_name", "last_name", "email") @@ -137,7 +153,7 @@ func TestExample(t *testing.T) { } t.Log(p) - // {Patricia Citizen [patricia.citzen@gocqlx_test.com patricia1.citzen@gocqlx_test.com]} + // {Patricia Citizen [patricia.citzen@gocqlx_test.com patricia1.citzen@gocqlx_test.com patricia2.citzen@gocqlx_test.com patricia3.citzen@gocqlx_test.com]} } // Select, load all the results into a slice. @@ -156,7 +172,7 @@ func TestExample(t *testing.T) { } t.Log(people) - // [{Patricia Citizen [patricia.citzen@gocqlx_test.com patricia1.citzen@gocqlx_test.com]} {Igy Citizen [igy.citzen@gocqlx_test.com]} {Ian Citizen [ian.citzen@gocqlx_test.com]}] + // [{Ian Citizen [ian.citzen@gocqlx_test.com]} {Igy Citizen [igy.citzen@gocqlx_test.com]} {Patricia Citizen [patricia.citzen@gocqlx_test.com patricia1.citzen@gocqlx_test.com patricia2.citzen@gocqlx_test.com patricia3.citzen@gocqlx_test.com]}] } // Easy token based pagination. @@ -168,6 +184,7 @@ func TestExample(t *testing.T) { } stmt, names := qb.Select("gocqlx_test.person"). + Columns("first_name"). Where(qb.Token("first_name").Gt()). Limit(10). ToCql() @@ -180,7 +197,7 @@ func TestExample(t *testing.T) { } t.Log(people) - // [{Patricia Citizen [patricia.citzen@gocqlx_test.com patricia1.citzen@gocqlx_test.com]} {Igy Citizen [igy.citzen@gocqlx_test.com]}] + // [{Patricia []} {Igy []}] } // Named query compilation. diff --git a/qb/cmp.go b/qb/cmp.go index 0d95ac5..e1efb3b 100644 --- a/qb/cmp.go +++ b/qb/cmp.go @@ -53,15 +53,15 @@ func (c Cmp) writeCql(cql *bytes.Buffer) (names []string) { cql.WriteString(" CONTAINS ") } - if c.fn == nil { + if c.fn != nil { + names = append(names, c.fn.writeCql(cql)...) + } else { cql.WriteByte('?') if c.name == "" { names = append(names, c.column) } else { names = append(names, c.name) } - } else { - names = append(names, c.fn.writeCql(cql)...) } return @@ -188,7 +188,7 @@ func GtOrEqNamed(column, name string) Cmp { } } -// GtFunc produces column>=someFunc(?...). +// GtOrEqFunc produces column>=someFunc(?...). func GtOrEqFunc(column string, fn *Func) Cmp { return Cmp{ op: geq, diff --git a/qb/update.go b/qb/update.go index 1b9ff1f..ad1a695 100644 --- a/qb/update.go +++ b/qb/update.go @@ -9,16 +9,41 @@ package qb import ( "bytes" + "fmt" ) +// assignment specifies an assignment in a set operation. +type assignment struct { + column string + name string + expr bool + fn *Func +} + +func (a assignment) writeCql(cql *bytes.Buffer) (names []string) { + cql.WriteString(a.column) + switch { + case a.expr: + names = append(names, a.name) + case a.fn != nil: + cql.WriteByte('=') + names = append(names, a.fn.writeCql(cql)...) + default: + cql.WriteByte('=') + cql.WriteByte('?') + names = append(names, a.name) + } + return +} + // UpdateBuilder builds CQL UPDATE statements. type UpdateBuilder struct { - table string - using using - columns columns - where where - _if _if - exists bool + table string + using using + assignments []assignment + where where + _if _if + exists bool } // Update returns a new UpdateBuilder with the given table name. @@ -39,14 +64,12 @@ func (b *UpdateBuilder) ToCql() (stmt string, names []string) { names = append(names, b.using.writeCql(&cql)...) cql.WriteString("SET ") - for i, c := range b.columns { - cql.WriteString(c) - cql.WriteString("=?") - if i < len(b.columns)-1 { + for i, a := range b.assignments { + names = append(names, a.writeCql(&cql)...) + if i < len(b.assignments)-1 { cql.WriteByte(',') } } - names = append(names, b.columns...) cql.WriteByte(' ') names = append(names, b.where.writeCql(&cql)...) @@ -80,7 +103,51 @@ func (b *UpdateBuilder) TTL() *UpdateBuilder { // Set adds SET clauses to the query. func (b *UpdateBuilder) Set(columns ...string) *UpdateBuilder { - b.columns = append(b.columns, columns...) + for _, c := range columns { + b.assignments = append(b.assignments, assignment{ + column: c, + name: c, + }) + } + + return b +} + +// SetFunc adds SET column=someFunc(?...) clause to the query. +func (b *UpdateBuilder) SetFunc(column string, fn *Func) *UpdateBuilder { + b.assignments = append(b.assignments, assignment{column: column, fn: fn}) + return b +} + +// Add adds SET column=column+? clauses to the query. +func (b *UpdateBuilder) Add(column string) *UpdateBuilder { + return b.AddNamed(column, column) +} + +// AddNamed adds SET column=column+? clauses to the query with a custom +// parameter name. +func (b *UpdateBuilder) AddNamed(column, name string) *UpdateBuilder { + b.assignments = append(b.assignments, assignment{ + column: fmt.Sprint(column, "=", column, "+?"), + name: name, + expr: true, + }) + return b +} + +// Remove adds SET column=column-? clauses to the query. +func (b *UpdateBuilder) Remove(column string) *UpdateBuilder { + return b.RemoveNamed(column, column) +} + +// RemoveNamed adds SET column=column-? clauses to the query with a custom +// parameter name. +func (b *UpdateBuilder) RemoveNamed(column, name string) *UpdateBuilder { + b.assignments = append(b.assignments, assignment{ + column: fmt.Sprint(column, "=", column, "-?"), + name: name, + expr: true, + }) return b } diff --git a/qb/update_test.go b/qb/update_test.go index dd954a6..ba93dbe 100644 --- a/qb/update_test.go +++ b/qb/update_test.go @@ -36,6 +36,36 @@ func TestUpdateBuilder(t *testing.T) { S: "UPDATE cycling.cyclist_name SET id=?,user_uuid=?,firstname=?,stars=? WHERE id=? ", N: []string{"id", "user_uuid", "firstname", "stars", "expr"}, }, + // Add SET SetFunc + { + B: Update("cycling.cyclist_name").SetFunc("user_uuid", Fn("someFunc", "param_0", "param_1")).Where(w).Set("stars"), + S: "UPDATE cycling.cyclist_name SET user_uuid=someFunc(?,?),stars=? WHERE id=? ", + N: []string{"param_0", "param_1", "stars", "expr"}, + }, + // Add SET Add + { + B: Update("cycling.cyclist_name").Add("total").Where(w), + S: "UPDATE cycling.cyclist_name SET total=total+? WHERE id=? ", + N: []string{"total", "expr"}, + }, + // Add SET AddNamed + { + B: Update("cycling.cyclist_name").AddNamed("total", "inc").Where(w), + S: "UPDATE cycling.cyclist_name SET total=total+? WHERE id=? ", + N: []string{"inc", "expr"}, + }, + // Add SET Remove + { + B: Update("cycling.cyclist_name").Remove("total").Where(w), + S: "UPDATE cycling.cyclist_name SET total=total-? WHERE id=? ", + N: []string{"total", "expr"}, + }, + // Add SET RemoveNamed + { + B: Update("cycling.cyclist_name").RemoveNamed("total", "dec").Where(w), + S: "UPDATE cycling.cyclist_name SET total=total-? WHERE id=? ", + N: []string{"dec", "expr"}, + }, // Add WHERE { B: Update("cycling.cyclist_name").Set("id", "user_uuid", "firstname").Where(w, Gt("firstname")),