summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/goamz/goamz/dynamodb
diff options
context:
space:
mode:
authorChristopher Speller <crspeller@gmail.com>2016-05-12 23:56:07 -0400
committerChristopher Speller <crspeller@gmail.com>2016-05-12 23:56:07 -0400
commit38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8 (patch)
treea4fde09672192b97d453ad605b030bd5a10c5a45 /vendor/github.com/goamz/goamz/dynamodb
parent84d2482ddbff9564c9ad75b2d30af66e3ddfd44d (diff)
downloadchat-38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8.tar.gz
chat-38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8.tar.bz2
chat-38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8.zip
Moving to glide
Diffstat (limited to 'vendor/github.com/goamz/goamz/dynamodb')
-rw-r--r--vendor/github.com/goamz/goamz/dynamodb/.gitignore1
-rw-r--r--vendor/github.com/goamz/goamz/dynamodb/Makefile13
-rw-r--r--vendor/github.com/goamz/goamz/dynamodb/README.md27
-rwxr-xr-xvendor/github.com/goamz/goamz/dynamodb/attribute.go185
-rw-r--r--vendor/github.com/goamz/goamz/dynamodb/const.go11
-rwxr-xr-xvendor/github.com/goamz/goamz/dynamodb/dynamodb.go142
-rwxr-xr-xvendor/github.com/goamz/goamz/dynamodb/dynamodb_test.go166
-rwxr-xr-xvendor/github.com/goamz/goamz/dynamodb/item.go351
-rw-r--r--vendor/github.com/goamz/goamz/dynamodb/item_test.go446
-rw-r--r--vendor/github.com/goamz/goamz/dynamodb/marshaller.go626
-rw-r--r--vendor/github.com/goamz/goamz/dynamodb/marshaller_test.go283
-rw-r--r--vendor/github.com/goamz/goamz/dynamodb/query.go111
-rw-r--r--vendor/github.com/goamz/goamz/dynamodb/query_builder.go362
-rwxr-xr-xvendor/github.com/goamz/goamz/dynamodb/query_builder_test.go380
-rw-r--r--vendor/github.com/goamz/goamz/dynamodb/scan.go51
-rw-r--r--vendor/github.com/goamz/goamz/dynamodb/stream.go307
-rwxr-xr-xvendor/github.com/goamz/goamz/dynamodb/stream_test.go198
-rwxr-xr-xvendor/github.com/goamz/goamz/dynamodb/table.go259
-rwxr-xr-xvendor/github.com/goamz/goamz/dynamodb/table_test.go79
-rw-r--r--vendor/github.com/goamz/goamz/dynamodb/update_item.go94
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
+}