diff --git a/README.md b/README.md index 86b216e..09df909 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## dynamo [![GoDoc](https://godoc.org/github.com/guregu/dynamo?status.svg)](https://godoc.org/github.com/guregu/dynamo) `import "github.com/guregu/dynamo"` -dynamo is an expressive [DynamoDB](https://aws.amazon.com/dynamodb/) client for Go, with an easy but powerful API. dynamo integrates with the official [AWS SDK](https://github.com/aws/aws-sdk-go/). +dynamo is an expressive [DynamoDB](https://aws.amazon.com/dynamodb/) client for Go, with an easy but powerful API. dynamo integrates with the official [AWS SDK v2](https://github.com/aws/aws-sdk-go-v2/). This library is stable and versioned with Go modules. @@ -12,9 +12,11 @@ package dynamo import ( "time" + "context" + "log" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" "github.com/guregu/dynamo" ) @@ -34,13 +36,16 @@ type widget struct { func main() { - sess := session.Must(session.NewSession()) - db := dynamo.New(sess, &aws.Config{Region: aws.String("us-west-2")}) + cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion("us-east-1")) + if err != nil { + log.Fatalf("unable to load SDK config, %v", err) + } + db := dynamo.New(cfg) table := db.Table("Widgets") // put item w := widget{UserID: 613, Time: time.Now(), Msg: "hello"} - err := table.Put(w).Run() + err = table.Put(w).Run() // get the same item var result widget diff --git a/batchget.go b/batchget.go index 4c1d68e..f38b98f 100644 --- a/batchget.go +++ b/batchget.go @@ -1,11 +1,13 @@ package dynamo import ( + "context" "errors" "fmt" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/aws/smithy-go/time" "github.com/cenkalti/backoff/v4" ) @@ -106,7 +108,7 @@ func (bg *BatchGet) All(out interface{}) error { } // AllWithContext executes this request and unmarshals all results to out, which must be a pointer to a slice. -func (bg *BatchGet) AllWithContext(ctx aws.Context, out interface{}) error { +func (bg *BatchGet) AllWithContext(ctx context.Context, out interface{}) error { iter := newBGIter(bg, unmarshalAppend, bg.err) for iter.NextWithContext(ctx, out) { } @@ -128,7 +130,7 @@ func (bg *BatchGet) input(start int) *dynamodb.BatchGetItemInput { } in := &dynamodb.BatchGetItemInput{ - RequestItems: make(map[string]*dynamodb.KeysAndAttributes, 1), + RequestItems: make(map[string]types.KeysAndAttributes, 1), } if bg.projection != "" { @@ -138,10 +140,10 @@ func (bg *BatchGet) input(start int) *dynamodb.BatchGetItemInput { } } if bg.cc != nil { - in.ReturnConsumedCapacity = aws.String(dynamodb.ReturnConsumedCapacityIndexes) + in.ReturnConsumedCapacity = types.ReturnConsumedCapacityIndexes } - var kas *dynamodb.KeysAndAttributes + var kas *types.KeysAndAttributes for _, get := range bg.reqs[start:end] { if kas == nil { kas = get.keysAndAttribs() @@ -155,7 +157,7 @@ func (bg *BatchGet) input(start int) *dynamodb.BatchGetItemInput { if bg.consistent { kas.ConsistentRead = &bg.consistent } - in.RequestItems[bg.batch.table.Name()] = kas + in.RequestItems[bg.batch.table.Name()] = *kas return in } @@ -201,7 +203,7 @@ func (itr *bgIter) Next(out interface{}) bool { return itr.NextWithContext(ctx, out) } -func (itr *bgIter) NextWithContext(ctx aws.Context, out interface{}) bool { +func (itr *bgIter) NextWithContext(ctx context.Context, out interface{}) bool { // stop if we have an error if ctx.Err() != nil { itr.err = ctx.Err() @@ -230,8 +232,12 @@ redo: if itr.output != nil && itr.idx >= len(itr.output.Responses[tableName]) { var unprocessed int - if itr.output.UnprocessedKeys != nil && itr.output.UnprocessedKeys[tableName] != nil { - unprocessed = len(itr.output.UnprocessedKeys[tableName].Keys) + + if itr.output.UnprocessedKeys != nil { + _, ok := itr.output.UnprocessedKeys[tableName] + if ok { + unprocessed = len(itr.output.UnprocessedKeys[tableName].Keys) + } } itr.processed += len(itr.input.RequestItems[tableName].Keys) - unprocessed // have we exhausted all results? @@ -248,7 +254,7 @@ redo: // no, prepare a new request with the remaining keys itr.input.RequestItems = itr.output.UnprocessedKeys // we need to sleep here a bit as per the official docs - if err := aws.SleepWithContext(ctx, itr.backoff.NextBackOff()); err != nil { + if err := time.SleepWithContext(ctx, itr.backoff.NextBackOff()); err != nil { // timed out itr.err = err return false @@ -259,7 +265,7 @@ redo: itr.err = retry(ctx, func() error { var err error - itr.output, err = itr.bg.batch.table.db.client.BatchGetItemWithContext(ctx, itr.input) + itr.output, err = itr.bg.batch.table.db.client.BatchGetItem(ctx, itr.input) return err }) if itr.err != nil { @@ -267,7 +273,7 @@ redo: } if itr.bg.cc != nil { for _, cc := range itr.output.ConsumedCapacity { - addConsumedCapacity(itr.bg.cc, cc) + addConsumedCapacity(itr.bg.cc, &cc) } } diff --git a/batchwrite.go b/batchwrite.go index 8661f55..5e15fec 100644 --- a/batchwrite.go +++ b/batchwrite.go @@ -1,10 +1,12 @@ package dynamo import ( + "context" "math" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/aws/smithy-go/time" "github.com/cenkalti/backoff/v4" ) @@ -14,7 +16,7 @@ const maxWriteOps = 25 // BatchWrite is a BatchWriteItem operation. type BatchWrite struct { batch Batch - ops []*dynamodb.WriteRequest + ops []types.WriteRequest err error cc *ConsumedCapacity } @@ -33,7 +35,7 @@ func (bw *BatchWrite) Put(items ...interface{}) *BatchWrite { for _, item := range items { encoded, err := marshalItem(item) bw.setError(err) - bw.ops = append(bw.ops, &dynamodb.WriteRequest{PutRequest: &dynamodb.PutRequest{ + bw.ops = append(bw.ops, types.WriteRequest{PutRequest: &types.PutRequest{ Item: encoded, }}) } @@ -48,7 +50,7 @@ func (bw *BatchWrite) Delete(keys ...Keyed) *BatchWrite { del.Range(bw.batch.rangeKey, rk) bw.setError(del.err) } - bw.ops = append(bw.ops, &dynamodb.WriteRequest{DeleteRequest: &dynamodb.DeleteRequest{ + bw.ops = append(bw.ops, types.WriteRequest{DeleteRequest: &types.DeleteRequest{ Key: del.key(), }}) } @@ -71,7 +73,7 @@ func (bw *BatchWrite) Run() (wrote int, err error) { return bw.RunWithContext(ctx) } -func (bw *BatchWrite) RunWithContext(ctx aws.Context) (wrote int, err error) { +func (bw *BatchWrite) RunWithContext(ctx context.Context) (wrote int, err error) { if bw.err != nil { return 0, bw.err } @@ -95,7 +97,7 @@ func (bw *BatchWrite) RunWithContext(ctx aws.Context) (wrote int, err error) { req := bw.input(ops) err := retry(ctx, func() error { var err error - res, err = bw.batch.table.db.client.BatchWriteItemWithContext(ctx, req) + res, err = bw.batch.table.db.client.BatchWriteItem(ctx, req) return err }) if err != nil { @@ -103,7 +105,7 @@ func (bw *BatchWrite) RunWithContext(ctx aws.Context) (wrote int, err error) { } if bw.cc != nil { for _, cc := range res.ConsumedCapacity { - addConsumedCapacity(bw.cc, cc) + addConsumedCapacity(bw.cc, &cc) } } @@ -115,7 +117,7 @@ func (bw *BatchWrite) RunWithContext(ctx aws.Context) (wrote int, err error) { ops = unprocessed // need to sleep when re-requesting, per spec - if err := aws.SleepWithContext(ctx, boff.NextBackOff()); err != nil { + if err := time.SleepWithContext(ctx, boff.NextBackOff()); err != nil { // timed out return wrote, err } @@ -125,14 +127,14 @@ func (bw *BatchWrite) RunWithContext(ctx aws.Context) (wrote int, err error) { return wrote, nil } -func (bw *BatchWrite) input(ops []*dynamodb.WriteRequest) *dynamodb.BatchWriteItemInput { +func (bw *BatchWrite) input(ops []types.WriteRequest) *dynamodb.BatchWriteItemInput { input := &dynamodb.BatchWriteItemInput{ - RequestItems: map[string][]*dynamodb.WriteRequest{ + RequestItems: map[string][]types.WriteRequest{ bw.batch.table.Name(): ops, }, } if bw.cc != nil { - input.ReturnConsumedCapacity = aws.String(dynamodb.ReturnConsumedCapacityIndexes) + input.ReturnConsumedCapacity = types.ReturnConsumedCapacityIndexes } return input } diff --git a/conditioncheck.go b/conditioncheck.go index 33ddfc9..d37ef39 100644 --- a/conditioncheck.go +++ b/conditioncheck.go @@ -1,8 +1,8 @@ package dynamo import ( - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) // ConditionCheck represents a condition for a write transaction to succeed. @@ -10,9 +10,9 @@ import ( type ConditionCheck struct { table Table hashKey string - hashValue *dynamodb.AttributeValue + hashValue types.AttributeValue rangeKey string - rangeValue *dynamodb.AttributeValue + rangeValue types.AttributeValue condition string subber @@ -66,11 +66,11 @@ func (check *ConditionCheck) IfNotExists() *ConditionCheck { return check.If("attribute_not_exists($)", check.hashKey) } -func (check *ConditionCheck) writeTxItem() (*dynamodb.TransactWriteItem, error) { +func (check *ConditionCheck) writeTxItem() (*types.TransactWriteItem, error) { if check.err != nil { return nil, check.err } - item := &dynamodb.ConditionCheck{ + item := &types.ConditionCheck{ TableName: aws.String(check.table.name), Key: check.keys(), ExpressionAttributeNames: check.nameExpr, @@ -79,13 +79,13 @@ func (check *ConditionCheck) writeTxItem() (*dynamodb.TransactWriteItem, error) if check.condition != "" { item.ConditionExpression = aws.String(check.condition) } - return &dynamodb.TransactWriteItem{ + return &types.TransactWriteItem{ ConditionCheck: item, }, nil } -func (check *ConditionCheck) keys() map[string]*dynamodb.AttributeValue { - keys := map[string]*dynamodb.AttributeValue{check.hashKey: check.hashValue} +func (check *ConditionCheck) keys() map[string]types.AttributeValue { + keys := map[string]types.AttributeValue{check.hashKey: check.hashValue} if check.rangeKey != "" { keys[check.rangeKey] = check.rangeValue } diff --git a/createtable.go b/createtable.go index 43c34e9..cdaf972 100644 --- a/createtable.go +++ b/createtable.go @@ -1,15 +1,17 @@ package dynamo import ( + "context" "encoding" "fmt" "reflect" "strconv" "strings" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" - "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) // StreamView determines what information is written to a table's stream. @@ -17,13 +19,13 @@ type StreamView string var ( // Only the key attributes of the modified item are written to the stream. - KeysOnlyView StreamView = dynamodb.StreamViewTypeKeysOnly + KeysOnlyView = StreamView(types.StreamViewTypeKeysOnly) // The entire item, as it appears after it was modified, is written to the stream. - NewImageView StreamView = dynamodb.StreamViewTypeNewImage + NewImageView = StreamView(types.StreamViewTypeNewImage) // The entire item, as it appeared before it was modified, is written to the stream. - OldImageView StreamView = dynamodb.StreamViewTypeOldImage + OldImageView = StreamView(types.StreamViewTypeOldImage) // Both the new and the old item images of the item are written to the stream. - NewAndOldImagesView StreamView = dynamodb.StreamViewTypeNewAndOldImages + NewAndOldImagesView = StreamView(types.StreamViewTypeNewAndOldImages) ) // IndexProjection determines which attributes are mirrored into indices. @@ -31,11 +33,11 @@ type IndexProjection string var ( // Only the key attributes of the modified item are written to the stream. - KeysOnlyProjection IndexProjection = dynamodb.ProjectionTypeKeysOnly + KeysOnlyProjection = IndexProjection(types.ProjectionTypeKeysOnly) // All of the table attributes are projected into the index. - AllProjection IndexProjection = dynamodb.ProjectionTypeAll + AllProjection = IndexProjection(types.ProjectionTypeAll) // Only the specified table attributes are projected into the index. - IncludeProjection IndexProjection = dynamodb.ProjectionTypeInclude + IncludeProjection = IndexProjection(types.ProjectionTypeInclude) ) // CreateTable is a request to create a new table. @@ -43,16 +45,16 @@ var ( type CreateTable struct { db *DB tableName string - attribs []*dynamodb.AttributeDefinition - schema []*dynamodb.KeySchemaElement - globalIndices map[string]dynamodb.GlobalSecondaryIndex - localIndices map[string]dynamodb.LocalSecondaryIndex + attribs []types.AttributeDefinition + schema []types.KeySchemaElement + globalIndices map[string]types.GlobalSecondaryIndex + localIndices map[string]types.LocalSecondaryIndex readUnits int64 writeUnits int64 streamView StreamView ondemand bool - tags []*dynamodb.Tag - encryptionSpecification *dynamodb.SSESpecification + tags []types.Tag + encryptionSpecification *types.SSESpecification err error } @@ -74,12 +76,12 @@ func (db *DB) CreateTable(name string, from interface{}) *CreateTable { ct := &CreateTable{ db: db, tableName: name, - schema: []*dynamodb.KeySchemaElement{}, - globalIndices: make(map[string]dynamodb.GlobalSecondaryIndex), - localIndices: make(map[string]dynamodb.LocalSecondaryIndex), + schema: []types.KeySchemaElement{}, + globalIndices: make(map[string]types.GlobalSecondaryIndex), + localIndices: make(map[string]types.LocalSecondaryIndex), readUnits: 1, writeUnits: 1, - tags: []*dynamodb.Tag{}, + tags: []types.Tag{}, } rv := reflect.ValueOf(from) ct.setError(ct.from(rv)) @@ -104,7 +106,7 @@ func (ct *CreateTable) Provision(readUnits, writeUnits int64) *CreateTable { // global secondary index. Local secondary indices share their capacity with the table. func (ct *CreateTable) ProvisionIndex(index string, readUnits, writeUnits int64) *CreateTable { idx := ct.globalIndices[index] - idx.ProvisionedThroughput = &dynamodb.ProvisionedThroughput{ + idx.ProvisionedThroughput = &types.ProvisionedThroughput{ ReadCapacityUnits: &readUnits, WriteCapacityUnits: &writeUnits, } @@ -122,19 +124,19 @@ func (ct *CreateTable) Stream(view StreamView) *CreateTable { // Project specifies the projection type for the given table. // When using IncludeProjection, you must specify the additional attributes to include via includeAttribs. func (ct *CreateTable) Project(index string, projection IndexProjection, includeAttribs ...string) *CreateTable { - projectionStr := string(projection) - proj := &dynamodb.Projection{ - ProjectionType: &projectionStr, + projectionStr := types.ProjectionType(projection) + proj := &types.Projection{ + ProjectionType: projectionStr, } if projection == IncludeProjection { attribs: for _, attr := range includeAttribs { for _, a := range proj.NonKeyAttributes { - if attr == *a { + if attr == a { continue attribs } } - proj.NonKeyAttributes = append(proj.NonKeyAttributes, &attr) + proj.NonKeyAttributes = append(proj.NonKeyAttributes, attr) } } if idx, global := ct.globalIndices[index]; global { @@ -152,27 +154,27 @@ func (ct *CreateTable) Project(index string, projection IndexProjection, include // Index specifies an index to add to this table. func (ct *CreateTable) Index(index Index) *CreateTable { ct.add(index.HashKey, string(index.HashKeyType)) - ks := []*dynamodb.KeySchemaElement{ + ks := []types.KeySchemaElement{ { AttributeName: &index.HashKey, - KeyType: aws.String(dynamodb.KeyTypeHash), + KeyType: types.KeyTypeHash, }, } if index.RangeKey != "" { ct.add(index.RangeKey, string(index.RangeKeyType)) - ks = append(ks, &dynamodb.KeySchemaElement{ + ks = append(ks, types.KeySchemaElement{ AttributeName: &index.RangeKey, - KeyType: aws.String(dynamodb.KeyTypeRange), + KeyType: types.KeyTypeRange, }) } - var proj *dynamodb.Projection + var proj *types.Projection if index.ProjectionType != "" { - proj = &dynamodb.Projection{ - ProjectionType: aws.String((string)(index.ProjectionType)), + proj = &types.Projection{ + ProjectionType: types.ProjectionType(index.ProjectionType), } if index.ProjectionType == IncludeProjection { - proj.NonKeyAttributes = aws.StringSlice(index.ProjectionAttribs) + proj.NonKeyAttributes = index.ProjectionAttribs } } @@ -189,7 +191,7 @@ func (ct *CreateTable) Index(index Index) *CreateTable { idx := ct.globalIndices[index.Name] idx.KeySchema = ks if index.Throughput.Read != 0 || index.Throughput.Write != 0 { - idx.ProvisionedThroughput = &dynamodb.ProvisionedThroughput{ + idx.ProvisionedThroughput = &types.ProvisionedThroughput{ ReadCapacityUnits: &index.Throughput.Read, WriteCapacityUnits: &index.Throughput.Write, } @@ -209,7 +211,7 @@ func (ct *CreateTable) Tag(key, value string) *CreateTable { return ct } } - tag := &dynamodb.Tag{ + tag := types.Tag{ Key: aws.String(key), Value: aws.String(value), } @@ -220,12 +222,12 @@ func (ct *CreateTable) Tag(key, value string) *CreateTable { // SSEEncryption specifies the server side encryption for this table. // Encryption is disabled by default. func (ct *CreateTable) SSEEncryption(enabled bool, keyID string, sseType SSEType) *CreateTable { - encryption := &dynamodb.SSESpecification{ + encryption := types.SSESpecification{ Enabled: aws.Bool(enabled), KMSMasterKeyId: aws.String(keyID), - SSEType: aws.String(string(sseType)), + SSEType: types.SSEType(string(sseType)), } - ct.encryptionSpecification = encryption + ct.encryptionSpecification = &encryption return ct } @@ -237,14 +239,14 @@ func (ct *CreateTable) Run() error { } // RunWithContext creates this table or returns an error. -func (ct *CreateTable) RunWithContext(ctx aws.Context) error { +func (ct *CreateTable) RunWithContext(ctx context.Context) error { if ct.err != nil { return ct.err } input := ct.input() return retry(ctx, func() error { - _, err := ct.db.client.CreateTableWithContext(ctx, input) + _, err := ct.db.client.CreateTable(ctx, input) return err }) } @@ -257,7 +259,7 @@ func (ct *CreateTable) Wait() error { } // WaitWithContext creates this table and blocks until it exists and is ready to use. -func (ct *CreateTable) WaitWithContext(ctx aws.Context) error { +func (ct *CreateTable) WaitWithContext(ctx context.Context) error { if err := ct.RunWithContext(ctx); err != nil { return err } @@ -293,9 +295,9 @@ func (ct *CreateTable) from(rv reflect.Value) error { // primary keys if keyType := keyTypeFromTag(field.Tag.Get("dynamo")); keyType != "" { ct.add(name, typeOf(fv, field.Tag.Get("dynamo"))) - ct.schema = append(ct.schema, &dynamodb.KeySchemaElement{ + ct.schema = append(ct.schema, types.KeySchemaElement{ AttributeName: &name, - KeyType: &keyType, + KeyType: types.KeyType(keyType), }) } @@ -306,9 +308,9 @@ func (ct *CreateTable) from(rv reflect.Value) error { keyType := keyTypeFromTag(index) indexName := index[:len(index)-len(keyType)-1] idx := ct.globalIndices[indexName] - idx.KeySchema = append(idx.KeySchema, &dynamodb.KeySchemaElement{ + idx.KeySchema = append(idx.KeySchema, types.KeySchemaElement{ AttributeName: &name, - KeyType: &keyType, + KeyType: types.KeyType(keyType), }) ct.globalIndices[indexName] = idx } @@ -321,9 +323,9 @@ func (ct *CreateTable) from(rv reflect.Value) error { keyType := keyTypeFromTag(localIndex) indexName := localIndex[:len(localIndex)-len(keyType)-1] idx := ct.localIndices[indexName] - idx.KeySchema = append(idx.KeySchema, &dynamodb.KeySchemaElement{ + idx.KeySchema = append(idx.KeySchema, types.KeySchemaElement{ AttributeName: &name, - KeyType: &keyType, + KeyType: types.KeyType(keyType), }) ct.localIndices[indexName] = idx } @@ -342,9 +344,9 @@ func (ct *CreateTable) input() *dynamodb.CreateTableInput { SSESpecification: ct.encryptionSpecification, } if ct.ondemand { - input.BillingMode = aws.String(dynamodb.BillingModePayPerRequest) + input.BillingMode = types.BillingModePayPerRequest } else { - input.ProvisionedThroughput = &dynamodb.ProvisionedThroughput{ + input.ProvisionedThroughput = &types.ProvisionedThroughput{ ReadCapacityUnits: &ct.readUnits, WriteCapacityUnits: &ct.writeUnits, } @@ -352,9 +354,9 @@ func (ct *CreateTable) input() *dynamodb.CreateTableInput { if ct.streamView != "" { enabled := true view := string(ct.streamView) - input.StreamSpecification = &dynamodb.StreamSpecification{ + input.StreamSpecification = &types.StreamSpecification{ StreamEnabled: &enabled, - StreamViewType: &view, + StreamViewType: types.StreamViewType(view), } } for name, idx := range ct.localIndices { @@ -362,40 +364,40 @@ func (ct *CreateTable) input() *dynamodb.CreateTableInput { idx.IndexName = &name if idx.Projection == nil { all := string(AllProjection) - idx.Projection = &dynamodb.Projection{ - ProjectionType: &all, + idx.Projection = &types.Projection{ + ProjectionType: types.ProjectionType(all), } } // add the primary hash key if len(idx.KeySchema) == 1 { - idx.KeySchema = []*dynamodb.KeySchemaElement{ + idx.KeySchema = []types.KeySchemaElement{ ct.schema[0], idx.KeySchema[0], } } sortKeySchemas(idx.KeySchema) - input.LocalSecondaryIndexes = append(input.LocalSecondaryIndexes, &idx) + input.LocalSecondaryIndexes = append(input.LocalSecondaryIndexes, idx) } for name, idx := range ct.globalIndices { name, idx := name, idx idx.IndexName = &name if idx.Projection == nil { all := string(AllProjection) - idx.Projection = &dynamodb.Projection{ - ProjectionType: &all, + idx.Projection = &types.Projection{ + ProjectionType: types.ProjectionType(all), } } if ct.ondemand { idx.ProvisionedThroughput = nil } else if idx.ProvisionedThroughput == nil { units := int64(1) - idx.ProvisionedThroughput = &dynamodb.ProvisionedThroughput{ + idx.ProvisionedThroughput = &types.ProvisionedThroughput{ ReadCapacityUnits: &units, WriteCapacityUnits: &units, } } sortKeySchemas(idx.KeySchema) - input.GlobalSecondaryIndexes = append(input.GlobalSecondaryIndexes, &idx) + input.GlobalSecondaryIndexes = append(input.GlobalSecondaryIndexes, idx) } if len(ct.tags) > 0 { input.Tags = ct.tags @@ -415,9 +417,9 @@ func (ct *CreateTable) add(name string, typ string) { } } - ct.attribs = append(ct.attribs, &dynamodb.AttributeDefinition{ + ct.attribs = append(ct.attribs, types.AttributeDefinition{ AttributeName: &name, - AttributeType: &typ, + AttributeType: types.ScalarAttributeType(typ), }) } @@ -444,9 +446,9 @@ func typeOf(rv reflect.Value, tag string) string { return typeOf(reflect.ValueOf(iface), tag) } } - case dynamodbattribute.Marshaler: - av := &dynamodb.AttributeValue{} - if err := x.MarshalDynamoDBAttributeValue(av); err == nil { + case attributevalue.Marshaler: + + if av, err := x.MarshalDynamoDBAttributeValue(); err == nil { if iface, err := av2iface(av); err == nil { return typeOf(reflect.ValueOf(iface), tag) } @@ -477,7 +479,7 @@ check: return "" } -func keyTypeFromTag(tag string) string { +func keyTypeFromTag(tag string) types.KeyType { split := strings.Split(tag, ",") if len(split) <= 1 { return "" @@ -485,16 +487,16 @@ func keyTypeFromTag(tag string) string { for _, v := range split[1:] { switch v { case "hash", "partition": - return dynamodb.KeyTypeHash + return types.KeyTypeHash case "range", "sort": - return dynamodb.KeyTypeRange + return types.KeyTypeRange } } return "" } -func sortKeySchemas(schemas []*dynamodb.KeySchemaElement) { - if *schemas[0].KeyType == dynamodb.KeyTypeRange { +func sortKeySchemas(schemas []types.KeySchemaElement) { + if schemas[0].KeyType == types.KeyTypeRange { schemas[0], schemas[1] = schemas[1], schemas[0] } } diff --git a/createtable_test.go b/createtable_test.go index 4e47c29..248620e 100644 --- a/createtable_test.go +++ b/createtable_test.go @@ -5,9 +5,10 @@ import ( "testing" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" - "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) type UserAction struct { @@ -23,8 +24,8 @@ type embeddedWithKeys struct { } type Metric struct { - ID uint64 `dynamo:"ID,hash"` - Time dynamodbattribute.UnixTime `dynamo:",range"` + ID uint64 `dynamo:"ID,hash"` + Time attributevalue.UnixTime `dynamo:",range"` Value uint64 } @@ -50,73 +51,73 @@ func TestCreateTable(t *testing.T) { input() expected := &dynamodb.CreateTableInput{ - AttributeDefinitions: []*dynamodb.AttributeDefinition{ + AttributeDefinitions: []types.AttributeDefinition{ { AttributeName: aws.String("ID"), - AttributeType: aws.String("S"), + AttributeType: types.ScalarAttributeTypeS, }, { AttributeName: aws.String("Time"), - AttributeType: aws.String("S"), + AttributeType: types.ScalarAttributeTypeS, }, { AttributeName: aws.String("Seq"), - AttributeType: aws.String("N"), + AttributeType: types.ScalarAttributeTypeN, }, { AttributeName: aws.String("Embedded"), - AttributeType: aws.String("B"), + AttributeType: types.ScalarAttributeTypeB, }, }, - GlobalSecondaryIndexes: []*dynamodb.GlobalSecondaryIndex{{ + GlobalSecondaryIndexes: []types.GlobalSecondaryIndex{{ IndexName: aws.String("Embedded-index"), - KeySchema: []*dynamodb.KeySchemaElement{{ + KeySchema: []types.KeySchemaElement{{ AttributeName: aws.String("Embedded"), - KeyType: aws.String("HASH"), + KeyType: types.KeyTypeHash, }}, - Projection: &dynamodb.Projection{ - ProjectionType: aws.String("ALL"), + Projection: &types.Projection{ + ProjectionType: types.ProjectionTypeAll, }, - ProvisionedThroughput: &dynamodb.ProvisionedThroughput{ + ProvisionedThroughput: &types.ProvisionedThroughput{ ReadCapacityUnits: aws.Int64(1), WriteCapacityUnits: aws.Int64(2), }, }}, - KeySchema: []*dynamodb.KeySchemaElement{{ + KeySchema: []types.KeySchemaElement{{ AttributeName: aws.String("ID"), - KeyType: aws.String("HASH"), + KeyType: types.KeyTypeHash, }, { AttributeName: aws.String("Time"), - KeyType: aws.String("RANGE"), + KeyType: types.KeyTypeRange, }}, - LocalSecondaryIndexes: []*dynamodb.LocalSecondaryIndex{{ + LocalSecondaryIndexes: []types.LocalSecondaryIndex{{ IndexName: aws.String("ID-Seq-index"), - KeySchema: []*dynamodb.KeySchemaElement{{ + KeySchema: []types.KeySchemaElement{{ AttributeName: aws.String("ID"), - KeyType: aws.String("HASH"), + KeyType: types.KeyTypeHash, }, { AttributeName: aws.String("Seq"), - KeyType: aws.String("RANGE"), + KeyType: types.KeyTypeRange, }}, - Projection: &dynamodb.Projection{ - ProjectionType: aws.String("INCLUDE"), - NonKeyAttributes: []*string{aws.String("UUID")}, + Projection: &types.Projection{ + ProjectionType: types.ProjectionTypeInclude, + NonKeyAttributes: []string{"UUID"}, }, }}, - ProvisionedThroughput: &dynamodb.ProvisionedThroughput{ + ProvisionedThroughput: &types.ProvisionedThroughput{ ReadCapacityUnits: aws.Int64(4), WriteCapacityUnits: aws.Int64(2), }, - Tags: []*dynamodb.Tag{ + Tags: []types.Tag{ { Key: aws.String("Tag-Key"), Value: aws.String("Tag-Value"), }, }, - SSESpecification: &dynamodb.SSESpecification{ + SSESpecification: &types.SSESpecification{ Enabled: aws.Bool(true), KMSMasterKeyId: aws.String("alias/key"), - SSEType: aws.String("KMS"), + SSEType: types.SSEType("KMS"), }, TableName: aws.String("UserActions"), } @@ -134,24 +135,24 @@ func TestCreateTableUintUnixTime(t *testing.T) { OnDemand(true). input() expected := &dynamodb.CreateTableInput{ - AttributeDefinitions: []*dynamodb.AttributeDefinition{ + AttributeDefinitions: []types.AttributeDefinition{ { AttributeName: aws.String("ID"), - AttributeType: aws.String("N"), + AttributeType: types.ScalarAttributeTypeN, }, { AttributeName: aws.String("Time"), - AttributeType: aws.String("N"), + AttributeType: types.ScalarAttributeTypeN, }, }, - KeySchema: []*dynamodb.KeySchemaElement{{ + KeySchema: []types.KeySchemaElement{{ AttributeName: aws.String("ID"), - KeyType: aws.String("HASH"), + KeyType: types.KeyTypeHash, }, { AttributeName: aws.String("Time"), - KeyType: aws.String("RANGE"), + KeyType: types.KeyTypeRange, }}, - BillingMode: aws.String(dynamodb.BillingModePayPerRequest), + BillingMode: types.BillingModePayPerRequest, TableName: aws.String("Metrics"), } if !reflect.DeepEqual(input, expected) { diff --git a/db.go b/db.go index 0b9f805..8743eb3 100644 --- a/db.go +++ b/db.go @@ -2,31 +2,33 @@ package dynamo import ( + "context" "errors" "fmt" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/aws/client" - "github.com/aws/aws-sdk-go/service/dynamodb" - "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface" + "os" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/aws/smithy-go" + "github.com/aws/smithy-go/logging" + "github.com/guregu/dynamo/dynamodbiface" ) // DB is a DynamoDB client. type DB struct { client dynamodbiface.DynamoDBAPI - logger aws.Logger + logger logging.Logger } // New creates a new client with the given configuration. -func New(p client.ConfigProvider, cfgs ...*aws.Config) *DB { - cfg := p.ClientConfig(dynamodb.EndpointsID, cfgs...) +func New(cfg aws.Config) *DB { db := &DB{ - client: dynamodb.New(p, cfgs...), - logger: cfg.Config.Logger, + client: dynamodb.NewFromConfig(cfg), + logger: cfg.Logger, } if db.logger == nil { - db.logger = aws.NewDefaultLogger() + db.logger = logging.NewStandardLogger(os.Stdout) } return db } @@ -35,7 +37,7 @@ func New(p client.ConfigProvider, cfgs ...*aws.Config) *DB { func NewFromIface(client dynamodbiface.DynamoDBAPI) *DB { return &DB{ client: client, - logger: aws.NewDefaultLogger(), + logger: logging.NewStandardLogger(os.Stdout), } } @@ -44,8 +46,8 @@ func (db *DB) Client() dynamodbiface.DynamoDBAPI { return db.client } -func (db *DB) log(v ...interface{}) { - db.logger.Log(v...) +func (db *DB) log(format string, v ...interface{}) { + db.logger.Logf(logging.Debug, format, v...) } // ListTables is a request to list tables. @@ -67,7 +69,7 @@ func (lt *ListTables) All() ([]string, error) { } // AllWithContext returns every table or an error. -func (lt *ListTables) AllWithContext(ctx aws.Context) ([]string, error) { +func (lt *ListTables) AllWithContext(ctx context.Context) ([]string, error) { var tables []string itr := lt.Iter() var name string @@ -96,7 +98,7 @@ func (itr *ltIter) Next(out interface{}) bool { return itr.NextWithContext(ctx, out) } -func (itr *ltIter) NextWithContext(ctx aws.Context, out interface{}) bool { +func (itr *ltIter) NextWithContext(ctx context.Context, out interface{}) bool { if ctx.Err() != nil { itr.err = ctx.Err() } @@ -111,7 +113,7 @@ func (itr *ltIter) NextWithContext(ctx aws.Context, out interface{}) bool { if itr.result != nil { if itr.idx < len(itr.result.TableNames) { - *out.(*string) = *itr.result.TableNames[itr.idx] + *out.(*string) = itr.result.TableNames[itr.idx] itr.idx++ return true } @@ -123,7 +125,7 @@ func (itr *ltIter) NextWithContext(ctx aws.Context, out interface{}) bool { } itr.err = retry(ctx, func() error { - res, err := itr.lt.db.client.ListTablesWithContext(ctx, itr.input()) + res, err := itr.lt.db.client.ListTables(ctx, itr.input()) if err != nil { return err } @@ -138,7 +140,7 @@ func (itr *ltIter) NextWithContext(ctx aws.Context, out interface{}) bool { return false } - *out.(*string) = *itr.result.TableNames[0] + *out.(*string) = itr.result.TableNames[0] itr.idx = 1 return true } @@ -162,7 +164,7 @@ type Iter interface { Next(out interface{}) bool // NextWithContext tries to unmarshal the next result into out. // Returns false when it is complete or if it runs into an error. - NextWithContext(ctx aws.Context, out interface{}) bool + NextWithContext(ctx context.Context, out interface{}) bool // Err returns the error encountered, if any. // You should check this after Next is finished. Err() error @@ -179,13 +181,13 @@ type PagingIter interface { // PagingKey is a key used for splitting up partial results. // Get a PagingKey from a PagingIter and pass it to StartFrom in Query or Scan. -type PagingKey map[string]*dynamodb.AttributeValue +type PagingKey map[string]types.AttributeValue // IsCondCheckFailed returns true if the given error is a "conditional check failed" error. // This corresponds with a ConditionalCheckFailedException in most APIs, // or a TransactionCanceledException with a ConditionalCheckFailed cancellation reason in transactions. func IsCondCheckFailed(err error) bool { - var txe *dynamodb.TransactionCanceledException + var txe *types.TransactionCanceledException if errors.As(err, &txe) { for _, cr := range txe.CancellationReasons { if cr.Code != nil && *cr.Code == "ConditionalCheckFailed" { @@ -195,8 +197,8 @@ func IsCondCheckFailed(err error) bool { return false } - var ae awserr.Error - if errors.As(err, &ae) && ae.Code() == "ConditionalCheckFailedException" { + var ae smithy.APIError + if errors.As(err, &ae) && ae.ErrorCode() == "ConditionalCheckFailedException" { return true } diff --git a/db_test.go b/db_test.go index 1948901..03db273 100644 --- a/db_test.go +++ b/db_test.go @@ -1,12 +1,14 @@ package dynamo import ( + "context" + "log" "os" "testing" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" ) var ( @@ -22,11 +24,21 @@ func init() { if dte := os.Getenv("DYNAMO_TEST_ENDPOINT"); dte != "" { endpoint = aws.String(dte) } - testDB = New(session.Must(session.NewSession()), &aws.Config{ - Region: aws.String(region), - Endpoint: endpoint, - // LogLevel: aws.LogLevel(aws.LogDebugWithHTTPBody), - }) + cfg, err := config.LoadDefaultConfig( + context.Background(), + config.WithRegion(region), + config.WithEndpointResolverWithOptions( + aws.EndpointResolverWithOptionsFunc( + func(service, region string, options ...interface{}) (aws.Endpoint, error) { + return aws.Endpoint{URL: *endpoint}, nil + }, + ), + ), + ) + if err != nil { + log.Fatal(err) + } + testDB = New(cfg) } if table := os.Getenv("DYNAMO_TEST_TABLE"); table != "" { testTable = table diff --git a/decode.go b/decode.go index 29a4c30..81e1327 100644 --- a/decode.go +++ b/decode.go @@ -7,41 +7,41 @@ import ( "strconv" "time" - "github.com/aws/aws-sdk-go/service/dynamodb" - "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" + "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) // Unmarshaler is the interface implemented by objects that can unmarshal // an AttributeValue into themselves. type Unmarshaler interface { - UnmarshalDynamo(av *dynamodb.AttributeValue) error + UnmarshalDynamo(av types.AttributeValue) error } // ItemUnmarshaler is the interface implemented by objects that can unmarshal // an Item (a map of strings to AttributeValues) into themselves. type ItemUnmarshaler interface { - UnmarshalDynamoItem(item map[string]*dynamodb.AttributeValue) error + UnmarshalDynamoItem(item map[string]types.AttributeValue) error } // Unmarshal decodes a DynamoDB item into out, which must be a pointer. -func UnmarshalItem(item map[string]*dynamodb.AttributeValue, out interface{}) error { +func UnmarshalItem(item map[string]types.AttributeValue, out interface{}) error { return unmarshalItem(item, out) } // Unmarshal decodes a DynamoDB value into out, which must be a pointer. -func Unmarshal(av *dynamodb.AttributeValue, out interface{}) error { +func Unmarshal(av types.AttributeValue, out interface{}) error { rv := reflect.ValueOf(out) return unmarshalReflect(av, rv) } // used in iterators for unmarshaling one item -type unmarshalFunc func(map[string]*dynamodb.AttributeValue, interface{}) error +type unmarshalFunc func(map[string]types.AttributeValue, interface{}) error var nilTum encoding.TextUnmarshaler var tumType = reflect.TypeOf(&nilTum).Elem() // unmarshals one value -func unmarshalReflect(av *dynamodb.AttributeValue, rv reflect.Value) error { +func unmarshalReflect(av types.AttributeValue, rv reflect.Value) error { // first try interface unmarshal stuff if rv.CanInterface() { var iface interface{} @@ -51,29 +51,73 @@ func unmarshalReflect(av *dynamodb.AttributeValue, rv reflect.Value) error { iface = rv.Interface() } - if x, ok := iface.(*time.Time); ok && av.N != nil { - // implicit unixtime - // TODO(guregu): have unixtime unmarshal explicitly check struct tags - ts, err := strconv.ParseInt(*av.N, 10, 64) - if err != nil { - return err - } + if x, ok := iface.(*time.Time); ok { + if t, ok := av.(*types.AttributeValueMemberN); ok { - *x = time.Unix(ts, 0).UTC() - return nil + // implicit unixtime + // TODO(guregu): have unixtime unmarshal explicitly check struct tags + ts, err := strconv.ParseInt(t.Value, 10, 64) + if err != nil { + return err + } + + *x = time.Unix(ts, 0).UTC() + return nil + } } switch x := iface.(type) { - case *dynamodb.AttributeValue: - *x = *av - return nil + case types.AttributeValue: + switch x.(type) { + case *types.AttributeValueMemberB: + res := av.(*types.AttributeValueMemberB) + *x.(*types.AttributeValueMemberB) = *res + return nil + case *types.AttributeValueMemberBOOL: + res := av.(*types.AttributeValueMemberBOOL) + *x.(*types.AttributeValueMemberBOOL) = *res + return nil + case *types.AttributeValueMemberBS: + res := av.(*types.AttributeValueMemberBS) + *x.(*types.AttributeValueMemberBS) = *res + return nil + case *types.AttributeValueMemberL: + res := av.(*types.AttributeValueMemberL) + *x.(*types.AttributeValueMemberL) = *res + return nil + case *types.AttributeValueMemberM: + res := av.(*types.AttributeValueMemberM) + *x.(*types.AttributeValueMemberM) = *res + return nil + case *types.AttributeValueMemberN: + res := av.(*types.AttributeValueMemberN) + *x.(*types.AttributeValueMemberN) = *res + return nil + case *types.AttributeValueMemberNS: + res := av.(*types.AttributeValueMemberNS) + *x.(*types.AttributeValueMemberNS) = *res + return nil + case *types.AttributeValueMemberNULL: + res := av.(*types.AttributeValueMemberNULL) + *x.(*types.AttributeValueMemberNULL) = *res + return nil + case *types.AttributeValueMemberS: + res := av.(*types.AttributeValueMemberS) + *x.(*types.AttributeValueMemberS) = *res + return nil + case *types.AttributeValueMemberSS: + res := av.(*types.AttributeValueMemberSS) + *x.(*types.AttributeValueMemberSS) = *res + return nil + } + case Unmarshaler: return x.UnmarshalDynamo(av) - case dynamodbattribute.Unmarshaler: + case attributevalue.Unmarshaler: return x.UnmarshalDynamoDBAttributeValue(av) case encoding.TextUnmarshaler: - if av.S != nil { - return x.UnmarshalText([]byte(*av.S)) + if value, ok := av.(*types.AttributeValueMemberS); ok && value != nil { + return x.UnmarshalText([]byte(value.Value)) } } } @@ -81,8 +125,8 @@ func unmarshalReflect(av *dynamodb.AttributeValue, rv reflect.Value) error { if !rv.CanSet() { return nil } - - if av.NULL != nil { + nullValue, valueIsNull := av.(*types.AttributeValueMemberNULL) + if valueIsNull { rv.Set(reflect.Zero(rv.Type())) return nil } @@ -91,57 +135,63 @@ func unmarshalReflect(av *dynamodb.AttributeValue, rv reflect.Value) error { case reflect.Ptr: pt := reflect.New(rv.Type().Elem()) rv.Set(pt) - if av.NULL == nil || !(*av.NULL) { + if !valueIsNull || (valueIsNull && !nullValue.Value) { return unmarshalReflect(av, rv.Elem()) } return nil case reflect.Bool: - if av.BOOL == nil { - return fmt.Errorf("dynamo: cannot unmarshal %s data into bool", avTypeName(av)) + boolValue, valueIsBool := av.(*types.AttributeValueMemberBOOL) + if !valueIsBool { + return fmt.Errorf("dynamo: cannot unmarshal %s data into bool", avTypeName(boolValue)) } - rv.SetBool(*av.BOOL) + rv.SetBool(boolValue.Value) return nil case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: - if av.N == nil { + nValue, valueIsN := av.(*types.AttributeValueMemberN) + if !valueIsN { return fmt.Errorf("dynamo: cannot unmarshal %s data into int", avTypeName(av)) } - n, err := strconv.ParseInt(*av.N, 10, 64) + n, err := strconv.ParseInt(nValue.Value, 10, 64) if err != nil { return err } rv.SetInt(n) return nil case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8: - if av.N == nil { + nValue, valueIsN := av.(*types.AttributeValueMemberN) + if !valueIsN { return fmt.Errorf("dynamo: cannot unmarshal %s data into uint", avTypeName(av)) } - n, err := strconv.ParseUint(*av.N, 10, 64) + n, err := strconv.ParseUint(nValue.Value, 10, 64) if err != nil { return err } rv.SetUint(n) return nil case reflect.Float64, reflect.Float32: - if av.N == nil { + nValue, valueIsN := av.(*types.AttributeValueMemberN) + if !valueIsN { return fmt.Errorf("dynamo: cannot unmarshal %s data into float", avTypeName(av)) } - n, err := strconv.ParseFloat(*av.N, 64) + n, err := strconv.ParseFloat(nValue.Value, 64) if err != nil { return err } rv.SetFloat(n) return nil case reflect.String: - if av.S == nil { + sValue, valueIsS := av.(*types.AttributeValueMemberS) + if !valueIsS { return fmt.Errorf("dynamo: cannot unmarshal %s data into string", avTypeName(av)) } - rv.SetString(*av.S) + rv.SetString(sValue.Value) return nil case reflect.Struct: - if av.M == nil { + mValue, valueIsM := av.(*types.AttributeValueMemberM) + if !valueIsM { return fmt.Errorf("dynamo: cannot unmarshal %s data into struct", avTypeName(av)) } - if err := unmarshalItem(av.M, rv.Addr().Interface()); err != nil { + if err := unmarshalItem(mValue.Value, rv.Addr().Interface()); err != nil { return err } return nil @@ -161,17 +211,19 @@ func unmarshalReflect(av *dynamodb.AttributeValue, rv reflect.Value) error { case rv.Type().Elem().Kind() == reflect.Struct && rv.Type().Elem().NumField() == 0: truthy = reflect.ValueOf(struct{}{}) default: - if av.M == nil { + _, valueIsM := av.(*types.AttributeValueMemberM) + if !valueIsM { return fmt.Errorf("dynamo: unmarshal map set: value type must be struct{} or bool, got %v", rv.Type()) } } - switch { - case av.M != nil: + switch item := av.(type) { + case *types.AttributeValueMemberM: + // TODO: this is probably slow kp := reflect.New(rv.Type().Key()) kv := kp.Elem() - for k, v := range av.M { + for k, v := range item.Value { innerRV := reflect.New(rv.Type().Elem()) if err := unmarshalReflect(v, innerRV.Elem()); err != nil { return err @@ -187,32 +239,32 @@ func unmarshalReflect(av *dynamodb.AttributeValue, rv reflect.Value) error { rv.SetMapIndex(kv, innerRV.Elem()) } return nil - case av.SS != nil: + case *types.AttributeValueMemberSS: kp := reflect.New(rv.Type().Key()) kv := kp.Elem() - for _, s := range av.SS { + for _, s := range item.Value { if kp.Type().Implements(tumType) { tm := kp.Interface().(encoding.TextUnmarshaler) - if err := tm.UnmarshalText([]byte(*s)); err != nil { + if err := tm.UnmarshalText([]byte(s)); err != nil { return fmt.Errorf("dynamo: unmarshal map (SS): key error: %v", err) } } else { - kv.SetString(*s) + kv.SetString(s) } rv.SetMapIndex(kv, truthy) } return nil - case av.NS != nil: + case *types.AttributeValueMemberNS: kv := reflect.New(rv.Type().Key()).Elem() - for _, n := range av.NS { - if err := unmarshalReflect(&dynamodb.AttributeValue{N: n}, kv); err != nil { + for _, n := range item.Value { + if err := unmarshalReflect(&types.AttributeValueMemberN{Value: n}, kv); err != nil { return err } rv.SetMapIndex(kv, truthy) } return nil - case av.BS != nil: - for _, bb := range av.BS { + case *types.AttributeValueMemberBS: + for _, bb := range item.Value { kv := reflect.New(rv.Type().Key()).Elem() reflect.Copy(kv, reflect.ValueOf(bb)) rv.SetMapIndex(kv, truthy) @@ -225,19 +277,19 @@ func unmarshalReflect(av *dynamodb.AttributeValue, rv reflect.Value) error { case reflect.Array: arr := reflect.New(rv.Type()).Elem() elemtype := arr.Type().Elem() - switch { - case av.B != nil: - if len(av.B) > arr.Len() { - return fmt.Errorf("dynamo: cannot marshal %s into %s; too small (dst len: %d, src len: %d)", avTypeName(av), arr.Type().String(), arr.Len(), len(av.B)) + switch t := av.(type) { + case *types.AttributeValueMemberB: + if len(t.Value) > arr.Len() { + return fmt.Errorf("dynamo: cannot marshal %s into %s; too small (dst len: %d, src len: %d)", avTypeName(av), arr.Type().String(), arr.Len(), len(t.Value)) } - reflect.Copy(arr, reflect.ValueOf(av.B)) + reflect.Copy(arr, reflect.ValueOf(t.Value)) rv.Set(arr) return nil - case av.L != nil: - if len(av.L) > arr.Len() { - return fmt.Errorf("dynamo: cannot marshal %s into %s; too small (dst len: %d, src len: %d)", avTypeName(av), arr.Type().String(), arr.Len(), len(av.L)) + case *types.AttributeValueMemberL: + if len(t.Value) > arr.Len() { + return fmt.Errorf("dynamo: cannot marshal %s into %s; too small (dst len: %d, src len: %d)", avTypeName(av), arr.Type().String(), arr.Len(), len(t.Value)) } - for i, innerAV := range av.L { + for i, innerAV := range t.Value { innerRV := reflect.New(elemtype).Elem() if err := unmarshalReflect(innerAV, innerRV); err != nil { return err @@ -268,15 +320,15 @@ func unmarshalReflect(av *dynamodb.AttributeValue, rv reflect.Value) error { } // unmarshal for when rv's Kind is Slice -func unmarshalSlice(av *dynamodb.AttributeValue, rv reflect.Value) error { - switch { - case av.B != nil: - rv.SetBytes(av.B) +func unmarshalSlice(av types.AttributeValue, rv reflect.Value) error { + switch t := av.(type) { + case *types.AttributeValueMemberB: + rv.SetBytes(t.Value) return nil - case av.L != nil: - slicev := reflect.MakeSlice(rv.Type(), 0, len(av.L)) - for _, innerAV := range av.L { + case *types.AttributeValueMemberL: + slicev := reflect.MakeSlice(rv.Type(), 0, len(t.Value)) + for _, innerAV := range t.Value { innerRV := reflect.New(rv.Type().Elem()).Elem() if err := unmarshalReflect(innerAV, innerRV); err != nil { return err @@ -287,33 +339,33 @@ func unmarshalSlice(av *dynamodb.AttributeValue, rv reflect.Value) error { return nil // there's probably a better way to do these - case av.BS != nil: - slicev := reflect.MakeSlice(rv.Type(), 0, len(av.L)) - for _, b := range av.BS { + case *types.AttributeValueMemberBS: + slicev := reflect.MakeSlice(rv.Type(), 0, len(t.Value)) + for _, b := range t.Value { innerRV := reflect.New(rv.Type().Elem()).Elem() - if err := unmarshalReflect(&dynamodb.AttributeValue{B: b}, innerRV); err != nil { + if err := unmarshalReflect(&types.AttributeValueMemberB{Value: b}, innerRV); err != nil { return err } slicev = reflect.Append(slicev, innerRV) } rv.Set(slicev) return nil - case av.SS != nil: - slicev := reflect.MakeSlice(rv.Type(), 0, len(av.L)) - for _, str := range av.SS { + case *types.AttributeValueMemberSS: + slicev := reflect.MakeSlice(rv.Type(), 0, len(t.Value)) + for _, str := range t.Value { innerRV := reflect.New(rv.Type().Elem()).Elem() - if err := unmarshalReflect(&dynamodb.AttributeValue{S: str}, innerRV); err != nil { + if err := unmarshalReflect(&types.AttributeValueMemberS{Value: str}, innerRV); err != nil { return err } slicev = reflect.Append(slicev, innerRV) } rv.Set(slicev) return nil - case av.NS != nil: - slicev := reflect.MakeSlice(rv.Type(), 0, len(av.L)) - for _, n := range av.NS { + case *types.AttributeValueMemberNS: + slicev := reflect.MakeSlice(rv.Type(), 0, len(t.Value)) + for _, n := range t.Value { innerRV := reflect.New(rv.Type().Elem()).Elem() - if err := unmarshalReflect(&dynamodb.AttributeValue{N: n}, innerRV); err != nil { + if err := unmarshalReflect(&types.AttributeValueMemberN{Value: n}, innerRV); err != nil { return err } slicev = reflect.Append(slicev, innerRV) @@ -376,14 +428,14 @@ func fieldsInStruct(rv reflect.Value) map[string]reflect.Value { } // unmarshals a struct -func unmarshalItem(item map[string]*dynamodb.AttributeValue, out interface{}) error { +func unmarshalItem(item map[string]types.AttributeValue, out interface{}) error { switch x := out.(type) { - case *map[string]*dynamodb.AttributeValue: + case *map[string]types.AttributeValue: *x = item return nil case awsEncoder: // special case for AWSEncoding - return dynamodbattribute.UnmarshalMap(item, x.iface) + return attributevalue.UnmarshalMap(item, x.iface) case ItemUnmarshaler: return x.UnmarshalDynamoItem(item) } @@ -435,7 +487,7 @@ func unmarshalItem(item map[string]*dynamodb.AttributeValue, out interface{}) er return fmt.Errorf("dynamo: unmarshal: unsupported type: %T", out) } -func unmarshalAppend(item map[string]*dynamodb.AttributeValue, out interface{}) error { +func unmarshalAppend(item map[string]types.AttributeValue, out interface{}) error { if awsenc, ok := out.(awsEncoder); ok { return unmarshalAppendAWS(item, awsenc.iface) } @@ -457,21 +509,21 @@ func unmarshalAppend(item map[string]*dynamodb.AttributeValue, out interface{}) } // av2iface converts an av into interface{}. -func av2iface(av *dynamodb.AttributeValue) (interface{}, error) { - switch { - case av.B != nil: - return av.B, nil - case av.BS != nil: - return av.BS, nil - case av.BOOL != nil: - return *av.BOOL, nil - case av.N != nil: - return strconv.ParseFloat(*av.N, 64) - case av.S != nil: - return *av.S, nil - case av.L != nil: - list := make([]interface{}, 0, len(av.L)) - for _, item := range av.L { +func av2iface(av types.AttributeValue) (interface{}, error) { + switch v := av.(type) { + case *types.AttributeValueMemberB: + return v.Value, nil + case *types.AttributeValueMemberBS: + return v.Value, nil + case *types.AttributeValueMemberBOOL: + return v.Value, nil + case *types.AttributeValueMemberN: + return strconv.ParseFloat(v.Value, 64) + case *types.AttributeValueMemberS: + return v.Value, nil + case *types.AttributeValueMemberL: + list := make([]interface{}, 0, len(v.Value)) + for _, item := range v.Value { iface, err := av2iface(item) if err != nil { return nil, err @@ -479,25 +531,25 @@ func av2iface(av *dynamodb.AttributeValue) (interface{}, error) { list = append(list, iface) } return list, nil - case av.NS != nil: - set := make([]float64, 0, len(av.NS)) - for _, n := range av.NS { - f, err := strconv.ParseFloat(*n, 64) + case *types.AttributeValueMemberNS: + set := make([]float64, 0, len(v.Value)) + for _, n := range v.Value { + f, err := strconv.ParseFloat(n, 64) if err != nil { return nil, err } set = append(set, f) } return set, nil - case av.SS != nil: - set := make([]string, 0, len(av.SS)) - for _, s := range av.SS { - set = append(set, *s) + case *types.AttributeValueMemberSS: + set := make([]string, 0, len(v.Value)) + for _, s := range v.Value { + set = append(set, s) } return set, nil - case av.M != nil: - m := make(map[string]interface{}, len(av.M)) - for k, v := range av.M { + case *types.AttributeValueMemberM: + m := make(map[string]interface{}, len(v.Value)) + for k, v := range v.Value { iface, err := av2iface(v) if err != nil { return nil, err @@ -505,37 +557,12 @@ func av2iface(av *dynamodb.AttributeValue) (interface{}, error) { m[k] = iface } return m, nil - case av.NULL != nil: + case *types.AttributeValueMemberNULL: return nil, nil } - return nil, fmt.Errorf("dynamo: unsupported AV: %#v", *av) + return nil, fmt.Errorf("dynamo: unsupported AV: %#v", av) } -func avTypeName(av *dynamodb.AttributeValue) string { - if av == nil { - return "" - } - switch { - case av.B != nil: - return "binary" - case av.BS != nil: - return "binary set" - case av.BOOL != nil: - return "boolean" - case av.N != nil: - return "number" - case av.S != nil: - return "string" - case av.L != nil: - return "list" - case av.NS != nil: - return "number set" - case av.SS != nil: - return "string set" - case av.M != nil: - return "map" - case av.NULL != nil: - return "null" - } - return "" +func avTypeName(av types.AttributeValue) string { + return fmt.Sprintf("%T", av) } diff --git a/decode_aux_test.go b/decode_aux_test.go index 19dd36e..25aee83 100644 --- a/decode_aux_test.go +++ b/decode_aux_test.go @@ -5,10 +5,8 @@ import ( "reflect" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" - "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" - + "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/guregu/dynamo" ) @@ -21,9 +19,9 @@ func TestEncodingAux(t *testing.T) { // using the "aux" unmarshaling trick. // See: https://github.com/guregu/dynamo/issues/181 - in := map[string]*dynamodb.AttributeValue{ - "ID": {S: aws.String("intenso")}, - "Name": {S: aws.String("Intenso 12")}, + in := map[string]types.AttributeValue{ + "ID": &types.AttributeValueMemberS{Value: "intenso"}, + "Name": &types.AttributeValueMemberS{Value: "Intenso 12"}, } type coffeeItemDefault struct { @@ -62,7 +60,7 @@ type coffeeItemFlat struct { Name string } -func (c *coffeeItemFlat) UnmarshalDynamoItem(item map[string]*dynamodb.AttributeValue) error { +func (c *coffeeItemFlat) UnmarshalDynamoItem(item map[string]types.AttributeValue) error { type alias coffeeItemFlat aux := struct { *alias @@ -80,7 +78,7 @@ type coffeeItemInvalid struct { Name string } -func (c *coffeeItemInvalid) UnmarshalDynamoItem(item map[string]*dynamodb.AttributeValue) error { +func (c *coffeeItemInvalid) UnmarshalDynamoItem(item map[string]types.AttributeValue) error { type alias coffeeItemInvalid aux := struct { *alias @@ -98,7 +96,7 @@ type coffeeItemEmbedded struct { Coffee } -func (c *coffeeItemEmbedded) UnmarshalDynamoItem(item map[string]*dynamodb.AttributeValue) error { +func (c *coffeeItemEmbedded) UnmarshalDynamoItem(item map[string]types.AttributeValue) error { type alias coffeeItemEmbedded aux := struct { *alias @@ -116,7 +114,7 @@ type coffeeItemEmbeddedPointer struct { *Coffee } -func (c *coffeeItemEmbeddedPointer) UnmarshalDynamoItem(item map[string]*dynamodb.AttributeValue) error { +func (c *coffeeItemEmbeddedPointer) UnmarshalDynamoItem(item map[string]types.AttributeValue) error { type alias coffeeItemEmbeddedPointer aux := struct { *alias @@ -147,14 +145,14 @@ type coffeeItemSDKEmbeddedPointer struct { *Coffee } -func (c *coffeeItemSDKEmbeddedPointer) UnmarshalDynamoItem(item map[string]*dynamodb.AttributeValue) error { +func (c *coffeeItemSDKEmbeddedPointer) UnmarshalDynamoItem(item map[string]types.AttributeValue) error { type alias coffeeItemEmbeddedPointer aux := struct { *alias }{ alias: (*alias)(c), } - if err := dynamodbattribute.UnmarshalMap(item, &aux); err != nil { + if err := attributevalue.UnmarshalMap(item, &aux); err != nil { return err } return nil diff --git a/decode_test.go b/decode_test.go index dcecdf5..6227dbe 100644 --- a/decode_test.go +++ b/decode_test.go @@ -1,24 +1,27 @@ package dynamo import ( + "fmt" "reflect" + "strings" "testing" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" ) var itemDecodeOnlyTests = []struct { name string - given map[string]*dynamodb.AttributeValue + given map[string]types.AttributeValue expect interface{} }{ { // unexported embedded pointers should be ignored name: "embedded unexported pointer", - given: map[string]*dynamodb.AttributeValue{ - "Embedded": {BOOL: aws.Bool(true)}, + given: map[string]types.AttributeValue{ + "Embedded": &types.AttributeValueMemberBOOL{Value: true}, }, expect: struct { *embedded @@ -27,8 +30,8 @@ var itemDecodeOnlyTests = []struct { { // unexported fields should be ignored name: "unexported fields", - given: map[string]*dynamodb.AttributeValue{ - "a": {BOOL: aws.Bool(true)}, + given: map[string]types.AttributeValue{ + "a": &types.AttributeValueMemberBOOL{Value: true}, }, expect: struct { a bool @@ -37,8 +40,8 @@ var itemDecodeOnlyTests = []struct { { // embedded pointers shouldn't clobber existing fields name: "exported pointer embedded struct clobber", - given: map[string]*dynamodb.AttributeValue{ - "Embedded": {S: aws.String("OK")}, + given: map[string]types.AttributeValue{ + "Embedded": &types.AttributeValueMemberS{Value: "OK"}, }, expect: struct { Embedded string @@ -76,11 +79,11 @@ func TestUnmarshalAppend(t *testing.T) { page := "5" limit := "20" null := true - item := map[string]*dynamodb.AttributeValue{ - "UserID": {N: &id}, - "Page": {N: &page}, - "Limit": {N: &limit}, - "Null": {NULL: &null}, + item := map[string]types.AttributeValue{ + "UserID": &types.AttributeValueMemberN{Value: id}, + "Page": &types.AttributeValueMemberN{Value: page}, + "Limit": &types.AttributeValueMemberN{Value: limit}, + "Null": &types.AttributeValueMemberNULL{Value: null}, } for range [15]struct{}{} { @@ -112,20 +115,6 @@ func TestUnmarshalAppend(t *testing.T) { } } -func TestUnmarshal(t *testing.T) { - for _, tc := range encodingTests { - rv := reflect.New(reflect.TypeOf(tc.in)) - err := unmarshalReflect(tc.out, rv.Elem()) - if err != nil { - t.Errorf("%s: unexpected error: %v", tc.name, err) - } - - if !reflect.DeepEqual(rv.Elem().Interface(), tc.in) { - t.Errorf("%s: bad result: %#v ≠ %#v", tc.name, rv.Elem().Interface(), tc.out) - } - } -} - func TestUnmarshalItem(t *testing.T) { for _, tc := range itemEncodingTests { rv := reflect.New(reflect.TypeOf(tc.in)) @@ -135,54 +124,19 @@ func TestUnmarshalItem(t *testing.T) { } if !reflect.DeepEqual(rv.Elem().Interface(), tc.in) { - t.Errorf("%s: bad result: %#v ≠ %#v", tc.name, rv.Elem().Interface(), tc.in) - } - } -} - -func TestUnmarshalNULL(t *testing.T) { - tru := true - arbitrary := "hello world" - double := new(*int) - item := map[string]*dynamodb.AttributeValue{ - "String": {NULL: &tru}, - "Slice": {NULL: &tru}, - "Array": {NULL: &tru}, - "StringPtr": {NULL: &tru}, - "DoublePtr": {NULL: &tru}, - "Map": {NULL: &tru}, - "Interface": {NULL: &tru}, - } + var opts []cmp.Option + if rv.Elem().Kind() == reflect.Struct { + opts = append(opts, cmpopts.IgnoreUnexported(rv.Elem().Interface())) + } - type resultType struct { - String string - Slice []string - Array [2]byte - StringPtr *string - DoublePtr **int - Map map[string]int - Interface interface{} - } + diff := cmp.Diff(rv.Elem().Interface(), tc.in, opts...) + fmt.Println(diff) - // dirty result, we want this to be reset - result := resultType{ - String: "ABC", - Slice: []string{"A", "B"}, - Array: [2]byte{'A', 'B'}, - StringPtr: &arbitrary, - DoublePtr: double, - Map: map[string]int{ - "A": 1, - }, - Interface: "interface{}", - } - - if err := UnmarshalItem(item, &result); err != nil { - t.Error(err) - } + if strings.TrimSpace(diff) != "" { + t.Errorf("%s: bad result: %#v ≠ %#v", tc.name, rv.Elem().Interface(), tc.in) + } + } - if (!reflect.DeepEqual(result, resultType{})) { - t.Error("unmarshal null: bad result:", result, "≠", resultType{}) } } @@ -215,8 +169,8 @@ func TestUnmarshalMissing(t *testing.T) { }, } - replace := map[string]*dynamodb.AttributeValue{ - "UserID": {N: aws.String("112")}, + replace := map[string]types.AttributeValue{ + "UserID": &types.AttributeValueMemberN{Value: "112"}, } if err := UnmarshalItem(replace, &w); err != nil { @@ -227,11 +181,13 @@ func TestUnmarshalMissing(t *testing.T) { t.Error("bad unmarshal missing. want:", want, "got:", w) } - replace2 := map[string]*dynamodb.AttributeValue{ - "UserID": {N: aws.String("113")}, - "Foo": {M: map[string]*dynamodb.AttributeValue{ - "Bar": {N: aws.String("1338")}, - }}, + replace2 := map[string]types.AttributeValue{ + "UserID": &types.AttributeValueMemberN{Value: "113"}, + "Foo": &types.AttributeValueMemberM{ + Value: map[string]types.AttributeValue{ + "Bar": &types.AttributeValueMemberN{Value: "1338"}, + }, + }, } want = widget2{ diff --git a/delete.go b/delete.go index 21a96c5..ece7742 100644 --- a/delete.go +++ b/delete.go @@ -1,8 +1,10 @@ package dynamo import ( - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" + "context" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) // Delete is a request to delete an item. @@ -12,10 +14,10 @@ type Delete struct { returnType string hashKey string - hashValue *dynamodb.AttributeValue + hashValue types.AttributeValue rangeKey string - rangeValue *dynamodb.AttributeValue + rangeValue types.AttributeValue subber condition string @@ -75,7 +77,7 @@ func (d *Delete) Run() error { return d.RunWithContext(ctx) } -func (d *Delete) RunWithContext(ctx aws.Context) error { +func (d *Delete) RunWithContext(ctx context.Context) error { d.returnType = "NONE" _, err := d.run(ctx) return err @@ -89,7 +91,7 @@ func (d *Delete) OldValue(out interface{}) error { return d.OldValueWithContext(ctx, out) } -func (d *Delete) OldValueWithContext(ctx aws.Context, out interface{}) error { +func (d *Delete) OldValueWithContext(ctx context.Context, out interface{}) error { d.returnType = "ALL_OLD" output, err := d.run(ctx) switch { @@ -101,7 +103,7 @@ func (d *Delete) OldValueWithContext(ctx aws.Context, out interface{}) error { return unmarshalItem(output.Attributes, out) } -func (d *Delete) run(ctx aws.Context) (*dynamodb.DeleteItemOutput, error) { +func (d *Delete) run(ctx context.Context) (*dynamodb.DeleteItemOutput, error) { if d.err != nil { return nil, d.err } @@ -110,7 +112,7 @@ func (d *Delete) run(ctx aws.Context) (*dynamodb.DeleteItemOutput, error) { var output *dynamodb.DeleteItemOutput err := retry(ctx, func() error { var err error - output, err = d.table.db.client.DeleteItemWithContext(ctx, input) + output, err = d.table.db.client.DeleteItem(ctx, input) return err }) if d.cc != nil { @@ -123,7 +125,7 @@ func (d *Delete) deleteInput() *dynamodb.DeleteItemInput { input := &dynamodb.DeleteItemInput{ TableName: &d.table.name, Key: d.key(), - ReturnValues: &d.returnType, + ReturnValues: types.ReturnValue(d.returnType), ExpressionAttributeNames: d.nameExpr, ExpressionAttributeValues: d.valueExpr, } @@ -131,18 +133,18 @@ func (d *Delete) deleteInput() *dynamodb.DeleteItemInput { input.ConditionExpression = &d.condition } if d.cc != nil { - input.ReturnConsumedCapacity = aws.String(dynamodb.ReturnConsumedCapacityIndexes) + input.ReturnConsumedCapacity = types.ReturnConsumedCapacityIndexes } return input } -func (d *Delete) writeTxItem() (*dynamodb.TransactWriteItem, error) { +func (d *Delete) writeTxItem() (*types.TransactWriteItem, error) { if d.err != nil { return nil, d.err } input := d.deleteInput() - item := &dynamodb.TransactWriteItem{ - Delete: &dynamodb.Delete{ + item := &types.TransactWriteItem{ + Delete: &types.Delete{ TableName: input.TableName, Key: input.Key, ExpressionAttributeNames: input.ExpressionAttributeNames, @@ -153,8 +155,8 @@ func (d *Delete) writeTxItem() (*dynamodb.TransactWriteItem, error) { return item, nil } -func (d *Delete) key() map[string]*dynamodb.AttributeValue { - key := map[string]*dynamodb.AttributeValue{ +func (d *Delete) key() map[string]types.AttributeValue { + key := map[string]types.AttributeValue{ d.hashKey: d.hashValue, } if d.rangeKey != "" { diff --git a/describetable.go b/describetable.go index c3b560f..5d2dc95 100644 --- a/describetable.go +++ b/describetable.go @@ -1,10 +1,11 @@ package dynamo import ( + "context" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) // Description contains information about a table. @@ -89,7 +90,7 @@ type Index struct { ProjectionAttribs []string } -func newDescription(table *dynamodb.TableDescription) Description { +func newDescription(table *types.TableDescription) Description { desc := Description{ Name: *table.TableName, } @@ -97,8 +98,8 @@ func newDescription(table *dynamodb.TableDescription) Description { if table.TableArn != nil { desc.ARN = *table.TableArn } - if table.TableStatus != nil { - desc.Status = Status(*table.TableStatus) + if table.TableStatus != "" { + desc.Status = Status(table.TableStatus) } if table.CreationDateTime != nil { desc.Created = *table.CreationDateTime @@ -108,31 +109,31 @@ func newDescription(table *dynamodb.TableDescription) Description { desc.HashKeyType = lookupADType(table.AttributeDefinitions, desc.HashKey) desc.RangeKeyType = lookupADType(table.AttributeDefinitions, desc.RangeKey) - if table.BillingModeSummary != nil && table.BillingModeSummary.BillingMode != nil { - desc.OnDemand = *table.BillingModeSummary.BillingMode == dynamodb.BillingModePayPerRequest + if table.BillingModeSummary != nil && table.BillingModeSummary.BillingMode != "" { + desc.OnDemand = table.BillingModeSummary.BillingMode == types.BillingModePayPerRequest } if table.ProvisionedThroughput != nil { desc.Throughput = newThroughput(table.ProvisionedThroughput) } - if table.ItemCount != nil { - desc.Items = *table.ItemCount + if table.ItemCount != 0 { + desc.Items = table.ItemCount } - if table.TableSizeBytes != nil { - desc.Size = *table.TableSizeBytes + if table.TableSizeBytes != 0 { + desc.Size = table.TableSizeBytes } for _, index := range table.GlobalSecondaryIndexes { idx := Index{ Name: *index.IndexName, ARN: *index.IndexArn, - Status: Status(*index.IndexStatus), + Status: Status(index.IndexStatus), Throughput: newThroughput(index.ProvisionedThroughput), } - if index.Projection != nil && index.Projection.ProjectionType != nil { - idx.ProjectionType = IndexProjection(*index.Projection.ProjectionType) - idx.ProjectionAttribs = aws.StringValueSlice(index.Projection.NonKeyAttributes) + if index.Projection != nil && index.Projection.ProjectionType != "" { + idx.ProjectionType = IndexProjection(index.Projection.ProjectionType) + idx.ProjectionAttribs = index.Projection.NonKeyAttributes } if index.Backfilling != nil { idx.Backfilling = *index.Backfilling @@ -140,11 +141,11 @@ func newDescription(table *dynamodb.TableDescription) Description { idx.HashKey, idx.RangeKey = schemaKeys(index.KeySchema) idx.HashKeyType = lookupADType(table.AttributeDefinitions, idx.HashKey) idx.RangeKeyType = lookupADType(table.AttributeDefinitions, idx.RangeKey) - if index.ItemCount != nil { - idx.Items = *index.ItemCount + if index.ItemCount != 0 { + idx.Items = index.ItemCount } - if index.IndexSizeBytes != nil { - idx.Size = *index.IndexSizeBytes + if index.IndexSizeBytes != 0 { + idx.Size = index.IndexSizeBytes } desc.GSI = append(desc.GSI, idx) } @@ -156,18 +157,18 @@ func newDescription(table *dynamodb.TableDescription) Description { Local: true, Throughput: desc.Throughput, // has the same throughput as the table } - if index.Projection != nil && index.Projection.ProjectionType != nil { - idx.ProjectionType = IndexProjection(*index.Projection.ProjectionType) - idx.ProjectionAttribs = aws.StringValueSlice(index.Projection.NonKeyAttributes) + if index.Projection != nil && index.Projection.ProjectionType != "" { + idx.ProjectionType = IndexProjection(index.Projection.ProjectionType) + idx.ProjectionAttribs = index.Projection.NonKeyAttributes } idx.HashKey, idx.RangeKey = schemaKeys(index.KeySchema) idx.HashKeyType = lookupADType(table.AttributeDefinitions, idx.HashKey) idx.RangeKeyType = lookupADType(table.AttributeDefinitions, idx.RangeKey) - if index.ItemCount != nil { - idx.Items = *index.ItemCount + if index.ItemCount != 0 { + idx.Items = index.ItemCount } - if index.IndexSizeBytes != nil { - idx.Size = *index.IndexSizeBytes + if index.IndexSizeBytes != 0 { + idx.Size = index.IndexSizeBytes } desc.LSI = append(desc.LSI, idx) } @@ -176,8 +177,8 @@ func newDescription(table *dynamodb.TableDescription) Description { if table.StreamSpecification.StreamEnabled != nil { desc.StreamEnabled = *table.StreamSpecification.StreamEnabled } - if table.StreamSpecification.StreamViewType != nil { - desc.StreamView = StreamView(*table.StreamSpecification.StreamViewType) + if table.StreamSpecification.StreamViewType != "" { + desc.StreamView = StreamView(table.StreamSpecification.StreamViewType) } } if table.LatestStreamArn != nil { @@ -195,11 +196,11 @@ func newDescription(table *dynamodb.TableDescription) Description { if table.SSEDescription.KMSMasterKeyArn != nil { sseDesc.KMSMasterKeyArn = *table.SSEDescription.KMSMasterKeyArn } - if table.SSEDescription.SSEType != nil { - sseDesc.SSEType = lookupSSEType(*table.SSEDescription.SSEType) + if table.SSEDescription.SSEType != "" { + sseDesc.SSEType = table.SSEDescription.SSEType } - if table.SSEDescription.Status != nil { - sseDesc.Status = *table.SSEDescription.Status + if table.SSEDescription.Status != "" { + sseDesc.Status = table.SSEDescription.Status } desc.SSEDescription = sseDesc } @@ -255,13 +256,13 @@ func (dt *DescribeTable) Run() (Description, error) { return dt.RunWithContext(ctx) } -func (dt *DescribeTable) RunWithContext(ctx aws.Context) (Description, error) { +func (dt *DescribeTable) RunWithContext(ctx context.Context) (Description, error) { input := dt.input() var result *dynamodb.DescribeTableOutput err := retry(ctx, func() error { var err error - result, err = dt.table.db.client.DescribeTableWithContext(ctx, input) + result, err = dt.table.db.client.DescribeTable(ctx, input) return err }) if err != nil { @@ -280,7 +281,7 @@ func (dt *DescribeTable) input() *dynamodb.DescribeTableInput { } } -func newThroughput(td *dynamodb.ProvisionedThroughputDescription) Throughput { +func newThroughput(td *types.ProvisionedThroughputDescription) Throughput { if td == nil { return Throughput{} } @@ -301,25 +302,25 @@ func newThroughput(td *dynamodb.ProvisionedThroughputDescription) Throughput { return thru } -func schemaKeys(schema []*dynamodb.KeySchemaElement) (hashKey, rangeKey string) { +func schemaKeys(schema []types.KeySchemaElement) (hashKey, rangeKey string) { for _, ks := range schema { - switch *ks.KeyType { - case dynamodb.KeyTypeHash: + switch ks.KeyType { + case types.KeyTypeHash: hashKey = *ks.AttributeName - case dynamodb.KeyTypeRange: + case types.KeyTypeRange: rangeKey = *ks.AttributeName } } return } -func lookupADType(ads []*dynamodb.AttributeDefinition, name string) KeyType { +func lookupADType(ads []types.AttributeDefinition, name string) KeyType { if name == "" { return "" } for _, ad := range ads { if *ad.AttributeName == name { - return KeyType(*ad.AttributeType) + return KeyType(ad.AttributeType) } } return "" diff --git a/dynamodbiface/interface.go b/dynamodbiface/interface.go new file mode 100644 index 0000000..c214cbb --- /dev/null +++ b/dynamodbiface/interface.go @@ -0,0 +1,83 @@ +// Package dynamodbiface provides an interface to enable mocking the Amazon DynamoDB service client +// for testing your code. +// +// It is important to note that this interface will have breaking changes +// when the service model is updated and adds new API operations, paginators, +// and waiters. +package dynamodbiface + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb" +) + +// DynamoDBAPI provides an interface to enable mocking the +// dynamodb.DynamoDB service client's API operation, +// paginators, and waiters. This make unit testing your code that calls out +// to the SDK's service client's calls easier. +// +// The best way to use this interface is so the SDK's service client's calls +// can be stubbed out for unit testing your code with the SDK without needing +// to inject custom request handlers into the SDK's request pipeline. +// +// // myFunc uses an SDK service client to make a request to +// // Amazon DynamoDB. +// func myFunc(svc dynamodbiface.DynamoDBAPI) bool { +// // Make svc.BatchExecuteStatement request +// } +// +// func main() { +// cfg := config.LoadConfig() +// svc := dynamodb.New(cfg) +// +// myFunc(svc) +// } +// +// In your _test.go file: +// +// // Define a mock struct to be used in your unit tests of myFunc. +// type mockDynamoDBClient struct { +// dynamodbiface.DynamoDBAPI +// } +// func (m *mockDynamoDBClient) BatchExecuteStatement(input *dynamodb.BatchExecuteStatementInput) (*dynamodb.BatchExecuteStatementOutput, error) { +// // mock response/functionality +// } +// +// func TestMyFunc(t *testing.T) { +// // Setup Test +// mockSvc := &mockDynamoDBClient{} +// +// myfunc(mockSvc) +// +// // Verify myFunc's functionality +// } +// +// It is important to note that this interface will have breaking changes +// when the service model is updated and adds new API operations, paginators, +// and waiters. Its suggested to use the pattern above for testing, or using +// tooling to generate mocks to satisfy the interfaces. +type DynamoDBAPI interface { + CreateTable(ctx context.Context, params *dynamodb.CreateTableInput, optFns ...func(*dynamodb.Options)) (*dynamodb.CreateTableOutput, error) + ListTables(ctx context.Context, params *dynamodb.ListTablesInput, optFns ...func(*dynamodb.Options)) (*dynamodb.ListTablesOutput, error) + ListGlobalTables(ctx context.Context, params *dynamodb.ListGlobalTablesInput, optFns ...func(*dynamodb.Options)) (*dynamodb.ListGlobalTablesOutput, error) + DescribeTable(ctx context.Context, params *dynamodb.DescribeTableInput, optFns ...func(*dynamodb.Options)) (*dynamodb.DescribeTableOutput, error) + UpdateTable(ctx context.Context, params *dynamodb.UpdateTableInput, optFns ...func(*dynamodb.Options)) (*dynamodb.UpdateTableOutput, error) + + TransactGetItems(ctx context.Context, params *dynamodb.TransactGetItemsInput, optFns ...func(*dynamodb.Options)) (*dynamodb.TransactGetItemsOutput, error) + BatchGetItem(ctx context.Context, params *dynamodb.BatchGetItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.BatchGetItemOutput, error) + BatchWriteItem(ctx context.Context, params *dynamodb.BatchWriteItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.BatchWriteItemOutput, error) + + GetItem(ctx context.Context, params *dynamodb.GetItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.GetItemOutput, error) + DeleteItem(ctx context.Context, params *dynamodb.DeleteItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.DeleteItemOutput, error) + PutItem(ctx context.Context, params *dynamodb.PutItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.PutItemOutput, error) + UpdateItem(ctx context.Context, params *dynamodb.UpdateItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.UpdateItemOutput, error) + + UpdateTimeToLive(ctx context.Context, params *dynamodb.UpdateTimeToLiveInput, optFns ...func(*dynamodb.Options)) (*dynamodb.UpdateTimeToLiveOutput, error) + DescribeTimeToLive(ctx context.Context, params *dynamodb.DescribeTimeToLiveInput, optFns ...func(*dynamodb.Options)) (*dynamodb.DescribeTimeToLiveOutput, error) + + Query(ctx context.Context, params *dynamodb.QueryInput, optFns ...func(*dynamodb.Options)) (*dynamodb.QueryOutput, error) + Scan(ctx context.Context, params *dynamodb.ScanInput, optFns ...func(*dynamodb.Options)) (*dynamodb.ScanOutput, error) + DeleteTable(ctx context.Context, params *dynamodb.DeleteTableInput, optFns ...func(*dynamodb.Options)) (*dynamodb.DeleteTableOutput, error) + TransactWriteItems(ctx context.Context, params *dynamodb.TransactWriteItemsInput, optFns ...func(*dynamodb.Options)) (*dynamodb.TransactWriteItemsOutput, error) +} diff --git a/encode.go b/encode.go index 84eb752..1d5497a 100644 --- a/encode.go +++ b/encode.go @@ -8,35 +8,34 @@ import ( "strings" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" - "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" + "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) // Marshaler is the interface implemented by objects that can marshal themselves into // an AttributeValue. type Marshaler interface { - MarshalDynamo() (*dynamodb.AttributeValue, error) + MarshalDynamo() (types.AttributeValue, error) } // ItemMarshaler is the interface implemented by objects that can marshal themselves // into an Item (a map of strings to AttributeValues). type ItemMarshaler interface { - MarshalDynamoItem() (map[string]*dynamodb.AttributeValue, error) + MarshalDynamoItem() (map[string]types.AttributeValue, error) } // MarshalItem converts the given struct into a DynamoDB item. -func MarshalItem(v interface{}) (map[string]*dynamodb.AttributeValue, error) { +func MarshalItem(v interface{}) (map[string]types.AttributeValue, error) { return marshalItem(v) } -func marshalItem(v interface{}) (map[string]*dynamodb.AttributeValue, error) { +func marshalItem(v interface{}) (map[string]types.AttributeValue, error) { switch x := v.(type) { - case map[string]*dynamodb.AttributeValue: + case map[string]types.AttributeValue: return x, nil case awsEncoder: // special case for AWSEncoding - return dynamodbattribute.MarshalMap(x.iface) + return attributevalue.MarshalMap(x.iface) case ItemMarshaler: return x.MarshalDynamoItem() } @@ -54,20 +53,21 @@ func marshalItem(v interface{}) (map[string]*dynamodb.AttributeValue, error) { return nil, fmt.Errorf("dynamo: marshal item: unsupported type %T: %v", rv.Interface(), rv.Interface()) } -func marshalItemMap(v interface{}) (map[string]*dynamodb.AttributeValue, error) { +func marshalItemMap(v interface{}) (map[string]types.AttributeValue, error) { // TODO: maybe unify this with the map stuff in marshal av, err := marshal(v, flagNone) if err != nil { return nil, err } - if av.M == nil { + m, _ := av.(*types.AttributeValueMemberM) + if m == nil { return nil, fmt.Errorf("dynamo: internal error: encoding map but M was empty") } - return av.M, nil + return m.Value, nil } -func marshalStruct(rv reflect.Value) (map[string]*dynamodb.AttributeValue, error) { - item := make(map[string]*dynamodb.AttributeValue) +func marshalStruct(rv reflect.Value) (map[string]types.AttributeValue, error) { + item := make(map[string]types.AttributeValue) var err error for i := 0; i < rv.Type().NumField(); i++ { @@ -127,11 +127,11 @@ func marshalStruct(rv reflect.Value) (map[string]*dynamodb.AttributeValue, error } // Marshal converts the given value into a DynamoDB attribute value. -func Marshal(v interface{}) (*dynamodb.AttributeValue, error) { +func Marshal(v interface{}) (types.AttributeValue, error) { return marshal(v, flagNone) } -func marshal(v interface{}, flags encodeFlags) (*dynamodb.AttributeValue, error) { +func marshal(v interface{}, flags encodeFlags) (types.AttributeValue, error) { // encoders with precedence over interfaces if flags&flagUnixTime != 0 { switch x := v.(type) { @@ -146,44 +146,44 @@ func marshal(v interface{}, flags encodeFlags) (*dynamodb.AttributeValue, error) } ts := strconv.FormatInt(x.Unix(), 10) - return &dynamodb.AttributeValue{N: &ts}, nil + return &types.AttributeValueMemberN{Value: ts}, nil } } rv := reflect.ValueOf(v) switch x := v.(type) { - case *dynamodb.AttributeValue: + case types.AttributeValue: return x, nil case Marshaler: if rv.Kind() == reflect.Ptr && rv.IsNil() { if _, ok := rv.Type().Elem().MethodByName("MarshalDynamo"); ok { // MarshalDynamo is defined on value type, but this is a nil ptr if flags&flagNull != 0 { - return &dynamodb.AttributeValue{NULL: aws.Bool(true)}, nil + return &types.AttributeValueMemberNULL{Value: true}, nil } return nil, nil } } return x.MarshalDynamo() - case dynamodbattribute.Marshaler: + case attributevalue.Marshaler: if rv.Kind() == reflect.Ptr && rv.IsNil() { if _, ok := rv.Type().Elem().MethodByName("MarshalDynamoDBAttributeValue"); ok { // MarshalDynamoDBAttributeValue is defined on value type, but this is a nil ptr if flags&flagNull != 0 { - return &dynamodb.AttributeValue{NULL: aws.Bool(true)}, nil + return &types.AttributeValueMemberNULL{Value: true}, nil } return nil, nil } } - av := &dynamodb.AttributeValue{} - return av, x.MarshalDynamoDBAttributeValue(av) + + return x.MarshalDynamoDBAttributeValue() case encoding.TextMarshaler: if rv.Kind() == reflect.Ptr && rv.IsNil() { if _, ok := rv.Type().Elem().MethodByName("MarshalText"); ok { // MarshalText is defined on value type, but this is a nil ptr if flags&flagNull != 0 { - return &dynamodb.AttributeValue{NULL: aws.Bool(true)}, nil + return &types.AttributeValueMemberNULL{Value: true}, nil } return nil, nil } @@ -194,14 +194,14 @@ func marshal(v interface{}, flags encodeFlags) (*dynamodb.AttributeValue, error) } if len(text) == 0 { if flags&flagAllowEmpty != 0 { - return &dynamodb.AttributeValue{S: aws.String("")}, nil + return &types.AttributeValueMemberS{Value: ""}, nil } return nil, nil } - return &dynamodb.AttributeValue{S: aws.String(string(text))}, err + return &types.AttributeValueMemberS{Value: string(text)}, err case nil: if flags&flagNull != 0 { - return &dynamodb.AttributeValue{NULL: aws.Bool(true)}, nil + return &types.AttributeValueMemberNULL{Value: true}, nil } return nil, nil } @@ -211,42 +211,42 @@ func marshal(v interface{}, flags encodeFlags) (*dynamodb.AttributeValue, error) var nilTm encoding.TextMarshaler var tmType = reflect.TypeOf(&nilTm).Elem() -func marshalReflect(rv reflect.Value, flags encodeFlags) (*dynamodb.AttributeValue, error) { +func marshalReflect(rv reflect.Value, flags encodeFlags) (types.AttributeValue, error) { switch rv.Kind() { case reflect.Ptr: if rv.IsNil() { if flags&flagNull != 0 { - return &dynamodb.AttributeValue{NULL: aws.Bool(true)}, nil + return &types.AttributeValueMemberNULL{Value: true}, nil } return nil, nil } return marshal(rv.Elem().Interface(), flags) case reflect.Bool: - return &dynamodb.AttributeValue{BOOL: aws.Bool(rv.Bool())}, nil + return &types.AttributeValueMemberBOOL{Value: rv.Bool()}, nil case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: - return &dynamodb.AttributeValue{N: aws.String(strconv.FormatInt(rv.Int(), 10))}, nil + return &types.AttributeValueMemberN{Value: strconv.FormatInt(rv.Int(), 10)}, nil case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8: - return &dynamodb.AttributeValue{N: aws.String(strconv.FormatUint(rv.Uint(), 10))}, nil + return &types.AttributeValueMemberN{Value: strconv.FormatUint(rv.Uint(), 10)}, nil case reflect.Float32, reflect.Float64: - return &dynamodb.AttributeValue{N: aws.String(strconv.FormatFloat(rv.Float(), 'f', -1, 64))}, nil + return &types.AttributeValueMemberN{Value: strconv.FormatFloat(rv.Float(), 'f', -1, 64)}, nil case reflect.String: s := rv.String() if len(s) == 0 { if flags&flagAllowEmpty != 0 { - return &dynamodb.AttributeValue{S: aws.String("")}, nil + return &types.AttributeValueMemberS{Value: ""}, nil } if flags&flagNull != 0 { - return &dynamodb.AttributeValue{NULL: aws.Bool(true)}, nil + return &types.AttributeValueMemberNULL{Value: true}, nil } return nil, nil } - return &dynamodb.AttributeValue{S: aws.String(s)}, nil + return &types.AttributeValueMemberS{Value: s}, nil case reflect.Map: if flags&flagSet != 0 { // sets can't be empty if rv.Len() == 0 { if flags&flagNull != 0 { - return &dynamodb.AttributeValue{NULL: aws.Bool(true)}, nil + return &types.AttributeValueMemberNULL{Value: true}, nil } return nil, nil } @@ -256,10 +256,10 @@ func marshalReflect(rv reflect.Value, flags encodeFlags) (*dynamodb.AttributeVal // automatically omit nil maps if rv.IsNil() { if flags&flagAllowEmpty != 0 { - return &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{}}, nil + return &types.AttributeValueMemberM{Value: map[string]types.AttributeValue{}}, nil } if flags&flagNull != 0 { - return &dynamodb.AttributeValue{NULL: aws.Bool(true)}, nil + return &types.AttributeValueMemberNULL{Value: true}, nil } return nil, nil } @@ -282,7 +282,7 @@ func marshalReflect(rv reflect.Value, flags encodeFlags) (*dynamodb.AttributeVal return nil, fmt.Errorf("dynamo marshal: map key must be string: %T", rv.Interface()) } - avs := make(map[string]*dynamodb.AttributeValue) + avs := make(map[string]types.AttributeValue) subflags := flagNone if flags&flagAllowEmptyElem != 0 { subflags |= flagAllowEmpty | flagNull @@ -308,22 +308,22 @@ func marshalReflect(rv reflect.Value, flags encodeFlags) (*dynamodb.AttributeVal if flags&flagOmitEmpty != 0 && len(avs) == 0 { return nil, nil } - return &dynamodb.AttributeValue{M: avs}, nil + return &types.AttributeValueMemberM{Value: avs}, nil case reflect.Struct: avs, err := marshalStruct(rv) if err != nil { return nil, err } - return &dynamodb.AttributeValue{M: avs}, nil + return &types.AttributeValueMemberM{Value: avs}, nil case reflect.Slice, reflect.Array: // special case: byte slice is B if rv.Type().Elem().Kind() == reflect.Uint8 { if rv.Len() == 0 { if rv.IsNil() && flags&flagNull != 0 { - return &dynamodb.AttributeValue{NULL: aws.Bool(true)}, nil + return &types.AttributeValueMemberNULL{Value: true}, nil } if flags&flagAllowEmpty != 0 { - return &dynamodb.AttributeValue{B: []byte{}}, nil + return &types.AttributeValueMemberB{Value: []byte{}}, nil } return nil, nil } @@ -334,11 +334,11 @@ func marshalReflect(rv reflect.Value, flags encodeFlags) (*dynamodb.AttributeVal } else { data = rv.Bytes() } - return &dynamodb.AttributeValue{B: data}, nil + return &types.AttributeValueMemberB{Value: data}, nil } if flags&flagNull != 0 && rv.IsNil() { - return &dynamodb.AttributeValue{NULL: aws.Bool(true)}, nil + return &types.AttributeValueMemberNULL{Value: true}, nil } // sets @@ -351,7 +351,7 @@ func marshalReflect(rv reflect.Value, flags encodeFlags) (*dynamodb.AttributeVal } // lists CAN be empty - avs := make([]*dynamodb.AttributeValue, 0, rv.Len()) + avs := make([]types.AttributeValue, 0, rv.Len()) subflags := flagNone if flags&flagOmitEmptyElem == 0 { // unless "omitemptyelem" flag is set, include empty/null values @@ -376,17 +376,17 @@ func marshalReflect(rv reflect.Value, flags encodeFlags) (*dynamodb.AttributeVal if flags&flagOmitEmpty != 0 && len(avs) == 0 { return nil, nil } - return &dynamodb.AttributeValue{L: avs}, nil + return &types.AttributeValueMemberL{Value: avs}, nil default: return nil, fmt.Errorf("dynamo marshal: unknown type %s", rv.Type().String()) } } -func marshalSet(rv reflect.Value, flags encodeFlags) (*dynamodb.AttributeValue, error) { +func marshalSet(rv reflect.Value, flags encodeFlags) (types.AttributeValue, error) { iface := reflect.Zero(rv.Type().Elem()).Interface() switch iface.(type) { case encoding.TextMarshaler: - ss := make([]*string, 0, rv.Len()) + ss := make([]string, 0, rv.Len()) for i := 0; i < rv.Len(); i++ { tm := rv.Index(i).Interface().(encoding.TextMarshaler) text, err := tm.MarshalText() @@ -396,69 +396,69 @@ func marshalSet(rv reflect.Value, flags encodeFlags) (*dynamodb.AttributeValue, if flags&flagOmitEmptyElem != 0 && len(text) == 0 { continue } - ss = append(ss, aws.String(string(text))) + ss = append(ss, string(text)) } if len(ss) == 0 { return nil, nil } - return &dynamodb.AttributeValue{SS: ss}, nil + return &types.AttributeValueMemberSS{Value: ss}, nil } switch rv.Type().Kind() { case reflect.Slice: switch rv.Type().Elem().Kind() { case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: - ns := make([]*string, 0, rv.Len()) + ns := make([]string, 0, rv.Len()) for i := 0; i < rv.Len(); i++ { n := rv.Index(i).Int() if flags&flagOmitEmptyElem != 0 && n == 0 { continue } - ns = append(ns, aws.String(strconv.FormatInt(n, 10))) + ns = append(ns, strconv.FormatInt(n, 10)) } if len(ns) == 0 { return nil, nil } - return &dynamodb.AttributeValue{NS: ns}, nil + return &types.AttributeValueMemberNS{Value: ns}, nil case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8: - ns := make([]*string, 0, rv.Len()) + ns := make([]string, 0, rv.Len()) for i := 0; i < rv.Len(); i++ { n := rv.Index(i).Uint() if flags&flagOmitEmptyElem != 0 && n == 0 { continue } - ns = append(ns, aws.String(strconv.FormatUint(n, 10))) + ns = append(ns, strconv.FormatUint(n, 10)) } if len(ns) == 0 { return nil, nil } - return &dynamodb.AttributeValue{NS: ns}, nil + return &types.AttributeValueMemberNS{Value: ns}, nil case reflect.Float32, reflect.Float64: - ns := make([]*string, 0, rv.Len()) + ns := make([]string, 0, rv.Len()) for i := 0; i < rv.Len(); i++ { n := rv.Index(i).Float() if flags&flagOmitEmptyElem != 0 && n == 0 { continue } - ns = append(ns, aws.String(strconv.FormatFloat(n, 'f', -1, 64))) + ns = append(ns, strconv.FormatFloat(n, 'f', -1, 64)) } if len(ns) == 0 { return nil, nil } - return &dynamodb.AttributeValue{NS: ns}, nil + return &types.AttributeValueMemberNS{Value: ns}, nil case reflect.String: - ss := make([]*string, 0, rv.Len()) + ss := make([]string, 0, rv.Len()) for i := 0; i < rv.Len(); i++ { s := rv.Index(i).String() if flags&flagOmitEmptyElem != 0 && s == "" { continue } - ss = append(ss, aws.String(s)) + ss = append(ss, s) } if len(ss) == 0 { return nil, nil } - return &dynamodb.AttributeValue{SS: ss}, nil + return &types.AttributeValueMemberSS{Value: ss}, nil case reflect.Slice: if rv.Type().Elem().Elem().Kind() == reflect.Uint8 { bs := make([][]byte, 0, rv.Len()) @@ -472,7 +472,7 @@ func marshalSet(rv reflect.Value, flags encodeFlags) (*dynamodb.AttributeValue, if len(bs) == 0 { return nil, nil } - return &dynamodb.AttributeValue{BS: bs}, nil + return &types.AttributeValueMemberBS{Value: bs}, nil } } case reflect.Map: @@ -482,7 +482,7 @@ func marshalSet(rv reflect.Value, flags encodeFlags) (*dynamodb.AttributeValue, } if rv.Type().Key().Implements(tmType) { - ss := make([]*string, 0, rv.Len()) + ss := make([]string, 0, rv.Len()) for _, k := range rv.MapKeys() { if !useBool || rv.MapIndex(k).Bool() { txt, err := k.Interface().(encoding.TextMarshaler).MarshalText() @@ -492,76 +492,76 @@ func marshalSet(rv reflect.Value, flags encodeFlags) (*dynamodb.AttributeValue, if flags&flagOmitEmptyElem != 0 && len(txt) == 0 { continue } - ss = append(ss, aws.String(string(txt))) + ss = append(ss, string(txt)) } } if len(ss) == 0 { return nil, nil } - return &dynamodb.AttributeValue{SS: ss}, nil + return &types.AttributeValueMemberSS{Value: ss}, nil } switch rv.Type().Key().Kind() { case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: - ns := make([]*string, 0, rv.Len()) + ns := make([]string, 0, rv.Len()) for _, k := range rv.MapKeys() { if !useBool || rv.MapIndex(k).Bool() { n := k.Int() if flags&flagOmitEmptyElem != 0 && n == 0 { continue } - ns = append(ns, aws.String(strconv.FormatInt(n, 10))) + ns = append(ns, strconv.FormatInt(n, 10)) } } if len(ns) == 0 { return nil, nil } - return &dynamodb.AttributeValue{NS: ns}, nil + return &types.AttributeValueMemberNS{Value: ns}, nil case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8: - ns := make([]*string, 0, rv.Len()) + ns := make([]string, 0, rv.Len()) for _, k := range rv.MapKeys() { if !useBool || rv.MapIndex(k).Bool() { n := k.Uint() if flags&flagOmitEmptyElem != 0 && n == 0 { continue } - ns = append(ns, aws.String(strconv.FormatUint(n, 10))) + ns = append(ns, strconv.FormatUint(n, 10)) } } if len(ns) == 0 { return nil, nil } - return &dynamodb.AttributeValue{NS: ns}, nil + return &types.AttributeValueMemberNS{Value: ns}, nil case reflect.Float32, reflect.Float64: - ns := make([]*string, 0, rv.Len()) + ns := make([]string, 0, rv.Len()) for _, k := range rv.MapKeys() { if !useBool || rv.MapIndex(k).Bool() { n := k.Float() if flags&flagOmitEmptyElem != 0 && n == 0 { continue } - ns = append(ns, aws.String(strconv.FormatFloat(n, 'f', -1, 64))) + ns = append(ns, strconv.FormatFloat(n, 'f', -1, 64)) } } if len(ns) == 0 { return nil, nil } - return &dynamodb.AttributeValue{NS: ns}, nil + return &types.AttributeValueMemberNS{Value: ns}, nil case reflect.String: - ss := make([]*string, 0, rv.Len()) + ss := make([]string, 0, rv.Len()) for _, k := range rv.MapKeys() { if !useBool || rv.MapIndex(k).Bool() { s := k.String() if flags&flagOmitEmptyElem != 0 && s == "" { continue } - ss = append(ss, aws.String(s)) + ss = append(ss, s) } } if len(ss) == 0 { return nil, nil } - return &dynamodb.AttributeValue{SS: ss}, nil + return &types.AttributeValueMemberSS{Value: ss}, nil case reflect.Array: if rv.Type().Key().Elem().Kind() == reflect.Uint8 { bs := make([][]byte, 0, rv.Len()) @@ -576,7 +576,7 @@ func marshalSet(rv reflect.Value, flags encodeFlags) (*dynamodb.AttributeValue, if len(bs) == 0 { return nil, nil } - return &dynamodb.AttributeValue{BS: bs}, nil + return &types.AttributeValueMemberBS{Value: bs}, nil } } } @@ -586,8 +586,8 @@ func marshalSet(rv reflect.Value, flags encodeFlags) (*dynamodb.AttributeValue, var emptyStructType = reflect.TypeOf(struct{}{}) -func marshalSlice(values []interface{}) ([]*dynamodb.AttributeValue, error) { - avs := make([]*dynamodb.AttributeValue, 0, len(values)) +func marshalSlice(values []interface{}) ([]types.AttributeValue, error) { + avs := make([]types.AttributeValue, 0, len(values)) for _, v := range values { av, err := marshal(v, flagNone) if err != nil { diff --git a/encode_test.go b/encode_test.go index 27e2449..78b261b 100644 --- a/encode_test.go +++ b/encode_test.go @@ -4,14 +4,14 @@ import ( "reflect" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) var itemEncodeOnlyTests = []struct { name string in interface{} - out map[string]*dynamodb.AttributeValue + out map[string]types.AttributeValue }{ { name: "omitemptyelem", @@ -26,10 +26,10 @@ var itemEncodeOnlyTests = []struct { M: map[string]string{"test": ""}, Other: true, }, - out: map[string]*dynamodb.AttributeValue{ - "L": {L: []*dynamodb.AttributeValue{}}, - "M": {M: map[string]*dynamodb.AttributeValue{}}, - "Other": {BOOL: aws.Bool(true)}, + out: map[string]types.AttributeValue{ + "L": &types.AttributeValueMemberL{Value: []types.AttributeValue{}}, + "M": &types.AttributeValueMemberM{Value: map[string]types.AttributeValue{}}, + "Other": &types.AttributeValueMemberBOOL{Value: true}, }, }, { @@ -43,8 +43,8 @@ var itemEncodeOnlyTests = []struct { M: map[string]string{"test": ""}, Other: true, }, - out: map[string]*dynamodb.AttributeValue{ - "Other": {BOOL: aws.Bool(true)}, + out: map[string]types.AttributeValue{ + "Other": &types.AttributeValueMemberBOOL{Value: (true)}, }, }, { @@ -62,14 +62,20 @@ var itemEncodeOnlyTests = []struct { }, }, }, - out: map[string]*dynamodb.AttributeValue{ - "M": {M: map[string]*dynamodb.AttributeValue{ - "struct": {M: map[string]*dynamodb.AttributeValue{ - "InnerMap": {M: map[string]*dynamodb.AttributeValue{ - // expected empty inside - }}, - }}, - }}, + out: map[string]types.AttributeValue{ + "M": &types.AttributeValueMemberM{ + Value: map[string]types.AttributeValue{ + "struct": &types.AttributeValueMemberM{ + Value: map[string]types.AttributeValue{ + "InnerMap": &types.AttributeValueMemberM{ + Value: map[string]types.AttributeValue{ + // expected empty inside + }, + }, + }, + }, + }, + }, }, }, { @@ -83,8 +89,8 @@ var itemEncodeOnlyTests = []struct { private: 1337, private2: new(int), }, - out: map[string]*dynamodb.AttributeValue{ - "Public": {N: aws.String("555")}, + out: map[string]types.AttributeValue{ + "Public": &types.AttributeValueMemberN{Value: ("555")}, }, }, } diff --git a/encoding_aws.go b/encoding_aws.go index 5d49ead..ba8b11f 100644 --- a/encoding_aws.go +++ b/encoding_aws.go @@ -4,8 +4,8 @@ import ( "fmt" "reflect" - "github.com/aws/aws-sdk-go/service/dynamodb" - "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" + "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) type Coder interface { @@ -17,12 +17,12 @@ type awsEncoder struct { iface interface{} } -func (w awsEncoder) MarshalDynamo() (*dynamodb.AttributeValue, error) { - return dynamodbattribute.Marshal(w.iface) +func (w awsEncoder) MarshalDynamo() (types.AttributeValue, error) { + return attributevalue.Marshal(w.iface) } -func (w awsEncoder) UnmarshalDynamo(av *dynamodb.AttributeValue) error { - return dynamodbattribute.Unmarshal(av, w.iface) +func (w awsEncoder) UnmarshalDynamo(av types.AttributeValue) error { + return attributevalue.Unmarshal(av, w.iface) } // AWSEncoding wraps an object, forcing it to use AWS's official dynamodbattribute package @@ -32,7 +32,7 @@ func AWSEncoding(v interface{}) Coder { return awsEncoder{v} } -func unmarshalAppendAWS(item map[string]*dynamodb.AttributeValue, out interface{}) error { +func unmarshalAppendAWS(item map[string]types.AttributeValue, out interface{}) error { rv := reflect.ValueOf(out) if rv.Kind() != reflect.Ptr || rv.Elem().Kind() != reflect.Slice { return fmt.Errorf("dynamo: unmarshal append AWS: result argument must be a slice pointer") @@ -40,7 +40,7 @@ func unmarshalAppendAWS(item map[string]*dynamodb.AttributeValue, out interface{ slicev := rv.Elem() innerRV := reflect.New(slicev.Type().Elem()) - if err := dynamodbattribute.UnmarshalMap(item, innerRV.Interface()); err != nil { + if err := attributevalue.UnmarshalMap(item, innerRV.Interface()); err != nil { return err } slicev = reflect.Append(slicev, innerRV.Elem()) diff --git a/encoding_aws_test.go b/encoding_aws_test.go index c2a8a00..f099485 100644 --- a/encoding_aws_test.go +++ b/encoding_aws_test.go @@ -5,9 +5,8 @@ import ( "testing" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" - "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" + "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) type awsTestWidget struct { @@ -33,7 +32,7 @@ func TestAWSEncoding(t *testing.T) { if err != nil { t.Error(err) } - official, err := dynamodbattribute.Marshal(w) + official, err := attributevalue.Marshal(w) if err != nil { t.Error(err) } @@ -56,12 +55,12 @@ func TestAWSEncoding(t *testing.T) { } func TestAWSIfaces(t *testing.T) { - unix := dynamodbattribute.UnixTime(time.Now()) + unix := attributevalue.UnixTime(time.Now()) av, err := Marshal(unix) if err != nil { t.Error(err) } - official, err := dynamodbattribute.Marshal(unix) + official, err := attributevalue.Marshal(unix) if err != nil { t.Error(err) } @@ -69,12 +68,12 @@ func TestAWSIfaces(t *testing.T) { t.Error("marshal not equal.", av, "≠", official) } - var result, officialResult dynamodbattribute.UnixTime + var result, officialResult attributevalue.UnixTime err = Unmarshal(official, &result) if err != nil { t.Error(err) } - err = dynamodbattribute.Unmarshal(official, &officialResult) + err = attributevalue.Unmarshal(official, &officialResult) if err != nil { t.Error(err) } @@ -96,7 +95,7 @@ func TestAWSItems(t *testing.T) { if err != nil { t.Error(err) } - official, err := dynamodbattribute.MarshalMap(item) + official, err := attributevalue.MarshalMap(item) if err != nil { t.Error(err) } @@ -109,7 +108,7 @@ func TestAWSItems(t *testing.T) { if err != nil { t.Error(err) } - err = dynamodbattribute.UnmarshalMap(official, &unmarshaledOfficial) + err = attributevalue.UnmarshalMap(official, &unmarshaledOfficial) if err != nil { t.Error(err) } @@ -132,20 +131,20 @@ func TestAWSUnmarshalAppend(t *testing.T) { A: "two", B: 222, } - err := unmarshalAppend(map[string]*dynamodb.AttributeValue{ - "one": {S: aws.String("test")}, - "two": {N: aws.String("555")}, - }, AWSEncoding(&list)) + err := unmarshalAppend(map[string]types.AttributeValue{ + "one": &types.AttributeValueMemberS{Value: "test"}, + "two": &types.AttributeValueMemberN{Value: "555"}, + }, &list) if err != nil { t.Error(err) } if len(list) != 1 && reflect.DeepEqual(list, []foo{expect1}) { t.Error("bad AWS unmarshal append:", list) } - err = unmarshalAppend(map[string]*dynamodb.AttributeValue{ - "one": {S: aws.String("two")}, - "two": {N: aws.String("222")}, - }, AWSEncoding(&list)) + err = unmarshalAppend(map[string]types.AttributeValue{ + "one": &types.AttributeValueMemberS{Value: ("two")}, + "two": &types.AttributeValueMemberN{Value: ("222")}, + }, &list) if err != nil { t.Error(err) } diff --git a/encoding_test.go b/encoding_test.go index 3f5df7b..79d4353 100644 --- a/encoding_test.go +++ b/encoding_test.go @@ -6,9 +6,9 @@ import ( "strconv" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" - "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) const ( @@ -21,51 +21,55 @@ var ( maxUintStr = strconv.FormatUint(uint64(maxUint), 10) ) +func init() { + time.Local = time.UTC +} + type customString string type customEmpty struct{} var encodingTests = []struct { name string in interface{} - out *dynamodb.AttributeValue + out types.AttributeValue }{ { name: "strings", in: "hello", - out: &dynamodb.AttributeValue{S: aws.String("hello")}, + out: &types.AttributeValueMemberS{Value: "hello"}, }, { name: "bools", in: true, - out: &dynamodb.AttributeValue{BOOL: aws.Bool(true)}, + out: &types.AttributeValueMemberBOOL{Value: true}, }, { name: "ints", in: 123, - out: &dynamodb.AttributeValue{N: aws.String("123")}, + out: &types.AttributeValueMemberN{Value: "123"}, }, { name: "uints", in: uint(123), - out: &dynamodb.AttributeValue{N: aws.String("123")}, + out: &types.AttributeValueMemberN{Value: "123"}, }, { name: "floats", in: 1.2, - out: &dynamodb.AttributeValue{N: aws.String("1.2")}, + out: &types.AttributeValueMemberN{Value: "1.2"}, }, { name: "pointer (int)", in: new(int), - out: &dynamodb.AttributeValue{N: aws.String("0")}, + out: &types.AttributeValueMemberN{Value: "0"}, }, { name: "maps", in: map[string]bool{ "OK": true, }, - out: &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{ - "OK": {BOOL: aws.Bool(true)}, + out: &types.AttributeValueMemberM{Value: map[string]types.AttributeValue{ + "OK": &types.AttributeValueMemberBOOL{Value: true}, }}, }, { @@ -76,8 +80,8 @@ var encodingTests = []struct { }{ Empty: map[string]bool{}, }, - out: &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{ - "Empty": {M: map[string]*dynamodb.AttributeValue{}}, + out: &types.AttributeValueMemberM{Value: map[string]types.AttributeValue{ + "Empty": &types.AttributeValueMemberM{Value: map[string]types.AttributeValue{}}, }}, }, { @@ -87,9 +91,9 @@ var encodingTests = []struct { }{ M1: map[textMarshaler]bool{textMarshaler(true): true}, }, - out: &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{ - "M1": {M: map[string]*dynamodb.AttributeValue{ - "true": {BOOL: aws.Bool(true)}, + out: &types.AttributeValueMemberM{Value: map[string]types.AttributeValue{ + "M1": &types.AttributeValueMemberM{Value: map[string]types.AttributeValue{ + "true": &types.AttributeValueMemberBOOL{Value: true}, }}, }}, }, @@ -98,147 +102,147 @@ var encodingTests = []struct { in: struct { OK bool }{OK: true}, - out: &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{ - "OK": {BOOL: aws.Bool(true)}, + out: &types.AttributeValueMemberM{Value: map[string]types.AttributeValue{ + "OK": &types.AttributeValueMemberBOOL{Value: true}, }}, }, { name: "[]byte", in: []byte{'O', 'K'}, - out: &dynamodb.AttributeValue{B: []byte{'O', 'K'}}, + out: &types.AttributeValueMemberB{Value: []byte{'O', 'K'}}, }, { name: "slice", in: []int{1, 2, 3}, - out: &dynamodb.AttributeValue{L: []*dynamodb.AttributeValue{ - {N: aws.String("1")}, - {N: aws.String("2")}, - {N: aws.String("3")}, + out: &types.AttributeValueMemberL{Value: []types.AttributeValue{ + &types.AttributeValueMemberN{Value: "1"}, + &types.AttributeValueMemberN{Value: "2"}, + &types.AttributeValueMemberN{Value: "3"}, }}, }, { name: "array", in: [3]int{1, 2, 3}, - out: &dynamodb.AttributeValue{L: []*dynamodb.AttributeValue{ - {N: aws.String("1")}, - {N: aws.String("2")}, - {N: aws.String("3")}, + out: &types.AttributeValueMemberL{Value: []types.AttributeValue{ + &types.AttributeValueMemberN{Value: "1"}, + &types.AttributeValueMemberN{Value: "2"}, + &types.AttributeValueMemberN{Value: "3"}, }}, }, { name: "byte array", in: [4]byte{'a', 'b', 'c', 'd'}, - out: &dynamodb.AttributeValue{B: []byte{'a', 'b', 'c', 'd'}}, + out: &types.AttributeValueMemberB{Value: []byte{'a', 'b', 'c', 'd'}}, }, { name: "dynamo.Marshaler", in: customMarshaler(1), - out: &dynamodb.AttributeValue{BOOL: aws.Bool(true)}, + out: &types.AttributeValueMemberBOOL{Value: true}, }, { name: "encoding.TextMarshaler", in: textMarshaler(true), - out: &dynamodb.AttributeValue{S: aws.String("true")}, + out: &types.AttributeValueMemberS{Value: "true"}, }, { name: "dynamodb.AttributeValue", - in: &dynamodb.AttributeValue{L: []*dynamodb.AttributeValue{ - {N: aws.String("1")}, - {N: aws.String("2")}, - {N: aws.String("3")}, + in: &types.AttributeValueMemberL{Value: []types.AttributeValue{ + &types.AttributeValueMemberN{Value: "1"}, + &types.AttributeValueMemberN{Value: "2"}, + &types.AttributeValueMemberN{Value: "3"}, }}, - out: &dynamodb.AttributeValue{L: []*dynamodb.AttributeValue{ - {N: aws.String("1")}, - {N: aws.String("2")}, - {N: aws.String("3")}, + out: &types.AttributeValueMemberL{Value: []types.AttributeValue{ + &types.AttributeValueMemberN{Value: "1"}, + &types.AttributeValueMemberN{Value: "2"}, + &types.AttributeValueMemberN{Value: "3"}, }}, }, { name: "slice with nil", in: []*int64{nil, aws.Int64(0), nil, aws.Int64(1337), nil}, - out: &dynamodb.AttributeValue{L: []*dynamodb.AttributeValue{ - {NULL: aws.Bool(true)}, - {N: aws.String("0")}, - {NULL: aws.Bool(true)}, - {N: aws.String("1337")}, - {NULL: aws.Bool(true)}, + out: &types.AttributeValueMemberL{Value: []types.AttributeValue{ + &types.AttributeValueMemberNULL{Value: true}, + &types.AttributeValueMemberN{Value: "0"}, + &types.AttributeValueMemberNULL{Value: true}, + &types.AttributeValueMemberN{Value: "1337"}, + &types.AttributeValueMemberNULL{Value: true}, }}, }, { name: "array with nil", in: [...]*int64{nil, aws.Int64(0), nil, aws.Int64(1337), nil}, - out: &dynamodb.AttributeValue{L: []*dynamodb.AttributeValue{ - {NULL: aws.Bool(true)}, - {N: aws.String("0")}, - {NULL: aws.Bool(true)}, - {N: aws.String("1337")}, - {NULL: aws.Bool(true)}, + out: &types.AttributeValueMemberL{Value: []types.AttributeValue{ + &types.AttributeValueMemberNULL{Value: true}, + &types.AttributeValueMemberN{Value: "0"}, + &types.AttributeValueMemberNULL{Value: true}, + &types.AttributeValueMemberN{Value: "1337"}, + &types.AttributeValueMemberNULL{Value: true}, }}, }, { name: "slice with empty string", in: []string{"", "hello", "", "world", ""}, - out: &dynamodb.AttributeValue{L: []*dynamodb.AttributeValue{ - {S: aws.String("")}, - {S: aws.String("hello")}, - {S: aws.String("")}, - {S: aws.String("world")}, - {S: aws.String("")}, + out: &types.AttributeValueMemberL{Value: []types.AttributeValue{ + &types.AttributeValueMemberS{Value: ""}, + &types.AttributeValueMemberS{Value: "hello"}, + &types.AttributeValueMemberS{Value: ""}, + &types.AttributeValueMemberS{Value: "world"}, + &types.AttributeValueMemberS{Value: ""}, }}, }, { name: "array with empty string", in: [...]string{"", "hello", "", "world", ""}, - out: &dynamodb.AttributeValue{L: []*dynamodb.AttributeValue{ - {S: aws.String("")}, - {S: aws.String("hello")}, - {S: aws.String("")}, - {S: aws.String("world")}, - {S: aws.String("")}, + out: &types.AttributeValueMemberL{Value: []types.AttributeValue{ + &types.AttributeValueMemberS{Value: ""}, + &types.AttributeValueMemberS{Value: "hello"}, + &types.AttributeValueMemberS{Value: ""}, + &types.AttributeValueMemberS{Value: "world"}, + &types.AttributeValueMemberS{Value: ""}, }}, }, { name: "slice of string pointers", in: []*string{nil, aws.String("hello"), aws.String(""), aws.String("world"), nil}, - out: &dynamodb.AttributeValue{L: []*dynamodb.AttributeValue{ - {NULL: aws.Bool(true)}, - {S: aws.String("hello")}, - {S: aws.String("")}, - {S: aws.String("world")}, - {NULL: aws.Bool(true)}, + out: &types.AttributeValueMemberL{Value: []types.AttributeValue{ + &types.AttributeValueMemberNULL{Value: true}, + &types.AttributeValueMemberS{Value: "hello"}, + &types.AttributeValueMemberS{Value: ""}, + &types.AttributeValueMemberS{Value: "world"}, + &types.AttributeValueMemberNULL{Value: true}, }}, }, { name: "slice with empty binary", in: [][]byte{{}, []byte("hello"), {}, []byte("world"), {}}, - out: &dynamodb.AttributeValue{L: []*dynamodb.AttributeValue{ - {B: []byte{}}, - {B: []byte{'h', 'e', 'l', 'l', 'o'}}, - {B: []byte{}}, - {B: []byte{'w', 'o', 'r', 'l', 'd'}}, - {B: []byte{}}, + out: &types.AttributeValueMemberL{Value: []types.AttributeValue{ + &types.AttributeValueMemberB{Value: []byte{}}, + &types.AttributeValueMemberB{Value: []byte{'h', 'e', 'l', 'l', 'o'}}, + &types.AttributeValueMemberB{Value: []byte{}}, + &types.AttributeValueMemberB{Value: []byte{'w', 'o', 'r', 'l', 'd'}}, + &types.AttributeValueMemberB{Value: []byte{}}, }}, }, { name: "array with empty binary", in: [...][]byte{{}, []byte("hello"), {}, []byte("world"), {}}, - out: &dynamodb.AttributeValue{L: []*dynamodb.AttributeValue{ - {B: []byte{}}, - {B: []byte{'h', 'e', 'l', 'l', 'o'}}, - {B: []byte{}}, - {B: []byte{'w', 'o', 'r', 'l', 'd'}}, - {B: []byte{}}, + out: &types.AttributeValueMemberL{Value: []types.AttributeValue{ + &types.AttributeValueMemberB{Value: []byte{}}, + &types.AttributeValueMemberB{Value: []byte{'h', 'e', 'l', 'l', 'o'}}, + &types.AttributeValueMemberB{Value: []byte{}}, + &types.AttributeValueMemberB{Value: []byte{'w', 'o', 'r', 'l', 'd'}}, + &types.AttributeValueMemberB{Value: []byte{}}, }}, }, { name: "array with empty binary ptrs", in: [...]*[]byte{byteSlicePtr([]byte{}), byteSlicePtr([]byte("hello")), nil, byteSlicePtr([]byte("world")), byteSlicePtr([]byte{})}, - out: &dynamodb.AttributeValue{L: []*dynamodb.AttributeValue{ - {B: []byte{}}, - {B: []byte{'h', 'e', 'l', 'l', 'o'}}, - {NULL: aws.Bool(true)}, - {B: []byte{'w', 'o', 'r', 'l', 'd'}}, - {B: []byte{}}, + out: &types.AttributeValueMemberL{Value: []types.AttributeValue{ + &types.AttributeValueMemberB{Value: []byte{}}, + &types.AttributeValueMemberB{Value: []byte{'h', 'e', 'l', 'l', 'o'}}, + &types.AttributeValueMemberNULL{Value: true}, + &types.AttributeValueMemberB{Value: []byte{'w', 'o', 'r', 'l', 'd'}}, + &types.AttributeValueMemberB{Value: []byte{}}, }}, }, } @@ -246,7 +250,7 @@ var encodingTests = []struct { var itemEncodingTests = []struct { name string in interface{} - out map[string]*dynamodb.AttributeValue + out map[string]types.AttributeValue }{ { name: "strings", @@ -255,8 +259,8 @@ var itemEncodingTests = []struct { }{ A: "hello", }, - out: map[string]*dynamodb.AttributeValue{ - "A": {S: aws.String("hello")}, + out: map[string]types.AttributeValue{ + "A": &types.AttributeValueMemberS{Value: "hello"}, }, }, { @@ -266,8 +270,8 @@ var itemEncodingTests = []struct { }{ A: "hello", }, - out: map[string]*dynamodb.AttributeValue{ - "A": {S: aws.String("hello")}, + out: map[string]types.AttributeValue{ + "A": &types.AttributeValueMemberS{Value: "hello"}, }, }, { @@ -277,8 +281,8 @@ var itemEncodingTests = []struct { }{ A: new(textMarshaler), }, - out: map[string]*dynamodb.AttributeValue{ - "A": {S: aws.String("false")}, + out: map[string]types.AttributeValue{ + "A": &types.AttributeValueMemberS{Value: "false"}, }, }, { @@ -288,8 +292,8 @@ var itemEncodingTests = []struct { }{ A: "hello", }, - out: map[string]*dynamodb.AttributeValue{ - "renamed": {S: aws.String("hello")}, + out: map[string]types.AttributeValue{ + "renamed": &types.AttributeValueMemberS{Value: "hello"}, }, }, { @@ -301,8 +305,8 @@ var itemEncodingTests = []struct { A: "", Other: true, }, - out: map[string]*dynamodb.AttributeValue{ - "Other": {BOOL: aws.Bool(true)}, + out: map[string]types.AttributeValue{ + "Other": &types.AttributeValueMemberBOOL{Value: true}, }, }, { @@ -316,8 +320,8 @@ var itemEncodingTests = []struct { }{ Other: true, }, - out: map[string]*dynamodb.AttributeValue{ - "Other": {BOOL: aws.Bool(true)}, + out: map[string]types.AttributeValue{ + "Other": &types.AttributeValueMemberBOOL{Value: true}, }, }, { @@ -334,14 +338,14 @@ var itemEncodingTests = []struct { NilTime *time.Time NilCustom *customMarshaler NilText *textMarshaler - NilAWS *dynamodbattribute.UnixTime + NilAWS *attributevalue.UnixTime }{ OK: "OK", EmptyL: []int{}, }, - out: map[string]*dynamodb.AttributeValue{ - "OK": {S: aws.String("OK")}, - "EmptyL": {L: []*dynamodb.AttributeValue{}}, + out: map[string]types.AttributeValue{ + "OK": &types.AttributeValueMemberS{Value: "OK"}, + "EmptyL": &types.AttributeValueMemberL{Value: []types.AttributeValue{}}, }, }, { @@ -352,9 +356,9 @@ var itemEncodingTests = []struct { }{ B: []byte{}, }, - out: map[string]*dynamodb.AttributeValue{ - "S": {S: aws.String("")}, - "B": {B: []byte{}}, + out: map[string]types.AttributeValue{ + "S": &types.AttributeValueMemberS{Value: ""}, + "B": &types.AttributeValueMemberB{Value: []byte{}}, }, }, { @@ -364,11 +368,11 @@ var itemEncodingTests = []struct { }{ M: map[string]*string{"null": nil, "empty": aws.String(""), "normal": aws.String("hello")}, }, - out: map[string]*dynamodb.AttributeValue{ - "M": {M: map[string]*dynamodb.AttributeValue{ - "null": {NULL: aws.Bool(true)}, - "empty": {S: aws.String("")}, - "normal": {S: aws.String("hello")}, + out: map[string]types.AttributeValue{ + "M": &types.AttributeValueMemberM{Value: map[string]types.AttributeValue{ + "null": &types.AttributeValueMemberNULL{Value: true}, + "empty": &types.AttributeValueMemberS{Value: ""}, + "normal": &types.AttributeValueMemberS{Value: "hello"}, }}, }, }, @@ -383,12 +387,16 @@ var itemEncodingTests = []struct { }, }, }, - out: map[string]*dynamodb.AttributeValue{ - "M": {M: map[string]*dynamodb.AttributeValue{ - "nestedmap": {M: map[string]*dynamodb.AttributeValue{ - "empty": {S: aws.String("")}, - }}, - }}, + out: map[string]types.AttributeValue{ + "M": &types.AttributeValueMemberM{ + Value: map[string]types.AttributeValue{ + "nestedmap": &types.AttributeValueMemberM{ + Value: map[string]types.AttributeValue{ + "empty": &types.AttributeValueMemberS{Value: ""}, + }, + }, + }, + }, }, }, { @@ -402,14 +410,20 @@ var itemEncodingTests = []struct { }, }, }, - out: map[string]*dynamodb.AttributeValue{ - "M": {M: map[string]*dynamodb.AttributeValue{ - "slice": {L: []*dynamodb.AttributeValue{ - {M: map[string]*dynamodb.AttributeValue{ - "empty": {S: aws.String("")}, - }}, - }}, - }}, + out: map[string]types.AttributeValue{ + "M": &types.AttributeValueMemberM{ + Value: map[string]types.AttributeValue{ + "slice": &types.AttributeValueMemberL{ + Value: []types.AttributeValue{ + &types.AttributeValueMemberM{ + Value: map[string]types.AttributeValue{ + "empty": &types.AttributeValueMemberS{Value: ""}, + }, + }, + }, + }, + }, + }, }, }, { @@ -423,15 +437,16 @@ var itemEncodingTests = []struct { }, }, }, - out: map[string]*dynamodb.AttributeValue{ - "L": {L: []*dynamodb.AttributeValue{ - { - M: map[string]*dynamodb.AttributeValue{ - "empty": {S: aws.String("")}, + out: map[string]types.AttributeValue{ + "L": &types.AttributeValueMemberL{ + Value: []types.AttributeValue{ + &types.AttributeValueMemberM{ + Value: map[string]types.AttributeValue{ + "empty": &types.AttributeValueMemberS{Value: ""}, + }, }, }, }, - }, }, }, { @@ -443,12 +458,12 @@ var itemEncodingTests = []struct { M map[string]*string `dynamo:",null"` SS []string `dynamo:",null,set"` }{}, - out: map[string]*dynamodb.AttributeValue{ - "S": {NULL: aws.Bool(true)}, - "B": {NULL: aws.Bool(true)}, - "NilTime": {NULL: aws.Bool(true)}, - "M": {NULL: aws.Bool(true)}, - "SS": {NULL: aws.Bool(true)}, + out: map[string]types.AttributeValue{ + "S": &types.AttributeValueMemberNULL{Value: true}, + "B": &types.AttributeValueMemberNULL{Value: true}, + "NilTime": &types.AttributeValueMemberNULL{Value: true}, + "M": &types.AttributeValueMemberNULL{Value: true}, + "SS": &types.AttributeValueMemberNULL{Value: true}, }, }, { @@ -460,8 +475,8 @@ var itemEncodingTests = []struct { Embedded: true, }, }, - out: map[string]*dynamodb.AttributeValue{ - "Embedded": {BOOL: aws.Bool(true)}, + out: map[string]types.AttributeValue{ + "Embedded": &types.AttributeValueMemberBOOL{Value: true}, }, }, { @@ -473,8 +488,8 @@ var itemEncodingTests = []struct { Embedded: true, }, }, - out: map[string]*dynamodb.AttributeValue{ - "Embedded": {BOOL: aws.Bool(true)}, + out: map[string]types.AttributeValue{ + "Embedded": &types.AttributeValueMemberBOOL{Value: true}, }, }, { @@ -486,8 +501,8 @@ var itemEncodingTests = []struct { Embedded: true, }, }, - out: map[string]*dynamodb.AttributeValue{ - "Embedded": {BOOL: aws.Bool(true)}, + out: map[string]types.AttributeValue{ + "Embedded": &types.AttributeValueMemberBOOL{Value: true}, }, }, { @@ -498,8 +513,8 @@ var itemEncodingTests = []struct { }{ Embedded: "OK", }, - out: map[string]*dynamodb.AttributeValue{ - "Embedded": {S: aws.String("OK")}, + out: map[string]types.AttributeValue{ + "Embedded": &types.AttributeValueMemberS{Value: "OK"}, }, }, { @@ -510,8 +525,8 @@ var itemEncodingTests = []struct { }{ Embedded: "OK", }, - out: map[string]*dynamodb.AttributeValue{ - "Embedded": {S: aws.String("OK")}, + out: map[string]types.AttributeValue{ + "Embedded": &types.AttributeValueMemberS{Value: "OK"}, }, }, { @@ -522,8 +537,8 @@ var itemEncodingTests = []struct { }{ Embedded: "OK", }, - out: map[string]*dynamodb.AttributeValue{ - "Embedded": {S: aws.String("OK")}, + out: map[string]types.AttributeValue{ + "Embedded": &types.AttributeValueMemberS{Value: "OK"}, }, }, { @@ -569,26 +584,26 @@ var itemEncodingTests = []struct { NS4: map[int]struct{}{maxInt: {}}, NS5: map[uint]bool{maxUint: true}, }, - out: map[string]*dynamodb.AttributeValue{ - "SS1": {SS: []*string{aws.String("A"), aws.String("B")}}, - "SS2": {SS: []*string{aws.String("true"), aws.String("false")}}, - "SS3": {SS: []*string{aws.String("A")}}, - "SS4": {SS: []*string{aws.String("A")}}, - "SS5": {SS: []*string{aws.String("A")}}, - "SS6": {SS: []*string{aws.String("A"), aws.String("B")}}, - "SS7": {SS: []*string{aws.String("true")}}, - "SS8": {SS: []*string{aws.String("false")}}, - "SS9": {SS: []*string{aws.String("A"), aws.String("B"), aws.String("")}}, - "SS10": {SS: []*string{aws.String("A")}}, - "BS1": {BS: [][]byte{{'A'}, {'B'}}}, - "BS2": {BS: [][]byte{{'A'}}}, - "BS3": {BS: [][]byte{{'A'}}}, - "BS4": {BS: [][]byte{{'A'}, {'B'}, {}}}, - "NS1": {NS: []*string{aws.String("1"), aws.String("2")}}, - "NS2": {NS: []*string{aws.String("1"), aws.String("2")}}, - "NS3": {NS: []*string{aws.String("1"), aws.String("2")}}, - "NS4": {NS: []*string{aws.String(maxIntStr)}}, - "NS5": {NS: []*string{aws.String(maxUintStr)}}, + out: map[string]types.AttributeValue{ + "SS1": &types.AttributeValueMemberSS{Value: []string{"A", "B"}}, + "SS2": &types.AttributeValueMemberSS{Value: []string{"true", "false"}}, + "SS3": &types.AttributeValueMemberSS{Value: []string{"A"}}, + "SS4": &types.AttributeValueMemberSS{Value: []string{"A"}}, + "SS5": &types.AttributeValueMemberSS{Value: []string{"A"}}, + "SS6": &types.AttributeValueMemberSS{Value: []string{"A", "B"}}, + "SS7": &types.AttributeValueMemberSS{Value: []string{"true"}}, + "SS8": &types.AttributeValueMemberSS{Value: []string{"false"}}, + "SS9": &types.AttributeValueMemberSS{Value: []string{"A", "B", ""}}, + "SS10": &types.AttributeValueMemberSS{Value: []string{"A"}}, + "BS1": &types.AttributeValueMemberBS{Value: [][]byte{{'A'}, {'B'}}}, + "BS2": &types.AttributeValueMemberBS{Value: [][]byte{{'A'}}}, + "BS3": &types.AttributeValueMemberBS{Value: [][]byte{{'A'}}}, + "BS4": &types.AttributeValueMemberBS{Value: [][]byte{{'A'}, {'B'}, {}}}, + "NS1": &types.AttributeValueMemberNS{Value: []string{"1", "2"}}, + "NS2": &types.AttributeValueMemberNS{Value: []string{"1", "2"}}, + "NS3": &types.AttributeValueMemberNS{Value: []string{"1", "2"}}, + "NS4": &types.AttributeValueMemberNS{Value: []string{maxIntStr}}, + "NS5": &types.AttributeValueMemberNS{Value: []string{maxUintStr}}, }, }, { @@ -602,17 +617,17 @@ var itemEncodingTests = []struct { "OK": true, }, }, - out: map[string]*dynamodb.AttributeValue{ - "S": {S: aws.String("Hello")}, - "B": {B: []byte{'A', 'B'}}, - "N": {N: aws.String("1.2")}, - "L": {L: []*dynamodb.AttributeValue{ - {S: aws.String("A")}, - {S: aws.String("B")}, - {N: aws.String("1.2")}, + out: map[string]types.AttributeValue{ + "S": &types.AttributeValueMemberS{Value: "Hello"}, + "B": &types.AttributeValueMemberB{Value: []byte{'A', 'B'}}, + "N": &types.AttributeValueMemberN{Value: "1.2"}, + "L": &types.AttributeValueMemberL{Value: []types.AttributeValue{ + &types.AttributeValueMemberS{Value: "A"}, + &types.AttributeValueMemberS{Value: "B"}, + &types.AttributeValueMemberN{Value: "1.2"}, }}, - "M": {M: map[string]*dynamodb.AttributeValue{ - "OK": {BOOL: aws.Bool(true)}, + "M": &types.AttributeValueMemberM{Value: map[string]types.AttributeValue{ + "OK": &types.AttributeValueMemberBOOL{Value: true}, }}, }, }, @@ -625,22 +640,9 @@ var itemEncodingTests = []struct { "Hello": "world", }, }, - out: map[string]*dynamodb.AttributeValue{ - "M": {M: map[string]*dynamodb.AttributeValue{ - "Hello": {S: aws.String("world")}, - }}, - }, - }, - { - name: "map string attributevalue", - in: map[string]*dynamodb.AttributeValue{ - "M": {M: map[string]*dynamodb.AttributeValue{ - "Hello": {S: aws.String("world")}, - }}, - }, - out: map[string]*dynamodb.AttributeValue{ - "M": {M: map[string]*dynamodb.AttributeValue{ - "Hello": {S: aws.String("world")}, + out: map[string]types.AttributeValue{ + "M": &types.AttributeValueMemberM{Value: map[string]types.AttributeValue{ + "Hello": &types.AttributeValueMemberS{Value: "world"}, }}, }, }, @@ -651,8 +653,8 @@ var itemEncodingTests = []struct { }{ TTL: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), }, - out: map[string]*dynamodb.AttributeValue{ - "TTL": {S: aws.String("2019-01-01T00:00:00Z")}, + out: map[string]types.AttributeValue{ + "TTL": &types.AttributeValueMemberS{Value: "2019-01-01T00:00:00Z"}, }, }, { @@ -662,8 +664,8 @@ var itemEncodingTests = []struct { }{ TTL: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), }, - out: map[string]*dynamodb.AttributeValue{ - "TTL": {N: aws.String("1546300800")}, + out: map[string]types.AttributeValue{ + "TTL": &types.AttributeValueMemberN{Value: "1546300800"}, }, }, { @@ -673,7 +675,7 @@ var itemEncodingTests = []struct { }{ TTL: time.Time{}, }, - out: map[string]*dynamodb.AttributeValue{}, + out: map[string]types.AttributeValue{}, }, { name: "*time.Time (unixtime encoding)", @@ -682,8 +684,8 @@ var itemEncodingTests = []struct { }{ TTL: aws.Time(time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)), }, - out: map[string]*dynamodb.AttributeValue{ - "TTL": {N: aws.String("1546300800")}, + out: map[string]types.AttributeValue{ + "TTL": &types.AttributeValueMemberN{Value: "1546300800"}, }, }, { @@ -693,20 +695,20 @@ var itemEncodingTests = []struct { }{ TTL: nil, }, - out: map[string]*dynamodb.AttributeValue{}, + out: map[string]types.AttributeValue{}, }, { name: "dynamodb.ItemUnmarshaler", in: customItemMarshaler{Thing: 52}, - out: map[string]*dynamodb.AttributeValue{ - "thing": {N: aws.String("52")}, + out: map[string]types.AttributeValue{ + "thing": &types.AttributeValueMemberN{Value: "52"}, }, }, { name: "*dynamodb.ItemUnmarshaler", in: &customItemMarshaler{Thing: 52}, - out: map[string]*dynamodb.AttributeValue{ - "thing": {N: aws.String("52")}, + out: map[string]types.AttributeValue{ + "thing": &types.AttributeValueMemberN{Value: "52"}, }, }, } @@ -721,14 +723,13 @@ type ExportedEmbedded struct { type customMarshaler int -func (cm customMarshaler) MarshalDynamo() (*dynamodb.AttributeValue, error) { - return &dynamodb.AttributeValue{ - BOOL: aws.Bool(cm != 0), - }, nil +func (cm customMarshaler) MarshalDynamo() (types.AttributeValue, error) { + return &types.AttributeValueMemberBOOL{Value: cm != 0}, nil } -func (cm *customMarshaler) UnmarshalDynamo(av *dynamodb.AttributeValue) error { - if *av.BOOL == true { +func (cm *customMarshaler) UnmarshalDynamo(av types.AttributeValue) error { + + if res, ok := av.(*types.AttributeValueMemberBOOL); ok && res.Value == true { *cm = 1 } return nil @@ -772,30 +773,29 @@ type customItemMarshaler struct { Thing interface{} `dynamo:"thing"` } -func (cim *customItemMarshaler) MarshalDynamoItem() (map[string]*dynamodb.AttributeValue, error) { +func (cim *customItemMarshaler) MarshalDynamoItem() (map[string]types.AttributeValue, error) { thing := strconv.Itoa(cim.Thing.(int)) - attrs := map[string]*dynamodb.AttributeValue{ - "thing": { - N: &thing, - }, + attrs := map[string]types.AttributeValue{ + "thing": &types.AttributeValueMemberN{Value: thing}, } return attrs, nil } -func (cim *customItemMarshaler) UnmarshalDynamoItem(item map[string]*dynamodb.AttributeValue) error { +func (cim *customItemMarshaler) UnmarshalDynamoItem(item map[string]types.AttributeValue) error { thingAttr := item["thing"] - if thingAttr == nil || thingAttr.N == nil { + if res, ok := thingAttr.(*types.AttributeValueMemberN); !ok { return errors.New("Missing or not a number") - } + } else { - thing, err := strconv.Atoi(*thingAttr.N) - if err != nil { - return errors.New("Invalid number") - } + thing, err := strconv.Atoi(res.Value) + if err != nil { + return errors.New("Invalid number") + } - cim.Thing = thing + cim.Thing = thing + } return nil } diff --git a/go.mod b/go.mod index 2f0d4d0..f0c5c1e 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,16 @@ module github.com/guregu/dynamo require ( - github.com/aws/aws-sdk-go v1.42.47 + github.com/aws/aws-sdk-go v1.38.0 + github.com/aws/aws-sdk-go-v2 v1.11.2 + github.com/aws/aws-sdk-go-v2/config v1.11.0 + github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.4.4 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.10.0 + github.com/aws/smithy-go v1.9.0 github.com/cenkalti/backoff/v4 v4.1.2 - github.com/gofrs/uuid v4.2.0+incompatible - golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd + github.com/gofrs/uuid v3.2.0+incompatible + github.com/google/go-cmp v0.5.6 + golang.org/x/net v0.1.0 // indirect ) go 1.13 diff --git a/go.sum b/go.sum index a9e5907..296020d 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,46 @@ -github.com/aws/aws-sdk-go v1.42.47 h1:Faabrbp+bOBiZjHje7Hbhvni212aQYQIXZMruwkgmmA= -github.com/aws/aws-sdk-go v1.42.47/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc= +github.com/aws/aws-sdk-go v1.38.0 h1:mqnmtdW8rGIQmp2d0WRFLua0zW0Pel0P6/vd3gJuViY= +github.com/aws/aws-sdk-go v1.38.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go-v2 v1.11.2 h1:SDiCYqxdIYi6HgQfAWRhgdZrdnOuGyLDJVRSWLeHWvs= +github.com/aws/aws-sdk-go-v2 v1.11.2/go.mod h1:SQfA+m2ltnu1cA0soUkj4dRSsmITiVQUJvBIZjzfPyQ= +github.com/aws/aws-sdk-go-v2/config v1.11.0 h1:Czlld5zBB61A3/aoegA9/buZulwL9mHHfizh/Oq+Kqs= +github.com/aws/aws-sdk-go-v2/config v1.11.0/go.mod h1:VrQDJGFBM5yZe+IOeenNZ/DWoErdny+k2MHEIpwDsEY= +github.com/aws/aws-sdk-go-v2/credentials v1.6.4 h1:2hvbUoHufns0lDIsaK8FVCMukT1WngtZPavN+W2FkSw= +github.com/aws/aws-sdk-go-v2/credentials v1.6.4/go.mod h1:tTrhvBPHyPde4pdIPSba4Nv7RYr4wP9jxXEDa1bKn/8= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.4.4 h1:9WteVf5jmManG9HlxTFsk1+MT1IZ8S/8rvR+3A3OKng= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.4.4/go.mod h1:MWyvQ5I9fEsoV+Im6IgpILXlAaypjlRqUkyS5GP5pIo= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.2 h1:KiN5TPOLrEjbGCvdTQR4t0U4T87vVwALZ5Bg3jpMqPY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.2/go.mod h1:dF2F6tXEOgmW5X1ZFO/EPtWrcm7XkW07KNcJUGNtt4s= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.2 h1:XJLnluKuUxQG255zPNe+04izXl7GSyUVafIsgfv9aw4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.2/go.mod h1:SgKKNBIoDC/E1ZCDhhMW3yalWjwuLjMcpLzsM/QQnWo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.2 h1:EauRoYZVNPlidZSZJDscjJBQ22JhVF2+tdteatax2Ak= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.2/go.mod h1:xT4XX6w5Sa3dhg50JrYyy3e4WPYo/+WjY/BXtqXVunU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.2 h1:IQup8Q6lorXeiA/rK72PeToWoWK8h7VAPgHNWdSrtgE= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.2/go.mod h1:VITe/MdW6EMXPb0o0txu/fsonXbMHUU2OC2Qp7ivU4o= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.10.0 h1:jzvWaPf99rIjqEBxh9uGKxtnIykU/SOXY/nfvThhJvI= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.10.0/go.mod h1:ELltfl9ri0n4sZ/VjPZBgemNMd9mYIpCAuZhc7NP7l4= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.8.1 h1:AQurjazY9KPUxvq4EBN9Q3iWGaDrcqfpfSWtkP0Qy+g= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.8.1/go.mod h1:RiesWyLiePOOwyT5ySDupQosvbG+OTMv9pws/EhDu4U= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.5.0 h1:lPLbw4Gn59uoKqvOfSnkJr54XWk5Ak1NK20ZEiSWb3U= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.5.0/go.mod h1:80NaCIH9YU3rzTTs/J/ECATjXuRqzo/wB6ukO6MZ0XY= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.3.3 h1:ru9+IpkVIuDvIkm9Q0DEjtWHnh6ITDoZo8fH2dIjlqQ= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.3.3/go.mod h1:zOyLMYyg60yyZpOCniAUuibWVqTU4TuLmMa/Wh4P+HA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.2 h1:CKdUNKmuilw/KNmO2Q53Av8u+ZyXMC2M9aX8Z+c/gzg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.2/go.mod h1:FgR1tCsn8C6+Hf+N5qkfrE4IXvUL1RgW87sunJ+5J4I= +github.com/aws/aws-sdk-go-v2/service/sso v1.6.2 h1:2IDmvSb86KT44lSg1uU4ONpzgWLOuApRl6Tg54mZ6Dk= +github.com/aws/aws-sdk-go-v2/service/sso v1.6.2/go.mod h1:KnIpszaIdwI33tmc/W/GGXyn22c1USYxA/2KyvoeDY0= +github.com/aws/aws-sdk-go-v2/service/sts v1.11.1 h1:QKR7wy5e650q70PFKMfGF9sTo0rZgUevSSJ4wxmyWXk= +github.com/aws/aws-sdk-go-v2/service/sts v1.11.1/go.mod h1:UV2N5HaPfdbDpkgkz4sRzWCvQswZjdO1FfqCWl0t7RA= +github.com/aws/smithy-go v1.9.0 h1:c7FUdEqrQA1/UVKKCNDFQPNKGp4FQg3YW4Ck5SLTG58= +github.com/aws/smithy-go v1.9.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= -github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -14,19 +49,42 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= diff --git a/put.go b/put.go index 746c2f5..1148f48 100644 --- a/put.go +++ b/put.go @@ -1,8 +1,10 @@ package dynamo import ( - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" + "context" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) // Put is a request to create or replace an item. @@ -11,7 +13,7 @@ type Put struct { table Table returnType string - item map[string]*dynamodb.AttributeValue + item map[string]types.AttributeValue subber condition string @@ -58,7 +60,7 @@ func (p *Put) Run() error { } // Run executes this put. -func (p *Put) RunWithContext(ctx aws.Context) error { +func (p *Put) RunWithContext(ctx context.Context) error { p.returnType = "NONE" _, err := p.run(ctx) return err @@ -74,7 +76,7 @@ func (p *Put) OldValue(out interface{}) error { // OldValueWithContext executes this put, unmarshaling the previous value into out. // Returns ErrNotFound is there was no previous value. -func (p *Put) OldValueWithContext(ctx aws.Context, out interface{}) error { +func (p *Put) OldValueWithContext(ctx context.Context, out interface{}) error { p.returnType = "ALL_OLD" output, err := p.run(ctx) switch { @@ -86,14 +88,14 @@ func (p *Put) OldValueWithContext(ctx aws.Context, out interface{}) error { return unmarshalItem(output.Attributes, out) } -func (p *Put) run(ctx aws.Context) (output *dynamodb.PutItemOutput, err error) { +func (p *Put) run(ctx context.Context) (output *dynamodb.PutItemOutput, err error) { if p.err != nil { return nil, p.err } req := p.input() retry(ctx, func() error { - output, err = p.table.db.client.PutItemWithContext(ctx, req) + output, err = p.table.db.client.PutItem(ctx, req) return err }) if p.cc != nil { @@ -106,7 +108,7 @@ func (p *Put) input() *dynamodb.PutItemInput { input := &dynamodb.PutItemInput{ TableName: &p.table.name, Item: p.item, - ReturnValues: &p.returnType, + ReturnValues: types.ReturnValue(p.returnType), ExpressionAttributeNames: p.nameExpr, ExpressionAttributeValues: p.valueExpr, } @@ -114,18 +116,18 @@ func (p *Put) input() *dynamodb.PutItemInput { input.ConditionExpression = &p.condition } if p.cc != nil { - input.ReturnConsumedCapacity = aws.String(dynamodb.ReturnConsumedCapacityIndexes) + input.ReturnConsumedCapacity = types.ReturnConsumedCapacityIndexes } return input } -func (p *Put) writeTxItem() (*dynamodb.TransactWriteItem, error) { +func (p *Put) writeTxItem() (*types.TransactWriteItem, error) { if p.err != nil { return nil, p.err } input := p.input() - item := &dynamodb.TransactWriteItem{ - Put: &dynamodb.Put{ + item := &types.TransactWriteItem{ + Put: &types.Put{ TableName: input.TableName, Item: input.Item, ExpressionAttributeNames: input.ExpressionAttributeNames, diff --git a/query.go b/query.go index ded36c8..51f6501 100644 --- a/query.go +++ b/query.go @@ -1,11 +1,12 @@ package dynamo import ( + "context" "errors" "strings" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) // Query is a request to get one or more items in a table. @@ -14,21 +15,21 @@ import ( // and http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html type Query struct { table Table - startKey map[string]*dynamodb.AttributeValue + startKey map[string]types.AttributeValue index string hashKey string - hashValue *dynamodb.AttributeValue + hashValue types.AttributeValue rangeKey string - rangeValues []*dynamodb.AttributeValue + rangeValues []types.AttributeValue rangeOp Operator projection string filters []string consistent bool - limit int64 - searchLimit int64 + limit int32 + searchLimit int32 order *Order subber @@ -68,7 +69,7 @@ const ( Descending = false // ScanIndexForward = false ) -var selectCount = aws.String("COUNT") +var selectCount types.Select = "COUNT" // Get creates a new request to get an item. // Name is the name of the hash key (a.k.a. partition key). @@ -155,7 +156,7 @@ func (q *Query) Consistent(on bool) *Query { } // Limit specifies the maximum amount of results to return. -func (q *Query) Limit(limit int64) *Query { +func (q *Query) Limit(limit int32) *Query { q.limit = limit return q } @@ -163,7 +164,7 @@ func (q *Query) Limit(limit int64) *Query { // SearchLimit specifies the maximum amount of results to examine. // If a filter is not specified, the number of results will be limited. // If a filter is specified, the number of results to consider for filtering will be limited. -func (q *Query) SearchLimit(limit int64) *Query { +func (q *Query) SearchLimit(limit int32) *Query { q.searchLimit = limit return q } @@ -189,7 +190,7 @@ func (q *Query) One(out interface{}) error { return q.OneWithContext(ctx, out) } -func (q *Query) OneWithContext(ctx aws.Context, out interface{}) error { +func (q *Query) OneWithContext(ctx context.Context, out interface{}) error { if q.err != nil { return q.err } @@ -201,7 +202,7 @@ func (q *Query) OneWithContext(ctx aws.Context, out interface{}) error { var res *dynamodb.GetItemOutput err := retry(ctx, func() error { var err error - res, err = q.table.db.client.GetItemWithContext(ctx, req) + res, err = q.table.db.client.GetItem(ctx, req) if err != nil { return err } @@ -226,7 +227,7 @@ func (q *Query) OneWithContext(ctx aws.Context, out interface{}) error { var res *dynamodb.QueryOutput err := retry(ctx, func() error { var err error - res, err = q.table.db.client.QueryWithContext(ctx, req) + res, err = q.table.db.client.Query(ctx, req) if err != nil { return err } @@ -253,18 +254,18 @@ func (q *Query) OneWithContext(ctx aws.Context, out interface{}) error { } // Count executes this request, returning the number of results. -func (q *Query) Count() (int64, error) { +func (q *Query) Count() (int32, error) { ctx, cancel := defaultContext() defer cancel() return q.CountWithContext(ctx) } -func (q *Query) CountWithContext(ctx aws.Context) (int64, error) { +func (q *Query) CountWithContext(ctx context.Context) (int32, error) { if q.err != nil { return 0, q.err } - var count int64 + var count int32 var res *dynamodb.QueryOutput for { req := q.queryInput() @@ -272,14 +273,14 @@ func (q *Query) CountWithContext(ctx aws.Context) (int64, error) { err := retry(ctx, func() error { var err error - res, err = q.table.db.client.QueryWithContext(ctx, req) + res, err = q.table.db.client.Query(ctx, req) if err != nil { return err } - if res.Count == nil { + if res.Count == 0 { return errors.New("nil count") } - count += *res.Count + count += res.Count return nil }) if err != nil { @@ -305,15 +306,15 @@ type queryIter struct { output *dynamodb.QueryOutput err error idx int - n int64 + n int32 // last item evaluated - last map[string]*dynamodb.AttributeValue + last map[string]types.AttributeValue // cache of primary keys, used for generating LEKs keys map[string]struct{} // example LastEvaluatedKey and ExclusiveStartKey, used to lazily evaluate the primary keys if possible - exLEK map[string]*dynamodb.AttributeValue - exESK map[string]*dynamodb.AttributeValue + exLEK map[string]types.AttributeValue + exESK map[string]types.AttributeValue keyErr error unmarshal unmarshalFunc @@ -327,7 +328,7 @@ func (itr *queryIter) Next(out interface{}) bool { return itr.NextWithContext(ctx, out) } -func (itr *queryIter) NextWithContext(ctx aws.Context, out interface{}) bool { +func (itr *queryIter) NextWithContext(ctx context.Context, out interface{}) bool { // stop if we have an error if ctx.Err() != nil { itr.err = ctx.Err() @@ -373,7 +374,7 @@ func (itr *queryIter) NextWithContext(ctx aws.Context, out interface{}) bool { itr.err = retry(ctx, func() error { var err error - itr.output, err = itr.query.table.db.client.QueryWithContext(ctx, itr.input) + itr.output, err = itr.query.table.db.client.Query(ctx, itr.input) return err }) @@ -452,7 +453,7 @@ func (q *Query) All(out interface{}) error { return q.AllWithContext(ctx, out) } -func (q *Query) AllWithContext(ctx aws.Context, out interface{}) error { +func (q *Query) AllWithContext(ctx context.Context, out interface{}) error { iter := &queryIter{ query: q, unmarshal: unmarshalAppend, @@ -471,7 +472,7 @@ func (q *Query) AllWithLastEvaluatedKey(out interface{}) (PagingKey, error) { return q.AllWithLastEvaluatedKeyContext(ctx, out) } -func (q *Query) AllWithLastEvaluatedKeyContext(ctx aws.Context, out interface{}) (PagingKey, error) { +func (q *Query) AllWithLastEvaluatedKeyContext(ctx context.Context, out interface{}) (PagingKey, error) { iter := &queryIter{ query: q, unmarshal: unmarshalAppend, @@ -541,22 +542,22 @@ func (q *Query) queryInput() *dynamodb.QueryInput { req.ScanIndexForward = (*bool)(q.order) } if q.cc != nil { - req.ReturnConsumedCapacity = aws.String(dynamodb.ReturnConsumedCapacityIndexes) + req.ReturnConsumedCapacity = types.ReturnConsumedCapacityIndexes } return req } -func (q *Query) keyConditions() map[string]*dynamodb.Condition { - conds := map[string]*dynamodb.Condition{ +func (q *Query) keyConditions() map[string]types.Condition { + conds := map[string]types.Condition{ q.hashKey: { - AttributeValueList: []*dynamodb.AttributeValue{q.hashValue}, - ComparisonOperator: aws.String(string(Equal)), + AttributeValueList: []types.AttributeValue{q.hashValue}, + ComparisonOperator: types.ComparisonOperatorEq, }, } if q.rangeKey != "" && q.rangeOp != "" { - conds[q.rangeKey] = &dynamodb.Condition{ + conds[q.rangeKey] = types.Condition{ AttributeValueList: q.rangeValues, - ComparisonOperator: aws.String(string(q.rangeOp)), + ComparisonOperator: types.ComparisonOperator(q.rangeOp), } } return conds @@ -575,18 +576,18 @@ func (q *Query) getItemInput() *dynamodb.GetItemInput { req.ProjectionExpression = &q.projection } if q.cc != nil { - req.ReturnConsumedCapacity = aws.String(dynamodb.ReturnConsumedCapacityIndexes) + req.ReturnConsumedCapacity = types.ReturnConsumedCapacityIndexes } return req } -func (q *Query) getTxItem() (*dynamodb.TransactGetItem, error) { +func (q *Query) getTxItem() (types.TransactGetItem, error) { if !q.canGetItem() { - return nil, errors.New("dynamo: transaction Query is too complex; no indexes or filters are allowed") + return types.TransactGetItem{}, errors.New("dynamo: transaction Query is too complex; no indexes or filters are allowed") } input := q.getItemInput() - return &dynamodb.TransactGetItem{ - Get: &dynamodb.Get{ + return types.TransactGetItem{ + Get: &types.Get{ TableName: input.TableName, Key: input.Key, ExpressionAttributeNames: input.ExpressionAttributeNames, @@ -595,8 +596,8 @@ func (q *Query) getTxItem() (*dynamodb.TransactGetItem, error) { }, nil } -func (q *Query) keys() map[string]*dynamodb.AttributeValue { - keys := map[string]*dynamodb.AttributeValue{ +func (q *Query) keys() map[string]types.AttributeValue { + keys := map[string]types.AttributeValue{ q.hashKey: q.hashValue, } if q.rangeKey != "" && len(q.rangeValues) > 0 { @@ -605,9 +606,9 @@ func (q *Query) keys() map[string]*dynamodb.AttributeValue { return keys } -func (q *Query) keysAndAttribs() *dynamodb.KeysAndAttributes { - kas := &dynamodb.KeysAndAttributes{ - Keys: []map[string]*dynamodb.AttributeValue{q.keys()}, +func (q *Query) keysAndAttribs() *types.KeysAndAttributes { + kas := &types.KeysAndAttributes{ + Keys: []map[string]types.AttributeValue{q.keys()}, ExpressionAttributeNames: q.nameExpr, ConsistentRead: &q.consistent, } diff --git a/query_test.go b/query_test.go index 276f5ae..ec6988a 100644 --- a/query_test.go +++ b/query_test.go @@ -5,8 +5,8 @@ import ( "testing" "time" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" ) func TestGetAllCount(t *testing.T) { @@ -37,8 +37,8 @@ func TestGetAllCount(t *testing.T) { "#meta": "Meta", "#foo": "foo", }), - AttributeValues: map[string]*dynamodb.AttributeValue{ - ":bar": {S: aws.String("bar")}, + AttributeValues: map[string]types.AttributeValue{ + ":bar": &types.AttributeValueMemberS{Value: "bar"}, }, } diff --git a/retry.go b/retry.go index 2ee55d4..da96bde 100644 --- a/retry.go +++ b/retry.go @@ -1,14 +1,12 @@ package dynamo import ( + "context" "errors" "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/cenkalti/backoff/v4" - "golang.org/x/net/context" ) // RetryTimeout defines the maximum amount of time that requests will @@ -18,7 +16,7 @@ import ( // Higher values are better when using tables with lower throughput. var RetryTimeout = 1 * time.Minute -func defaultContext() (aws.Context, context.CancelFunc) { +func defaultContext() (context.Context, context.CancelFunc) { if RetryTimeout == 0 { return aws.BackgroundContext(), (func() {}) } @@ -52,37 +50,5 @@ func retry(ctx aws.Context, f func() error) error { var errRetry = errors.New("dynamo: retry") func canRetry(err error) bool { - if errors.Is(err, errRetry) { - return true - } - - if txe, ok := err.(*dynamodb.TransactionCanceledException); ok && txe.StatusCode() == 400 { - retry := false - for _, reason := range txe.CancellationReasons { - if reason.Code == nil { - continue - } - switch *reason.Code { - case "ValidationError", "ConditionalCheckFailed", "ItemCollectionSizeLimitExceeded": - return false - case "ThrottlingError", "ProvisionedThroughputExceeded", "TransactionConflict": - retry = true - } - } - return retry - } - - if ae, ok := err.(awserr.RequestFailure); ok { - switch ae.StatusCode() { - case 500, 503: - return true - case 400: - switch ae.Code() { - case "ProvisionedThroughputExceededException", - "ThrottlingException": - return true - } - } - } - return false + return errors.Is(err, errRetry) } diff --git a/scan.go b/scan.go index 4311e92..e39dbcc 100644 --- a/scan.go +++ b/scan.go @@ -1,24 +1,25 @@ package dynamo import ( + "context" "strings" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) // Scan is a request to scan all the data in a table. // See: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html type Scan struct { table Table - startKey map[string]*dynamodb.AttributeValue + startKey map[string]types.AttributeValue index string projection string filters []string consistent bool - limit int64 - searchLimit int64 + limit int32 + searchLimit int32 subber @@ -75,7 +76,7 @@ func (s *Scan) Consistent(on bool) *Scan { } // Limit specifies the maximum amount of results to return. -func (s *Scan) Limit(limit int64) *Scan { +func (s *Scan) Limit(limit int32) *Scan { s.limit = limit return s } @@ -83,7 +84,7 @@ func (s *Scan) Limit(limit int64) *Scan { // SearchLimit specifies a maximum amount of results to evaluate. // Use this along with StartFrom and Iter's LastEvaluatedKey to split up results. // Note that DynamoDB limits result sets to 1MB. -func (s *Scan) SearchLimit(limit int64) *Scan { +func (s *Scan) SearchLimit(limit int32) *Scan { s.searchLimit = limit return s } @@ -112,7 +113,7 @@ func (s *Scan) All(out interface{}) error { } // AllWithContext executes this request and unmarshals all results to out, which must be a pointer to a slice. -func (s *Scan) AllWithContext(ctx aws.Context, out interface{}) error { +func (s *Scan) AllWithContext(ctx context.Context, out interface{}) error { itr := &scanIter{ scan: s, unmarshal: unmarshalAppend, @@ -133,7 +134,7 @@ func (s *Scan) AllWithLastEvaluatedKey(out interface{}) (PagingKey, error) { // AllWithLastEvaluatedKeyContext executes this request and unmarshals all results to out, which must be a pointer to a slice. // It returns a key you can use with StartWith to continue this query. -func (s *Scan) AllWithLastEvaluatedKeyContext(ctx aws.Context, out interface{}) (PagingKey, error) { +func (s *Scan) AllWithLastEvaluatedKeyContext(ctx context.Context, out interface{}) (PagingKey, error) { itr := &scanIter{ scan: s, unmarshal: unmarshalAppend, @@ -147,7 +148,7 @@ func (s *Scan) AllWithLastEvaluatedKeyContext(ctx aws.Context, out interface{}) // Count executes this request and returns the number of items matching the scan. // It takes into account the filter, limit, search limit, and all other parameters given. // It may return a higher count than the limits. -func (s *Scan) Count() (int64, error) { +func (s *Scan) Count() (int32, error) { ctx, cancel := defaultContext() defer cancel() return s.CountWithContext(ctx) @@ -156,26 +157,26 @@ func (s *Scan) Count() (int64, error) { // CountWithContext executes this request and returns the number of items matching the scan. // It takes into account the filter, limit, search limit, and all other parameters given. // It may return a higher count than the limits. -func (s *Scan) CountWithContext(ctx aws.Context) (int64, error) { +func (s *Scan) CountWithContext(ctx context.Context) (int32, error) { if s.err != nil { return 0, s.err } - var count, scanned int64 + var count, scanned int32 input := s.scanInput() - input.Select = aws.String(dynamodb.SelectCount) + input.Select = types.SelectCount for { var out *dynamodb.ScanOutput err := retry(ctx, func() error { var err error - out, err = s.table.db.client.ScanWithContext(ctx, input) + out, err = s.table.db.client.Scan(ctx, input) return err }) if err != nil { return count, err } - count += *out.Count - scanned += *out.ScannedCount + count += out.Count + scanned += out.ScannedCount if s.cc != nil { addConsumedCapacity(s.cc, out.ConsumedCapacity) @@ -223,7 +224,7 @@ func (s *Scan) scanInput() *dynamodb.ScanInput { input.FilterExpression = &filter } if s.cc != nil { - input.ReturnConsumedCapacity = aws.String(dynamodb.ReturnConsumedCapacityIndexes) + input.ReturnConsumedCapacity = types.ReturnConsumedCapacityIndexes } return input } @@ -241,15 +242,15 @@ type scanIter struct { output *dynamodb.ScanOutput err error idx int - n int64 + n int32 // last item evaluated - last map[string]*dynamodb.AttributeValue + last map[string]types.AttributeValue // cache of primary keys, used for generating LEKs keys map[string]struct{} // example LastEvaluatedKey and ExclusiveStartKey, used to lazily evaluate the primary keys if possible - exLEK map[string]*dynamodb.AttributeValue - exESK map[string]*dynamodb.AttributeValue + exLEK map[string]types.AttributeValue + exESK map[string]types.AttributeValue keyErr error unmarshal unmarshalFunc @@ -263,7 +264,7 @@ func (itr *scanIter) Next(out interface{}) bool { return itr.NextWithContext(ctx, out) } -func (itr *scanIter) NextWithContext(ctx aws.Context, out interface{}) bool { +func (itr *scanIter) NextWithContext(ctx context.Context, out interface{}) bool { // stop if we have an error if ctx.Err() != nil { itr.err = ctx.Err() @@ -309,7 +310,7 @@ func (itr *scanIter) NextWithContext(ctx aws.Context, out interface{}) bool { itr.err = retry(ctx, func() error { var err error - itr.output, err = itr.scan.table.db.client.ScanWithContext(ctx, itr.input) + itr.output, err = itr.scan.table.db.client.Scan(ctx, itr.input) return err }) diff --git a/sse.go b/sse.go index b678f1e..ec76536 100644 --- a/sse.go +++ b/sse.go @@ -1,6 +1,10 @@ package dynamo -import "time" +import ( + "time" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" +) // SSEType is used to specify the type of server side encryption // to use on a table @@ -15,8 +19,8 @@ const ( type SSEDescription struct { InaccessibleEncryptionDateTime time.Time KMSMasterKeyArn string - SSEType SSEType - Status string + SSEType types.SSEType + Status types.SSEStatus } func lookupSSEType(sseType string) SSEType { diff --git a/substitute.go b/substitute.go index e302e9d..2c9d415 100644 --- a/substitute.go +++ b/substitute.go @@ -8,31 +8,29 @@ import ( "strconv" "strings" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" - + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/guregu/dynamo/internal/exprs" ) // subber is a "mixin" for operators for keep track of subtituted keys and values type subber struct { - nameExpr map[string]*string - valueExpr map[string]*dynamodb.AttributeValue + nameExpr map[string]string + valueExpr map[string]types.AttributeValue } func (s *subber) subName(name string) string { if s.nameExpr == nil { - s.nameExpr = make(map[string]*string) + s.nameExpr = make(map[string]string) } sub := "#s" + encodeName(name) - s.nameExpr[sub] = aws.String(name) + s.nameExpr[sub] = name return sub } func (s *subber) subValue(value interface{}, flags encodeFlags) (string, error) { if s.valueExpr == nil { - s.valueExpr = make(map[string]*dynamodb.AttributeValue) + s.valueExpr = make(map[string]types.AttributeValue) } if lit, ok := value.(ExpressionLiteral); ok { @@ -132,7 +130,7 @@ type ExpressionLiteral struct { // AttributeNames is a map of placeholders (such as #foo) to attribute names. AttributeNames map[string]*string // AttributeValues is a map of placeholders (such as :bar) to attribute values. - AttributeValues map[string]*dynamodb.AttributeValue + AttributeValues map[string]types.AttributeValue } // we don't want people to accidentally refer to our placeholders, so just slap an x_ in front of theirs @@ -146,15 +144,15 @@ func (s *subber) merge(lit ExpressionLiteral) string { } if len(lit.AttributeNames) > 0 && s.nameExpr == nil { - s.nameExpr = make(map[string]*string) + s.nameExpr = make(map[string]string) } for k, v := range lit.AttributeNames { safe := prefix(k) - s.nameExpr[safe] = v + s.nameExpr[safe] = *v } if len(lit.AttributeValues) > 0 && s.valueExpr == nil { - s.valueExpr = make(map[string]*dynamodb.AttributeValue) + s.valueExpr = make(map[string]types.AttributeValue) } for k, v := range lit.AttributeValues { safe := prefix(k) diff --git a/substitute_test.go b/substitute_test.go index bf64b54..8379d04 100644 --- a/substitute_test.go +++ b/substitute_test.go @@ -5,8 +5,8 @@ import ( "reflect" "testing" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" ) func TestSubExpr(t *testing.T) { @@ -57,9 +57,9 @@ func TestSubMerge(t *testing.T) { "#abc": aws.String("custom"), "#abcdef": aws.String("model"), }, - AttributeValues: map[string]*dynamodb.AttributeValue{ - ":v": {S: aws.String("abc")}, - ":v0": {N: aws.String("555")}, + AttributeValues: map[string]types.AttributeValue{ + ":v": &types.AttributeValueMemberS{Value: "abc"}, + ":v0": &types.AttributeValueMemberN{Value: "555"}, }, } rewrite, err := s.subExpr("?", lit) @@ -77,8 +77,8 @@ func TestSubMerge(t *testing.T) { if !ok { t.Error("missing merged name:", k, foreign) } - if !reflect.DeepEqual(v, got) { - t.Error("merged name mismatch. want:", v, "got:", got) + if !reflect.DeepEqual(*v, got) { + t.Error("merged name mismatch. want:", *v, "got:", got) } } diff --git a/table.go b/table.go index cef1e97..db28ad8 100644 --- a/table.go +++ b/table.go @@ -6,9 +6,9 @@ import ( "fmt" "sync/atomic" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/aws/smithy-go" ) // Status is an enumeration of table and index statuses. @@ -62,7 +62,7 @@ func (table Table) Wait(want ...Status) error { // Wait blocks until this table's status matches any status provided by want. // If no statuses are specified, the active status is used. -func (table Table) WaitWithContext(ctx aws.Context, want ...Status) error { +func (table Table) WaitWithContext(ctx context.Context, want ...Status) error { if len(want) == 0 { want = []Status{ActiveStatus} } @@ -75,9 +75,9 @@ func (table Table) WaitWithContext(ctx aws.Context, want ...Status) error { err := retry(ctx, func() error { desc, err := table.Describe().RunWithContext(ctx) - var aerr awserr.RequestFailure + var aerr smithy.APIError if errors.As(err, &aerr) { - if aerr.Code() == "ResourceNotFoundException" { + if aerr.ErrorCode() == "ResourceNotFoundException" { if wantGone { return nil } @@ -103,8 +103,8 @@ func (table Table) WaitWithContext(ctx aws.Context, want ...Status) error { // - output LastEvaluatedKey // - input ExclusiveStartKey // - DescribeTable as a last resort (cached inside table) -func (table Table) primaryKeys(ctx aws.Context, lek, esk map[string]*dynamodb.AttributeValue, index string) (map[string]struct{}, error) { - extract := func(item map[string]*dynamodb.AttributeValue) map[string]struct{} { +func (table Table) primaryKeys(ctx context.Context, lek, esk map[string]types.AttributeValue, index string) (map[string]struct{}, error) { + extract := func(item map[string]types.AttributeValue) map[string]struct{} { keys := make(map[string]struct{}, len(item)) for k := range item { keys[k] = struct{}{} @@ -150,7 +150,7 @@ func (table Table) primaryKeys(ctx aws.Context, lek, esk map[string]*dynamodb.At return keys, nil } -func lekify(item map[string]*dynamodb.AttributeValue, keys map[string]struct{}) (map[string]*dynamodb.AttributeValue, error) { +func lekify(item map[string]types.AttributeValue, keys map[string]struct{}) (map[string]types.AttributeValue, error) { if item == nil { // this shouldn't happen because in queries without results, a LastEvaluatedKey should be given to us by AWS return nil, fmt.Errorf("dynamo: can't determine LastEvaluatedKey: no keys or results") @@ -158,7 +158,7 @@ func lekify(item map[string]*dynamodb.AttributeValue, keys map[string]struct{}) if keys == nil { return nil, fmt.Errorf("dynamo: can't determine LastEvaluatedKey: failed to infer primary keys") } - lek := make(map[string]*dynamodb.AttributeValue, len(keys)) + lek := make(map[string]types.AttributeValue, len(keys)) for k := range keys { v, ok := item[k] if !ok { @@ -188,10 +188,10 @@ func (dt *DeleteTable) Run() error { } // RunWithContext executes this request and deletes the table. -func (dt *DeleteTable) RunWithContext(ctx aws.Context) error { +func (dt *DeleteTable) RunWithContext(ctx context.Context) error { input := dt.input() return retry(ctx, func() error { - _, err := dt.table.db.client.DeleteTableWithContext(ctx, input) + _, err := dt.table.db.client.DeleteTable(ctx, input) return err }) } @@ -256,7 +256,7 @@ type ConsumedCapacity struct { TableName string } -func addConsumedCapacity(cc *ConsumedCapacity, raw *dynamodb.ConsumedCapacity) { +func addConsumedCapacity(cc *ConsumedCapacity, raw *types.ConsumedCapacity) { if cc == nil || raw == nil { return } diff --git a/table_test.go b/table_test.go index 4c93ae7..a1bbd91 100644 --- a/table_test.go +++ b/table_test.go @@ -7,8 +7,8 @@ import ( "testing" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) func TestTableLifecycle(t *testing.T) { @@ -62,7 +62,7 @@ func TestTableLifecycle(t *testing.T) { RangeKeyType: NumberType, Throughput: Throughput{Read: 1, Write: 1}, ProjectionType: AllProjection, - ProjectionAttribs: []string{}, + ProjectionAttribs: []string(nil), }, { Name: "Seq-ID-index", @@ -74,7 +74,7 @@ func TestTableLifecycle(t *testing.T) { RangeKeyType: StringType, Throughput: Throughput{Read: 1, Write: 1}, ProjectionType: AllProjection, - ProjectionAttribs: []string{}, + ProjectionAttribs: []string(nil), }, { Name: "UUID-index", @@ -84,7 +84,7 @@ func TestTableLifecycle(t *testing.T) { HashKeyType: StringType, Throughput: Throughput{Read: 1, Write: 1}, ProjectionType: AllProjection, - ProjectionAttribs: []string{}, + ProjectionAttribs: []string(nil), }, }, LSI: []Index{ @@ -100,7 +100,7 @@ func TestTableLifecycle(t *testing.T) { RangeKeyType: NumberType, Throughput: Throughput{Read: 1, Write: 1}, ProjectionType: AllProjection, - ProjectionAttribs: []string{}, + ProjectionAttribs: []string(nil), }, }, } @@ -122,21 +122,21 @@ func TestTableLifecycle(t *testing.T) { } func TestAddConsumedCapacity(t *testing.T) { - raw := &dynamodb.ConsumedCapacity{ + raw := &types.ConsumedCapacity{ TableName: aws.String("TestTable"), - Table: &dynamodb.Capacity{ + Table: &types.Capacity{ CapacityUnits: aws.Float64(9), ReadCapacityUnits: aws.Float64(4), WriteCapacityUnits: aws.Float64(5), }, - GlobalSecondaryIndexes: map[string]*dynamodb.Capacity{ + GlobalSecondaryIndexes: map[string]types.Capacity{ "TestGSI": { CapacityUnits: aws.Float64(3), ReadCapacityUnits: aws.Float64(1), WriteCapacityUnits: aws.Float64(2), }, }, - LocalSecondaryIndexes: map[string]*dynamodb.Capacity{ + LocalSecondaryIndexes: map[string]types.Capacity{ "TestLSI": { CapacityUnits: aws.Float64(30), ReadCapacityUnits: aws.Float64(10), @@ -147,7 +147,7 @@ func TestAddConsumedCapacity(t *testing.T) { ReadCapacityUnits: aws.Float64(15), WriteCapacityUnits: aws.Float64(27), } - expected := ConsumedCapacity{ + expected := &ConsumedCapacity{ TableName: *raw.TableName, Table: *raw.Table.CapacityUnits, TableRead: *raw.Table.ReadCapacityUnits, @@ -163,8 +163,8 @@ func TestAddConsumedCapacity(t *testing.T) { Write: *raw.WriteCapacityUnits, } - var cc ConsumedCapacity - addConsumedCapacity(&cc, raw) + var cc = new(ConsumedCapacity) + addConsumedCapacity(cc, raw) if !reflect.DeepEqual(cc, expected) { t.Error("bad ConsumedCapacity:", cc, "≠", expected) diff --git a/ttl.go b/ttl.go index ca4bb69..48f21e8 100644 --- a/ttl.go +++ b/ttl.go @@ -1,8 +1,11 @@ package dynamo import ( - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) // UpdateTTL is a request to enable or disable a table's time to live functionality. @@ -36,11 +39,11 @@ func (ttl *UpdateTTL) Run() error { } // RunWithContext executes this request. -func (ttl *UpdateTTL) RunWithContext(ctx aws.Context) error { +func (ttl *UpdateTTL) RunWithContext(ctx context.Context) error { input := ttl.input() err := retry(ctx, func() error { - _, err := ttl.table.db.client.UpdateTimeToLiveWithContext(ctx, input) + _, err := ttl.table.db.client.UpdateTimeToLive(ctx, input) return err }) return err @@ -49,7 +52,7 @@ func (ttl *UpdateTTL) RunWithContext(ctx aws.Context) error { func (ttl *UpdateTTL) input() *dynamodb.UpdateTimeToLiveInput { return &dynamodb.UpdateTimeToLiveInput{ TableName: aws.String(ttl.table.Name()), - TimeToLiveSpecification: &dynamodb.TimeToLiveSpecification{ + TimeToLiveSpecification: &types.TimeToLiveSpecification{ Enabled: aws.Bool(ttl.enabled), AttributeName: aws.String(ttl.attrib), }, @@ -74,13 +77,13 @@ func (d *DescribeTTL) Run() (TTLDescription, error) { } // RunWithContext executes this request and returns details about time to live, or an error. -func (d *DescribeTTL) RunWithContext(ctx aws.Context) (TTLDescription, error) { +func (d *DescribeTTL) RunWithContext(ctx context.Context) (TTLDescription, error) { input := d.input() var result *dynamodb.DescribeTimeToLiveOutput err := retry(ctx, func() error { var err error - result, err = d.table.db.client.DescribeTimeToLiveWithContext(ctx, input) + result, err = d.table.db.client.DescribeTimeToLive(ctx, input) return err }) if err != nil { @@ -90,8 +93,8 @@ func (d *DescribeTTL) RunWithContext(ctx aws.Context) (TTLDescription, error) { desc := TTLDescription{ Status: TTLDisabled, } - if result.TimeToLiveDescription.TimeToLiveStatus != nil { - desc.Status = TTLStatus(*result.TimeToLiveDescription.TimeToLiveStatus) + if result.TimeToLiveDescription.TimeToLiveStatus != "" { + desc.Status = TTLStatus(result.TimeToLiveDescription.TimeToLiveStatus) } if result.TimeToLiveDescription.AttributeName != nil { desc.Attribute = *result.TimeToLiveDescription.AttributeName diff --git a/tx.go b/tx.go index b8e1abd..afe1601 100644 --- a/tx.go +++ b/tx.go @@ -1,10 +1,12 @@ package dynamo import ( + "context" "errors" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/gofrs/uuid" ) @@ -13,7 +15,7 @@ import ( var ErrNoInput = errors.New("dynamo: no input items") type getTxOp interface { - getTxItem() (*dynamodb.TransactGetItem, error) + getTxItem() (types.TransactGetItem, error) } // GetTx is a transaction to retrieve items. @@ -65,7 +67,7 @@ func (tx *GetTx) Run() error { } // RunWithContext executes this transaction and unmarshals everything specified by GetOne. -func (tx *GetTx) RunWithContext(ctx aws.Context) error { +func (tx *GetTx) RunWithContext(ctx context.Context) error { input, err := tx.input() if err != nil { return err @@ -73,10 +75,10 @@ func (tx *GetTx) RunWithContext(ctx aws.Context) error { var resp *dynamodb.TransactGetItemsOutput err = retry(ctx, func() error { var err error - resp, err = tx.db.client.TransactGetItemsWithContext(ctx, input) + resp, err = tx.db.client.TransactGetItems(ctx, input) if tx.cc != nil && resp != nil { for _, cc := range resp.ConsumedCapacity { - addConsumedCapacity(tx.cc, cc) + addConsumedCapacity(tx.cc, &cc) } } return err @@ -112,7 +114,7 @@ func (tx *GetTx) All(out interface{}) error { } // AllWithContext executes this transaction and unmarshals every value to out, which must be a pointer to a slice. -func (tx *GetTx) AllWithContext(ctx aws.Context, out interface{}) error { +func (tx *GetTx) AllWithContext(ctx context.Context, out interface{}) error { input, err := tx.input() if err != nil { return err @@ -120,10 +122,10 @@ func (tx *GetTx) AllWithContext(ctx aws.Context, out interface{}) error { var resp *dynamodb.TransactGetItemsOutput err = retry(ctx, func() error { var err error - resp, err = tx.db.client.TransactGetItems(input) + resp, err = tx.db.client.TransactGetItems(ctx, input) if tx.cc != nil && resp != nil { for _, cc := range resp.ConsumedCapacity { - addConsumedCapacity(tx.cc, cc) + addConsumedCapacity(tx.cc, &cc) } } return err @@ -161,13 +163,13 @@ func (tx *GetTx) input() (*dynamodb.TransactGetItemsInput, error) { input.TransactItems = append(input.TransactItems, tgi) } if tx.cc != nil { - input.ReturnConsumedCapacity = aws.String(dynamodb.ReturnConsumedCapacityIndexes) + input.ReturnConsumedCapacity = types.ReturnConsumedCapacityIndexes } return input, nil } type writeTxOp interface { - writeTxItem() (*dynamodb.TransactWriteItem, error) + writeTxItem() (*types.TransactWriteItem, error) } // WriteTx is a transaction to delete, put, update, and check items. @@ -257,7 +259,7 @@ func (tx *WriteTx) Run() error { } // RunWithContext executes this transaction. -func (tx *WriteTx) RunWithContext(ctx aws.Context) error { +func (tx *WriteTx) RunWithContext(ctx context.Context) error { if tx.err != nil { return tx.err } @@ -266,10 +268,10 @@ func (tx *WriteTx) RunWithContext(ctx aws.Context) error { return err } err = retry(ctx, func() error { - out, err := tx.db.client.TransactWriteItemsWithContext(ctx, input) + out, err := tx.db.client.TransactWriteItems(ctx, input) if tx.cc != nil && out != nil { for _, cc := range out.ConsumedCapacity { - addConsumedCapacity(tx.cc, cc) + addConsumedCapacity(tx.cc, &cc) } } return err @@ -287,13 +289,13 @@ func (tx *WriteTx) input() (*dynamodb.TransactWriteItemsInput, error) { if err != nil { return nil, err } - input.TransactItems = append(input.TransactItems, wti) + input.TransactItems = append(input.TransactItems, *wti) } if tx.token != "" { input.ClientRequestToken = aws.String(tx.token) } if tx.cc != nil { - input.ReturnConsumedCapacity = aws.String(dynamodb.ReturnConsumedCapacityIndexes) + input.ReturnConsumedCapacity = types.ReturnConsumedCapacityIndexes } return input, nil } @@ -304,7 +306,7 @@ func (tx *WriteTx) setError(err error) { } } -func isResponsesEmpty(resps []*dynamodb.ItemResponse) bool { +func isResponsesEmpty(resps []types.ItemResponse) bool { for _, resp := range resps { if resp.Item != nil { return false diff --git a/tx_test.go b/tx_test.go index 6f99c9f..224f705 100644 --- a/tx_test.go +++ b/tx_test.go @@ -1,14 +1,14 @@ package dynamo import ( + "errors" "reflect" "sync" "testing" "time" + "github.com/aws/smithy-go" "github.com/gofrs/uuid" - - "github.com/aws/aws-sdk-go/aws/awserr" ) func TestTx(t *testing.T) { @@ -158,7 +158,8 @@ func TestTx(t *testing.T) { if err == nil { t.Error("expected error") } else { - if err.(awserr.Error).Code() != "TransactionCanceledException" { + var apiErr smithy.APIError + if errors.As(err, &apiErr) && apiErr.ErrorCode() != "TransactionCanceledException" { t.Error("unexpected error:", err) } } diff --git a/update.go b/update.go index a341ad0..c59c825 100644 --- a/update.go +++ b/update.go @@ -1,11 +1,12 @@ package dynamo import ( + "context" "fmt" "strings" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) // Update represents changes to an existing item. @@ -16,10 +17,10 @@ type Update struct { returnType string hashKey string - hashValue *dynamodb.AttributeValue + hashValue types.AttributeValue rangeKey string - rangeValue *dynamodb.AttributeValue + rangeValue types.AttributeValue set []string add map[string]string @@ -202,19 +203,17 @@ func (u *Update) DeleteFromSet(path string, value interface{}) *Update { u.setError(err) return u } - switch { + switch t := v.(type) { // ok: - case v.NS != nil: - case v.SS != nil: - case v.BS != nil: + case *types.AttributeValueMemberNS, *types.AttributeValueMemberSS, *types.AttributeValueMemberBS: // need to box: - case v.N != nil: - v = &dynamodb.AttributeValue{NS: []*string{v.N}} - case v.S != nil: - v = &dynamodb.AttributeValue{SS: []*string{v.S}} - case v.B != nil: - v = &dynamodb.AttributeValue{BS: [][]byte{v.B}} + case *types.AttributeValueMemberN: + v = &types.AttributeValueMemberNS{Value: []string{t.Value}} + case *types.AttributeValueMemberS: + v = &types.AttributeValueMemberSS{Value: []string{t.Value}} + case *types.AttributeValueMemberB: + v = &types.AttributeValueMemberBS{Value: [][]byte{t.Value}} default: u.setError(fmt.Errorf("dynamo: Update.DeleteFromSet given unsupported value: %v (%T: %s)", value, value, avTypeName(v))) @@ -287,7 +286,7 @@ func (u *Update) Run() error { } // RunWithContext executes this update. -func (u *Update) RunWithContext(ctx aws.Context) error { +func (u *Update) RunWithContext(ctx context.Context) error { u.returnType = "NONE" _, err := u.run(ctx) return err @@ -303,7 +302,7 @@ func (u *Update) Value(out interface{}) error { // ValueWithContext executes this update, encoding out with the new value after the update. // This is equivalent to ReturnValues = ALL_NEW in the DynamoDB API. -func (u *Update) ValueWithContext(ctx aws.Context, out interface{}) error { +func (u *Update) ValueWithContext(ctx context.Context, out interface{}) error { u.returnType = "ALL_NEW" output, err := u.run(ctx) if err != nil { @@ -322,7 +321,7 @@ func (u *Update) OldValue(out interface{}) error { // OldValueWithContext executes this update, encoding out with the old value before the update. // This is equivalent to ReturnValues = ALL_OLD in the DynamoDB API. -func (u *Update) OldValueWithContext(ctx aws.Context, out interface{}) error { +func (u *Update) OldValueWithContext(ctx context.Context, out interface{}) error { u.returnType = "ALL_OLD" output, err := u.run(ctx) if err != nil { @@ -341,7 +340,7 @@ func (u *Update) OnlyUpdatedValue(out interface{}) error { // OnlyUpdatedValueWithContext executes this update, encoding out with only with new values of the attributes that were changed. // This is equivalent to ReturnValues = UPDATED_NEW in the DynamoDB API. -func (u *Update) OnlyUpdatedValueWithContext(ctx aws.Context, out interface{}) error { +func (u *Update) OnlyUpdatedValueWithContext(ctx context.Context, out interface{}) error { u.returnType = "UPDATED_NEW" output, err := u.run(ctx) if err != nil { @@ -360,7 +359,7 @@ func (u *Update) OnlyUpdatedOldValue(out interface{}) error { // OnlyUpdatedOldValueWithContext executes this update, encoding out with only with old values of the attributes that were changed. // This is equivalent to ReturnValues = UPDATED_OLD in the DynamoDB API. -func (u *Update) OnlyUpdatedOldValueWithContext(ctx aws.Context, out interface{}) error { +func (u *Update) OnlyUpdatedOldValueWithContext(ctx context.Context, out interface{}) error { u.returnType = "UPDATED_OLD" output, err := u.run(ctx) if err != nil { @@ -369,7 +368,7 @@ func (u *Update) OnlyUpdatedOldValueWithContext(ctx aws.Context, out interface{} return unmarshalItem(output.Attributes, out) } -func (u *Update) run(ctx aws.Context) (*dynamodb.UpdateItemOutput, error) { +func (u *Update) run(ctx context.Context) (*dynamodb.UpdateItemOutput, error) { if u.err != nil { return nil, u.err } @@ -378,7 +377,7 @@ func (u *Update) run(ctx aws.Context) (*dynamodb.UpdateItemOutput, error) { var output *dynamodb.UpdateItemOutput err := retry(ctx, func() error { var err error - output, err = u.table.db.client.UpdateItemWithContext(ctx, input) + output, err = u.table.db.client.UpdateItem(ctx, input) return err }) if u.cc != nil { @@ -394,24 +393,24 @@ func (u *Update) updateInput() *dynamodb.UpdateItemInput { UpdateExpression: u.updateExpr(), ExpressionAttributeNames: u.nameExpr, ExpressionAttributeValues: u.valueExpr, - ReturnValues: &u.returnType, + ReturnValues: types.ReturnValue(u.returnType), } if u.condition != "" { input.ConditionExpression = &u.condition } if u.cc != nil { - input.ReturnConsumedCapacity = aws.String(dynamodb.ReturnConsumedCapacityIndexes) + input.ReturnConsumedCapacity = types.ReturnConsumedCapacityIndexes } return input } -func (u *Update) writeTxItem() (*dynamodb.TransactWriteItem, error) { +func (u *Update) writeTxItem() (*types.TransactWriteItem, error) { if u.err != nil { return nil, u.err } input := u.updateInput() - item := &dynamodb.TransactWriteItem{ - Update: &dynamodb.Update{ + item := &types.TransactWriteItem{ + Update: &types.Update{ TableName: input.TableName, Key: input.Key, UpdateExpression: input.UpdateExpression, @@ -424,8 +423,8 @@ func (u *Update) writeTxItem() (*dynamodb.TransactWriteItem, error) { return item, nil } -func (u *Update) key() map[string]*dynamodb.AttributeValue { - key := map[string]*dynamodb.AttributeValue{ +func (u *Update) key() map[string]types.AttributeValue { + key := map[string]types.AttributeValue{ u.hashKey: u.hashValue, } if u.rangeKey != "" { diff --git a/update_test.go b/update_test.go index 56dce4d..f02f485 100644 --- a/update_test.go +++ b/update_test.go @@ -5,8 +5,8 @@ import ( "testing" "time" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" ) func TestUpdate(t *testing.T) { @@ -51,8 +51,8 @@ func TestUpdate(t *testing.T) { "#meta": "Meta", "#pet": "pet", }), - AttributeValues: map[string]*dynamodb.AttributeValue{ - ":cat": {S: aws.String("猫")}, + AttributeValues: map[string]types.AttributeValue{ + ":cat": &types.AttributeValueMemberS{Value: "猫"}, }, } rmLit := ExpressionLiteral{ @@ -67,8 +67,8 @@ func TestUpdate(t *testing.T) { AttributeNames: aws.StringMap(map[string]string{ "#msg": "Msg", }), - AttributeValues: map[string]*dynamodb.AttributeValue{ - ":hi": {S: aws.String("hello")}, + AttributeValues: map[string]types.AttributeValue{ + ":hi": &types.AttributeValueMemberS{Value: "hello"}, }, } diff --git a/updatetable.go b/updatetable.go index 34a33d0..ea15fb1 100644 --- a/updatetable.go +++ b/updatetable.go @@ -1,10 +1,12 @@ package dynamo import ( + "context" "errors" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) // UpdateTable is a request to change a table's settings. @@ -13,7 +15,7 @@ type UpdateTable struct { table Table r, w int64 // throughput - billingMode *string + billingMode types.BillingMode disableStream bool streamView StreamView @@ -21,7 +23,7 @@ type UpdateTable struct { updateIdx map[string]Throughput createIdx []Index deleteIdx []string - ads []*dynamodb.AttributeDefinition + ads []types.AttributeDefinition err error } @@ -38,9 +40,9 @@ func (table Table) UpdateTable() *UpdateTable { // If enabled is false, this table will be changed to provisioned billing mode. func (ut *UpdateTable) OnDemand(enabled bool) *UpdateTable { if enabled { - ut.billingMode = aws.String(dynamodb.BillingModePayPerRequest) + ut.billingMode = types.BillingModePayPerRequest } else { - ut.billingMode = aws.String(dynamodb.BillingModeProvisioned) + ut.billingMode = types.BillingModeProvisioned } return ut } @@ -111,7 +113,7 @@ func (ut *UpdateTable) Run() (Description, error) { return ut.RunWithContext(ctx) } -func (ut *UpdateTable) RunWithContext(ctx aws.Context) (Description, error) { +func (ut *UpdateTable) RunWithContext(ctx context.Context) (Description, error) { if ut.err != nil { return Description{}, ut.err } @@ -121,7 +123,7 @@ func (ut *UpdateTable) RunWithContext(ctx aws.Context) (Description, error) { var result *dynamodb.UpdateTableOutput err := retry(ctx, func() error { var err error - result, err = ut.table.db.client.UpdateTableWithContext(ctx, input) + result, err = ut.table.db.client.UpdateTable(ctx, input) return err }) if err != nil { @@ -139,27 +141,27 @@ func (ut *UpdateTable) input() *dynamodb.UpdateTableInput { } if ut.r != 0 || ut.w != 0 { - input.ProvisionedThroughput = &dynamodb.ProvisionedThroughput{ + input.ProvisionedThroughput = &types.ProvisionedThroughput{ ReadCapacityUnits: &ut.r, WriteCapacityUnits: &ut.w, } } if ut.disableStream { - input.StreamSpecification = &dynamodb.StreamSpecification{ + input.StreamSpecification = &types.StreamSpecification{ StreamEnabled: aws.Bool(false), } } else if ut.streamView != "" { - input.StreamSpecification = &dynamodb.StreamSpecification{ + input.StreamSpecification = &types.StreamSpecification{ StreamEnabled: aws.Bool(true), - StreamViewType: aws.String((string)(ut.streamView)), + StreamViewType: types.StreamViewType(ut.streamView), } } for index, thru := range ut.updateIdx { - up := &dynamodb.GlobalSecondaryIndexUpdate{Update: &dynamodb.UpdateGlobalSecondaryIndexAction{ + up := types.GlobalSecondaryIndexUpdate{Update: &types.UpdateGlobalSecondaryIndexAction{ IndexName: aws.String(index), - ProvisionedThroughput: &dynamodb.ProvisionedThroughput{ + ProvisionedThroughput: &types.ProvisionedThroughput{ ReadCapacityUnits: aws.Int64(thru.Read), WriteCapacityUnits: aws.Int64(thru.Write), }, @@ -167,11 +169,11 @@ func (ut *UpdateTable) input() *dynamodb.UpdateTableInput { input.GlobalSecondaryIndexUpdates = append(input.GlobalSecondaryIndexUpdates, up) } for _, index := range ut.createIdx { - up := &dynamodb.GlobalSecondaryIndexUpdate{Create: createIndexAction(index)} + up := types.GlobalSecondaryIndexUpdate{Create: createIndexAction(index)} input.GlobalSecondaryIndexUpdates = append(input.GlobalSecondaryIndexUpdates, up) } for _, del := range ut.deleteIdx { - up := &dynamodb.GlobalSecondaryIndexUpdate{Delete: &dynamodb.DeleteGlobalSecondaryIndexAction{ + up := types.GlobalSecondaryIndexUpdate{Delete: &types.DeleteGlobalSecondaryIndexAction{ IndexName: aws.String(del), }} input.GlobalSecondaryIndexUpdates = append(input.GlobalSecondaryIndexUpdates, up) @@ -186,40 +188,40 @@ func (ut *UpdateTable) addAD(name string, typ KeyType) { } } - ut.ads = append(ut.ads, &dynamodb.AttributeDefinition{ + ut.ads = append(ut.ads, types.AttributeDefinition{ AttributeName: &name, - AttributeType: aws.String((string)(typ)), + AttributeType: types.ScalarAttributeType(typ), }) } -func createIndexAction(index Index) *dynamodb.CreateGlobalSecondaryIndexAction { - ks := []*dynamodb.KeySchemaElement{ +func createIndexAction(index Index) *types.CreateGlobalSecondaryIndexAction { + ks := []types.KeySchemaElement{ { AttributeName: &index.HashKey, - KeyType: aws.String(dynamodb.KeyTypeHash), + KeyType: types.KeyTypeHash, }, } if index.RangeKey != "" { - ks = append(ks, &dynamodb.KeySchemaElement{ + ks = append(ks, types.KeySchemaElement{ AttributeName: &index.RangeKey, - KeyType: aws.String(dynamodb.KeyTypeRange), + KeyType: types.KeyTypeRange, }) } - add := &dynamodb.CreateGlobalSecondaryIndexAction{ + add := &types.CreateGlobalSecondaryIndexAction{ IndexName: &index.Name, KeySchema: ks, - Projection: &dynamodb.Projection{ - ProjectionType: aws.String((string)(index.ProjectionType)), + Projection: &types.Projection{ + ProjectionType: types.ProjectionType(index.ProjectionType), }, } if index.Throughput.Read > 0 && index.Throughput.Write > 0 { - add.ProvisionedThroughput = &dynamodb.ProvisionedThroughput{ + add.ProvisionedThroughput = &types.ProvisionedThroughput{ ReadCapacityUnits: aws.Int64(index.Throughput.Read), WriteCapacityUnits: aws.Int64(index.Throughput.Write), } } if index.ProjectionType == IncludeProjection { - add.Projection.NonKeyAttributes = aws.StringSlice(index.ProjectionAttribs) + add.Projection.NonKeyAttributes = index.ProjectionAttribs } return add }