Skip to content

Commit

Permalink
Merge pull request #14 from pingginp/runtime/delete-if
Browse files Browse the repository at this point in the history
[runtime] Support if in delete statement #13
  • Loading branch information
pingginp authored Mar 10, 2019
2 parents 1de1d77 + 1910b32 commit 292abab
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 62 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ NOTE: this file format is based on [gaocegege/maintainer](https://github.com/gao

## Unreleased

## 0.13.0 (2019-03-10)

Was going to make it 0.12.2 but since it breaks both runtime and generated code, bump minor version number

- support `IF` in `DELETE` [#13](https://github.com/pingginp/cqlc/issues/13)
- in generated column bindings allow `Eq` on all columns, previously only primary key, index are allowed, which blocks using
other columns in condition queries after `If`

## 0.12.1 (2019-02-18)

- previous release didn't update all the mapping in generator
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION = 0.12.1
VERSION = 0.13.0
LDFLAGS = -X main.Version=$(VERSION)
GO = CGO_ENABLED=0 go
GO_LINUX = GOOS=linux GOARCH=amd64 $(GO)
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,15 @@ it is mainly for generating runtime code that ships with the library
The main modification to the runtime are listed below

- [support update map value by key](doc/set-map-value-by-key.md), previously, cqlc can only update entire map. (This change only requires update runtime)
- support `IF` in `DELETE` [#13](https://github.com/pingginp/cqlc/issues/13)

### Generator

The main modification to the generator are listed below

- generator now compiles, caused by breaking change of constant name in gocql
- support Cassandra 3 by adding a new literalType mapping for text -> string [#12](https://github.com/pingginp/cqlc/pull/12)
- allow `Eq` on all columns to support `IF` in `DELETE`

The overall generator logic is

Expand Down
103 changes: 47 additions & 56 deletions cqlc/cqlc.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,15 @@ type ReadOptions struct {

// Context represents the state of the CQL statement that is being built by the application.
type Context struct {
Operation OperationType
Table Table
Columns []Column
Bindings []ColumnBinding
CASBindings []ColumnBinding
Conditions []Condition
Operation OperationType
Table Table
Columns []Column
Bindings []ColumnBinding
CASBindings []ColumnBinding
// Conditions is used by WHERE to locate primary key
Conditions []Condition
// IfConditions is used by IF which is put after WHERE to restrict non primary key columns
IfConditions []Condition
ResultBindings map[string]ColumnBinding
// Debug flag will cause all CQL statements to get logged
Debug bool
Expand Down Expand Up @@ -146,6 +149,11 @@ type UniqueFetchable interface {
FetchOne(*gocql.Session) (bool, error)
}

type IfQueryStep interface {
If(conditions ...Condition) Query
Query
}

type Query interface {
Executable
Fetchable
Expand All @@ -155,7 +163,7 @@ type Query interface {

type SelectWhereStep interface {
Fetchable
Where(conditions ...Condition) Query
Where(conditions ...Condition) IfQueryStep
}

type SelectFromStep interface {
Expand Down Expand Up @@ -370,14 +378,19 @@ func (c *Context) Apply(cols ...ColumnBinding) SetValueStep {
return c
}

// Adds column bindings whose values will nbe populated if a CAS operation
// Adds column bindings whose values will be populated if a CAS operation
// is applied.
func (c *Context) IfExists(cols ...ColumnBinding) CompareAndSwap {
c.CASBindings = cols
return c
}

func (c *Context) Where(cond ...Condition) Query {
func (c *Context) If(cond ...Condition) Query {
c.IfConditions = cond
return c
}

func (c *Context) Where(cond ...Condition) IfQueryStep {
c.Conditions = cond
return c
}
Expand Down Expand Up @@ -579,60 +592,37 @@ func BuildStatement(c *Context) (stmt string, placeHolders []interface{}, err er
}
}

for _, cond := range c.Conditions {
v := cond.Binding.Value
switch reflect.TypeOf(v).Kind() {
case reflect.Slice:
s := reflect.ValueOf(v)
for i := 0; i < s.Len(); i++ {
placeHolders = append(placeHolders, s.Index(i).Interface())
}
case reflect.Array:
bindCondition := func(conditions []Condition) error {
for _, cond := range conditions {
v := cond.Binding.Value
switch reflect.TypeOf(v).Kind() {
case reflect.Slice:
s := reflect.ValueOf(v)
for i := 0; i < s.Len(); i++ {
placeHolders = append(placeHolders, s.Index(i).Interface())
}
case reflect.Array:

// Not really happy about having to special case UUIDs
// but this works for now
// Not really happy about having to special case UUIDs
// but this works for now

if val, ok := v.(gocql.UUID); ok {
placeHolders = append(placeHolders, val.Bytes())
} else {
return "", nil, bindingErrorf("Cannot bind component: %+v (type: %s)", v, reflect.TypeOf(v))
if val, ok := v.(gocql.UUID); ok {
placeHolders = append(placeHolders, val.Bytes())
} else {
return bindingErrorf("Cannot bind component: %+v (type: %s)", v, reflect.TypeOf(v))
}
default:
placeHolders = append(placeHolders, v)
}
default:
placeHolders = append(placeHolders, v)
}
return nil
}

c.Dispose()

return stmt, placeHolders, nil
}

// Deprecated
// NOTE: (pingginp) this is used by Exec and unlike Prepare, it didn't handle expand binding
func BuildStatementOld(c *Context) (stmt string, placeHolders []interface{}, err error) {
// TODO Does this function need to get exported?
stmt, err = c.RenderCQL()
if err != nil {
return stmt, nil, err
if err := bindCondition(c.Conditions); err != nil {
return "", nil, err
}

bindings := len(c.Bindings) // TODO check whether this is nil
conditions := 0

if c.Conditions != nil {
conditions = len(c.Conditions)
}

placeHolders = make([]interface{}, bindings+conditions)

for i, bind := range c.Bindings {
placeHolders[i] = bind.Value
}

if c.Conditions != nil {
for i, cond := range c.Conditions {
placeHolders[i+bindings] = cond.Binding.Value
}
if err := bindCondition(c.IfConditions); err != nil {
return "", nil, err
}

c.Dispose()
Expand Down Expand Up @@ -681,6 +671,7 @@ func (c *Context) Dispose() {
c.Table = nil
c.Bindings = nil
c.Conditions = nil
c.IfConditions = nil
c.CASBindings = nil
}

Expand Down
10 changes: 10 additions & 0 deletions cqlc/cqlc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,16 @@ func (s *CqlTestSuite) TestDeleteRow() {
assert.Equal(s.T(), cql, "DELETE FROM foo WHERE id = ?")
}

func (s *CqlTestSuite) TestDeleteRowIf() {
idCol := &MockAsciiColumn{name: "id"}
ageCol := &MockInt32Column{name: "age"}
c := NewContext()
c.Delete().From(s.table).Where(idCol.Eq("x")).If(ageCol.Eq(28))
cql, err := c.RenderCQL()
assert.NoError(s.T(), err)
assert.Equal(s.T(), cql, "DELETE FROM foo WHERE id = ? IF age = ?")
}

func (s *CqlTestSuite) TestDeleteColumn() {
idCol := &MockAsciiColumn{name: "id"}
barCol := &MockAsciiColumn{name: "bar"}
Expand Down
16 changes: 14 additions & 2 deletions cqlc/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,13 +184,25 @@ func renderDelete(ctx *Context, buf *bytes.Buffer) {
}

renderWhereClause(ctx, buf)

if len(ctx.IfConditions) > 0 {
renderIfClause(ctx, buf)
}
}

func renderWhereClause(ctx *Context, buf *bytes.Buffer) {
fmt.Fprint(buf, "WHERE ")
renderCondition(buf, ctx.Conditions)
}

func renderIfClause(ctx *Context, buf *bytes.Buffer) {
fmt.Fprint(buf, " IF ")
renderCondition(buf, ctx.IfConditions)
}

whereFragments := make([]string, len(ctx.Conditions))
for i, condition := range ctx.Conditions {
func renderCondition(buf *bytes.Buffer, conditions []Condition) {
whereFragments := make([]string, len(conditions))
for i, condition := range conditions {
col := condition.Binding.Column.ColumnName()

pred := condition.Predicate
Expand Down
5 changes: 4 additions & 1 deletion generator/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,10 @@ func columnType(c gocql.ColumnMetadata, table *gocql.TableMetadata) string {
replacement = ".LastPartitioned"
}
baseType = strings.Replace(baseType, ".", replacement, 1)
} else if c.Index.Name != "" {
//} else if c.Index.Name != "" {
} else {
// NOTE: this is changed to allow Eq on all columns to use DELETE ... WHERE ... IF ...
// see https://github.com/pingginp/cqlc/issues/13
replacement := ".Equality"
baseType = strings.Replace(baseType, ".", replacement, 1)
}
Expand Down
11 changes: 9 additions & 2 deletions generator/tmpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ const (
return cqlc.Condition{Binding: binding, Predicate: cqlc.InPredicate}
}
{{ end }}
{{ end }}
{{ if supportsClustering $col }}
{{ else if supportsClustering $col }}
func (b * {{$QualifiedColStructType}}Column ) ClusterWith() string {
return b.ColumnName()
Expand Down Expand Up @@ -131,6 +130,14 @@ const (
binding := cqlc.ColumnBinding{Column: column, Value: value}
return cqlc.Condition{Binding: binding, Predicate: cqlc.LePredicate}
}
{{ else }}
// Eq is used in DELETE IF statement to filter on non primary key columns,
// introduced in https://github.com/pingginp/cqlc/issues/13
func (b * {{$QualifiedColStructType}}Column ) Eq(value {{valueType $col}}) cqlc.Condition {
column := &{{$QualifiedColStructType}}Column{}
binding := cqlc.ColumnBinding{Column: column, Value: value}
return cqlc.Condition{Binding: binding, Predicate: cqlc.EqPredicate}
}
{{ end }}
{{ end }}
Expand Down

0 comments on commit 292abab

Please sign in to comment.