Skip to content

Commit

Permalink
Add condition expression support
Browse files Browse the repository at this point in the history
Reimplement older mechanisms on expressions

Update query builder tests
  • Loading branch information
SteelPangolin committed Dec 1, 2014
1 parent ef478e1 commit 00fd883
Show file tree
Hide file tree
Showing 6 changed files with 376 additions and 60 deletions.
4 changes: 4 additions & 0 deletions dynamodb/attribute.go
Expand Up @@ -157,6 +157,10 @@ func (a *Attribute) SetExists(exists bool) *Attribute {
return a
}

func (a Attribute) valueMsi() msi {
return msi{a.Type: map[bool]interface{}{true: a.SetValues, false: a.Value}[a.SetType()]}
}

func (k *PrimaryKey) HasRange() bool {
return k.RangeAttribute != nil
}
Expand Down
2 changes: 1 addition & 1 deletion dynamodb/dynamodb_test.go
Expand Up @@ -138,7 +138,7 @@ func setUpAuth(c *check.C) {
dynamodb_auth = aws.Auth{AccessKey: "DUMMY_KEY", SecretKey: "DUMMY_SECRET"}
} else {
c.Log("Using REAL AMAZON SERVER")
dynamodb_region = aws.USEast
dynamodb_region = aws.USWest2
auth, err := aws.EnvAuth()
if err != nil {
c.Fatal(err)
Expand Down
59 changes: 46 additions & 13 deletions dynamodb/item.go
Expand Up @@ -166,14 +166,18 @@ func (t *Table) getItem(key *Key, consistentRead bool) (map[string]*Attribute, e
}

func (t *Table) PutItem(hashKey string, rangeKey string, attributes []Attribute) (bool, error) {
return t.putItem(hashKey, rangeKey, attributes, nil)
return t.putItem(hashKey, rangeKey, attributes, nil, nil)
}

func (t *Table) ConditionalPutItem(hashKey, rangeKey string, attributes, expected []Attribute) (bool, error) {
return t.putItem(hashKey, rangeKey, attributes, expected)
return t.putItem(hashKey, rangeKey, attributes, expected, nil)
}

func (t *Table) putItem(hashKey, rangeKey string, attributes, expected []Attribute) (bool, error) {
func (t *Table) ConditionExpressionPutItem(hashKey, rangeKey string, attributes []Attribute, condition *Expression) (bool, error) {
return t.putItem(hashKey, rangeKey, attributes, nil, condition)
}

func (t *Table) putItem(hashKey, rangeKey string, attributes, expected []Attribute, condition *Expression) (bool, error) {
if len(attributes) == 0 {
return false, errors.New("At least one attribute is required.")
}
Expand All @@ -184,10 +188,15 @@ func (t *Table) putItem(hashKey, rangeKey string, attributes, expected []Attribu
attributes = append(attributes, keys...)

q.AddItem(attributes)

if expected != nil {
q.AddExpected(expected)
}

if condition != nil {
q.AddConditionExpression(condition)
}

var jsonResponse []byte
var err error
// based on:
Expand Down Expand Up @@ -230,14 +239,18 @@ func (t *Table) putItem(hashKey, rangeKey string, attributes, expected []Attribu
return true, nil
}

func (t *Table) deleteItem(key *Key, expected []Attribute) (bool, error) {
func (t *Table) deleteItem(key *Key, expected []Attribute, condition *Expression) (bool, error) {
q := NewQuery(t)
q.AddKey(t, key)

if expected != nil {
q.AddExpected(expected)
}

if condition != nil {
q.AddConditionExpression(condition)
}

jsonResponse, err := t.Server.queryServer(target("DeleteItem"), q)

if err != nil {
Expand All @@ -253,38 +266,54 @@ func (t *Table) deleteItem(key *Key, expected []Attribute) (bool, error) {
}

func (t *Table) DeleteItem(key *Key) (bool, error) {
return t.deleteItem(key, nil)
return t.deleteItem(key, nil, nil)
}

func (t *Table) ConditionalDeleteItem(key *Key, expected []Attribute) (bool, error) {
return t.deleteItem(key, expected)
return t.deleteItem(key, expected, nil)
}

func (t *Table) ConditionExpressionDeleteItem(key *Key, condition *Expression) (bool, error) {
return t.deleteItem(key, nil, condition)
}

func (t *Table) AddAttributes(key *Key, attributes []Attribute) (bool, error) {
return t.modifyAttributes(key, attributes, nil, "ADD")
return t.modifyAttributes(key, attributes, nil, nil, "ADD")
}

func (t *Table) UpdateAttributes(key *Key, attributes []Attribute) (bool, error) {
return t.modifyAttributes(key, attributes, nil, "PUT")
return t.modifyAttributes(key, attributes, nil, nil, "PUT")
}

func (t *Table) DeleteAttributes(key *Key, attributes []Attribute) (bool, error) {
return t.modifyAttributes(key, attributes, nil, "DELETE")
return t.modifyAttributes(key, attributes, nil, nil, "DELETE")
}

func (t *Table) ConditionalAddAttributes(key *Key, attributes, expected []Attribute) (bool, error) {
return t.modifyAttributes(key, attributes, expected, "ADD")
return t.modifyAttributes(key, attributes, expected, nil, "ADD")
}

func (t *Table) ConditionalUpdateAttributes(key *Key, attributes, expected []Attribute) (bool, error) {
return t.modifyAttributes(key, attributes, expected, "PUT")
return t.modifyAttributes(key, attributes, expected, nil, "PUT")
}

func (t *Table) ConditionalDeleteAttributes(key *Key, attributes, expected []Attribute) (bool, error) {
return t.modifyAttributes(key, attributes, expected, "DELETE")
return t.modifyAttributes(key, attributes, expected, nil, "DELETE")
}

func (t *Table) modifyAttributes(key *Key, attributes, expected []Attribute, action string) (bool, error) {
func (t *Table) ConditionExpressionAddAttributes(key *Key, attributes []Attribute, condition *Expression) (bool, error) {
return t.modifyAttributes(key, attributes, nil, condition, "ADD")
}

func (t *Table) ConditionExpressionUpdateAttributes(key *Key, attributes []Attribute, condition *Expression) (bool, error) {
return t.modifyAttributes(key, attributes, nil, condition, "PUT")
}

func (t *Table) ConditionExpressionDeleteAttributes(key *Key, attributes []Attribute, condition *Expression) (bool, error) {
return t.modifyAttributes(key, attributes, nil, condition, "DELETE")
}

func (t *Table) modifyAttributes(key *Key, attributes, expected []Attribute, condition *Expression, action string) (bool, error) {

if len(attributes) == 0 {
return false, errors.New("At least one attribute is required.")
Expand All @@ -298,6 +327,10 @@ func (t *Table) modifyAttributes(key *Key, attributes, expected []Attribute, act
q.AddExpected(expected)
}

if condition != nil {
q.AddConditionExpression(condition)
}

jsonResponse, err := t.Server.queryServer(target("UpdateItem"), q)

if err != nil {
Expand Down
158 changes: 157 additions & 1 deletion dynamodb/item_test.go
Expand Up @@ -153,7 +153,7 @@ func (s *ItemSuite) TestConditionalPutUpdateDeleteItem(c *check.C) {
*NewNumericAttribute("AddNewAttr1", "10"),
*NewNumericAttribute("AddNewAttr2", "20"),
}
if ok, err := s.table.ConditionalAddAttributes(pk, addNewAttrs, nil); !ok {
if ok, err := s.table.ConditionalAddAttributes(pk, addNewAttrs, expected); !ok {
c.Errorf("Expect condition met. %s", err)
}

Expand Down Expand Up @@ -242,6 +242,162 @@ func (s *ItemSuite) TestConditionalPutUpdateDeleteItem(c *check.C) {
}
}

func (s *ItemSuite) TestConditionExpressionPutUpdateDeleteItem(c *check.C) {
if s.WithRange {
// No rangekey test required
return
}

attrs := []Attribute{
*NewStringAttribute("Attr1", "Attr1Val"),
}
pk := &Key{HashKey: "NewHashKeyVal"}

// Put
if ok, err := s.table.PutItem("NewHashKeyVal", "", attrs); !ok {
c.Fatal(err)
}

{
// Put with condition failed
condition := &Expression{
Text: "Attr1 = :val AND attribute_not_exists (AttrNotExists)",
AttributeValues: []Attribute{
*NewStringAttribute(":val", "expectedAttr1Val"),
},
}
if ok, err := s.table.ConditionExpressionPutItem("NewHashKeyVal", "", attrs, condition); ok {
c.Errorf("Expect condition does not meet.")
} else {
c.Check(err.Error(), check.Matches, "ConditionalCheckFailedException.*")
}

// Update attributes with condition failed
if ok, err := s.table.ConditionExpressionUpdateAttributes(pk, attrs, condition); ok {
c.Errorf("Expect condition does not meet.")
} else {
c.Check(err.Error(), check.Matches, "ConditionalCheckFailedException.*")
}

// Delete attributes with condition failed
if ok, err := s.table.ConditionExpressionDeleteAttributes(pk, attrs, condition); ok {
c.Errorf("Expect condition does not meet.")
} else {
c.Check(err.Error(), check.Matches, "ConditionalCheckFailedException.*")
}
}

{
condition := &Expression{
Text: "Attr1 = :val",
AttributeValues: []Attribute{
*NewStringAttribute(":val", "Attr1Val"),
},
}

// Add attributes with condition met
addNewAttrs := []Attribute{
*NewNumericAttribute("AddNewAttr1", "10"),
*NewNumericAttribute("AddNewAttr2", "20"),
}
if ok, err := s.table.ConditionExpressionAddAttributes(pk, addNewAttrs, condition); !ok {
c.Errorf("Expect condition met. %s", err)
}

// Update attributes with condition met
updateAttrs := []Attribute{
*NewNumericAttribute("AddNewAttr1", "100"),
}
if ok, err := s.table.ConditionExpressionUpdateAttributes(pk, updateAttrs, condition); !ok {
c.Errorf("Expect condition met. %s", err)
}

// Delete attributes with condition met
deleteAttrs := []Attribute{
*NewNumericAttribute("AddNewAttr2", ""),
}
if ok, err := s.table.ConditionExpressionDeleteAttributes(pk, deleteAttrs, condition); !ok {
c.Errorf("Expect condition met. %s", err)
}

// Get to verify operations that condition are met
item, err := s.table.GetItem(pk)
if err != nil {
c.Fatal(err)
}

if val, ok := item["AddNewAttr1"]; ok {
c.Check(val, check.DeepEquals, NewNumericAttribute("AddNewAttr1", "100"))
} else {
c.Error("Expect AddNewAttr1 attribute to be added and updated")
}

if _, ok := item["AddNewAttr2"]; ok {
c.Error("Expect AddNewAttr2 attribute to be deleted")
}
}

{
// Put with condition met
condition := &Expression{
Text: "Attr1 = :val",
AttributeValues: []Attribute{
*NewStringAttribute(":val", "Attr1Val"),
},
}
newattrs := []Attribute{
*NewStringAttribute("Attr1", "Attr2Val"),
}
if ok, err := s.table.ConditionExpressionPutItem("NewHashKeyVal", "", newattrs, condition); !ok {
c.Errorf("Expect condition met. %s", err)
}

// Get to verify Put operation that condition are met
item, err := s.table.GetItem(pk)
if err != nil {
c.Fatal(err)
}

if val, ok := item["Attr1"]; ok {
c.Check(val, check.DeepEquals, NewStringAttribute("Attr1", "Attr2Val"))
} else {
c.Error("Expect Attr1 attribute to be updated")
}
}

{
// Delete with condition failed
condition := &Expression{
Text: "Attr1 = :val",
AttributeValues: []Attribute{
*NewStringAttribute(":val", "expectedAttr1Val"),
},
}
if ok, err := s.table.ConditionExpressionDeleteItem(pk, condition); ok {
c.Errorf("Expect condition does not meet.")
} else {
c.Check(err.Error(), check.Matches, "ConditionalCheckFailedException.*")
}
}

{
// Delete with condition met
condition := &Expression{
Text: "Attr1 = :val",
AttributeValues: []Attribute{
*NewStringAttribute(":val", "Attr2Val"),
},
}
if ok, _ := s.table.ConditionExpressionDeleteItem(pk, condition); !ok {
c.Errorf("Expect condition met.")
}

// Get to verify Delete operation
_, err := s.table.GetItem(pk)
c.Check(err.Error(), check.Matches, "Item not found")
}
}

func (s *ItemSuite) TestPutGetDeleteItem(c *check.C) {
attrs := []Attribute{
*NewStringAttribute("Attr1", "Attr1Val"),
Expand Down

0 comments on commit 00fd883

Please sign in to comment.