diff --git a/qb/cmp.go b/qb/cmp.go index 4a83a8c..a383694 100644 --- a/qb/cmp.go +++ b/qb/cmp.go @@ -66,6 +66,18 @@ func Eq(column string) Cmp { } } +// EqTuple produces column=(?,?,...) with count number of placeholders. +func EqTuple(column string, count int) Cmp { + return Cmp{ + op: eq, + column: column, + value: tupleParam{ + param: param(column), + count: count, + }, + } +} + // EqNamed produces column=? with a custom parameter name. func EqNamed(column, name string) Cmp { return Cmp{ @@ -102,6 +114,18 @@ func Lt(column string) Cmp { } } +// LtTuple produces column<(?,?,...) with count placeholders. +func LtTuple(column string, count int) Cmp { + return Cmp{ + op: lt, + column: column, + value: tupleParam{ + param: param(column), + count: count, + }, + } +} + // LtNamed produces column(?,?,...) with count placeholders. +func GtTuple(column string, count int) Cmp { + return Cmp{ + op: gt, + column: column, + value: tupleParam{ + param: param(column), + count: count, + }, + } +} + // GtNamed produces column>? with a custom parameter name. func GtNamed(column, name string) Cmp { return Cmp{ @@ -210,6 +258,18 @@ func GtOrEq(column string) Cmp { } } +// GtOrEqTuple produces column>=(?,?,...) with count placeholders. +func GtOrEqTuple(column string, count int) Cmp { + return Cmp{ + op: geq, + column: column, + value: tupleParam{ + param: param(column), + count: count, + }, + } +} + // GtOrEqNamed produces column>=? with a custom parameter name. func GtOrEqNamed(column, name string) Cmp { return Cmp{ @@ -246,6 +306,18 @@ func In(column string) Cmp { } } +// InTuple produces column IN ?. +func InTuple(column string, count int) Cmp { + return Cmp{ + op: in, + column: column, + value: tupleParam{ + param: param(column), + count: count, + }, + } +} + // InNamed produces column IN ? with a custom parameter name. func InNamed(column, name string) Cmp { return Cmp{ @@ -273,6 +345,18 @@ func Contains(column string) Cmp { } } +// ContainsTuple produces column CONTAINS (?,?,...) with count placeholders. +func ContainsTuple(column string, count int) Cmp { + return Cmp{ + op: cnt, + column: column, + value: tupleParam{ + param: param(column), + count: count, + }, + } +} + // ContainsKey produces column CONTAINS KEY ?. func ContainsKey(column string) Cmp { return Cmp{ @@ -282,6 +366,18 @@ func ContainsKey(column string) Cmp { } } +// ContainsKeyTuple produces column CONTAINS KEY (?,?,...) with count placehplders. +func ContainsKeyTuple(column string, count int) Cmp { + return Cmp{ + op: cntKey, + column: column, + value: tupleParam{ + param: param(column), + count: count, + }, + } +} + // ContainsNamed produces column CONTAINS ? with a custom parameter name. func ContainsNamed(column, name string) Cmp { return Cmp{ @@ -318,6 +414,18 @@ func Like(column string) Cmp { } } +// LikeTuple produces column LIKE (?,?,...) with count placeholders. +func LikeTuple(column string, count int) Cmp { + return Cmp{ + op: like, + column: column, + value: tupleParam{ + param: param(column), + count: count, + }, + } +} + type cmps []Cmp func (cs cmps) writeCql(cql *bytes.Buffer) (names []string) { diff --git a/qb/cmp_test.go b/qb/cmp_test.go index b6eb653..dcc4cdc 100644 --- a/qb/cmp_test.go +++ b/qb/cmp_test.go @@ -28,41 +28,81 @@ func TestCmp(t *testing.T) { S: "lt?", N: []string{"gt"}, }, + { + C: GtTuple("gt", 2), + S: "gt>(?,?)", + N: []string{"gt"}, + }, { C: GtOrEq("gt"), S: "gt>=?", N: []string{"gt"}, }, + { + C: GtOrEqTuple("gt", 2), + S: "gt>=(?,?)", + N: []string{"gt"}, + }, { C: In("in"), S: "in IN ?", N: []string{"in"}, }, + { + C: InTuple("in", 2), + S: "in IN (?,?)", + N: []string{"in"}, + }, { C: Contains("cnt"), S: "cnt CONTAINS ?", N: []string{"cnt"}, }, + { + C: ContainsTuple("cnt", 2), + S: "cnt CONTAINS (?,?)", + N: []string{"cnt"}, + }, { C: ContainsKey("cntKey"), S: "cntKey CONTAINS KEY ?", N: []string{"cntKey"}, }, + { + C: ContainsKeyTuple("cntKey", 2), + S: "cntKey CONTAINS KEY (?,?)", + N: []string{"cntKey"}, + }, { C: Like("like"), S: "like LIKE ?", N: []string{"like"}, }, + { + C: LikeTuple("like", 2), + S: "like LIKE (?,?)", + N: []string{"like"}, + }, // Custom bind names { diff --git a/qb/delete_test.go b/qb/delete_test.go index b2bc93e..f0fb7d3 100644 --- a/qb/delete_test.go +++ b/qb/delete_test.go @@ -43,6 +43,24 @@ func TestDeleteBuilder(t *testing.T) { S: "DELETE FROM cycling.cyclist_name WHERE id=? AND firstname>? ", N: []string{"expr", "firstname"}, }, + // Add a tuple column + { + B: Delete("cycling.cyclist_name").Where(EqTuple("id", 2)).Columns("stars"), + S: "DELETE stars FROM cycling.cyclist_name WHERE id=(?,?) ", + N: []string{"id"}, + }, + // Add WHERE for tuple column + { + B: Delete("cycling.cyclist_name").Where(w, GtTuple("firstname", 2)), + S: "DELETE FROM cycling.cyclist_name WHERE id=? AND firstname>(?,?) ", + N: []string{"expr", "firstname"}, + }, + // Add WHERE for all tuple columns + { + B: Delete("cycling.cyclist_name").Where(EqTuple("id", 2), GtTuple("firstname", 2)), + S: "DELETE FROM cycling.cyclist_name WHERE id=(?,?) AND firstname>(?,?) ", + N: []string{"id", "firstname"}, + }, // Add IF { B: Delete("cycling.cyclist_name").Where(w).If(Gt("firstname")), diff --git a/qb/insert.go b/qb/insert.go index 3caacf1..bf45771 100644 --- a/qb/insert.go +++ b/qb/insert.go @@ -128,6 +128,18 @@ func (b *InsertBuilder) FuncColumn(column string, fn *Func) *InsertBuilder { return b } +// TupleColumn adds an insert column for a tuple value to the query. +func (b *InsertBuilder) TupleColumn(column string, count int) *InsertBuilder { + b.columns = append(b.columns, initializer{ + column: column, + value: tupleParam{ + param: param(column), + count: count, + }, + }) + return b +} + // Unique sets a IF NOT EXISTS clause on the query. func (b *InsertBuilder) Unique() *InsertBuilder { b.unique = true diff --git a/qb/insert_test.go b/qb/insert_test.go index 7d8e3ca..9c8b233 100644 --- a/qb/insert_test.go +++ b/qb/insert_test.go @@ -76,6 +76,17 @@ func TestInsertBuilder(t *testing.T) { S: "INSERT INTO cycling.cyclist_name (id,user_uuid,firstname) VALUES (?,?,?) USING TIMESTAMP ? ", N: []string{"id", "user_uuid", "firstname", "ts"}, }, + // Add TupleColumn + { + B: Insert("cycling.cyclist_name").TupleColumn("id", 2), + S: "INSERT INTO cycling.cyclist_name (id) VALUES ((?,?)) ", + N: []string{"id"}, + }, + { + B: Insert("cycling.cyclist_name").TupleColumn("id", 2).Columns("user_uuid"), + S: "INSERT INTO cycling.cyclist_name (id,user_uuid) VALUES ((?,?),?) ", + N: []string{"id", "user_uuid"}, + }, // Add IF NOT EXISTS { B: Insert("cycling.cyclist_name").Columns("id", "user_uuid", "firstname").Unique(), diff --git a/qb/select_test.go b/qb/select_test.go index 00d20b3..8f0f307 100644 --- a/qb/select_test.go +++ b/qb/select_test.go @@ -65,6 +65,18 @@ func TestSelectBuilder(t *testing.T) { S: "SELECT * FROM cycling.cyclist_name WHERE id=? AND firstname>? ", N: []string{"expr", "firstname"}, }, + // Add WHERE with tuple + { + B: Select("cycling.cyclist_name").Where(EqTuple("id", 2), Gt("firstname")), + S: "SELECT * FROM cycling.cyclist_name WHERE id=(?,?) AND firstname>? ", + N: []string{"id", "firstname"}, + }, + // Add WHERE with only tuples + { + B: Select("cycling.cyclist_name").Where(EqTuple("id", 2), GtTuple("firstname", 2)), + S: "SELECT * FROM cycling.cyclist_name WHERE id=(?,?) AND firstname>(?,?) ", + N: []string{"id", "firstname"}, + }, // Add GROUP BY { B: Select("cycling.cyclist_name").Columns("MAX(stars) as max_stars").GroupBy("id"), diff --git a/qb/update.go b/qb/update.go index c21be4e..72160e6 100644 --- a/qb/update.go +++ b/qb/update.go @@ -105,6 +105,7 @@ func (b *UpdateBuilder) TimestampNamed(name string) *UpdateBuilder { } // Set adds SET clauses to the query. +// To set a tuple column use SetTuple instead. func (b *UpdateBuilder) Set(columns ...string) *UpdateBuilder { for _, c := range columns { b.assignments = append(b.assignments, assignment{ @@ -136,6 +137,18 @@ func (b *UpdateBuilder) SetFunc(column string, fn *Func) *UpdateBuilder { return b } +// SetTuple adds a SET clause for a tuple to the query. +func (b *UpdateBuilder) SetTuple(column string, count int) *UpdateBuilder { + b.assignments = append(b.assignments, assignment{ + column: column, + value: tupleParam{ + param: param(column), + count: count, + }, + }) + return b +} + // Add adds SET column=column+? clauses to the query. func (b *UpdateBuilder) Add(column string) *UpdateBuilder { return b.addValue(column, param(column)) diff --git a/qb/update_test.go b/qb/update_test.go index 94c4a05..d1212e4 100644 --- a/qb/update_test.go +++ b/qb/update_test.go @@ -43,6 +43,13 @@ func TestUpdateBuilder(t *testing.T) { S: "UPDATE cycling.cyclist_name SET user_uuid=literal_uuid,stars=? WHERE id=? ", N: []string{"stars", "expr"}, }, + + // Add SET tuple + { + B: Update("cycling.cyclist_name").SetTuple("id", 2).Set("user_uuid", "firstname").Where(EqTuple("id", 2)), + S: "UPDATE cycling.cyclist_name SET id=(?,?),user_uuid=?,firstname=? WHERE id=(?,?) ", + N: []string{"id", "user_uuid", "firstname", "id"}, + }, // Add SET SetFunc { B: Update("cycling.cyclist_name").SetFunc("user_uuid", Fn("someFunc", "param_0", "param_1")).Where(w).Set("stars"), diff --git a/qb/value.go b/qb/value.go index c66daff..8ac87ba 100644 --- a/qb/value.go +++ b/qb/value.go @@ -22,6 +22,23 @@ func (p param) writeCql(cql *bytes.Buffer) (names []string) { return []string{string(p)} } +// param is a named CQL tuple '?' parameter. +type tupleParam struct { + param param + count int +} + +func (t tupleParam) writeCql(cql *bytes.Buffer) (names []string) { + cql.WriteByte('(') + for i := 0; i < t.count-1; i++ { + cql.WriteByte('?') + cql.WriteByte(',') + } + cql.WriteByte('?') + cql.WriteByte(')') + return []string{string(t.param)} +} + // lit is a literal CQL value. type lit string