This commit is contained in:
Michał Matczuk
2017-07-28 10:18:38 +02:00
parent 3b3c7087d2
commit 41bb8def2a
12 changed files with 247 additions and 132 deletions

View File

@@ -1,6 +1,7 @@
# gocqlx [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/scylladb/gocqlx) [![Go Report Card](https://goreportcard.com/badge/github.com/scylladb/gocqlx)](https://goreportcard.com/report/github.com/scylladb/gocqlx) [![Build Status](https://travis-ci.org/scylladb/gocqlx.svg?branch=master)](https://travis-ci.org/scylladb/gocqlx) # gocqlx [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/scylladb/gocqlx) [![Go Report Card](https://goreportcard.com/badge/github.com/scylladb/gocqlx)](https://goreportcard.com/report/github.com/scylladb/gocqlx) [![Build Status](https://travis-ci.org/scylladb/gocqlx.svg?branch=master)](https://travis-ci.org/scylladb/gocqlx)
Package `gocqlx` is a `gocql` extension, similar to what `sqlx` is to `database/sql`. Package `gocqlx` is a Scylla / Cassandra productivity toolkit for `gocql`, it's
similar to what `sqlx` is to `database/sql`.
It contains wrappers over `gocql` types that provide convenience methods which It contains wrappers over `gocql` types that provide convenience methods which
are useful in the development of database driven applications. Under the are useful in the development of database driven applications. Under the
@@ -12,43 +13,59 @@ hood it uses `sqlx/reflectx` package so `sqlx` models will also work with `gocql
## Features ## Features
Read all rows into a slice. Fast, boilerplate free and flexible `SELECTS`, `INSERTS`, `UPDATES` and `DELETES`.
```go ```go
var v []*Item type Person struct {
if err := gocqlx.Select(&v, session.Query(`SELECT * FROM items WHERE id = ?`, id)); err != nil { FirstName string // no need to add `db:"first_name"` etc.
log.Fatal("select failed", err) LastName string
Email []string
} }
```
Read a single row into a struct. p := &Person{
"Patricia",
```go "Citizen",
var v Item []string{"patricia.citzen@gocqlx_test.com"},
if err := gocqlx.Get(&v, session.Query(`SELECT * FROM items WHERE id = ?`, id)); err != nil {
log.Fatal("get failed", err)
} }
```
Bind named query parameters from a struct or map. // Insert
{
```go q := Query(qb.Insert("person").Columns("first_name", "last_name", "email").ToCql())
stmt, names, err := gocqlx.CompileNamedQuery([]byte("INSERT INTO items (id, name) VALUES (:id, :name)")) if err := q.BindStruct(p); err != nil {
if err != nil {
t.Fatal("compile:", err)
}
q := gocqlx.Queryx{
Query: session.Query(stmt),
Names: names,
}
if err := q.BindStruct(&Item{"id", "name"}); err != nil {
t.Fatal("bind:", err) t.Fatal("bind:", err)
} }
if err := q.Query.Exec(); err != nil { mustExec(q.Query)
log.Fatal("get failed", err) }
// Update
{
p.Email = append(p.Email, "patricia1.citzen@gocqlx_test.com")
q := Query(qb.Update("person").Set("email").Where(qb.Eq("first_name"), qb.Eq("last_name")).ToCql())
if err := q.BindStruct(p); err != nil {
t.Fatal("bind:", err)
}
mustExec(q.Query)
}
// Select
{
q := Query(qb.Select("person").Where(qb.In("first_name")).ToCql())
m := map[string]interface{}{
"first_name": []string{"Patricia", "John"},
}
if err := q.BindMap(m); err != nil {
t.Fatal("bind:", err)
}
var people []Person
if err := gocqlx.Select(&people, q.Query); err != nil {
t.Fatal(err)
}
t.Log(people)
// [{Patricia Citizen [patricia.citzen@gocqlx_test.com patricia1.citzen@gocqlx_test.com]} {John Doe [johndoeDNE@gmail.net]}]
} }
``` ```
## Example For more details see [example test](https://github.com/scylladb/gocqlx/blob/master/example_test.go).
See [example test](https://github.com/scylladb/gocqlx/blob/master/example_test.go).

35
doc.go
View File

@@ -1,38 +1,7 @@
// Package gocqlx is a gocql extension, similar to what sqlx is to database/sql. // Package gocqlx is a Scylla / Cassandra productivity toolkit for `gocql`, it's
// similar to what `sqlx` is to `database/sql`.
// //
// It contains wrappers over gocql types that provide convenience methods which // It contains wrappers over gocql types that provide convenience methods which
// are useful in the development of database driven applications. Under the // are useful in the development of database driven applications. Under the
// hood it uses sqlx/reflectx package so sqlx models will also work with gocqlx. // hood it uses sqlx/reflectx package so sqlx models will also work with gocqlx.
//
// Example, read all rows into a slice
//
// var v []*Item
// if err := gocqlx.Select(&v, session.Query(`SELECT * FROM items WHERE id = ?`, id)); err != nil {
// log.Fatal("select failed", err)
// }
//
// Example, read a single row into a struct
//
// var v Item
// if err := gocqlx.Get(&v, session.Query(`SELECT * FROM items WHERE id = ?`, id)); err != nil {
// log.Fatal("get failed", err)
// }
//
// Example, bind named query parameters from a struct or map
//
// stmt, names, err := gocqlx.CompileNamedQuery([]byte("INSERT INTO items (id, name) VALUES (:id, :name)"))
// if err != nil {
// t.Fatal("compile:", err)
// }
// q := gocqlx.Queryx{
// Query: session.Query(stmt),
// Names: names,
// }
// if err := q.BindStruct(&Item{"id", "name"}); err != nil {
// t.Fatal("bind:", err)
// }
// if err := q.Query.Exec(); err != nil {
// log.Fatal("get failed", err)
// }
//
package gocqlx package gocqlx

View File

@@ -3,11 +3,11 @@
package gocqlx_test package gocqlx_test
import ( import (
"fmt"
"testing" "testing"
"github.com/gocql/gocql" "github.com/gocql/gocql"
"github.com/scylladb/gocqlx" "github.com/scylladb/gocqlx"
"github.com/scylladb/gocqlx/qb"
) )
var personSchema = ` var personSchema = `
@@ -27,7 +27,7 @@ CREATE TABLE gocqlx_test.place (
)` )`
// Field names are converted to camel case by default, no need to add // Field names are converted to camel case by default, no need to add
// `db:"first_name"`, if you want to disable a filed add `db:"-"` tag // `db:"first_name"`, if you want to disable a filed add `db:"-"` tag.
type Person struct { type Person struct {
FirstName string FirstName string
LastName string LastName string
@@ -46,13 +46,15 @@ func TestExample(t *testing.T) {
mustExec := func(q *gocql.Query) { mustExec := func(q *gocql.Query) {
if err := q.Exec(); err != nil { if err := q.Exec(); err != nil {
t.Fatal("insert:", q, err) t.Fatal("query:", q, err)
} }
} }
// Fill person table // Fill person table.
{ {
mustExec(session.Query(personSchema)) if err := createTable(session, personSchema); err != nil {
t.Fatal("create table:", err)
}
q := session.Query("INSERT INTO gocqlx_test.person (first_name, last_name, email) VALUES (?, ?, ?)") q := session.Query("INSERT INTO gocqlx_test.person (first_name, last_name, email) VALUES (?, ?, ?)")
mustExec(q.Bind("Jason", "Moiron", []string{"jmoiron@jmoiron.net"})) mustExec(q.Bind("Jason", "Moiron", []string{"jmoiron@jmoiron.net"}))
@@ -60,9 +62,11 @@ func TestExample(t *testing.T) {
q.Release() q.Release()
} }
// Fill place table // Fill place table.
{ {
mustExec(session.Query(placeSchema)) if err := createTable(session, placeSchema); err != nil {
t.Fatal("create table:", err)
}
q := session.Query("INSERT INTO gocqlx_test.place (country, city, code) VALUES (?, ?, ?)") q := session.Query("INSERT INTO gocqlx_test.place (country, city, code) VALUES (?, ?, ?)")
mustExec(q.Bind("United States", "New York", 1)) mustExec(q.Bind("United States", "New York", 1))
@@ -71,72 +75,131 @@ func TestExample(t *testing.T) {
q.Release() q.Release()
} }
// Query the database, storing results in a []Person (wrapped in []interface{}) // Query the database, storing results in a []Person (wrapped in []interface{}).
{ {
people := []Person{} var people []Person
if err := gocqlx.Select(&people, session.Query("SELECT * FROM person")); err != nil { if err := gocqlx.Select(&people, session.Query("SELECT * FROM person")); err != nil {
t.Fatal("select:", err) t.Fatal("select:", err)
} }
t.Log(people)
fmt.Printf("%#v\n%#v\n", people[0], people[1]) // [{John Doe [johndoeDNE@gmail.net]} {Jason Moiron [jmoiron@jmoiron.net]}]
// gocqlx_test.Person{FirstName:"John", LastName:"Doe", Email:[]string{"johndoeDNE@gmail.net"}}
// gocqlx_test.Person{FirstName:"Jason", LastName:"Moiron", Email:[]string{"jmoiron@jmoiron.net"}}
} }
// Get a single result, a la QueryRow // Get a single result.
{ {
var jason Person var jason Person
if err := gocqlx.Get(&jason, session.Query("SELECT * FROM person WHERE first_name=?", "Jason")); err != nil { if err := gocqlx.Get(&jason, session.Query("SELECT * FROM person WHERE first_name=?", "Jason")); err != nil {
t.Fatal("get:", err) t.Fatal("get:", err)
} }
fmt.Printf("%#v\n", jason) t.Log(jason)
// gocqlx_test.Person{FirstName:"Jason", LastName:"Moiron", Email:[]string{"jmoiron@jmoiron.net"}}
// Jason Moiron [jmoiron@jmoiron.net]}
} }
// Loop through rows using only one struct // Loop through rows using only one struct.
{ {
var place Place var place Place
iter := gocqlx.Iter(session.Query("SELECT * FROM place")) iter := gocqlx.Iter(session.Query("SELECT * FROM place"))
for iter.StructScan(&place) { for iter.StructScan(&place) {
fmt.Printf("%#v\n", place) t.Log(place)
} }
if err := iter.Close(); err != nil { if err := iter.Close(); err != nil {
t.Fatal("iter:", err) t.Fatal("iter:", err)
} }
iter.ReleaseQuery() iter.ReleaseQuery()
// gocqlx_test.Place{Country:"Hong Kong", City:"", TelCode:852}
// gocqlx_test.Place{Country:"United States", City:"New York", TelCode:1} // {Hong Kong 852}
// gocqlx_test.Place{Country:"Singapore", City:"", TelCode:65} // {United States New York 1}
// {Singapore 65}
} }
// Named queries, using `:name` as the bindvar // Query builder, using DSL to build queries, using `:name` as the bindvar.
{ {
// helper function for creating session queries
Query := gocqlx.SessionQuery(session)
p := &Person{
"Patricia",
"Citizen",
[]string{"patricia.citzen@gocqlx_test.com"},
}
// Insert
{
q := Query(qb.Insert("person").Columns("first_name", "last_name", "email").ToCql())
if err := q.BindStruct(p); err != nil {
t.Fatal("bind:", err)
}
mustExec(q.Query)
}
// Update
{
p.Email = append(p.Email, "patricia1.citzen@gocqlx_test.com")
q := Query(qb.Update("person").Set("email").Where(qb.Eq("first_name"), qb.Eq("last_name")).ToCql())
if err := q.BindStruct(p); err != nil {
t.Fatal("bind:", err)
}
mustExec(q.Query)
}
// Select
{
q := Query(qb.Select("person").Where(qb.In("first_name")).ToCql())
m := map[string]interface{}{
"first_name": []string{"Patricia", "John"},
}
if err := q.BindMap(m); err != nil {
t.Fatal("bind:", err)
}
var people []Person
if err := gocqlx.Select(&people, q.Query); err != nil {
t.Fatal("select:", err)
}
t.Log(people)
// [{Patricia Citizen [patricia.citzen@gocqlx_test.com patricia1.citzen@gocqlx_test.com]} {John Doe [johndoeDNE@gmail.net]}]
}
}
// Named queries, using `:name` as the bindvar.
{
// compile query to valid gocqlx query and list of named parameters
stmt, names, err := gocqlx.CompileNamedQuery([]byte("INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)")) stmt, names, err := gocqlx.CompileNamedQuery([]byte("INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)"))
if err != nil { if err != nil {
t.Fatal("compile:", err) t.Fatal("compile:", err)
} }
q := gocqlx.Query(session.Query(stmt), names)
q := gocqlx.Queryx{ // bind named parameters from a struct
Query: session.Query(stmt), {
Names: names, p := &Person{
}
if err := q.BindStruct(&Person{
"Jane", "Jane",
"Citizen", "Citizen",
[]string{"jane.citzen@gocqlx_test.com"}, []string{"jane.citzen@gocqlx_test.com"},
}); err != nil { }
if err := q.BindStruct(p); err != nil {
t.Fatal("bind:", err) t.Fatal("bind:", err)
} }
mustExec(q.Query) mustExec(q.Query)
}
if err := q.BindMap(map[string]interface{}{ // bind named parameters from a map
{
m := map[string]interface{}{
"first_name": "Bin", "first_name": "Bin",
"last_name": "Smuth", "last_name": "Smuth",
"email": []string{"bensmith@allblacks.nz"}, "email": []string{"bensmith@allblacks.nz"},
}); err != nil { }
if err := q.BindMap(m); err != nil {
t.Fatal("bind:", err) t.Fatal("bind:", err)
} }
mustExec(q.Query) mustExec(q.Query)
} }
} }
}

View File

@@ -2,6 +2,7 @@ package qb
import "bytes" import "bytes"
// op specifies Cmd operation type.
type op byte type op byte
const ( const (
@@ -14,6 +15,7 @@ const (
cnt cnt
) )
// Cmp if a filtering comparator that is used in WHERE and IF clauses.
type Cmp struct { type Cmp struct {
op op op op
column string column string
@@ -45,6 +47,7 @@ func (cmp Cmp) writeCql(cql *bytes.Buffer) string {
return cmp.name return cmp.name
} }
// Eq produces column=?.
func Eq(column string) Cmp { func Eq(column string) Cmp {
return Cmp{ return Cmp{
op: eq, op: eq,
@@ -53,6 +56,7 @@ func Eq(column string) Cmp {
} }
} }
// EqNamed produces column=? with a custom parameter name.
func EqNamed(column, name string) Cmp { func EqNamed(column, name string) Cmp {
return Cmp{ return Cmp{
op: eq, op: eq,
@@ -61,6 +65,7 @@ func EqNamed(column, name string) Cmp {
} }
} }
// Lt produces column<?.
func Lt(column string) Cmp { func Lt(column string) Cmp {
return Cmp{ return Cmp{
op: lt, op: lt,
@@ -69,6 +74,7 @@ func Lt(column string) Cmp {
} }
} }
// LtNamed produces column<? with a custom parameter name.
func LtNamed(column, name string) Cmp { func LtNamed(column, name string) Cmp {
return Cmp{ return Cmp{
op: lt, op: lt,
@@ -77,6 +83,7 @@ func LtNamed(column, name string) Cmp {
} }
} }
// LtOrEq produces column<=?.
func LtOrEq(column string) Cmp { func LtOrEq(column string) Cmp {
return Cmp{ return Cmp{
op: leq, op: leq,
@@ -85,6 +92,7 @@ func LtOrEq(column string) Cmp {
} }
} }
// LtOrEqNamed produces column<=? with a custom parameter name.
func LtOrEqNamed(column, name string) Cmp { func LtOrEqNamed(column, name string) Cmp {
return Cmp{ return Cmp{
op: leq, op: leq,
@@ -93,6 +101,7 @@ func LtOrEqNamed(column, name string) Cmp {
} }
} }
// Gt produces column>?.
func Gt(column string) Cmp { func Gt(column string) Cmp {
return Cmp{ return Cmp{
op: gt, op: gt,
@@ -101,6 +110,7 @@ func Gt(column string) Cmp {
} }
} }
// GtNamed produces column>? with a custom parameter name.
func GtNamed(column, name string) Cmp { func GtNamed(column, name string) Cmp {
return Cmp{ return Cmp{
op: gt, op: gt,
@@ -109,6 +119,7 @@ func GtNamed(column, name string) Cmp {
} }
} }
// GtOrEq produces column>=?.
func GtOrEq(column string) Cmp { func GtOrEq(column string) Cmp {
return Cmp{ return Cmp{
op: geq, op: geq,
@@ -117,6 +128,7 @@ func GtOrEq(column string) Cmp {
} }
} }
// GtOrEqNamed produces column>=? with a custom parameter name.
func GtOrEqNamed(column, name string) Cmp { func GtOrEqNamed(column, name string) Cmp {
return Cmp{ return Cmp{
op: geq, op: geq,
@@ -125,6 +137,7 @@ func GtOrEqNamed(column, name string) Cmp {
} }
} }
// In produces column IN ?.
func In(column string) Cmp { func In(column string) Cmp {
return Cmp{ return Cmp{
op: in, op: in,
@@ -133,6 +146,7 @@ func In(column string) Cmp {
} }
} }
// InNamed produces column IN ? with a custom parameter name.
func InNamed(column, name string) Cmp { func InNamed(column, name string) Cmp {
return Cmp{ return Cmp{
op: in, op: in,
@@ -141,6 +155,7 @@ func InNamed(column, name string) Cmp {
} }
} }
// Contains produces column CONTAINS ?.
func Contains(column string) Cmp { func Contains(column string) Cmp {
return Cmp{ return Cmp{
op: cnt, op: cnt,
@@ -149,6 +164,7 @@ func Contains(column string) Cmp {
} }
} }
// ContainsNamed produces column CONTAINS ? with a custom parameter name.
func ContainsNamed(column, name string) Cmp { func ContainsNamed(column, name string) Cmp {
return Cmp{ return Cmp{
op: cnt, op: cnt,

View File

@@ -1,13 +1,14 @@
package qb package qb
// DELETE reference: // DELETE reference:
// http://docs.datastax.com/en/dse/5.1/cql/cql/cql_reference/cql_commands/cqlDelete.html // https://cassandra.apache.org/doc/latest/cql/dml.html#delete
import ( import (
"bytes" "bytes"
"time" "time"
) )
// DeleteBuilder builds CQL DELETE statements.
type DeleteBuilder struct { type DeleteBuilder struct {
table string table string
columns columns columns columns
@@ -24,6 +25,7 @@ func Delete(table string) *DeleteBuilder {
} }
} }
// ToCql builds the query into a CQL string and named args.
func (b *DeleteBuilder) ToCql() (stmt string, names []string) { func (b *DeleteBuilder) ToCql() (stmt string, names []string) {
cql := bytes.Buffer{} cql := bytes.Buffer{}
@@ -61,21 +63,27 @@ func (b *DeleteBuilder) Columns(columns ...string) *DeleteBuilder {
return b return b
} }
// Timestamp sets a USING TIMESTAMP clause on the query.
func (b *DeleteBuilder) Timestamp(t time.Time) *DeleteBuilder { func (b *DeleteBuilder) Timestamp(t time.Time) *DeleteBuilder {
b.using.timestamp = t b.using.timestamp = t
return b return b
} }
// Where adds an expression to the WHERE clause of the query. Expressions are
// ANDed together in the generated CQL.
func (b *DeleteBuilder) Where(w ...Cmp) *DeleteBuilder { func (b *DeleteBuilder) Where(w ...Cmp) *DeleteBuilder {
b.where = append(b.where, w...) b.where = append(b.where, w...)
return b return b
} }
// If adds an expression to the IF clause of the query. Expressions are ANDed
// together in the generated CQL.
func (b *DeleteBuilder) If(w ...Cmp) *DeleteBuilder { func (b *DeleteBuilder) If(w ...Cmp) *DeleteBuilder {
b._if = append(b._if, w...) b._if = append(b._if, w...)
return b return b
} }
// Existing sets a IF EXISTS clause on the query.
func (b *DeleteBuilder) Existing() *DeleteBuilder { func (b *DeleteBuilder) Existing() *DeleteBuilder {
b.exists = true b.exists = true
return b return b

4
qb/doc.go Normal file
View File

@@ -0,0 +1,4 @@
// Package qb provides CQL (Scylla / Cassandra query language) query builders.
// The builders create CQL statement and a list of named parameters that can
// later be bound using github.com/scylladb/gocqlx.
package qb

View File

@@ -1,13 +1,14 @@
package qb package qb
// INSERT reference: // INSERT reference:
// http://docs.datastax.com/en/dse/5.1/cql/cql/cql_reference/cql_commands/cqlInsert.html // https://cassandra.apache.org/doc/latest/cql/dml.html#insert
import ( import (
"bytes" "bytes"
"time" "time"
) )
// InsertBuilder builds CQL INSERT statements.
type InsertBuilder struct { type InsertBuilder struct {
table string table string
columns columns columns columns
@@ -22,6 +23,7 @@ func Insert(table string) *InsertBuilder {
} }
} }
// ToCql builds the query into a CQL string and named args.
func (b *InsertBuilder) ToCql() (stmt string, names []string) { func (b *InsertBuilder) ToCql() (stmt string, names []string) {
cql := bytes.Buffer{} cql := bytes.Buffer{}
@@ -49,26 +51,31 @@ func (b *InsertBuilder) ToCql() (stmt string, names []string) {
return return
} }
// Into sets the INTO clause of the query.
func (b *InsertBuilder) Into(table string) *InsertBuilder { func (b *InsertBuilder) Into(table string) *InsertBuilder {
b.table = table b.table = table
return b return b
} }
// Columns adds insert columns to the query.
func (b *InsertBuilder) Columns(columns ...string) *InsertBuilder { func (b *InsertBuilder) Columns(columns ...string) *InsertBuilder {
b.columns = append(b.columns, columns...) b.columns = append(b.columns, columns...)
return b return b
} }
// Unique sets a IF NOT EXISTS clause on the query.
func (b *InsertBuilder) Unique() *InsertBuilder { func (b *InsertBuilder) Unique() *InsertBuilder {
b.unique = true b.unique = true
return b return b
} }
// Timestamp sets a USING TIMESTAMP clause on the query.
func (b *InsertBuilder) Timestamp(t time.Time) *InsertBuilder { func (b *InsertBuilder) Timestamp(t time.Time) *InsertBuilder {
b.using.timestamp = t b.using.timestamp = t
return b return b
} }
// TTL sets a USING TTL clause on the query.
func (b *InsertBuilder) TTL(d time.Duration) *InsertBuilder { func (b *InsertBuilder) TTL(d time.Duration) *InsertBuilder {
b.using.ttl = d b.using.ttl = d
return b return b

View File

@@ -1,20 +1,24 @@
package qb package qb
// SELECT reference: // SELECT reference:
// http://docs.datastax.com/en/dse/5.1/cql/cql/cql_reference/cql_commands/cqlSelect.html // https://cassandra.apache.org/doc/latest/cql/dml.html#select
import ( import (
"bytes" "bytes"
"fmt" "fmt"
) )
// Order specifies sorting order.
type Order bool type Order bool
const ( const (
// ASC is ascending order
ASC Order = true ASC Order = true
// DESC is descending order
DESC = false DESC = false
) )
// SelectBuilder builds CQL SELECT statements.
type SelectBuilder struct { type SelectBuilder struct {
table string table string
columns columns columns columns
@@ -35,6 +39,7 @@ func Select(table string) *SelectBuilder {
} }
} }
// ToCql builds the query into a CQL string and named args.
func (b *SelectBuilder) ToCql() (stmt string, names []string) { func (b *SelectBuilder) ToCql() (stmt string, names []string) {
cql := bytes.Buffer{} cql := bytes.Buffer{}
@@ -100,16 +105,20 @@ func (b *SelectBuilder) From(table string) *SelectBuilder {
return b return b
} }
// Columns adds result columns to the query.
func (b *SelectBuilder) Columns(columns ...string) *SelectBuilder { func (b *SelectBuilder) Columns(columns ...string) *SelectBuilder {
b.columns = append(b.columns, columns...) b.columns = append(b.columns, columns...)
return b return b
} }
// Distinct sets DISTINCT clause on the query.
func (b *SelectBuilder) Distinct(columns ...string) *SelectBuilder { func (b *SelectBuilder) Distinct(columns ...string) *SelectBuilder {
b.distinct = append(b.distinct, columns...) b.distinct = append(b.distinct, columns...)
return b return b
} }
// Where adds an expression to the WHERE clause of the query. Expressions are
// ANDed together in the generated CQL.
func (b *SelectBuilder) Where(w ...Cmp) *SelectBuilder { func (b *SelectBuilder) Where(w ...Cmp) *SelectBuilder {
b.where = append(b.where, w...) b.where = append(b.where, w...)
return b return b
@@ -122,21 +131,25 @@ func (b *SelectBuilder) GroupBy(columns... string) *SelectBuilder {
return b return b
} }
// OrderBy sets ORDER BY clause on the query.
func (b *SelectBuilder) OrderBy(column string, o Order) *SelectBuilder { func (b *SelectBuilder) OrderBy(column string, o Order) *SelectBuilder {
b.orderBy, b.order = column, o b.orderBy, b.order = column, o
return b return b
} }
// Limit sets a LIMIT clause on the query.
func (b *SelectBuilder) Limit(limit uint) *SelectBuilder { func (b *SelectBuilder) Limit(limit uint) *SelectBuilder {
b.limit = limit b.limit = limit
return b return b
} }
// LimitPerPartition sets a PER PARTITION LIMIT clause on the query.
func (b *SelectBuilder) LimitPerPartition(limit uint) *SelectBuilder { func (b *SelectBuilder) LimitPerPartition(limit uint) *SelectBuilder {
b.limitPerPartition = limit b.limitPerPartition = limit
return b return b
} }
// AllowFiltering sets a ALLOW FILTERING clause on the query.
func (b *SelectBuilder) AllowFiltering() *SelectBuilder { func (b *SelectBuilder) AllowFiltering() *SelectBuilder {
b.allowFiltering = true b.allowFiltering = true
return b return b

View File

@@ -1,16 +1,14 @@
package qb package qb
// UPDATE reference:
// https://cassandra.apache.org/doc/latest/cql/dml.html#update
import ( import (
"bytes" "bytes"
)
// UPDATE reference:
// http://docs.datastax.com/en/dse/5.1/cql/cql/cql_reference/cql_commands/cqlUpdate.html
import (
"time" "time"
) )
// UpdateBuilder builds CQL UPDATE statements.
type UpdateBuilder struct { type UpdateBuilder struct {
table string table string
using using using using
@@ -27,6 +25,7 @@ func Update(table string) *UpdateBuilder {
} }
} }
// ToCql builds the query into a CQL string and named args.
func (b *UpdateBuilder) ToCql() (stmt string, names []string) { func (b *UpdateBuilder) ToCql() (stmt string, names []string) {
cql := bytes.Buffer{} cql := bytes.Buffer{}
@@ -64,31 +63,39 @@ func (b *UpdateBuilder) Table(table string) *UpdateBuilder {
return b return b
} }
// Timestamp sets a USING TIMESTAMP clause on the query.
func (b *UpdateBuilder) Timestamp(t time.Time) *UpdateBuilder { func (b *UpdateBuilder) Timestamp(t time.Time) *UpdateBuilder {
b.using.timestamp = t b.using.timestamp = t
return b return b
} }
// TTL sets a USING TTL clause on the query.
func (b *UpdateBuilder) TTL(d time.Duration) *UpdateBuilder { func (b *UpdateBuilder) TTL(d time.Duration) *UpdateBuilder {
b.using.ttl = d b.using.ttl = d
return b return b
} }
// Set adds SET clauses to the query.
func (b *UpdateBuilder) Set(columns ...string) *UpdateBuilder { func (b *UpdateBuilder) Set(columns ...string) *UpdateBuilder {
b.columns = append(b.columns, columns...) b.columns = append(b.columns, columns...)
return b return b
} }
// Where adds an expression to the WHERE clause of the query. Expressions are
// ANDed together in the generated CQL.
func (b *UpdateBuilder) Where(w ...Cmp) *UpdateBuilder { func (b *UpdateBuilder) Where(w ...Cmp) *UpdateBuilder {
b.where = append(b.where, w...) b.where = append(b.where, w...)
return b return b
} }
// If adds an expression to the IF clause of the query. Expressions are ANDed
// together in the generated CQL.
func (b *UpdateBuilder) If(w ...Cmp) *UpdateBuilder { func (b *UpdateBuilder) If(w ...Cmp) *UpdateBuilder {
b._if = append(b._if, w...) b._if = append(b._if, w...)
return b return b
} }
// Existing sets a IF EXISTS clause on the query.
func (b *UpdateBuilder) Existing() *UpdateBuilder { func (b *UpdateBuilder) Existing() *UpdateBuilder {
b.exists = true b.exists = true
return b return b

View File

@@ -82,14 +82,18 @@ type Queryx struct {
Mapper *reflectx.Mapper Mapper *reflectx.Mapper
} }
// BindStruct binds query named parameters using mapper. // Query creates a new Queryx from gocql.Query using a default mapper.
func (q Queryx) BindStruct(arg interface{}) error { func Query(q *gocql.Query, names []string) Queryx {
m := q.Mapper return Queryx{
if m == nil { Query: q,
m = DefaultMapper Names: names,
Mapper: DefaultMapper,
}
} }
arglist, err := bindStructArgs(q.Names, arg, m) // BindStruct binds query named parameters using mapper.
func (q Queryx) BindStruct(arg interface{}) error {
arglist, err := bindStructArgs(q.Names, arg, q.Mapper)
if err != nil { if err != nil {
return err return err
} }
@@ -144,3 +148,13 @@ func bindMapArgs(names []string, arg map[string]interface{}) ([]interface{}, err
} }
return arglist, nil return arglist, nil
} }
// QueryFunc creates Queryx from qb.Builder.ToCql() output.
type QueryFunc func(stmt string, names []string) Queryx
// SessionQuery creates QueryFunc that's session aware.
func SessionQuery(session *gocql.Session) QueryFunc {
return func(stmt string, names []string) Queryx {
return Query(session.Query(stmt), names)
}
}

View File

@@ -101,10 +101,7 @@ func TestBindStruct(t *testing.T) {
} }
func BenchmarkBindStruct(b *testing.B) { func BenchmarkBindStruct(b *testing.B) {
q := Queryx{ q := Query(&gocql.Query{}, []string{"name", "age", "first", "last"})
Query: &gocql.Query{},
Names: []string{"name", "age", "first", "last"},
}
type t struct { type t struct {
Name string Name string
Age int Age int