diff options
author | Christopher Speller <crspeller@gmail.com> | 2016-05-12 23:56:07 -0400 |
---|---|---|
committer | Christopher Speller <crspeller@gmail.com> | 2016-05-12 23:56:07 -0400 |
commit | 38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8 (patch) | |
tree | a4fde09672192b97d453ad605b030bd5a10c5a45 /vendor/github.com/goamz/goamz/dynamodb | |
parent | 84d2482ddbff9564c9ad75b2d30af66e3ddfd44d (diff) | |
download | chat-38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8.tar.gz chat-38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8.tar.bz2 chat-38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8.zip |
Moving to glide
Diffstat (limited to 'vendor/github.com/goamz/goamz/dynamodb')
20 files changed, 4092 insertions, 0 deletions
diff --git a/vendor/github.com/goamz/goamz/dynamodb/.gitignore b/vendor/github.com/goamz/goamz/dynamodb/.gitignore new file mode 100644 index 000000000..2385ddf57 --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/.gitignore @@ -0,0 +1 @@ +dynamodb_local* diff --git a/vendor/github.com/goamz/goamz/dynamodb/Makefile b/vendor/github.com/goamz/goamz/dynamodb/Makefile new file mode 100644 index 000000000..4c02cd4b7 --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/Makefile @@ -0,0 +1,13 @@ +DYNAMODB_LOCAL_VERSION = 2013-12-12 + +launch: DynamoDBLocal.jar + cd dynamodb_local_$(DYNAMODB_LOCAL_VERSION) && java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar + +DynamoDBLocal.jar: dynamodb_local_$(DYNAMODB_LOCAL_VERSION).tar.gz + [ -d dynamodb_local_$(DYNAMODB_LOCAL_VERSION) ] || tar -zxf dynamodb_local_$(DYNAMODB_LOCAL_VERSION).tar.gz + +dynamodb_local_$(DYNAMODB_LOCAL_VERSION).tar.gz: + curl -O https://s3-us-west-2.amazonaws.com/dynamodb-local/dynamodb_local_$(DYNAMODB_LOCAL_VERSION).tar.gz + +clean: + rm -rf dynamodb_local_$(DYNAMODB_LOCAL_VERSION)* diff --git a/vendor/github.com/goamz/goamz/dynamodb/README.md b/vendor/github.com/goamz/goamz/dynamodb/README.md new file mode 100644 index 000000000..5896d67b6 --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/README.md @@ -0,0 +1,27 @@ +# Running integration tests + +## against DynamoDB local + +To download and launch DynamoDB local: + +```sh +$ make +``` + +To test: + +```sh +$ go test -v -amazon +``` + +## against real DynamoDB server on us-east + +_WARNING_: Some dangerous operations such as `DeleteTable` will be performed during the tests. Please be careful. + +To test: + +```sh +$ go test -v -amazon -local=false +``` + +_Note_: Running tests against real DynamoDB will take several minutes. diff --git a/vendor/github.com/goamz/goamz/dynamodb/attribute.go b/vendor/github.com/goamz/goamz/dynamodb/attribute.go new file mode 100755 index 000000000..38389ada2 --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/attribute.go @@ -0,0 +1,185 @@ +package dynamodb + +import ( + "strconv" +) + +const ( + TYPE_STRING = "S" + TYPE_NUMBER = "N" + TYPE_BINARY = "B" + + TYPE_STRING_SET = "SS" + TYPE_NUMBER_SET = "NS" + TYPE_BINARY_SET = "BS" + + COMPARISON_EQUAL = "EQ" + COMPARISON_NOT_EQUAL = "NE" + COMPARISON_LESS_THAN_OR_EQUAL = "LE" + COMPARISON_LESS_THAN = "LT" + COMPARISON_GREATER_THAN_OR_EQUAL = "GE" + COMPARISON_GREATER_THAN = "GT" + COMPARISON_ATTRIBUTE_EXISTS = "NOT_NULL" + COMPARISON_ATTRIBUTE_DOES_NOT_EXIST = "NULL" + COMPARISON_CONTAINS = "CONTAINS" + COMPARISON_DOES_NOT_CONTAIN = "NOT_CONTAINS" + COMPARISON_BEGINS_WITH = "BEGINS_WITH" + COMPARISON_IN = "IN" + COMPARISON_BETWEEN = "BETWEEN" +) + +type Key struct { + HashKey string + RangeKey string +} + +type PrimaryKey struct { + KeyAttribute *Attribute + RangeAttribute *Attribute +} + +type Attribute struct { + Type string + Name string + Value string + SetValues []string + Exists string // exists on dynamodb? Values: "true", "false", or "" +} + +type AttributeComparison struct { + AttributeName string + ComparisonOperator string + AttributeValueList []Attribute // contains attributes with only types and names (value ignored) +} + +func NewEqualInt64AttributeComparison(attributeName string, equalToValue int64) *AttributeComparison { + numeric := NewNumericAttribute(attributeName, strconv.FormatInt(equalToValue, 10)) + return &AttributeComparison{attributeName, + COMPARISON_EQUAL, + []Attribute{*numeric}, + } +} + +func NewEqualStringAttributeComparison(attributeName string, equalToValue string) *AttributeComparison { + str := NewStringAttribute(attributeName, equalToValue) + return &AttributeComparison{attributeName, + COMPARISON_EQUAL, + []Attribute{*str}, + } +} + +func NewStringAttributeComparison(attributeName string, comparisonOperator string, value string) *AttributeComparison { + valueToCompare := NewStringAttribute(attributeName, value) + return &AttributeComparison{attributeName, + comparisonOperator, + []Attribute{*valueToCompare}, + } +} + +func NewNumericAttributeComparison(attributeName string, comparisonOperator string, value int64) *AttributeComparison { + valueToCompare := NewNumericAttribute(attributeName, strconv.FormatInt(value, 10)) + return &AttributeComparison{attributeName, + comparisonOperator, + []Attribute{*valueToCompare}, + } +} + +func NewBinaryAttributeComparison(attributeName string, comparisonOperator string, value bool) *AttributeComparison { + valueToCompare := NewBinaryAttribute(attributeName, strconv.FormatBool(value)) + return &AttributeComparison{attributeName, + comparisonOperator, + []Attribute{*valueToCompare}, + } +} + +func NewStringAttribute(name string, value string) *Attribute { + return &Attribute{ + Type: TYPE_STRING, + Name: name, + Value: value, + } +} + +func NewNumericAttribute(name string, value string) *Attribute { + return &Attribute{ + Type: TYPE_NUMBER, + Name: name, + Value: value, + } +} + +func NewBinaryAttribute(name string, value string) *Attribute { + return &Attribute{ + Type: TYPE_BINARY, + Name: name, + Value: value, + } +} + +func NewStringSetAttribute(name string, values []string) *Attribute { + return &Attribute{ + Type: TYPE_STRING_SET, + Name: name, + SetValues: values, + } +} + +func NewNumericSetAttribute(name string, values []string) *Attribute { + return &Attribute{ + Type: TYPE_NUMBER_SET, + Name: name, + SetValues: values, + } +} + +func NewBinarySetAttribute(name string, values []string) *Attribute { + return &Attribute{ + Type: TYPE_BINARY_SET, + Name: name, + SetValues: values, + } +} + +func (a *Attribute) SetType() bool { + switch a.Type { + case TYPE_BINARY_SET, TYPE_NUMBER_SET, TYPE_STRING_SET: + return true + } + return false +} + +func (a *Attribute) SetExists(exists bool) *Attribute { + if exists { + a.Exists = "true" + } else { + a.Exists = "false" + } + return a +} + +func (k *PrimaryKey) HasRange() bool { + return k.RangeAttribute != nil +} + +// Useful when you may have many goroutines using a primary key, so they don't fuxor up your values. +func (k *PrimaryKey) Clone(h string, r string) []Attribute { + pk := &Attribute{ + Type: k.KeyAttribute.Type, + Name: k.KeyAttribute.Name, + Value: h, + } + + result := []Attribute{*pk} + + if k.HasRange() { + rk := &Attribute{ + Type: k.RangeAttribute.Type, + Name: k.RangeAttribute.Name, + Value: r, + } + + result = append(result, *rk) + } + + return result +} diff --git a/vendor/github.com/goamz/goamz/dynamodb/const.go b/vendor/github.com/goamz/goamz/dynamodb/const.go new file mode 100644 index 000000000..b070d44cb --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/const.go @@ -0,0 +1,11 @@ +package dynamodb + +type ReturnValues string + +const ( + NONE ReturnValues = "NONE" + ALL_OLD ReturnValues = "ALL_HOLD" + UPDATED_OLD ReturnValues = "UPDATED_OLD" + ALL_NEW ReturnValues = "ALL_NEW" + UPDATED_NEW ReturnValues = "UPDATED_NEW" +) diff --git a/vendor/github.com/goamz/goamz/dynamodb/dynamodb.go b/vendor/github.com/goamz/goamz/dynamodb/dynamodb.go new file mode 100755 index 000000000..7881e8dc1 --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/dynamodb.go @@ -0,0 +1,142 @@ +package dynamodb + +import simplejson "github.com/bitly/go-simplejson" +import ( + "errors" + "github.com/goamz/goamz/aws" + "io/ioutil" + "log" + "net/http" + "strings" + "time" +) + +type Server struct { + Auth aws.Auth + Region aws.Region +} + +/* +type Query struct { + Query string +} +*/ + +/* +func NewQuery(queryParts []string) *Query { + return &Query{ + "{" + strings.Join(queryParts, ",") + "}", + } +} +*/ + +const ( + // DynamoDBAPIPrefix is the versioned prefix for DynamoDB API commands. + DynamoDBAPIPrefix = "DynamoDB_20120810." + // DynamoDBStreamsAPIPrefix is the versioned prefix for DynamoDB Streams API commands. + DynamoDBStreamsAPIPrefix = "DynamoDBStreams_20120810." +) + +// Specific error constants +var ErrNotFound = errors.New("Item not found") + +// Error represents an error in an operation with Dynamodb (following goamz/s3) +type Error struct { + StatusCode int // HTTP status code (200, 403, ...) + Status string + Code string // Dynamodb error code ("MalformedQueryString", ...) + Message string // The human-oriented error message +} + +func (e *Error) Error() string { + return e.Code + ": " + e.Message +} + +func buildError(r *http.Response, jsonBody []byte) error { + + ddbError := Error{ + StatusCode: r.StatusCode, + Status: r.Status, + } + // TODO return error if Unmarshal fails? + + json, err := simplejson.NewJson(jsonBody) + if err != nil { + log.Printf("Failed to parse body as JSON") + return err + } + ddbError.Message = json.Get("message").MustString() + + // Of the form: com.amazon.coral.validate#ValidationException + // We only want the last part + codeStr := json.Get("__type").MustString() + hashIndex := strings.Index(codeStr, "#") + if hashIndex > 0 { + codeStr = codeStr[hashIndex+1:] + } + ddbError.Code = codeStr + + return &ddbError +} + +func (s *Server) queryServer(target string, query *Query) ([]byte, error) { + data := strings.NewReader(query.String()) + var endpoint string + if isStreamsTarget(target) { + endpoint = s.Region.DynamoDBStreamsEndpoint + } else { + endpoint = s.Region.DynamoDBEndpoint + } + hreq, err := http.NewRequest("POST", endpoint+"/", data) + if err != nil { + return nil, err + } + + hreq.Header.Set("Content-Type", "application/x-amz-json-1.0") + hreq.Header.Set("X-Amz-Date", time.Now().UTC().Format(aws.ISO8601BasicFormat)) + hreq.Header.Set("X-Amz-Target", target) + + token := s.Auth.Token() + if token != "" { + hreq.Header.Set("X-Amz-Security-Token", token) + } + + signer := aws.NewV4Signer(s.Auth, "dynamodb", s.Region) + signer.Sign(hreq) + + resp, err := http.DefaultClient.Do(hreq) + + if err != nil { + log.Printf("Error calling Amazon") + return nil, err + } + + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Printf("Could not read response body") + return nil, err + } + + // http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ErrorHandling.html + // "A response code of 200 indicates the operation was successful." + if resp.StatusCode != 200 { + ddbErr := buildError(resp, body) + return nil, ddbErr + } + + return body, nil +} + +func target(name string) string { + return DynamoDBAPIPrefix + name +} + +func streamsTarget(name string) string { + return DynamoDBStreamsAPIPrefix + name +} + +func isStreamsTarget(target string) bool { + return strings.HasPrefix(target, DynamoDBStreamsAPIPrefix) +} diff --git a/vendor/github.com/goamz/goamz/dynamodb/dynamodb_test.go b/vendor/github.com/goamz/goamz/dynamodb/dynamodb_test.go new file mode 100755 index 000000000..63dd03da3 --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/dynamodb_test.go @@ -0,0 +1,166 @@ +package dynamodb_test + +import ( + "flag" + "testing" + "time" + + "github.com/goamz/goamz/aws" + "github.com/goamz/goamz/dynamodb" + . "gopkg.in/check.v1" +) + +const TIMEOUT = 3 * time.Minute + +var amazon = flag.Bool("amazon", false, "Enable tests against dynamodb") +var local = flag.Bool("local", true, "Use DynamoDB local on 8080 instead of real server on us-east.") + +var dynamodb_region aws.Region +var dynamodb_auth aws.Auth + +type DynamoDBTest struct { + server *dynamodb.Server + aws.Region // Exports Region + TableDescriptionT dynamodb.TableDescriptionT + table *dynamodb.Table +} + +// Delete all items in the table +func (s *DynamoDBTest) TearDownTest(c *C) { + pk, err := s.TableDescriptionT.BuildPrimaryKey() + if err != nil { + c.Fatal(err) + } + + attrs, err := s.table.Scan(nil) + if err != nil { + c.Fatal(err) + } + for _, a := range attrs { + key := &dynamodb.Key{ + HashKey: a[pk.KeyAttribute.Name].Value, + } + if pk.HasRange() { + key.RangeKey = a[pk.RangeAttribute.Name].Value + } + if ok, err := s.table.DeleteItem(key); !ok { + c.Fatal(err) + } + } +} + +func (s *DynamoDBTest) TearDownSuite(c *C) { + // return immediately in the case of calling c.Skip() in SetUpSuite() + if s.server == nil { + return + } + + // check whether the table exists + if tables, err := s.server.ListTables(); err != nil { + c.Fatal(err) + } else { + if !findTableByName(tables, s.TableDescriptionT.TableName) { + return + } + } + + // Delete the table and wait + if _, err := s.server.DeleteTable(s.TableDescriptionT); err != nil { + c.Fatal(err) + } + + done := make(chan bool) + timeout := time.After(TIMEOUT) + go func() { + for { + select { + case <-done: + return + default: + tables, err := s.server.ListTables() + if err != nil { + c.Fatal(err) + } + if findTableByName(tables, s.TableDescriptionT.TableName) { + time.Sleep(5 * time.Second) + } else { + done <- true + return + } + } + } + }() + select { + case <-done: + break + case <-timeout: + c.Error("Expect the table to be deleted but timed out") + close(done) + } +} + +func (s *DynamoDBTest) WaitUntilStatus(c *C, status string) { + // We should wait until the table is in specified status because a real DynamoDB has some delay for ready + done := make(chan bool) + timeout := time.After(TIMEOUT) + go func() { + for { + select { + case <-done: + return + default: + desc, err := s.table.DescribeTable() + if err != nil { + c.Fatal(err) + } + if desc.TableStatus == status { + done <- true + return + } + time.Sleep(5 * time.Second) + } + } + }() + select { + case <-done: + break + case <-timeout: + c.Errorf("Expect a status to be %s, but timed out", status) + close(done) + } +} + +func setUpAuth(c *C) { + if !*amazon { + c.Skip("Test against amazon not enabled.") + } + if *local { + c.Log("Using local server") + dynamodb_region = aws.Region{ + DynamoDBEndpoint: "http://127.0.0.1:8000", + DynamoDBStreamsEndpoint: "http://127.0.0.1:8000", + } + dynamodb_auth = aws.Auth{AccessKey: "DUMMY_KEY", SecretKey: "DUMMY_SECRET"} + } else { + c.Log("Using REAL AMAZON SERVER") + dynamodb_region = aws.USEast + auth, err := aws.EnvAuth() + if err != nil { + c.Fatal(err) + } + dynamodb_auth = auth + } +} + +func findTableByName(tables []string, name string) bool { + for _, t := range tables { + if t == name { + return true + } + } + return false +} + +func Test(t *testing.T) { + TestingT(t) +} diff --git a/vendor/github.com/goamz/goamz/dynamodb/item.go b/vendor/github.com/goamz/goamz/dynamodb/item.go new file mode 100755 index 000000000..a3814d9ad --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/item.go @@ -0,0 +1,351 @@ +package dynamodb + +import simplejson "github.com/bitly/go-simplejson" +import ( + "errors" + "fmt" + "log" +) + +type BatchGetItem struct { + Server *Server + Keys map[*Table][]Key +} + +type BatchWriteItem struct { + Server *Server + ItemActions map[*Table]map[string][][]Attribute +} + +func (t *Table) BatchGetItems(keys []Key) *BatchGetItem { + batchGetItem := &BatchGetItem{t.Server, make(map[*Table][]Key)} + + batchGetItem.Keys[t] = keys + return batchGetItem +} + +func (t *Table) BatchWriteItems(itemActions map[string][][]Attribute) *BatchWriteItem { + batchWriteItem := &BatchWriteItem{t.Server, make(map[*Table]map[string][][]Attribute)} + + batchWriteItem.ItemActions[t] = itemActions + return batchWriteItem +} + +func (batchGetItem *BatchGetItem) AddTable(t *Table, keys *[]Key) *BatchGetItem { + batchGetItem.Keys[t] = *keys + return batchGetItem +} + +func (batchWriteItem *BatchWriteItem) AddTable(t *Table, itemActions *map[string][][]Attribute) *BatchWriteItem { + batchWriteItem.ItemActions[t] = *itemActions + return batchWriteItem +} + +func (batchGetItem *BatchGetItem) Execute() (map[string][]map[string]*Attribute, error) { + q := NewEmptyQuery() + q.AddGetRequestItems(batchGetItem.Keys) + + jsonResponse, err := batchGetItem.Server.queryServer("DynamoDB_20120810.BatchGetItem", q) + if err != nil { + return nil, err + } + + json, err := simplejson.NewJson(jsonResponse) + + if err != nil { + return nil, err + } + + results := make(map[string][]map[string]*Attribute) + + tables, err := json.Get("Responses").Map() + if err != nil { + message := fmt.Sprintf("Unexpected response %s", jsonResponse) + return nil, errors.New(message) + } + + for table, entries := range tables { + var tableResult []map[string]*Attribute + + jsonEntriesArray, ok := entries.([]interface{}) + if !ok { + message := fmt.Sprintf("Unexpected response %s", jsonResponse) + return nil, errors.New(message) + } + + for _, entry := range jsonEntriesArray { + item, ok := entry.(map[string]interface{}) + if !ok { + message := fmt.Sprintf("Unexpected response %s", jsonResponse) + return nil, errors.New(message) + } + + unmarshalledItem := parseAttributes(item) + tableResult = append(tableResult, unmarshalledItem) + } + + results[table] = tableResult + } + + return results, nil +} + +func (batchWriteItem *BatchWriteItem) Execute() (map[string]interface{}, error) { + q := NewEmptyQuery() + q.AddWriteRequestItems(batchWriteItem.ItemActions) + + jsonResponse, err := batchWriteItem.Server.queryServer("DynamoDB_20120810.BatchWriteItem", q) + + if err != nil { + return nil, err + } + + json, err := simplejson.NewJson(jsonResponse) + + if err != nil { + return nil, err + } + + unprocessed, err := json.Get("UnprocessedItems").Map() + if err != nil { + message := fmt.Sprintf("Unexpected response %s", jsonResponse) + return nil, errors.New(message) + } + + if len(unprocessed) == 0 { + return nil, nil + } else { + return unprocessed, errors.New("One or more unprocessed items.") + } + +} + +func (t *Table) GetItem(key *Key) (map[string]*Attribute, error) { + return t.getItem(key, false) +} + +func (t *Table) GetItemConsistent(key *Key, consistentRead bool) (map[string]*Attribute, error) { + return t.getItem(key, consistentRead) +} + +func (t *Table) getItem(key *Key, consistentRead bool) (map[string]*Attribute, error) { + q := NewQuery(t) + q.AddKey(t, key) + + if consistentRead { + q.ConsistentRead(consistentRead) + } + + jsonResponse, err := t.Server.queryServer(target("GetItem"), q) + if err != nil { + return nil, err + } + + json, err := simplejson.NewJson(jsonResponse) + if err != nil { + return nil, err + } + + itemJson, ok := json.CheckGet("Item") + if !ok { + // We got an empty from amz. The item doesn't exist. + return nil, ErrNotFound + } + + item, err := itemJson.Map() + if err != nil { + message := fmt.Sprintf("Unexpected response %s", jsonResponse) + return nil, errors.New(message) + } + + return parseAttributes(item), nil + +} + +func (t *Table) PutItem(hashKey string, rangeKey string, attributes []Attribute) (bool, error) { + return t.putItem(hashKey, rangeKey, attributes, nil) +} + +func (t *Table) ConditionalPutItem(hashKey, rangeKey string, attributes, expected []Attribute) (bool, error) { + return t.putItem(hashKey, rangeKey, attributes, expected) +} + +func (t *Table) putItem(hashKey, rangeKey string, attributes, expected []Attribute) (bool, error) { + if len(attributes) == 0 { + return false, errors.New("At least one attribute is required.") + } + + q := NewQuery(t) + + keys := t.Key.Clone(hashKey, rangeKey) + attributes = append(attributes, keys...) + + q.AddItem(attributes) + if expected != nil { + q.AddExpected(expected) + } + + jsonResponse, err := t.Server.queryServer(target("PutItem"), q) + + if err != nil { + return false, err + } + + _, err = simplejson.NewJson(jsonResponse) + if err != nil { + return false, err + } + + return true, nil +} + +func (t *Table) deleteItem(key *Key, expected []Attribute) (bool, error) { + q := NewQuery(t) + q.AddKey(t, key) + + if expected != nil { + q.AddExpected(expected) + } + + jsonResponse, err := t.Server.queryServer(target("DeleteItem"), q) + + if err != nil { + return false, err + } + + _, err = simplejson.NewJson(jsonResponse) + if err != nil { + return false, err + } + + return true, nil +} + +func (t *Table) DeleteItem(key *Key) (bool, error) { + return t.deleteItem(key, nil) +} + +func (t *Table) ConditionalDeleteItem(key *Key, expected []Attribute) (bool, error) { + return t.deleteItem(key, expected) +} + +func (t *Table) AddAttributes(key *Key, attributes []Attribute) (bool, error) { + return t.modifyAttributes(key, attributes, nil, "ADD") +} + +func (t *Table) UpdateAttributes(key *Key, attributes []Attribute) (bool, error) { + return t.modifyAttributes(key, attributes, nil, "PUT") +} + +func (t *Table) DeleteAttributes(key *Key, attributes []Attribute) (bool, error) { + return t.modifyAttributes(key, attributes, nil, "DELETE") +} + +func (t *Table) ConditionalAddAttributes(key *Key, attributes, expected []Attribute) (bool, error) { + return t.modifyAttributes(key, attributes, expected, "ADD") +} + +func (t *Table) ConditionalUpdateAttributes(key *Key, attributes, expected []Attribute) (bool, error) { + return t.modifyAttributes(key, attributes, expected, "PUT") +} + +func (t *Table) ConditionalDeleteAttributes(key *Key, attributes, expected []Attribute) (bool, error) { + return t.modifyAttributes(key, attributes, expected, "DELETE") +} + +func (t *Table) modifyAttributes(key *Key, attributes, expected []Attribute, action string) (bool, error) { + + if len(attributes) == 0 { + return false, errors.New("At least one attribute is required.") + } + + q := NewQuery(t) + q.AddKey(t, key) + q.AddUpdates(attributes, action) + + if expected != nil { + q.AddExpected(expected) + } + + jsonResponse, err := t.Server.queryServer(target("UpdateItem"), q) + + if err != nil { + return false, err + } + + _, err = simplejson.NewJson(jsonResponse) + if err != nil { + return false, err + } + + return true, nil +} + +func parseAttributes(s map[string]interface{}) map[string]*Attribute { + results := map[string]*Attribute{} + + for key, value := range s { + if v, ok := value.(map[string]interface{}); ok { + if val, ok := v[TYPE_STRING].(string); ok { + results[key] = &Attribute{ + Type: TYPE_STRING, + Name: key, + Value: val, + } + } else if val, ok := v[TYPE_NUMBER].(string); ok { + results[key] = &Attribute{ + Type: TYPE_NUMBER, + Name: key, + Value: val, + } + } else if val, ok := v[TYPE_BINARY].(string); ok { + results[key] = &Attribute{ + Type: TYPE_BINARY, + Name: key, + Value: val, + } + } else if vals, ok := v[TYPE_STRING_SET].([]interface{}); ok { + arry := make([]string, len(vals)) + for i, ivalue := range vals { + if val, ok := ivalue.(string); ok { + arry[i] = val + } + } + results[key] = &Attribute{ + Type: TYPE_STRING_SET, + Name: key, + SetValues: arry, + } + } else if vals, ok := v[TYPE_NUMBER_SET].([]interface{}); ok { + arry := make([]string, len(vals)) + for i, ivalue := range vals { + if val, ok := ivalue.(string); ok { + arry[i] = val + } + } + results[key] = &Attribute{ + Type: TYPE_NUMBER_SET, + Name: key, + SetValues: arry, + } + } else if vals, ok := v[TYPE_BINARY_SET].([]interface{}); ok { + arry := make([]string, len(vals)) + for i, ivalue := range vals { + if val, ok := ivalue.(string); ok { + arry[i] = val + } + } + results[key] = &Attribute{ + Type: TYPE_BINARY_SET, + Name: key, + SetValues: arry, + } + } + } else { + log.Printf("type assertion to map[string] interface{} failed for : %s\n ", value) + } + + } + + return results +} diff --git a/vendor/github.com/goamz/goamz/dynamodb/item_test.go b/vendor/github.com/goamz/goamz/dynamodb/item_test.go new file mode 100644 index 000000000..37b4b8838 --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/item_test.go @@ -0,0 +1,446 @@ +package dynamodb_test + +import ( + "github.com/goamz/goamz/dynamodb" + . "gopkg.in/check.v1" +) + +type ItemSuite struct { + TableDescriptionT dynamodb.TableDescriptionT + DynamoDBTest + WithRange bool +} + +func (s *ItemSuite) SetUpSuite(c *C) { + setUpAuth(c) + s.DynamoDBTest.TableDescriptionT = s.TableDescriptionT + s.server = &dynamodb.Server{dynamodb_auth, dynamodb_region} + pk, err := s.TableDescriptionT.BuildPrimaryKey() + if err != nil { + c.Skip(err.Error()) + } + s.table = s.server.NewTable(s.TableDescriptionT.TableName, pk) + + // Cleanup + s.TearDownSuite(c) + _, err = s.server.CreateTable(s.TableDescriptionT) + if err != nil { + c.Fatal(err) + } + s.WaitUntilStatus(c, "ACTIVE") +} + +var item_suite = &ItemSuite{ + TableDescriptionT: dynamodb.TableDescriptionT{ + TableName: "DynamoDBTestMyTable", + AttributeDefinitions: []dynamodb.AttributeDefinitionT{ + dynamodb.AttributeDefinitionT{"TestHashKey", "S"}, + dynamodb.AttributeDefinitionT{"TestRangeKey", "N"}, + }, + KeySchema: []dynamodb.KeySchemaT{ + dynamodb.KeySchemaT{"TestHashKey", "HASH"}, + dynamodb.KeySchemaT{"TestRangeKey", "RANGE"}, + }, + ProvisionedThroughput: dynamodb.ProvisionedThroughputT{ + ReadCapacityUnits: 1, + WriteCapacityUnits: 1, + }, + }, + WithRange: true, +} + +var item_without_range_suite = &ItemSuite{ + TableDescriptionT: dynamodb.TableDescriptionT{ + TableName: "DynamoDBTestMyTable", + AttributeDefinitions: []dynamodb.AttributeDefinitionT{ + dynamodb.AttributeDefinitionT{"TestHashKey", "S"}, + }, + KeySchema: []dynamodb.KeySchemaT{ + dynamodb.KeySchemaT{"TestHashKey", "HASH"}, + }, + ProvisionedThroughput: dynamodb.ProvisionedThroughputT{ + ReadCapacityUnits: 1, + WriteCapacityUnits: 1, + }, + }, + WithRange: false, +} + +var _ = Suite(item_suite) +var _ = Suite(item_without_range_suite) + +func (s *ItemSuite) TestConditionalPutUpdateDeleteItem(c *C) { + if s.WithRange { + // No rangekey test required + return + } + + attrs := []dynamodb.Attribute{ + *dynamodb.NewStringAttribute("Attr1", "Attr1Val"), + } + pk := &dynamodb.Key{HashKey: "NewHashKeyVal"} + + // Put + if ok, err := s.table.PutItem("NewHashKeyVal", "", attrs); !ok { + c.Fatal(err) + } + + { + // Put with condition failed + expected := []dynamodb.Attribute{ + *dynamodb.NewStringAttribute("Attr1", "expectedAttr1Val").SetExists(true), + *dynamodb.NewStringAttribute("AttrNotExists", "").SetExists(false), + } + if ok, err := s.table.ConditionalPutItem("NewHashKeyVal", "", attrs, expected); ok { + c.Errorf("Expect condition does not meet.") + } else { + c.Check(err.Error(), Matches, "ConditionalCheckFailedException.*") + } + + // Add attributes with condition failed + if ok, err := s.table.ConditionalAddAttributes(pk, attrs, expected); ok { + c.Errorf("Expect condition does not meet.") + } else { + c.Check(err.Error(), Matches, "ConditionalCheckFailedException.*") + } + + // Update attributes with condition failed + if ok, err := s.table.ConditionalUpdateAttributes(pk, attrs, expected); ok { + c.Errorf("Expect condition does not meet.") + } else { + c.Check(err.Error(), Matches, "ConditionalCheckFailedException.*") + } + + // Delete attributes with condition failed + if ok, err := s.table.ConditionalDeleteAttributes(pk, attrs, expected); ok { + c.Errorf("Expect condition does not meet.") + } else { + c.Check(err.Error(), Matches, "ConditionalCheckFailedException.*") + } + } + + { + expected := []dynamodb.Attribute{ + *dynamodb.NewStringAttribute("Attr1", "Attr1Val").SetExists(true), + } + + // Add attributes with condition met + addNewAttrs := []dynamodb.Attribute{ + *dynamodb.NewNumericAttribute("AddNewAttr1", "10"), + *dynamodb.NewNumericAttribute("AddNewAttr2", "20"), + } + if ok, err := s.table.ConditionalAddAttributes(pk, addNewAttrs, nil); !ok { + c.Errorf("Expect condition met. %s", err) + } + + // Update attributes with condition met + updateAttrs := []dynamodb.Attribute{ + *dynamodb.NewNumericAttribute("AddNewAttr1", "100"), + } + if ok, err := s.table.ConditionalUpdateAttributes(pk, updateAttrs, expected); !ok { + c.Errorf("Expect condition met. %s", err) + } + + // Delete attributes with condition met + deleteAttrs := []dynamodb.Attribute{ + *dynamodb.NewNumericAttribute("AddNewAttr2", ""), + } + if ok, err := s.table.ConditionalDeleteAttributes(pk, deleteAttrs, expected); !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, DeepEquals, dynamodb.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 + expected := []dynamodb.Attribute{ + *dynamodb.NewStringAttribute("Attr1", "Attr1Val").SetExists(true), + } + newattrs := []dynamodb.Attribute{ + *dynamodb.NewStringAttribute("Attr1", "Attr2Val"), + } + if ok, err := s.table.ConditionalPutItem("NewHashKeyVal", "", newattrs, expected); !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, DeepEquals, dynamodb.NewStringAttribute("Attr1", "Attr2Val")) + } else { + c.Error("Expect Attr1 attribute to be updated") + } + } + + { + // Delete with condition failed + expected := []dynamodb.Attribute{ + *dynamodb.NewStringAttribute("Attr1", "expectedAttr1Val").SetExists(true), + } + if ok, err := s.table.ConditionalDeleteItem(pk, expected); ok { + c.Errorf("Expect condition does not meet.") + } else { + c.Check(err.Error(), Matches, "ConditionalCheckFailedException.*") + } + } + + { + // Delete with condition met + expected := []dynamodb.Attribute{ + *dynamodb.NewStringAttribute("Attr1", "Attr2Val").SetExists(true), + } + if ok, _ := s.table.ConditionalDeleteItem(pk, expected); !ok { + c.Errorf("Expect condition met.") + } + + // Get to verify Delete operation + _, err := s.table.GetItem(pk) + c.Check(err.Error(), Matches, "Item not found") + } +} + +func (s *ItemSuite) TestPutGetDeleteItem(c *C) { + attrs := []dynamodb.Attribute{ + *dynamodb.NewStringAttribute("Attr1", "Attr1Val"), + } + + var rk string + if s.WithRange { + rk = "1" + } + + // Put + if ok, err := s.table.PutItem("NewHashKeyVal", rk, attrs); !ok { + c.Fatal(err) + } + + // Get to verify Put operation + pk := &dynamodb.Key{HashKey: "NewHashKeyVal", RangeKey: rk} + item, err := s.table.GetItem(pk) + if err != nil { + c.Fatal(err) + } + + if val, ok := item["TestHashKey"]; ok { + c.Check(val, DeepEquals, dynamodb.NewStringAttribute("TestHashKey", "NewHashKeyVal")) + } else { + c.Error("Expect TestHashKey to be found") + } + + if s.WithRange { + if val, ok := item["TestRangeKey"]; ok { + c.Check(val, DeepEquals, dynamodb.NewNumericAttribute("TestRangeKey", "1")) + } else { + c.Error("Expect TestRangeKey to be found") + } + } + + // Delete + if ok, _ := s.table.DeleteItem(pk); !ok { + c.Fatal(err) + } + + // Get to verify Delete operation + _, err = s.table.GetItem(pk) + c.Check(err.Error(), Matches, "Item not found") +} + +func (s *ItemSuite) TestUpdateItem(c *C) { + attrs := []dynamodb.Attribute{ + *dynamodb.NewNumericAttribute("count", "0"), + } + + var rk string + if s.WithRange { + rk = "1" + } + + if ok, err := s.table.PutItem("NewHashKeyVal", rk, attrs); !ok { + c.Fatal(err) + } + + // UpdateItem with Add + attrs = []dynamodb.Attribute{ + *dynamodb.NewNumericAttribute("count", "10"), + } + pk := &dynamodb.Key{HashKey: "NewHashKeyVal", RangeKey: rk} + if ok, err := s.table.AddAttributes(pk, attrs); !ok { + c.Error(err) + } + + // Get to verify Add operation + if item, err := s.table.GetItemConsistent(pk, true); err != nil { + c.Error(err) + } else { + if val, ok := item["count"]; ok { + c.Check(val, DeepEquals, dynamodb.NewNumericAttribute("count", "10")) + } else { + c.Error("Expect count to be found") + } + } + + // UpdateItem with Put + attrs = []dynamodb.Attribute{ + *dynamodb.NewNumericAttribute("count", "100"), + } + if ok, err := s.table.UpdateAttributes(pk, attrs); !ok { + c.Error(err) + } + + // Get to verify Put operation + if item, err := s.table.GetItem(pk); err != nil { + c.Fatal(err) + } else { + if val, ok := item["count"]; ok { + c.Check(val, DeepEquals, dynamodb.NewNumericAttribute("count", "100")) + } else { + c.Error("Expect count to be found") + } + } + + // UpdateItem with Delete + attrs = []dynamodb.Attribute{ + *dynamodb.NewNumericAttribute("count", ""), + } + if ok, err := s.table.DeleteAttributes(pk, attrs); !ok { + c.Error(err) + } + + // Get to verify Delete operation + if item, err := s.table.GetItem(pk); err != nil { + c.Error(err) + } else { + if _, ok := item["count"]; ok { + c.Error("Expect count not to be found") + } + } +} + +func (s *ItemSuite) TestUpdateItemWithSet(c *C) { + attrs := []dynamodb.Attribute{ + *dynamodb.NewStringSetAttribute("list", []string{"A", "B"}), + } + + var rk string + if s.WithRange { + rk = "1" + } + + if ok, err := s.table.PutItem("NewHashKeyVal", rk, attrs); !ok { + c.Error(err) + } + + // UpdateItem with Add + attrs = []dynamodb.Attribute{ + *dynamodb.NewStringSetAttribute("list", []string{"C"}), + } + pk := &dynamodb.Key{HashKey: "NewHashKeyVal", RangeKey: rk} + if ok, err := s.table.AddAttributes(pk, attrs); !ok { + c.Error(err) + } + + // Get to verify Add operation + if item, err := s.table.GetItem(pk); err != nil { + c.Error(err) + } else { + if val, ok := item["list"]; ok { + c.Check(val, DeepEquals, dynamodb.NewStringSetAttribute("list", []string{"A", "B", "C"})) + } else { + c.Error("Expect count to be found") + } + } + + // UpdateItem with Delete + attrs = []dynamodb.Attribute{ + *dynamodb.NewStringSetAttribute("list", []string{"A"}), + } + if ok, err := s.table.DeleteAttributes(pk, attrs); !ok { + c.Error(err) + } + + // Get to verify Delete operation + if item, err := s.table.GetItem(pk); err != nil { + c.Error(err) + } else { + if val, ok := item["list"]; ok { + c.Check(val, DeepEquals, dynamodb.NewStringSetAttribute("list", []string{"B", "C"})) + } else { + c.Error("Expect list to be remained") + } + } +} + +func (s *ItemSuite) TestUpdateItem_new(c *C) { + attrs := []dynamodb.Attribute{ + *dynamodb.NewStringAttribute("intval", "1"), + } + var rk string + if s.WithRange { + rk = "1" + } + pk := &dynamodb.Key{HashKey: "UpdateKeyVal", RangeKey: rk} + + num := func(a, b string) dynamodb.Attribute { + return *dynamodb.NewNumericAttribute(a, b) + } + + checkVal := func(i string) { + if item, err := s.table.GetItem(pk); err != nil { + c.Error(err) + } else { + c.Check(item["intval"], DeepEquals, dynamodb.NewNumericAttribute("intval", i)) + } + } + + if ok, err := s.table.PutItem("UpdateKeyVal", rk, attrs); !ok { + c.Error(err) + } + checkVal("1") + + // Simple Increment + s.table.UpdateItem(pk).UpdateExpression("SET intval = intval + :incr", num(":incr", "5")).Execute() + checkVal("6") + + conditionalUpdate := func(check string) { + s.table.UpdateItem(pk). + ConditionExpression("intval = :check"). + UpdateExpression("SET intval = intval + :incr"). + ExpressionAttributes(num(":check", check), num(":incr", "4")). + Execute() + } + // Conditional increment should be a no-op. + conditionalUpdate("42") + checkVal("6") + + // conditional increment should succeed this time + conditionalUpdate("6") + checkVal("10") + + // Update with new values getting values + result, err := s.table.UpdateItem(pk). + ReturnValues(dynamodb.UPDATED_NEW). + UpdateExpression("SET intval = intval + :incr", num(":incr", "2")). + Execute() + c.Check(err, IsNil) + c.Check(result.Attributes["intval"], DeepEquals, num("intval", "12")) + checkVal("12") +} diff --git a/vendor/github.com/goamz/goamz/dynamodb/marshaller.go b/vendor/github.com/goamz/goamz/dynamodb/marshaller.go new file mode 100644 index 000000000..2898fbda9 --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/marshaller.go @@ -0,0 +1,626 @@ +package dynamodb + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "math" + "reflect" + "sort" + "strconv" + "strings" + "sync" + "unicode" +) + +func MarshalAttributes(m interface{}) ([]Attribute, error) { + v := reflect.ValueOf(m).Elem() + + builder := &attributeBuilder{} + builder.buffer = []Attribute{} + for _, f := range cachedTypeFields(v.Type()) { // loop on each field + fv := fieldByIndex(v, f.index) + if !fv.IsValid() || isEmptyValueToOmit(fv) { + continue + } + + err := builder.reflectToDynamoDBAttribute(f.name, fv) + if err != nil { + return builder.buffer, err + } + } + + return builder.buffer, nil +} + +func UnmarshalAttributes(attributesRef *map[string]*Attribute, m interface{}) error { + rv := reflect.ValueOf(m) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + return fmt.Errorf("InvalidUnmarshalError reflect.ValueOf(v): %#v, m interface{}: %#v", rv, reflect.TypeOf(m)) + } + + v := reflect.ValueOf(m).Elem() + + attributes := *attributesRef + for _, f := range cachedTypeFields(v.Type()) { // loop on each field + fv := fieldByIndex(v, f.index) + correlatedAttribute := attributes[f.name] + if correlatedAttribute == nil { + continue + } + err := unmarshallAttribute(correlatedAttribute, fv) + if err != nil { + return err + } + } + + return nil +} + +type attributeBuilder struct { + buffer []Attribute +} + +func (builder *attributeBuilder) Push(attribute *Attribute) { + builder.buffer = append(builder.buffer, *attribute) +} + +func unmarshallAttribute(a *Attribute, v reflect.Value) error { + switch v.Kind() { + case reflect.Bool: + n, err := strconv.ParseInt(a.Value, 10, 64) + if err != nil { + return fmt.Errorf("UnmarshalTypeError (bool) %#v: %#v", a.Value, err) + } + v.SetBool(n != 0) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + n, err := strconv.ParseInt(a.Value, 10, 64) + if err != nil || v.OverflowInt(n) { + return fmt.Errorf("UnmarshalTypeError (number) %#v: %#v", a.Value, err) + } + v.SetInt(n) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + n, err := strconv.ParseUint(a.Value, 10, 64) + if err != nil || v.OverflowUint(n) { + return fmt.Errorf("UnmarshalTypeError (number) %#v: %#v", a.Value, err) + } + v.SetUint(n) + + case reflect.Float32, reflect.Float64: + n, err := strconv.ParseFloat(a.Value, v.Type().Bits()) + if err != nil || v.OverflowFloat(n) { + return fmt.Errorf("UnmarshalTypeError (number) %#v: %#v", a.Value, err) + } + v.SetFloat(n) + + case reflect.String: + v.SetString(a.Value) + + case reflect.Slice: + if v.Type().Elem().Kind() == reflect.Uint8 { // byte arrays are a special case + b := make([]byte, base64.StdEncoding.DecodedLen(len(a.Value))) + n, err := base64.StdEncoding.Decode(b, []byte(a.Value)) + if err != nil { + return fmt.Errorf("UnmarshalTypeError (byte) %#v: %#v", a.Value, err) + } + v.Set(reflect.ValueOf(b[0:n])) + break + } + + if a.SetType() { // Special NS and SS types should be correctly handled + nativeSetCreated := false + switch v.Type().Elem().Kind() { + case reflect.Bool: + nativeSetCreated = true + arry := reflect.MakeSlice(v.Type(), len(a.SetValues), len(a.SetValues)) + for i, aval := range a.SetValues { + n, err := strconv.ParseInt(aval, 10, 64) + if err != nil { + return fmt.Errorf("UnmarshalSetTypeError (bool) %#v: %#v", aval, err) + } + arry.Index(i).SetBool(n != 0) + } + v.Set(arry) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + nativeSetCreated = true + arry := reflect.MakeSlice(v.Type(), len(a.SetValues), len(a.SetValues)) + for i, aval := range a.SetValues { + n, err := strconv.ParseInt(aval, 10, 64) + if err != nil || arry.Index(i).OverflowInt(n) { + return fmt.Errorf("UnmarshalSetTypeError (number) %#v: %#v", aval, err) + } + arry.Index(i).SetInt(n) + } + v.Set(arry) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + nativeSetCreated = true + arry := reflect.MakeSlice(v.Type(), len(a.SetValues), len(a.SetValues)) + for i, aval := range a.SetValues { + n, err := strconv.ParseUint(aval, 10, 64) + if err != nil || arry.Index(i).OverflowUint(n) { + return fmt.Errorf("UnmarshalSetTypeError (number) %#v: %#v", aval, err) + } + arry.Index(i).SetUint(n) + } + v.Set(arry) + + case reflect.Float32, reflect.Float64: + nativeSetCreated = true + arry := reflect.MakeSlice(v.Type(), len(a.SetValues), len(a.SetValues)) + for i, aval := range a.SetValues { + n, err := strconv.ParseFloat(aval, arry.Index(i).Type().Bits()) + if err != nil || arry.Index(i).OverflowFloat(n) { + return fmt.Errorf("UnmarshalSetTypeError (number) %#v: %#v", aval, err) + } + arry.Index(i).SetFloat(n) + } + v.Set(arry) + + case reflect.String: + nativeSetCreated = true + arry := reflect.MakeSlice(v.Type(), len(a.SetValues), len(a.SetValues)) + for i, aval := range a.SetValues { + arry.Index(i).SetString(aval) + } + v.Set(arry) + } + + if nativeSetCreated { + break + } + } + + // Slices can be marshalled as nil, but otherwise are handled + // as arrays. + fallthrough + case reflect.Array, reflect.Struct, reflect.Map, reflect.Interface, reflect.Ptr: + unmarshalled := reflect.New(v.Type()) + err := json.Unmarshal([]byte(a.Value), unmarshalled.Interface()) + if err != nil { + return err + } + v.Set(unmarshalled.Elem()) + + default: + return fmt.Errorf("UnsupportedTypeError %#v", v.Type()) + } + + return nil +} + +// reflectValueQuoted writes the value in v to the output. +// If quoted is true, the serialization is wrapped in a JSON string. +func (e *attributeBuilder) reflectToDynamoDBAttribute(name string, v reflect.Value) error { + if !v.IsValid() { + return nil + } // don't build + + switch v.Kind() { + case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64: + rv, err := numericReflectedValueString(v) + if err != nil { + return err + } + e.Push(NewNumericAttribute(name, rv)) + + case reflect.String: + e.Push(NewStringAttribute(name, v.String())) + + case reflect.Slice: + if v.IsNil() { + break + } + if v.Type().Elem().Kind() == reflect.Uint8 { + // Byte slices are treated as errors + s := v.Bytes() + dst := make([]byte, base64.StdEncoding.EncodedLen(len(s))) + base64.StdEncoding.Encode(dst, s) + e.Push(NewStringAttribute(name, string(dst))) + break + } + + // Special NS and SS types should be correctly handled + nativeSetCreated := false + switch v.Type().Elem().Kind() { + case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64: + nativeSetCreated = true + arrystrings := make([]string, v.Len()) + for i, _ := range arrystrings { + var err error + arrystrings[i], err = numericReflectedValueString(v.Index(i)) + if err != nil { + return err + } + } + e.Push(NewNumericSetAttribute(name, arrystrings)) + case reflect.String: // simple copy will suffice + nativeSetCreated = true + arrystrings := make([]string, v.Len()) + for i, _ := range arrystrings { + arrystrings[i] = v.Index(i).String() + } + e.Push(NewStringSetAttribute(name, arrystrings)) + } + + if nativeSetCreated { + break + } + + // Slices can be marshalled as nil, but otherwise are handled + // as arrays. + fallthrough + case reflect.Array, reflect.Struct, reflect.Map, reflect.Interface, reflect.Ptr: + jsonVersion, err := json.Marshal(v.Interface()) + if err != nil { + return err + } + escapedJson := `"` + string(jsonVersion) + `"` // strconv.Quote not required because the entire string is escaped from json Marshall + e.Push(NewStringAttribute(name, escapedJson[1:len(escapedJson)-1])) + + default: + return fmt.Errorf("UnsupportedTypeError %#v", v.Type()) + } + return nil +} + +func numericReflectedValueString(v reflect.Value) (string, error) { + switch v.Kind() { + case reflect.Bool: + x := v.Bool() + if x { + return "1", nil + } else { + return "0", nil + } + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.FormatInt(v.Int(), 10), nil + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return strconv.FormatUint(v.Uint(), 10), nil + + case reflect.Float32, reflect.Float64: + f := v.Float() + if math.IsInf(f, 0) || math.IsNaN(f) { + return "", fmt.Errorf("UnsupportedValueError %#v (formatted float: %s)", v, strconv.FormatFloat(f, 'g', -1, v.Type().Bits())) + } + return strconv.FormatFloat(f, 'g', -1, v.Type().Bits()), nil + } + return "", fmt.Errorf("UnsupportedNumericValueError %#v", v.Type()) +} + +// In DynamoDB we should omit empty value in some type +// See http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html +func isEmptyValueToOmit(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String, reflect.Interface, reflect.Ptr: + // should omit if empty value + return isEmptyValue(v) + } + // otherwise should not omit + return false +} + +// ---------------- Below are copied handy functions from http://golang.org/src/pkg/encoding/json/encode.go -------------------------------- +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + return false +} + +func fieldByIndex(v reflect.Value, index []int) reflect.Value { + for _, i := range index { + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return reflect.Value{} + } + v = v.Elem() + } + v = v.Field(i) + } + return v +} + +// A field represents a single field found in a struct. +type field struct { + name string + tag bool + index []int + typ reflect.Type + omitEmpty bool + quoted bool +} + +// byName sorts field by name, breaking ties with depth, +// then breaking ties with "name came from json tag", then +// breaking ties with index sequence. +type byName []field + +func (x byName) Len() int { return len(x) } + +func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byName) Less(i, j int) bool { + if x[i].name != x[j].name { + return x[i].name < x[j].name + } + if len(x[i].index) != len(x[j].index) { + return len(x[i].index) < len(x[j].index) + } + if x[i].tag != x[j].tag { + return x[i].tag + } + return byIndex(x).Less(i, j) +} + +// byIndex sorts field by index sequence. +type byIndex []field + +func (x byIndex) Len() int { return len(x) } + +func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byIndex) Less(i, j int) bool { + for k, xik := range x[i].index { + if k >= len(x[j].index) { + return false + } + if xik != x[j].index[k] { + return xik < x[j].index[k] + } + } + return len(x[i].index) < len(x[j].index) +} + +func isValidTag(s string) bool { + if s == "" { + return false + } + for _, c := range s { + switch { + case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c): + // Backslash and quote chars are reserved, but + // otherwise any punctuation chars are allowed + // in a tag name. + default: + if !unicode.IsLetter(c) && !unicode.IsDigit(c) { + return false + } + } + } + return true +} + +// tagOptions is the string following a comma in a struct field's "json" +// tag, or the empty string. It does not include the leading comma. +type tagOptions string + +// Contains returns whether checks that a comma-separated list of options +// contains a particular substr flag. substr must be surrounded by a +// string boundary or commas. +func (o tagOptions) Contains(optionName string) bool { + if len(o) == 0 { + return false + } + s := string(o) + for s != "" { + var next string + i := strings.Index(s, ",") + if i >= 0 { + s, next = s[:i], s[i+1:] + } + if s == optionName { + return true + } + s = next + } + return false +} + +// parseTag splits a struct field's json tag into its name and +// comma-separated options. +func parseTag(tag string) (string, tagOptions) { + if idx := strings.Index(tag, ","); idx != -1 { + return tag[:idx], tagOptions(tag[idx+1:]) + } + return tag, tagOptions("") +} + +// typeFields returns a list of fields that JSON should recognize for the given type. +// The algorithm is breadth-first search over the set of structs to include - the top struct +// and then any reachable anonymous structs. +func typeFields(t reflect.Type) []field { + // Anonymous fields to explore at the current level and the next. + current := []field{} + next := []field{{typ: t}} + + // Count of queued names for current level and the next. + count := map[reflect.Type]int{} + nextCount := map[reflect.Type]int{} + + // Types already visited at an earlier level. + visited := map[reflect.Type]bool{} + + // Fields found. + var fields []field + + for len(next) > 0 { + current, next = next, current[:0] + count, nextCount = nextCount, map[reflect.Type]int{} + + for _, f := range current { + if visited[f.typ] { + continue + } + visited[f.typ] = true + + // Scan f.typ for fields to include. + for i := 0; i < f.typ.NumField(); i++ { + sf := f.typ.Field(i) + if sf.PkgPath != "" { // unexported + continue + } + tag := sf.Tag.Get("json") + if tag == "-" { + continue + } + name, opts := parseTag(tag) + if !isValidTag(name) { + name = "" + } + index := make([]int, len(f.index)+1) + copy(index, f.index) + index[len(f.index)] = i + + ft := sf.Type + if ft.Name() == "" && ft.Kind() == reflect.Ptr { + // Follow pointer. + ft = ft.Elem() + } + + // Record found field and index sequence. + if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { + tagged := name != "" + if name == "" { + name = sf.Name + } + fields = append(fields, field{name, tagged, index, ft, + opts.Contains("omitempty"), opts.Contains("string")}) + if count[f.typ] > 1 { + // If there were multiple instances, add a second, + // so that the annihilation code will see a duplicate. + // It only cares about the distinction between 1 or 2, + // so don't bother generating any more copies. + fields = append(fields, fields[len(fields)-1]) + } + continue + } + + // Record new anonymous struct to explore in next round. + nextCount[ft]++ + if nextCount[ft] == 1 { + next = append(next, field{name: ft.Name(), index: index, typ: ft}) + } + } + } + } + + sort.Sort(byName(fields)) + + // Delete all fields that are hidden by the Go rules for embedded fields, + // except that fields with JSON tags are promoted. + + // The fields are sorted in primary order of name, secondary order + // of field index length. Loop over names; for each name, delete + // hidden fields by choosing the one dominant field that survives. + out := fields[:0] + for advance, i := 0, 0; i < len(fields); i += advance { + // One iteration per name. + // Find the sequence of fields with the name of this first field. + fi := fields[i] + name := fi.name + for advance = 1; i+advance < len(fields); advance++ { + fj := fields[i+advance] + if fj.name != name { + break + } + } + if advance == 1 { // Only one field with this name + out = append(out, fi) + continue + } + dominant, ok := dominantField(fields[i : i+advance]) + if ok { + out = append(out, dominant) + } + } + + fields = out + sort.Sort(byIndex(fields)) + + return fields +} + +// dominantField looks through the fields, all of which are known to +// have the same name, to find the single field that dominates the +// others using Go's embedding rules, modified by the presence of +// JSON tags. If there are multiple top-level fields, the boolean +// will be false: This condition is an error in Go and we skip all +// the fields. +func dominantField(fields []field) (field, bool) { + // The fields are sorted in increasing index-length order. The winner + // must therefore be one with the shortest index length. Drop all + // longer entries, which is easy: just truncate the slice. + length := len(fields[0].index) + tagged := -1 // Index of first tagged field. + for i, f := range fields { + if len(f.index) > length { + fields = fields[:i] + break + } + if f.tag { + if tagged >= 0 { + // Multiple tagged fields at the same level: conflict. + // Return no field. + return field{}, false + } + tagged = i + } + } + if tagged >= 0 { + return fields[tagged], true + } + // All remaining fields have the same length. If there's more than one, + // we have a conflict (two fields named "X" at the same level) and we + // return no field. + if len(fields) > 1 { + return field{}, false + } + return fields[0], true +} + +var fieldCache struct { + sync.RWMutex + m map[reflect.Type][]field +} + +// cachedTypeFields is like typeFields but uses a cache to avoid repeated work. +func cachedTypeFields(t reflect.Type) []field { + fieldCache.RLock() + f := fieldCache.m[t] + fieldCache.RUnlock() + if f != nil { + return f + } + + // Compute fields without lock. + // Might duplicate effort but won't hold other computations back. + f = typeFields(t) + if f == nil { + f = []field{} + } + + fieldCache.Lock() + if fieldCache.m == nil { + fieldCache.m = map[reflect.Type][]field{} + } + fieldCache.m[t] = f + fieldCache.Unlock() + return f +} diff --git a/vendor/github.com/goamz/goamz/dynamodb/marshaller_test.go b/vendor/github.com/goamz/goamz/dynamodb/marshaller_test.go new file mode 100644 index 000000000..8b9d2fc08 --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/marshaller_test.go @@ -0,0 +1,283 @@ +package dynamodb_test + +import ( + "time" + + "github.com/goamz/goamz/dynamodb" + . "gopkg.in/check.v1" +) + +type TestSubStruct struct { + SubBool bool + SubInt int + SubString string + SubStringArray []string +} + +type TestStruct struct { + TestBool bool + TestInt int + TestInt32 int32 + TestInt64 int64 + TestUint uint + TestFloat32 float32 + TestFloat64 float64 + TestString string + TestByteArray []byte + TestStringArray []string + TestIntArray []int + TestInt8Array []int8 + TestFloatArray []float64 + TestSub TestSubStruct +} + +type TestStructTime struct { + TestTime time.Time +} + +func testObject() *TestStruct { + return &TestStruct{ + TestBool: true, + TestInt: -99, + TestInt32: 999, + TestInt64: 9999, + TestUint: 99, + TestFloat32: 9.9999, + TestFloat64: 99.999999, + TestString: "test", + TestByteArray: []byte("bytes"), + TestStringArray: []string{"test1", "test2", "test3", "test4"}, + TestIntArray: []int{0, 1, 12, 123, 1234, 12345}, + TestInt8Array: []int8{0, 1, 12, 123}, + TestFloatArray: []float64{0.1, 1.1, 1.2, 1.23, 1.234, 1.2345}, + TestSub: TestSubStruct{ + SubBool: true, + SubInt: 2, + SubString: "subtest", + SubStringArray: []string{"sub1", "sub2", "sub3"}, + }, + } +} + +func testObjectTime() *TestStructTime { + t, _ := time.Parse("Jan 2, 2006 at 3:04pm", "Mar 3, 2003 at 5:03pm") + return &TestStructTime{ + TestTime: t, + } +} + +func testObjectWithZeroValues() *TestStruct { + return &TestStruct{} +} + +func testObjectWithNilSets() *TestStruct { + return &TestStruct{ + TestBool: true, + TestInt: -99, + TestInt32: 999, + TestInt64: 9999, + TestUint: 99, + TestFloat32: 9.9999, + TestFloat64: 99.999999, + TestString: "test", + TestByteArray: []byte("bytes"), + TestStringArray: []string(nil), + TestIntArray: []int(nil), + TestFloatArray: []float64(nil), + TestSub: TestSubStruct{ + SubBool: true, + SubInt: 2, + SubString: "subtest", + SubStringArray: []string{"sub1", "sub2", "sub3"}, + }, + } +} +func testObjectWithEmptySets() *TestStruct { + return &TestStruct{ + TestBool: true, + TestInt: -99, + TestInt32: 999, + TestInt64: 9999, + TestUint: 99, + TestFloat32: 9.9999, + TestFloat64: 99.999999, + TestString: "test", + TestByteArray: []byte("bytes"), + TestStringArray: []string{}, + TestIntArray: []int{}, + TestFloatArray: []float64{}, + TestSub: TestSubStruct{ + SubBool: true, + SubInt: 2, + SubString: "subtest", + SubStringArray: []string{"sub1", "sub2", "sub3"}, + }, + } +} + +func testAttrs() []dynamodb.Attribute { + return []dynamodb.Attribute{ + dynamodb.Attribute{Type: "N", Name: "TestBool", Value: "1", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "N", Name: "TestInt", Value: "-99", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "N", Name: "TestInt32", Value: "999", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "N", Name: "TestInt64", Value: "9999", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "N", Name: "TestUint", Value: "99", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "N", Name: "TestFloat32", Value: "9.9999", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "N", Name: "TestFloat64", Value: "99.999999", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "S", Name: "TestString", Value: "test", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "S", Name: "TestByteArray", Value: "Ynl0ZXM=", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "SS", Name: "TestStringArray", Value: "", SetValues: []string{"test1", "test2", "test3", "test4"}}, + dynamodb.Attribute{Type: "NS", Name: "TestIntArray", Value: "", SetValues: []string{"0", "1", "12", "123", "1234", "12345"}}, + dynamodb.Attribute{Type: "NS", Name: "TestInt8Array", Value: "", SetValues: []string{"0", "1", "12", "123"}}, + dynamodb.Attribute{Type: "NS", Name: "TestFloatArray", Value: "", SetValues: []string{"0.1", "1.1", "1.2", "1.23", "1.234", "1.2345"}}, + dynamodb.Attribute{Type: "S", Name: "TestSub", Value: `{"SubBool":true,"SubInt":2,"SubString":"subtest","SubStringArray":["sub1","sub2","sub3"]}`, SetValues: []string(nil)}, + } +} + +func testAttrsTime() []dynamodb.Attribute { + return []dynamodb.Attribute{ + dynamodb.Attribute{Type: "S", Name: "TestTime", Value: "\"2003-03-03T17:03:00Z\"", SetValues: []string(nil)}, + } +} + +func testAttrsWithZeroValues() []dynamodb.Attribute { + return []dynamodb.Attribute{ + dynamodb.Attribute{Type: "N", Name: "TestBool", Value: "0", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "N", Name: "TestInt", Value: "0", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "N", Name: "TestInt32", Value: "0", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "N", Name: "TestInt64", Value: "0", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "N", Name: "TestUint", Value: "0", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "N", Name: "TestFloat32", Value: "0", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "N", Name: "TestFloat64", Value: "0", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "S", Name: "TestSub", Value: `{"SubBool":false,"SubInt":0,"SubString":"","SubStringArray":null}`, SetValues: []string(nil)}, + } +} + +func testAttrsWithNilSets() []dynamodb.Attribute { + return []dynamodb.Attribute{ + dynamodb.Attribute{Type: "N", Name: "TestBool", Value: "1", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "N", Name: "TestInt", Value: "-99", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "N", Name: "TestInt32", Value: "999", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "N", Name: "TestInt64", Value: "9999", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "N", Name: "TestUint", Value: "99", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "N", Name: "TestFloat32", Value: "9.9999", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "N", Name: "TestFloat64", Value: "99.999999", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "S", Name: "TestString", Value: "test", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "S", Name: "TestByteArray", Value: "Ynl0ZXM=", SetValues: []string(nil)}, + dynamodb.Attribute{Type: "S", Name: "TestSub", Value: `{"SubBool":true,"SubInt":2,"SubString":"subtest","SubStringArray":["sub1","sub2","sub3"]}`, SetValues: []string(nil)}, + } +} + +type MarshallerSuite struct { +} + +var _ = Suite(&MarshallerSuite{}) + +func (s *MarshallerSuite) TestMarshal(c *C) { + testObj := testObject() + attrs, err := dynamodb.MarshalAttributes(testObj) + if err != nil { + c.Errorf("Error from dynamodb.MarshalAttributes: %#v", err) + } + + expected := testAttrs() + c.Check(attrs, DeepEquals, expected) +} + +func (s *MarshallerSuite) TestUnmarshal(c *C) { + testObj := &TestStruct{} + + attrMap := map[string]*dynamodb.Attribute{} + attrs := testAttrs() + for i, _ := range attrs { + attrMap[attrs[i].Name] = &attrs[i] + } + + err := dynamodb.UnmarshalAttributes(&attrMap, testObj) + if err != nil { + c.Fatalf("Error from dynamodb.UnmarshalAttributes: %#v (Built: %#v)", err, testObj) + } + + expected := testObject() + c.Check(testObj, DeepEquals, expected) +} + +func (s *MarshallerSuite) TestMarshalTime(c *C) { + testObj := testObjectTime() + attrs, err := dynamodb.MarshalAttributes(testObj) + if err != nil { + c.Errorf("Error from dynamodb.MarshalAttributes: %#v", err) + } + + expected := testAttrsTime() + c.Check(attrs, DeepEquals, expected) +} + +func (s *MarshallerSuite) TestUnmarshalTime(c *C) { + testObj := &TestStructTime{} + + attrMap := map[string]*dynamodb.Attribute{} + attrs := testAttrsTime() + for i, _ := range attrs { + attrMap[attrs[i].Name] = &attrs[i] + } + + err := dynamodb.UnmarshalAttributes(&attrMap, testObj) + if err != nil { + c.Fatalf("Error from dynamodb.UnmarshalAttributes: %#v (Built: %#v)", err, testObj) + } + + expected := testObjectTime() + c.Check(testObj, DeepEquals, expected) +} + +func (s *MarshallerSuite) TestMarshalNilSets(c *C) { + testObj := testObjectWithNilSets() + attrs, err := dynamodb.MarshalAttributes(testObj) + if err != nil { + c.Errorf("Error from dynamodb.MarshalAttributes: %#v", err) + } + + expected := testAttrsWithNilSets() + c.Check(attrs, DeepEquals, expected) +} + +func (s *MarshallerSuite) TestMarshalZeroValues(c *C) { + testObj := testObjectWithZeroValues() + attrs, err := dynamodb.MarshalAttributes(testObj) + if err != nil { + c.Errorf("Error from dynamodb.MarshalAttributes: %#v", err) + } + + expected := testAttrsWithZeroValues() + c.Check(attrs, DeepEquals, expected) +} + +func (s *MarshallerSuite) TestMarshalEmptySets(c *C) { + testObj := testObjectWithEmptySets() + attrs, err := dynamodb.MarshalAttributes(testObj) + if err != nil { + c.Errorf("Error from dynamodb.MarshalAttributes: %#v", err) + } + + expected := testAttrsWithNilSets() + c.Check(attrs, DeepEquals, expected) +} + +func (s *MarshallerSuite) TestUnmarshalEmptySets(c *C) { + testObj := &TestStruct{} + + attrMap := map[string]*dynamodb.Attribute{} + attrs := testAttrsWithNilSets() + for i, _ := range attrs { + attrMap[attrs[i].Name] = &attrs[i] + } + + err := dynamodb.UnmarshalAttributes(&attrMap, testObj) + if err != nil { + c.Fatalf("Error from dynamodb.UnmarshalAttributes: %#v (Built: %#v)", err, testObj) + } + + expected := testObjectWithNilSets() + c.Check(testObj, DeepEquals, expected) +} diff --git a/vendor/github.com/goamz/goamz/dynamodb/query.go b/vendor/github.com/goamz/goamz/dynamodb/query.go new file mode 100644 index 000000000..453e38733 --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/query.go @@ -0,0 +1,111 @@ +package dynamodb + +import ( + "errors" + "fmt" + simplejson "github.com/bitly/go-simplejson" +) + +func (t *Table) Query(attributeComparisons []AttributeComparison) ([]map[string]*Attribute, error) { + q := NewQuery(t) + q.AddKeyConditions(attributeComparisons) + return runQuery(q, t) +} + +func (t *Table) QueryOnIndex(attributeComparisons []AttributeComparison, indexName string) ([]map[string]*Attribute, error) { + q := NewQuery(t) + q.AddKeyConditions(attributeComparisons) + q.AddIndex(indexName) + return runQuery(q, t) +} + +func (t *Table) QueryOnIndexDescending(attributeComparisons []AttributeComparison, indexName string) ([]map[string]*Attribute, error) { + q := NewQuery(t) + q.AddKeyConditions(attributeComparisons) + q.AddIndex(indexName) + q.ScanIndexDescending() + return runQuery(q, t) +} + +func (t *Table) LimitedQuery(attributeComparisons []AttributeComparison, limit int64) ([]map[string]*Attribute, error) { + q := NewQuery(t) + q.AddKeyConditions(attributeComparisons) + q.AddLimit(limit) + return runQuery(q, t) +} + +func (t *Table) LimitedQueryOnIndex(attributeComparisons []AttributeComparison, indexName string, limit int64) ([]map[string]*Attribute, error) { + q := NewQuery(t) + q.AddKeyConditions(attributeComparisons) + q.AddIndex(indexName) + q.AddLimit(limit) + return runQuery(q, t) +} + +func (t *Table) LimitedQueryDescending(attributeComparisons []AttributeComparison, limit int64) ([]map[string]*Attribute, error) { + q := NewQuery(t) + q.AddKeyConditions(attributeComparisons) + q.AddLimit(limit) + q.ScanIndexDescending() + return runQuery(q, t) +} + +func (t *Table) LimitedQueryOnIndexDescending(attributeComparisons []AttributeComparison, indexName string, limit int64) ([]map[string]*Attribute, error) { + q := NewQuery(t) + q.AddKeyConditions(attributeComparisons) + q.AddIndex(indexName) + q.AddLimit(limit) + q.ScanIndexDescending() + return runQuery(q, t) +} + +func (t *Table) CountQuery(attributeComparisons []AttributeComparison) (int64, error) { + q := NewQuery(t) + q.AddKeyConditions(attributeComparisons) + q.AddSelect("COUNT") + jsonResponse, err := t.Server.queryServer("DynamoDB_20120810.Query", q) + if err != nil { + return 0, err + } + json, err := simplejson.NewJson(jsonResponse) + if err != nil { + return 0, err + } + + itemCount, err := json.Get("Count").Int64() + if err != nil { + return 0, err + } + + return itemCount, nil +} + +func runQuery(q *Query, t *Table) ([]map[string]*Attribute, error) { + jsonResponse, err := t.Server.queryServer("DynamoDB_20120810.Query", q) + if err != nil { + return nil, err + } + + json, err := simplejson.NewJson(jsonResponse) + if err != nil { + return nil, err + } + + itemCount, err := json.Get("Count").Int() + if err != nil { + message := fmt.Sprintf("Unexpected response %s", jsonResponse) + return nil, errors.New(message) + } + + results := make([]map[string]*Attribute, itemCount) + + for i, _ := range results { + item, err := json.Get("Items").GetIndex(i).Map() + if err != nil { + message := fmt.Sprintf("Unexpected response %s", jsonResponse) + return nil, errors.New(message) + } + results[i] = parseAttributes(item) + } + return results, nil +} diff --git a/vendor/github.com/goamz/goamz/dynamodb/query_builder.go b/vendor/github.com/goamz/goamz/dynamodb/query_builder.go new file mode 100644 index 000000000..47a90bb1c --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/query_builder.go @@ -0,0 +1,362 @@ +package dynamodb + +import ( + "encoding/json" + "sort" +) + +type msi map[string]interface{} +type Query struct { + buffer msi +} + +func NewEmptyQuery() *Query { + return &Query{msi{}} +} + +func NewQuery(t *Table) *Query { + q := &Query{msi{}} + q.addTable(t) + return q +} + +// This way of specifing the key is used when doing a Get. +// If rangeKey is "", it is assumed to not want to be used +func (q *Query) AddKey(t *Table, key *Key) { + k := t.Key + keymap := msi{ + k.KeyAttribute.Name: msi{ + k.KeyAttribute.Type: key.HashKey}, + } + if k.HasRange() { + keymap[k.RangeAttribute.Name] = msi{k.RangeAttribute.Type: key.RangeKey} + } + + q.buffer["Key"] = keymap +} + +func keyAttributes(t *Table, key *Key) msi { + k := t.Key + + out := msi{} + out[k.KeyAttribute.Name] = msi{k.KeyAttribute.Type: key.HashKey} + if k.HasRange() { + out[k.RangeAttribute.Name] = msi{k.RangeAttribute.Type: key.RangeKey} + } + return out +} + +func (q *Query) AddAttributesToGet(attributes []string) { + if len(attributes) == 0 { + return + } + + q.buffer["AttributesToGet"] = attributes +} + +func (q *Query) ConsistentRead(c bool) { + if c == true { + q.buffer["ConsistentRead"] = "true" //String "true", not bool true + } +} + +func (q *Query) AddGetRequestItems(tableKeys map[*Table][]Key) { + requestitems := msi{} + for table, keys := range tableKeys { + keyslist := []msi{} + for _, key := range keys { + keyslist = append(keyslist, keyAttributes(table, &key)) + } + requestitems[table.Name] = msi{"Keys": keyslist} + } + q.buffer["RequestItems"] = requestitems +} + +func (q *Query) AddWriteRequestItems(tableItems map[*Table]map[string][][]Attribute) { + b := q.buffer + + b["RequestItems"] = func() msi { + out := msi{} + for table, itemActions := range tableItems { + out[table.Name] = func() interface{} { + out2 := []interface{}{} + + // here breaks an order of array.... + // For now, we iterate over sorted key by action for stable testing + keys := []string{} + for k := range itemActions { + keys = append(keys, k) + } + sort.Strings(keys) + + for ki := range keys { + action := keys[ki] + items := itemActions[action] + for _, attributes := range items { + Item_or_Key := map[bool]string{true: "Item", false: "Key"}[action == "Put"] + out2 = append(out2, msi{action + "Request": msi{Item_or_Key: attributeList(attributes)}}) + } + } + return out2 + }() + } + return out + }() +} + +func (q *Query) AddCreateRequestTable(description TableDescriptionT) { + b := q.buffer + + attDefs := []interface{}{} + for _, attr := range description.AttributeDefinitions { + attDefs = append(attDefs, msi{ + "AttributeName": attr.Name, + "AttributeType": attr.Type, + }) + } + b["AttributeDefinitions"] = attDefs + b["KeySchema"] = description.KeySchema + b["TableName"] = description.TableName + b["ProvisionedThroughput"] = msi{ + "ReadCapacityUnits": int(description.ProvisionedThroughput.ReadCapacityUnits), + "WriteCapacityUnits": int(description.ProvisionedThroughput.WriteCapacityUnits), + } + + if description.StreamSpecification.StreamEnabled { + b["StreamSpecification"] = msi{ + "StreamEnabled": "true", + "StreamViewType": description.StreamSpecification.StreamViewType, + } + } + + localSecondaryIndexes := []interface{}{} + + for _, ind := range description.LocalSecondaryIndexes { + localSecondaryIndexes = append(localSecondaryIndexes, msi{ + "IndexName": ind.IndexName, + "KeySchema": ind.KeySchema, + "Projection": ind.Projection, + }) + } + + globalSecondaryIndexes := []interface{}{} + intmax := func(x, y int64) int64 { + if x > y { + return x + } + return y + } + for _, ind := range description.GlobalSecondaryIndexes { + rec := msi{ + "IndexName": ind.IndexName, + "KeySchema": ind.KeySchema, + "Projection": ind.Projection, + } + // need at least one unit, and since go's max() is float based. + rec["ProvisionedThroughput"] = msi{ + "ReadCapacityUnits": intmax(1, ind.ProvisionedThroughput.ReadCapacityUnits), + "WriteCapacityUnits": intmax(1, ind.ProvisionedThroughput.WriteCapacityUnits), + } + globalSecondaryIndexes = append(globalSecondaryIndexes, rec) + } + + if len(localSecondaryIndexes) > 0 { + b["LocalSecondaryIndexes"] = localSecondaryIndexes + } + + if len(globalSecondaryIndexes) > 0 { + b["GlobalSecondaryIndexes"] = globalSecondaryIndexes + } +} + +func (q *Query) AddDeleteRequestTable(description TableDescriptionT) { + b := q.buffer + b["TableName"] = description.TableName +} + +func (q *Query) AddUpdateRequestTable(description TableDescriptionT) { + b := q.buffer + + attDefs := []interface{}{} + for _, attr := range description.AttributeDefinitions { + attDefs = append(attDefs, msi{ + "AttributeName": attr.Name, + "AttributeType": attr.Type, + }) + } + if len(attDefs) > 0 { + b["AttributeDefinitions"] = attDefs + } + b["TableName"] = description.TableName + b["ProvisionedThroughput"] = msi{ + "ReadCapacityUnits": int(description.ProvisionedThroughput.ReadCapacityUnits), + "WriteCapacityUnits": int(description.ProvisionedThroughput.WriteCapacityUnits), + } + +} + +func (q *Query) AddKeyConditions(comparisons []AttributeComparison) { + q.buffer["KeyConditions"] = buildComparisons(comparisons) +} + +func (q *Query) AddLimit(limit int64) { + q.buffer["Limit"] = limit +} +func (q *Query) AddSelect(value string) { + q.buffer["Select"] = value +} + +func (q *Query) AddIndex(value string) { + q.buffer["IndexName"] = value +} + +func (q *Query) ScanIndexDescending() { + q.buffer["ScanIndexForward"] = "false" +} + +/* + "ScanFilter":{ + "AttributeName1":{"AttributeValueList":[{"S":"AttributeValue"}],"ComparisonOperator":"EQ"} + }, +*/ +func (q *Query) AddScanFilter(comparisons []AttributeComparison) { + q.buffer["ScanFilter"] = buildComparisons(comparisons) +} + +func (q *Query) AddParallelScanConfiguration(segment int, totalSegments int) { + q.buffer["Segment"] = segment + q.buffer["TotalSegments"] = totalSegments +} + +func buildComparisons(comparisons []AttributeComparison) msi { + out := msi{} + + for _, c := range comparisons { + avlist := []interface{}{} + for _, attributeValue := range c.AttributeValueList { + avlist = append(avlist, msi{attributeValue.Type: attributeValue.Value}) + } + out[c.AttributeName] = msi{ + "AttributeValueList": avlist, + "ComparisonOperator": c.ComparisonOperator, + } + } + + return out +} + +// The primary key must be included in attributes. +func (q *Query) AddItem(attributes []Attribute) { + q.buffer["Item"] = attributeList(attributes) +} + +func (q *Query) AddUpdates(attributes []Attribute, action string) { + updates := msi{} + for _, a := range attributes { + au := msi{ + "Value": msi{ + a.Type: map[bool]interface{}{true: a.SetValues, false: a.Value}[a.SetType()], + }, + "Action": action, + } + // Delete 'Value' from AttributeUpdates if Type is not Set + if action == "DELETE" && !a.SetType() { + delete(au, "Value") + } + updates[a.Name] = au + } + + q.buffer["AttributeUpdates"] = updates +} + +func (q *Query) AddExpected(attributes []Attribute) { + expected := msi{} + for _, a := range attributes { + value := msi{} + if a.Exists != "" { + value["Exists"] = a.Exists + } + // If set Exists to false, we must remove Value + if value["Exists"] != "false" { + value["Value"] = msi{a.Type: map[bool]interface{}{true: a.SetValues, false: a.Value}[a.SetType()]} + } + expected[a.Name] = value + } + q.buffer["Expected"] = expected +} + +// Add the ReturnValues parameter, used in UpdateItem queries. +func (q *Query) AddReturnValues(returnValues ReturnValues) { + q.buffer["ReturnValues"] = string(returnValues) +} + +// Add the UpdateExpression parameter, used in UpdateItem queries. +func (q *Query) AddUpdateExpression(expression string) { + q.buffer["UpdateExpression"] = expression +} + +// Add the ConditionExpression parameter, used in UpdateItem queries. +func (q *Query) AddConditionExpression(expression string) { + q.buffer["ConditionExpression"] = expression +} + +func (q *Query) AddExpressionAttributes(attributes []Attribute) { + existing, ok := q.buffer["ExpressionAttributes"].(msi) + if !ok { + existing = msi{} + q.buffer["ExpressionAttributes"] = existing + } + for key, val := range attributeList(attributes) { + existing[key] = val + } +} + +func (q *Query) AddExclusiveStartStreamArn(arn string) { + q.buffer["ExclusiveStartStreamArn"] = arn +} + +func (q *Query) AddStreamArn(arn string) { + q.buffer["StreamArn"] = arn +} + +func (q *Query) AddExclusiveStartShardId(shardId string) { + q.buffer["ExclusiveStartShardId"] = shardId +} + +func (q *Query) AddShardId(shardId string) { + q.buffer["ShardId"] = shardId +} + +func (q *Query) AddShardIteratorType(shardIteratorType string) { + q.buffer["ShardIteratorType"] = shardIteratorType +} + +func (q *Query) AddSequenceNumber(sequenceNumber string) { + q.buffer["SequenceNumber"] = sequenceNumber +} + +func (q *Query) AddShardIterator(shardIterator string) { + q.buffer["ShardIterator"] = shardIterator +} + +func attributeList(attributes []Attribute) msi { + b := msi{} + for _, a := range attributes { + //UGH!! (I miss the query operator) + b[a.Name] = msi{a.Type: map[bool]interface{}{true: a.SetValues, false: a.Value}[a.SetType()]} + } + return b +} + +func (q *Query) addTable(t *Table) { + q.addTableByName(t.Name) +} + +func (q *Query) addTableByName(tableName string) { + q.buffer["TableName"] = tableName +} + +func (q *Query) String() string { + bytes, _ := json.Marshal(q.buffer) + return string(bytes) +} diff --git a/vendor/github.com/goamz/goamz/dynamodb/query_builder_test.go b/vendor/github.com/goamz/goamz/dynamodb/query_builder_test.go new file mode 100755 index 000000000..9a1f6f2c5 --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/query_builder_test.go @@ -0,0 +1,380 @@ +package dynamodb_test + +import ( + simplejson "github.com/bitly/go-simplejson" + "github.com/goamz/goamz/aws" + "github.com/goamz/goamz/dynamodb" + . "gopkg.in/check.v1" +) + +type QueryBuilderSuite struct { + server *dynamodb.Server +} + +var _ = Suite(&QueryBuilderSuite{}) + +func (s *QueryBuilderSuite) SetUpSuite(c *C) { + auth := &aws.Auth{AccessKey: "", SecretKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"} + s.server = &dynamodb.Server{*auth, aws.USEast} +} + +func (s *QueryBuilderSuite) TestEmptyQuery(c *C) { + q := dynamodb.NewEmptyQuery() + queryString := q.String() + expectedString := "{}" + c.Check(queryString, Equals, expectedString) + + if expectedString != queryString { + c.Fatalf("Unexpected Query String : %s\n", queryString) + } +} + +func (s *QueryBuilderSuite) TestAddWriteRequestItems(c *C) { + primary := dynamodb.NewStringAttribute("WidgetFoo", "") + secondary := dynamodb.NewNumericAttribute("Created", "") + key := dynamodb.PrimaryKey{primary, secondary} + table := s.server.NewTable("FooData", key) + + primary2 := dynamodb.NewStringAttribute("TestHashKey", "") + secondary2 := dynamodb.NewNumericAttribute("TestRangeKey", "") + key2 := dynamodb.PrimaryKey{primary2, secondary2} + table2 := s.server.NewTable("TestTable", key2) + + q := dynamodb.NewEmptyQuery() + + attribute1 := dynamodb.NewNumericAttribute("testing", "4") + attribute2 := dynamodb.NewNumericAttribute("testingbatch", "2111") + attribute3 := dynamodb.NewStringAttribute("testingstrbatch", "mystr") + item1 := []dynamodb.Attribute{*attribute1, *attribute2, *attribute3} + + attribute4 := dynamodb.NewNumericAttribute("testing", "444") + attribute5 := dynamodb.NewNumericAttribute("testingbatch", "93748249272") + attribute6 := dynamodb.NewStringAttribute("testingstrbatch", "myotherstr") + item2 := []dynamodb.Attribute{*attribute4, *attribute5, *attribute6} + + attributeDel1 := dynamodb.NewStringAttribute("TestHashKeyDel", "DelKey") + attributeDel2 := dynamodb.NewNumericAttribute("TestRangeKeyDel", "7777777") + itemDel := []dynamodb.Attribute{*attributeDel1, *attributeDel2} + + attributeTest1 := dynamodb.NewStringAttribute("TestHashKey", "MyKey") + attributeTest2 := dynamodb.NewNumericAttribute("TestRangeKey", "0193820384293") + itemTest := []dynamodb.Attribute{*attributeTest1, *attributeTest2} + + tableItems := map[*dynamodb.Table]map[string][][]dynamodb.Attribute{} + actionItems := make(map[string][][]dynamodb.Attribute) + actionItems["Put"] = [][]dynamodb.Attribute{item1, item2} + actionItems["Delete"] = [][]dynamodb.Attribute{itemDel} + tableItems[table] = actionItems + + actionItems2 := make(map[string][][]dynamodb.Attribute) + actionItems2["Put"] = [][]dynamodb.Attribute{itemTest} + tableItems[table2] = actionItems2 + + q.AddWriteRequestItems(tableItems) + + queryJson, err := simplejson.NewJson([]byte(q.String())) + if err != nil { + c.Fatal(err) + } + + expectedJson, err := simplejson.NewJson([]byte(` +{ + "RequestItems": { + "TestTable": [ + { + "PutRequest": { + "Item": { + "TestRangeKey": { + "N": "0193820384293" + }, + "TestHashKey": { + "S": "MyKey" + } + } + } + } + ], + "FooData": [ + { + "DeleteRequest": { + "Key": { + "TestRangeKeyDel": { + "N": "7777777" + }, + "TestHashKeyDel": { + "S": "DelKey" + } + } + } + }, + { + "PutRequest": { + "Item": { + "testingstrbatch": { + "S": "mystr" + }, + "testingbatch": { + "N": "2111" + }, + "testing": { + "N": "4" + } + } + } + }, + { + "PutRequest": { + "Item": { + "testingstrbatch": { + "S": "myotherstr" + }, + "testingbatch": { + "N": "93748249272" + }, + "testing": { + "N": "444" + } + } + } + } + ] + } +} + `)) + if err != nil { + c.Fatal(err) + } + c.Check(queryJson, DeepEquals, expectedJson) +} + +func (s *QueryBuilderSuite) TestAddExpectedQuery(c *C) { + primary := dynamodb.NewStringAttribute("domain", "") + key := dynamodb.PrimaryKey{primary, nil} + table := s.server.NewTable("sites", key) + + q := dynamodb.NewQuery(table) + q.AddKey(table, &dynamodb.Key{HashKey: "test"}) + + expected := []dynamodb.Attribute{ + *dynamodb.NewStringAttribute("domain", "expectedTest").SetExists(true), + *dynamodb.NewStringAttribute("testKey", "").SetExists(false), + } + q.AddExpected(expected) + + queryJson, err := simplejson.NewJson([]byte(q.String())) + if err != nil { + c.Fatal(err) + } + + expectedJson, err := simplejson.NewJson([]byte(` + { + "Expected": { + "domain": { + "Exists": "true", + "Value": { + "S": "expectedTest" + } + }, + "testKey": { + "Exists": "false" + } + }, + "Key": { + "domain": { + "S": "test" + } + }, + "TableName": "sites" + } + `)) + if err != nil { + c.Fatal(err) + } + c.Check(queryJson, DeepEquals, expectedJson) +} + +func (s *QueryBuilderSuite) TestGetItemQuery(c *C) { + primary := dynamodb.NewStringAttribute("domain", "") + key := dynamodb.PrimaryKey{primary, nil} + table := s.server.NewTable("sites", key) + + q := dynamodb.NewQuery(table) + q.AddKey(table, &dynamodb.Key{HashKey: "test"}) + + { + queryJson, err := simplejson.NewJson([]byte(q.String())) + if err != nil { + c.Fatal(err) + } + + expectedJson, err := simplejson.NewJson([]byte(` + { + "Key": { + "domain": { + "S": "test" + } + }, + "TableName": "sites" + } + `)) + if err != nil { + c.Fatal(err) + } + c.Check(queryJson, DeepEquals, expectedJson) + } + + // Use ConsistentRead + { + q.ConsistentRead(true) + queryJson, err := simplejson.NewJson([]byte(q.String())) + if err != nil { + c.Fatal(err) + } + + expectedJson, err := simplejson.NewJson([]byte(` + { + "ConsistentRead": "true", + "Key": { + "domain": { + "S": "test" + } + }, + "TableName": "sites" + } + `)) + if err != nil { + c.Fatal(err) + } + c.Check(queryJson, DeepEquals, expectedJson) + } +} + +func (s *QueryBuilderSuite) TestUpdateQuery(c *C) { + primary := dynamodb.NewStringAttribute("domain", "") + rangek := dynamodb.NewNumericAttribute("time", "") + key := dynamodb.PrimaryKey{primary, rangek} + table := s.server.NewTable("sites", key) + + countAttribute := dynamodb.NewNumericAttribute("count", "4") + attributes := []dynamodb.Attribute{*countAttribute} + + q := dynamodb.NewQuery(table) + q.AddKey(table, &dynamodb.Key{HashKey: "test", RangeKey: "1234"}) + q.AddUpdates(attributes, "ADD") + + queryJson, err := simplejson.NewJson([]byte(q.String())) + if err != nil { + c.Fatal(err) + } + expectedJson, err := simplejson.NewJson([]byte(` +{ + "AttributeUpdates": { + "count": { + "Action": "ADD", + "Value": { + "N": "4" + } + } + }, + "Key": { + "domain": { + "S": "test" + }, + "time": { + "N": "1234" + } + }, + "TableName": "sites" +} + `)) + if err != nil { + c.Fatal(err) + } + c.Check(queryJson, DeepEquals, expectedJson) +} + +func (s *QueryBuilderSuite) TestAddUpdates(c *C) { + primary := dynamodb.NewStringAttribute("domain", "") + key := dynamodb.PrimaryKey{primary, nil} + table := s.server.NewTable("sites", key) + + q := dynamodb.NewQuery(table) + q.AddKey(table, &dynamodb.Key{HashKey: "test"}) + + attr := dynamodb.NewStringSetAttribute("StringSet", []string{"str", "str2"}) + + q.AddUpdates([]dynamodb.Attribute{*attr}, "ADD") + + queryJson, err := simplejson.NewJson([]byte(q.String())) + if err != nil { + c.Fatal(err) + } + expectedJson, err := simplejson.NewJson([]byte(` +{ + "AttributeUpdates": { + "StringSet": { + "Action": "ADD", + "Value": { + "SS": ["str", "str2"] + } + } + }, + "Key": { + "domain": { + "S": "test" + } + }, + "TableName": "sites" +} + `)) + if err != nil { + c.Fatal(err) + } + c.Check(queryJson, DeepEquals, expectedJson) +} + +func (s *QueryBuilderSuite) TestAddKeyConditions(c *C) { + primary := dynamodb.NewStringAttribute("domain", "") + key := dynamodb.PrimaryKey{primary, nil} + table := s.server.NewTable("sites", key) + + q := dynamodb.NewQuery(table) + acs := []dynamodb.AttributeComparison{ + *dynamodb.NewStringAttributeComparison("domain", "EQ", "example.com"), + *dynamodb.NewStringAttributeComparison("path", "EQ", "/"), + } + q.AddKeyConditions(acs) + queryJson, err := simplejson.NewJson([]byte(q.String())) + + if err != nil { + c.Fatal(err) + } + + expectedJson, err := simplejson.NewJson([]byte(` +{ + "KeyConditions": { + "domain": { + "AttributeValueList": [ + { + "S": "example.com" + } + ], + "ComparisonOperator": "EQ" + }, + "path": { + "AttributeValueList": [ + { + "S": "/" + } + ], + "ComparisonOperator": "EQ" + } + }, + "TableName": "sites" +} + `)) + if err != nil { + c.Fatal(err) + } + c.Check(queryJson, DeepEquals, expectedJson) +} diff --git a/vendor/github.com/goamz/goamz/dynamodb/scan.go b/vendor/github.com/goamz/goamz/dynamodb/scan.go new file mode 100644 index 000000000..e8ed62363 --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/scan.go @@ -0,0 +1,51 @@ +package dynamodb + +import ( + "errors" + "fmt" + simplejson "github.com/bitly/go-simplejson" +) + +func (t *Table) FetchResults(query *Query) ([]map[string]*Attribute, error) { + jsonResponse, err := t.Server.queryServer(target("Scan"), query) + if err != nil { + return nil, err + } + + json, err := simplejson.NewJson(jsonResponse) + if err != nil { + return nil, err + } + + itemCount, err := json.Get("Count").Int() + if err != nil { + message := fmt.Sprintf("Unexpected response %s", jsonResponse) + return nil, errors.New(message) + } + + results := make([]map[string]*Attribute, itemCount) + + for i, _ := range results { + item, err := json.Get("Items").GetIndex(i).Map() + if err != nil { + message := fmt.Sprintf("Unexpected response %s", jsonResponse) + return nil, errors.New(message) + } + results[i] = parseAttributes(item) + } + return results, nil + +} + +func (t *Table) Scan(attributeComparisons []AttributeComparison) ([]map[string]*Attribute, error) { + q := NewQuery(t) + q.AddScanFilter(attributeComparisons) + return t.FetchResults(q) +} + +func (t *Table) ParallelScan(attributeComparisons []AttributeComparison, segment int, totalSegments int) ([]map[string]*Attribute, error) { + q := NewQuery(t) + q.AddScanFilter(attributeComparisons) + q.AddParallelScanConfiguration(segment, totalSegments) + return t.FetchResults(q) +} diff --git a/vendor/github.com/goamz/goamz/dynamodb/stream.go b/vendor/github.com/goamz/goamz/dynamodb/stream.go new file mode 100644 index 000000000..57f3a145f --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/stream.go @@ -0,0 +1,307 @@ +package dynamodb + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + + simplejson "github.com/bitly/go-simplejson" +) + +type Stream struct { + Server *Server + Arn string +} + +type StreamListItemT struct { + StreamArn string + StreamLabel string + TableName string +} + +type SequenceNumberRangeT struct { + EndingSequenceNumber string + StartingSequenceNumber string +} + +type ShardT struct { + ParentShardId string + SequenceNumberRange SequenceNumberRangeT + ShardId string +} + +type StreamDescriptionT struct { + CreationDateTime float64 + KeySchema []KeySchemaT + LastEvaluatedShardId string + Shards []ShardT + StreamArn string + StreamLabel string + StreamStatus string + StreamViewType string + TableName string +} + +type RecordT struct { + AwsRegion string + EventID string + EventName string + EventSource string + EventVersion string + StreamRecord *StreamRecordT +} + +type StreamRecordT struct { + Keys map[string]*Attribute + NewImage map[string]*Attribute + OldImage map[string]*Attribute + SequenceNumber string + StreamViewType string + SizeBytes int64 +} + +type listStreamsResponse struct { + Streams []StreamListItemT +} + +type describeStreamResponse struct { + StreamDescription StreamDescriptionT +} + +var ErrNoRecords = errors.New("No records") + +func (s *Server) ListStreams(startArn string) ([]StreamListItemT, error) { + return s.LimitedListTableStreams("", startArn, 0) +} + +func (s *Server) LimitedListStreams(startArn string, limit int64) ([]StreamListItemT, error) { + return s.LimitedListTableStreams("", startArn, limit) +} + +func (s *Server) ListTableStreams(table, startArn string) ([]StreamListItemT, error) { + return s.LimitedListTableStreams(table, startArn, 0) +} + +func (s *Server) LimitedListTableStreams(table, startArn string, limit int64) ([]StreamListItemT, error) { + query := NewEmptyQuery() + + if len(table) != 0 { + query.addTableByName(table) + } + + if len(startArn) != 0 { + query.AddExclusiveStartStreamArn(startArn) + } + + if limit > 0 { + query.AddLimit(limit) + } + + jsonResponse, err := s.queryServer(streamsTarget("ListStreams"), query) + if err != nil { + return nil, err + } + + var r listStreamsResponse + err = json.Unmarshal(jsonResponse, &r) + if err != nil { + return nil, err + } + + return r.Streams, nil +} + +func (s *Server) DescribeStream(arn, startShardId string) (*StreamDescriptionT, error) { + return s.LimitedDescribeStream(arn, startShardId, 0) +} + +func (s *Server) LimitedDescribeStream(arn, startShardId string, limit int64) (*StreamDescriptionT, error) { + query := NewEmptyQuery() + query.AddStreamArn(arn) + + if len(startShardId) != 0 { + query.AddExclusiveStartShardId(startShardId) + } + + if limit > 0 { + query.AddLimit(limit) + } + + jsonResponse, err := s.queryServer(streamsTarget("DescribeStream"), query) + if err != nil { + return nil, err + } + + var r describeStreamResponse + err = json.Unmarshal(jsonResponse, &r) + if err != nil { + return nil, err + } + + return &r.StreamDescription, nil +} + +func (s *Server) NewStream(streamArn string) *Stream { + return &Stream{s, streamArn} +} + +func (s *Stream) DescribeStream(startShardId string) (*StreamDescriptionT, error) { + return s.Server.DescribeStream(s.Arn, startShardId) +} + +func (s *Stream) LimitedDescribeStream(startShardId string, limit int64) (*StreamDescriptionT, error) { + return s.Server.LimitedDescribeStream(s.Arn, startShardId, limit) +} + +func (s *Server) GetShardIterator(streamArn, shardId, shardIteratorType, sequenceNumber string) (string, error) { + query := NewEmptyQuery() + query.AddStreamArn(streamArn) + query.AddShardId(shardId) + query.AddShardIteratorType(shardIteratorType) + + if len(sequenceNumber) != 0 { + query.AddSequenceNumber(sequenceNumber) + } + + jsonResponse, err := s.queryServer(streamsTarget("GetShardIterator"), query) + + if err != nil { + return "unknown", err + } + + json, err := simplejson.NewJson(jsonResponse) + + if err != nil { + return "unknown", err + } + + return json.Get("ShardIterator").MustString(), nil +} + +func (s *Stream) GetShardIterator(shardId, shardIteratorType, sequenceNumber string) (string, error) { + return s.Server.GetShardIterator(s.Arn, shardId, shardIteratorType, sequenceNumber) +} + +func (s *Server) GetRecords(shardIterator string) (string, []*RecordT, error) { + return s.LimitedGetRecords(shardIterator, 0) +} + +func (s *Server) LimitedGetRecords(shardIterator string, limit int64) (string, []*RecordT, error) { + query := NewEmptyQuery() + query.AddShardIterator(shardIterator) + + if limit > 0 { + query.AddLimit(limit) + } + + jsonResponse, err := s.queryServer(streamsTarget("GetRecords"), query) + if err != nil { + return "", nil, err + } + + jsonParsed, err := simplejson.NewJson(jsonResponse) + if err != nil { + return "", nil, err + } + + nextShardIt := "" + nextShardItJson, ok := jsonParsed.CheckGet("NextShardIterator") + if ok { + nextShardIt, err = nextShardItJson.String() + if err != nil { + message := fmt.Sprintf("Unexpected response %s", jsonResponse) + return "", nil, errors.New(message) + } + } + + recordsJson, ok := jsonParsed.CheckGet("Records") + if !ok { + return nextShardIt, nil, ErrNoRecords + } + + recordsArray, err := recordsJson.Array() + if err != nil { + message := fmt.Sprintf("Unexpected response %s", jsonResponse) + return nextShardIt, nil, errors.New(message) + } + + var records []*RecordT + for _, record := range recordsArray { + if recordMap, ok := record.(map[string]interface{}); ok { + r := parseRecord(recordMap) + records = append(records, r) + } + } + + return nextShardIt, records, nil +} + +func (s *Stream) GetRecords(shardIterator string) (string, []*RecordT, error) { + return s.Server.GetRecords(shardIterator) +} + +func (s *Stream) LimitedGetRecords(shardIterator string, limit int64) (string, []*RecordT, error) { + return s.Server.LimitedGetRecords(shardIterator, limit) +} + +func parseRecord(r map[string]interface{}) *RecordT { + record := RecordT{} + rValue := reflect.ValueOf(&record) + + keys := []string{"awsRegion", "eventID", "eventName", "eventSource", "eventVersion"} + for i, key := range keys { + if value, ok := r[key]; ok { + if valueStr, ok := value.(string); ok { + rValue.Elem().Field(i).SetString(valueStr) + } + } + } + + if streamRecord, ok := r["dynamodb"]; ok { + if streamRecordMap, ok := streamRecord.(map[string]interface{}); ok { + record.StreamRecord = parseStreamRecord(streamRecordMap) + } + } + + return &record +} + +func parseStreamRecord(s map[string]interface{}) *StreamRecordT { + sr := StreamRecordT{} + rValue := reflect.ValueOf(&sr) + + attrKeys := []string{"Keys", "NewImage", "OldImage"} + numAttrKeys := len(attrKeys) + for i, key := range attrKeys { + if value, ok := s[key]; ok { + if valueMap, ok := value.(map[string]interface{}); ok { + attrs := parseAttributes(valueMap) + rValue.Elem().Field(i).Set(reflect.ValueOf(attrs)) + } + } + } + + strKeys := []string{"SequenceNumber", "StreamViewType"} + numStrKeys := len(strKeys) + for i, key := range strKeys { + if value, ok := s[key]; ok { + if valueStr, ok := value.(string); ok { + rValue.Elem().Field(i + numAttrKeys).SetString(valueStr) + } + } + } + + intKeys := []string{"SizeBytes"} + for i, key := range intKeys { + if value, ok := s[key]; ok { + if valueNumber, ok := value.(json.Number); ok { + if valueInt, err := valueNumber.Int64(); err == nil { + rValue.Elem().Field(i + numAttrKeys + numStrKeys).SetInt(valueInt) + } + } + } + } + + return &sr +} diff --git a/vendor/github.com/goamz/goamz/dynamodb/stream_test.go b/vendor/github.com/goamz/goamz/dynamodb/stream_test.go new file mode 100755 index 000000000..a982ffa65 --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/stream_test.go @@ -0,0 +1,198 @@ +package dynamodb_test + +import ( + "strconv" + + "github.com/goamz/goamz/dynamodb" + . "gopkg.in/check.v1" +) + +type StreamSuite struct { + TableDescriptionT dynamodb.TableDescriptionT + DynamoDBTest +} + +func (s *StreamSuite) SetUpSuite(c *C) { + setUpAuth(c) + s.DynamoDBTest.TableDescriptionT = s.TableDescriptionT + s.server = &dynamodb.Server{dynamodb_auth, dynamodb_region} + pk, err := s.TableDescriptionT.BuildPrimaryKey() + if err != nil { + c.Skip(err.Error()) + } + s.table = s.server.NewTable(s.TableDescriptionT.TableName, pk) + + // Cleanup + s.TearDownSuite(c) + _, err = s.server.CreateTable(s.TableDescriptionT) + if err != nil { + c.Fatal(err) + } + s.WaitUntilStatus(c, "ACTIVE") +} + +var stream_suite_keys_only = &StreamSuite{ + TableDescriptionT: dynamodb.TableDescriptionT{ + TableName: "StreamTable", + AttributeDefinitions: []dynamodb.AttributeDefinitionT{ + dynamodb.AttributeDefinitionT{"TestHashKey", "S"}, + dynamodb.AttributeDefinitionT{"TestRangeKey", "N"}, + }, + KeySchema: []dynamodb.KeySchemaT{ + dynamodb.KeySchemaT{"TestHashKey", "HASH"}, + dynamodb.KeySchemaT{"TestRangeKey", "RANGE"}, + }, + ProvisionedThroughput: dynamodb.ProvisionedThroughputT{ + ReadCapacityUnits: 1, + WriteCapacityUnits: 1, + }, + StreamSpecification: dynamodb.StreamSpecificationT{ + StreamEnabled: true, + StreamViewType: "KEYS_ONLY", + }, + }, +} + +var stream_suite_new_image = &StreamSuite{ + TableDescriptionT: dynamodb.TableDescriptionT{ + TableName: "StreamTable", + AttributeDefinitions: []dynamodb.AttributeDefinitionT{ + dynamodb.AttributeDefinitionT{"TestHashKey", "S"}, + dynamodb.AttributeDefinitionT{"TestRangeKey", "N"}, + }, + KeySchema: []dynamodb.KeySchemaT{ + dynamodb.KeySchemaT{"TestHashKey", "HASH"}, + dynamodb.KeySchemaT{"TestRangeKey", "RANGE"}, + }, + ProvisionedThroughput: dynamodb.ProvisionedThroughputT{ + ReadCapacityUnits: 1, + WriteCapacityUnits: 1, + }, + StreamSpecification: dynamodb.StreamSpecificationT{ + StreamEnabled: true, + StreamViewType: "NEW_IMAGE", + }, + }, +} + +var _ = Suite(stream_suite_keys_only) +var _ = Suite(stream_suite_new_image) + +func (s *StreamSuite) TestStream(c *C) { + checkStream(s.table, c) +} + +func checkStream(table *dynamodb.Table, c *C) { + // list the table's streams + streams, err := table.ListStreams("") + if err != nil { + c.Fatal(err) + } + c.Check(len(streams), Not(Equals), 0) + c.Check(streams[0].TableName, Equals, table.Name) + + // stick a couple of items in the table + attrs := []dynamodb.Attribute{ + *dynamodb.NewStringAttribute("TestAttr", "0"), + } + if ok, err := table.PutItem("0", "0", attrs); !ok { + c.Fatal(err) + } + attrs = []dynamodb.Attribute{ + *dynamodb.NewStringAttribute("TestAttr", "1"), + } + if ok, err := table.PutItem("1", "1", attrs); !ok { + c.Fatal(err) + } + + // create a stream object + stream := table.Server.NewStream(streams[0].StreamArn) + + // describe the steam + desc, err := stream.DescribeStream("") + if err != nil { + c.Fatal(err) + } + + tableDesc, err := table.DescribeTable() + if err != nil { + c.Fatal(err) + } + + c.Check(desc.KeySchema[0], Equals, tableDesc.KeySchema[0]) + c.Check(desc.StreamArn, Equals, streams[0].StreamArn) + c.Check(desc.StreamStatus, Equals, "ENABLED") + c.Check(desc.StreamViewType, Equals, tableDesc.StreamSpecification.StreamViewType) + c.Check(desc.TableName, Equals, table.Name) + c.Check(len(desc.Shards), Equals, 1) + + // get a shard iterator + shardIt, err := stream.GetShardIterator(desc.Shards[0].ShardId, "TRIM_HORIZON", "") + if err != nil { + c.Fatal(err) + } + c.Check(len(shardIt), Not(Equals), 0) + + // poll for records + nextIt, records, err := stream.GetRecords(shardIt) + if err != nil { + c.Fatal(err) + } + c.Check(len(nextIt), Not(Equals), 0) + c.Check(len(records), Equals, 2) + + for index, record := range records { + c.Check(record.EventSource, Equals, "aws:dynamodb") + c.Check(record.EventName, Equals, "INSERT") + c.Check(len(record.EventID), Not(Equals), 0) + + // look at the actual record + streamRec := record.StreamRecord + c.Check(streamRec.StreamViewType, Equals, desc.StreamViewType) + c.Check(len(streamRec.SequenceNumber), Not(Equals), 0) + if streamRec.SizeBytes <= 0 { + c.Errorf("Expected greater-than-zero size, got: %d", streamRec.SizeBytes) + } + // check the keys + if streamRec.StreamViewType == "KEYS_ONLY" { + checkKeys(streamRec.Keys, index, c) + } + // check the image + if streamRec.StreamViewType == "NEW_IMAGE" { + checkNewImage(streamRec.NewImage, index, c) + } + } +} + +func checkKeys(keys map[string]*dynamodb.Attribute, expect int, c *C) { + c.Check(len(keys), Equals, 2) + value, err := strconv.Atoi(keys["TestHashKey"].Value) + if err != nil { + c.Fatal(err) + } + c.Check(value, Equals, expect) + value, err = strconv.Atoi(keys["TestRangeKey"].Value) + if err != nil { + c.Fatal(err) + } + c.Check(value, Equals, expect) +} + +func checkNewImage(image map[string]*dynamodb.Attribute, expect int, c *C) { + c.Check(len(image), Equals, 3) + value, err := strconv.Atoi(image["TestHashKey"].Value) + if err != nil { + c.Fatal(err) + } + c.Check(value, Equals, expect) + value, err = strconv.Atoi(image["TestRangeKey"].Value) + if err != nil { + c.Fatal(err) + } + c.Check(value, Equals, expect) + value, err = strconv.Atoi(image["TestAttr"].Value) + if err != nil { + c.Fatal(err) + } + c.Check(value, Equals, expect) +} diff --git a/vendor/github.com/goamz/goamz/dynamodb/table.go b/vendor/github.com/goamz/goamz/dynamodb/table.go new file mode 100755 index 000000000..541433c13 --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/table.go @@ -0,0 +1,259 @@ +package dynamodb + +import ( + "encoding/json" + "errors" + "fmt" + simplejson "github.com/bitly/go-simplejson" +) + +type Table struct { + Server *Server + Name string + Key PrimaryKey +} + +type AttributeDefinitionT struct { + Name string `json:"AttributeName"` + Type string `json:"AttributeType"` +} + +type KeySchemaT struct { + AttributeName string + KeyType string +} + +type ProjectionT struct { + ProjectionType string +} + +type GlobalSecondaryIndexT struct { + IndexName string + IndexSizeBytes int64 + ItemCount int64 + KeySchema []KeySchemaT + Projection ProjectionT + ProvisionedThroughput ProvisionedThroughputT +} + +type LocalSecondaryIndexT struct { + IndexName string + IndexSizeBytes int64 + ItemCount int64 + KeySchema []KeySchemaT + Projection ProjectionT +} + +type ProvisionedThroughputT struct { + NumberOfDecreasesToday int64 + ReadCapacityUnits int64 + WriteCapacityUnits int64 +} + +type StreamSpecificationT struct { + StreamEnabled bool + StreamViewType string +} + +type TableDescriptionT struct { + AttributeDefinitions []AttributeDefinitionT + CreationDateTime float64 + ItemCount int64 + KeySchema []KeySchemaT + GlobalSecondaryIndexes []GlobalSecondaryIndexT + LocalSecondaryIndexes []LocalSecondaryIndexT + ProvisionedThroughput ProvisionedThroughputT + StreamSpecification StreamSpecificationT + TableName string + TableSizeBytes int64 + TableStatus string + LatestStreamArn string + LatestStreamLabel string +} + +type describeTableResponse struct { + Table TableDescriptionT +} + +func findAttributeDefinitionByName(ads []AttributeDefinitionT, name string) *AttributeDefinitionT { + for _, a := range ads { + if a.Name == name { + return &a + } + } + return nil +} + +func (a *AttributeDefinitionT) GetEmptyAttribute() *Attribute { + switch a.Type { + case "S": + return NewStringAttribute(a.Name, "") + case "N": + return NewNumericAttribute(a.Name, "") + case "B": + return NewBinaryAttribute(a.Name, "") + default: + return nil + } +} + +func (t *TableDescriptionT) BuildPrimaryKey() (pk PrimaryKey, err error) { + for _, k := range t.KeySchema { + var attr *Attribute + ad := findAttributeDefinitionByName(t.AttributeDefinitions, k.AttributeName) + if ad == nil { + return pk, errors.New("An inconsistency found in TableDescriptionT") + } + attr = ad.GetEmptyAttribute() + if attr == nil { + return pk, errors.New("An inconsistency found in TableDescriptionT") + } + + switch k.KeyType { + case "HASH": + pk.KeyAttribute = attr + case "RANGE": + pk.RangeAttribute = attr + } + } + return +} + +func (s *Server) NewTable(name string, key PrimaryKey) *Table { + return &Table{s, name, key} +} + +func (s *Server) ListTables() ([]string, error) { + var tables []string + + query := NewEmptyQuery() + + jsonResponse, err := s.queryServer(target("ListTables"), query) + + if err != nil { + return nil, err + } + + json, err := simplejson.NewJson(jsonResponse) + + if err != nil { + return nil, err + } + + response, err := json.Get("TableNames").Array() + + if err != nil { + message := fmt.Sprintf("Unexpected response %s", jsonResponse) + return nil, errors.New(message) + } + + for _, value := range response { + if t, ok := (value).(string); ok { + tables = append(tables, t) + } + } + + return tables, nil +} + +func (s *Server) CreateTable(tableDescription TableDescriptionT) (string, error) { + query := NewEmptyQuery() + query.AddCreateRequestTable(tableDescription) + + jsonResponse, err := s.queryServer(target("CreateTable"), query) + + if err != nil { + return "unknown", err + } + + json, err := simplejson.NewJson(jsonResponse) + + if err != nil { + return "unknown", err + } + + return json.Get("TableDescription").Get("TableStatus").MustString(), nil +} + +func (s *Server) DeleteTable(tableDescription TableDescriptionT) (string, error) { + query := NewEmptyQuery() + query.AddDeleteRequestTable(tableDescription) + + jsonResponse, err := s.queryServer(target("DeleteTable"), query) + + if err != nil { + return "unknown", err + } + + json, err := simplejson.NewJson(jsonResponse) + + if err != nil { + return "unknown", err + } + + return json.Get("TableDescription").Get("TableStatus").MustString(), nil +} + +func (t *Table) DescribeTable() (*TableDescriptionT, error) { + return t.Server.DescribeTable(t.Name) +} + +func (s *Server) DescribeTable(name string) (*TableDescriptionT, error) { + q := NewEmptyQuery() + q.addTableByName(name) + + jsonResponse, err := s.queryServer(target("DescribeTable"), q) + if err != nil { + return nil, err + } + + var r describeTableResponse + err = json.Unmarshal(jsonResponse, &r) + if err != nil { + return nil, err + } + + return &r.Table, nil +} + +func (s *Server) UpdateTable(tableDescription TableDescriptionT) (string, error) { + query := NewEmptyQuery() + query.AddUpdateRequestTable(tableDescription) + + jsonResponse, err := s.queryServer(target("UpdateTable"), query) + + if err != nil { + return "unknown", err + } + + json, err := simplejson.NewJson(jsonResponse) + + if err != nil { + return "unknown", err + } + + return json.Get("TableDescription").Get("TableStatus").MustString(), nil +} + +func (t *Table) ListStreams(startArn string) ([]StreamListItemT, error) { + return t.Server.ListTableStreams(t.Name, startArn) +} + +func (t *Table) LimitedListStreams(startArn string, limit int64) ([]StreamListItemT, error) { + return t.Server.LimitedListTableStreams(t.Name, startArn, limit) +} + +func keyParam(k *PrimaryKey, hashKey string, rangeKey string) string { + value := fmt.Sprintf("{\"HashKeyElement\":{%s}", keyValue(k.KeyAttribute.Type, hashKey)) + + if k.RangeAttribute != nil { + value = fmt.Sprintf("%s,\"RangeKeyElement\":{%s}", value, + keyValue(k.RangeAttribute.Type, rangeKey)) + } + + return fmt.Sprintf("\"Key\":%s}", value) +} + +func keyValue(key string, value string) string { + return fmt.Sprintf("\"%s\":\"%s\"", key, value) +} diff --git a/vendor/github.com/goamz/goamz/dynamodb/table_test.go b/vendor/github.com/goamz/goamz/dynamodb/table_test.go new file mode 100755 index 000000000..8925bdc1b --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/table_test.go @@ -0,0 +1,79 @@ +package dynamodb_test + +import ( + "github.com/goamz/goamz/dynamodb" + . "gopkg.in/check.v1" +) + +type TableSuite struct { + TableDescriptionT dynamodb.TableDescriptionT + DynamoDBTest +} + +func (s *TableSuite) SetUpSuite(c *C) { + setUpAuth(c) + s.DynamoDBTest.TableDescriptionT = s.TableDescriptionT + s.server = &dynamodb.Server{dynamodb_auth, dynamodb_region} + pk, err := s.TableDescriptionT.BuildPrimaryKey() + if err != nil { + c.Skip(err.Error()) + } + s.table = s.server.NewTable(s.TableDescriptionT.TableName, pk) + + // Cleanup + s.TearDownSuite(c) +} + +var table_suite = &TableSuite{ + TableDescriptionT: dynamodb.TableDescriptionT{ + TableName: "DynamoDBTestMyTable", + AttributeDefinitions: []dynamodb.AttributeDefinitionT{ + dynamodb.AttributeDefinitionT{"TestHashKey", "S"}, + dynamodb.AttributeDefinitionT{"TestRangeKey", "N"}, + dynamodb.AttributeDefinitionT{"TestSecKey", "N"}, + }, + KeySchema: []dynamodb.KeySchemaT{ + dynamodb.KeySchemaT{"TestHashKey", "HASH"}, + dynamodb.KeySchemaT{"TestRangeKey", "RANGE"}, + }, + GlobalSecondaryIndexes: []dynamodb.GlobalSecondaryIndexT{ + dynamodb.GlobalSecondaryIndexT{ + IndexName: "gsiTest", + KeySchema: []dynamodb.KeySchemaT{ + dynamodb.KeySchemaT{"TestHashKey", "HASH"}, + dynamodb.KeySchemaT{"TestSecKey", "RANGE"}, + }, + Projection: dynamodb.ProjectionT{"ALL"}, + ProvisionedThroughput: dynamodb.ProvisionedThroughputT{ + ReadCapacityUnits: 1, + WriteCapacityUnits: 1, + }, + }, + }, + ProvisionedThroughput: dynamodb.ProvisionedThroughputT{ + ReadCapacityUnits: 1, + WriteCapacityUnits: 1, + }, + }, +} + +var _ = Suite(table_suite) + +func (s *TableSuite) TestCreateListTable(c *C) { + status, err := s.server.CreateTable(s.TableDescriptionT) + if err != nil { + c.Fatal(err) + } + if status != "ACTIVE" && status != "CREATING" { + c.Error("Expect status to be ACTIVE or CREATING") + } + + s.WaitUntilStatus(c, "ACTIVE") + + tables, err := s.server.ListTables() + if err != nil { + c.Fatal(err) + } + c.Check(len(tables), Not(Equals), 0) + c.Check(findTableByName(tables, s.TableDescriptionT.TableName), Equals, true) +} diff --git a/vendor/github.com/goamz/goamz/dynamodb/update_item.go b/vendor/github.com/goamz/goamz/dynamodb/update_item.go new file mode 100644 index 000000000..280eb4bed --- /dev/null +++ b/vendor/github.com/goamz/goamz/dynamodb/update_item.go @@ -0,0 +1,94 @@ +package dynamodb + +import simplejson "github.com/bitly/go-simplejson" + +/* +Construct an update item query. + +The query can be composed via chaining and then executed via Execute() + +Usage: + update := table.UpdateItem(key) + .ReturnValues(dynamodb.UPDATED_NEW) + .UpdateExpression("SET Counter = Counter + :incr") + .UpdateCondition("Counter < :checkVal") + .ExpressionAttributes(NewNumberAttribute(":incr", "1"), NewNumberAttribute(":checkVal", 42)) + result, err := update.Execute() + if err == nil { + log.Printf("Counter is now %v", result.Attributes["Counter"].Value) + } + +*/ +func (t *Table) UpdateItem(key *Key) *UpdateItem { + q := NewQuery(t) + q.AddKey(t, key) + return &UpdateItem{table: t, query: q} +} + +type UpdateItem struct { + table *Table + query *Query + hasReturnValues bool +} + +// Specify how return values are to be provided. +func (u *UpdateItem) ReturnValues(returnValues ReturnValues) *UpdateItem { + u.hasReturnValues = (returnValues != NONE) + u.query.AddReturnValues(returnValues) + return u +} + +/* +Specify an update expression and optional attribute settings at the same time. + + update.UpdateExpression("SET Foo = Foo + :incr", dynamodb.NewNumberAttribute(":incr", "7")) + +is equivalent to + + update.UpdateExpression("SET Foo = Foo + :incr") + .ExpressionAttributes(NewNumberAttribute(":incr", "7")) + +*/ +func (u *UpdateItem) UpdateExpression(expression string, attributes ...Attribute) *UpdateItem { + u.query.AddUpdateExpression(expression) + u.ExpressionAttributes(attributes...) + return u +} + +// Specify attribute substitutions to be used in expressions. +func (u *UpdateItem) ExpressionAttributes(attributes ...Attribute) *UpdateItem { + u.query.AddExpressionAttributes(attributes) + return u +} + +// Specify a check condition for conditional updates. +func (u *UpdateItem) ConditionExpression(expression string) *UpdateItem { + u.query.AddConditionExpression(expression) + return u +} + +// Execute this query. +func (u *UpdateItem) Execute() (*UpdateResult, error) { + jsonResponse, err := u.table.Server.queryServer(target("UpdateItem"), u.query) + + if err != nil { + return nil, err + } + + if u.hasReturnValues { + resp, err := simplejson.NewJson(jsonResponse) + if err != nil { + return nil, err + } + attrib, err := resp.Get("Attributes").Map() + if err != nil { + return nil, err + } + return &UpdateResult{parseAttributes(attrib)}, nil + } + return nil, nil +} + +type UpdateResult struct { + Attributes map[string]*Attribute +} |