From 0d038a99ddca3df093713f376e69f3b8a21c85c2 Mon Sep 17 00:00:00 2001 From: Nilton Kummer Date: Fri, 10 Dec 2021 13:50:05 -0300 Subject: [PATCH 01/31] Feat/dynamo sdk v2 (#2) * using sdk-v2 --- README.md | 29 ++- batchget.go | 35 +-- batchwrite.go | 28 ++- conditioncheck.go | 18 +- createtable.go | 145 ++++++------ createtable_test.go | 76 +++--- db.go | 45 ++-- db_test.go | 34 ++- decode.go | 312 +++++++++++++------------ decode_aux_test.go | 24 +- decode_test.go | 117 +++------- delete.go | 32 +-- describetable.go | 84 +++---- dynamodbiface/interface.go | 83 +++++++ encode.go | 166 +++++++------- encode_test.go | 44 ++-- encoding_aws.go | 16 +- encoding_aws_test.go | 35 ++- encoding_test.go | 459 +++++++++++++++++++------------------ go.mod | 13 +- go.sum | 72 ++++-- put.go | 26 ++- query.go | 86 +++---- query_test.go | 6 +- retry.go | 38 +-- scan.go | 48 ++-- sse.go | 10 +- substitute.go | 21 +- substitute_test.go | 12 +- table.go | 20 +- table_test.go | 19 +- ttl.go | 22 +- tx.go | 37 +-- update.go | 56 ++--- update_test.go | 10 +- updatetable.go | 58 ++--- 36 files changed, 1245 insertions(+), 1091 deletions(-) create mode 100644 dynamodbiface/interface.go diff --git a/README.md b/README.md index 86b216e..2834b7d 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 [![GoDoc](https://godoc.org/github.com/niltonkummer/dynamo?status.svg)](https://godoc.org/github.com/niltonkummer/dynamo) +`import "github.com/niltonkummer/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,10 +12,12 @@ package dynamo import ( "time" + "context" + "log" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/guregu/dynamo" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/niltonkummer/dynamo" ) // Use struct tags much like the standard JSON library, @@ -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 @@ -85,7 +90,7 @@ table.Put(item{ID: 42}).If("attribute_not_exists(ID)").Run() dynamo automatically handles the following interfaces: -* [`dynamo.Marshaler`](https://godoc.org/github.com/guregu/dynamo#Marshaler) and [`dynamo.Unmarshaler`](https://godoc.org/github.com/guregu/dynamo#Unmarshaler) +* [`dynamo.Marshaler`](https://godoc.org/github.com/niltonkummer/dynamo#Marshaler) and [`dynamo.Unmarshaler`](https://godoc.org/github.com/niltonkummer/dynamo#Unmarshaler) * [`dynamodbattribute.Marshaler`](https://godoc.org/github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute#Marshaler) and [`dynamodbattribute.Unmarshaler`](https://godoc.org/github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute#Unmarshaler) * [`encoding.TextMarshaler`](https://godoc.org/encoding#TextMarshaler) and [`encoding.TextUnmarshaler`](https://godoc.org/encoding#TextUnmarshaler) @@ -180,7 +185,7 @@ This creates a table with the primary hash key ID and range key Time. It creates dynamo has been in development before the official AWS libraries were stable. We use a different encoder and decoder than the [dynamodbattribute](https://godoc.org/github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute) package. dynamo uses the `dynamo` struct tag instead of the `dynamodbav` struct tag, and we also prefer to automatically omit invalid values such as empty strings, whereas the dynamodbattribute package substitutes null values for them. Items that satisfy the [`dynamodbattribute.(Un)marshaler`](https://godoc.org/github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute#Marshaler) interfaces are compatibile with both libraries. -In order to use dynamodbattribute's encoding facilities, you must wrap objects passed to dynamo with [`dynamo.AWSEncoding`](https://godoc.org/github.com/guregu/dynamo#AWSEncoding). Here is a quick example: +In order to use dynamodbattribute's encoding facilities, you must wrap objects passed to dynamo with [`dynamo.AWSEncoding`](https://godoc.org/github.com/niltonkummer/dynamo#AWSEncoding). Here is a quick example: ```go // Notice the use of the dynamodbav struct tag @@ -206,7 +211,7 @@ Change the table name with the environment variable `DYNAMO_TEST_TABLE`. You mus ```bash -DYNAMO_TEST_REGION=us-west-2 go test github.com/guregu/dynamo/... -cover +DYNAMO_TEST_REGION=us-west-2 go test github.com/niltonkummer/dynamo/... -cover ``` If you want to use [DynamoDB Local](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html) to run local tests, specify `DYNAMO_TEST_ENDPOINT`. diff --git a/batchget.go b/batchget.go index 4c1d68e..cc7fc75 100644 --- a/batchget.go +++ b/batchget.go @@ -1,11 +1,15 @@ package dynamo import ( + "context" "errors" "fmt" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/smithy-go/time" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/cenkalti/backoff/v4" ) @@ -106,7 +110,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 +132,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 +142,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 +159,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 +205,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 +234,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 +256,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 +267,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 +275,8 @@ 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..f94583e 100644 --- a/batchwrite.go +++ b/batchwrite.go @@ -1,10 +1,14 @@ package dynamo import ( + "context" "math" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/smithy-go/time" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/cenkalti/backoff/v4" ) @@ -14,7 +18,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 +37,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 +52,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 +75,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 +99,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 +107,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 +119,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 +129,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..d32b1bf 100644 --- a/createtable.go +++ b/createtable.go @@ -7,9 +7,14 @@ import ( "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/feature/dynamodb/attributevalue" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "golang.org/x/net/context" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + + "github.com/aws/aws-sdk-go-v2/aws" ) // StreamView determines what information is written to a table's stream. @@ -17,13 +22,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 +36,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 +48,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 +79,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 +109,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 +127,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 +157,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 +194,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 +214,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 +225,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 +242,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 +262,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 +298,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 +311,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 +326,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 +347,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 +357,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 +367,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 +420,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 +449,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 +482,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 +490,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..9e985ed 100644 --- a/createtable_test.go +++ b/createtable_test.go @@ -5,9 +5,11 @@ 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/service/dynamodb/types" + + "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" ) type UserAction struct { @@ -23,8 +25,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 +52,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 +136,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..7427927 100644 --- a/db.go +++ b/db.go @@ -2,31 +2,34 @@ package dynamo import ( + "context" "errors" "fmt" + "os" - "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "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" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/smithy-go/logging" + "github.com/niltonkummer/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 +38,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 +47,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 +70,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 +99,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 +114,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 +126,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 +141,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 +165,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 +182,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" { diff --git a/db_test.go b/db_test.go index 1948901..e18dd07 100644 --- a/db_test.go +++ b/db_test.go @@ -1,12 +1,13 @@ package dynamo import ( + "context" "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 ( @@ -16,17 +17,36 @@ var ( const offlineSkipMsg = "DYNAMO_TEST_REGION not set" +type endpointResolver struct { + resolveEndpoint func(service, region string, options ...interface{}) (aws.Endpoint, error) +} + +func (e endpointResolver) ResolveEndpoint(service, region string, options ...interface{}) (aws.Endpoint, error) { + return e.resolveEndpoint(service, region, options...) +} + func init() { if region := os.Getenv("DYNAMO_TEST_REGION"); region != "" { var endpoint *string 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), - }) + + resolver := endpointResolver{ + resolveEndpoint: func(service, region string, options ...interface{}) (aws.Endpoint, error) { + return aws.Endpoint{ + URL: *endpoint, + }, nil + }, + } + + conf, err := config.LoadDefaultConfig(context.TODO(), config.WithEndpointResolverWithOptions(resolver), + config.WithRegion(os.Getenv("AWS_REGION"))) + testDB = New(conf) + + if err != nil { + return + } } if table := os.Getenv("DYNAMO_TEST_TABLE"); table != "" { testTable = table diff --git a/decode.go b/decode.go index 29a4c30..bcb0986 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.AttributeValueMemberL) + *x.(*types.AttributeValueMemberL) = *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,22 @@ 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: cannot unmarshal %s data into struct", avTypeName(av)) + } + 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 +242,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 +280,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 +323,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 +342,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 +431,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,11 +490,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 { - if awsenc, ok := out.(awsEncoder); ok { - return unmarshalAppendAWS(item, awsenc.iface) - } - +func unmarshalAppend(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: result argument must be a slice pointer") @@ -457,21 +508,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 +530,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 +556,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..07776cf 100644 --- a/decode_test.go +++ b/decode_test.go @@ -1,24 +1,28 @@ 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/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) 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 +31,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 +41,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 +80,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 +116,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 +125,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 +170,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 +182,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..a75e2e7 100644 --- a/describetable.go +++ b/describetable.go @@ -1,10 +1,12 @@ 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/types" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb" ) // Description contains information about a table. @@ -89,7 +91,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 +99,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 +110,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 +142,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 +158,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 +178,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 +197,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 +257,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 +282,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 +303,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..71ce0b7 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..c32236f 100644 --- a/encoding_test.go +++ b/encoding_test.go @@ -6,9 +6,10 @@ 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/feature/dynamodb/attributevalue" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) const ( @@ -21,51 +22,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 +81,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 +92,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 +103,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 +251,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 +260,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 +271,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 +282,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 +293,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 +306,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 +321,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 +339,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 +357,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 +369,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 +388,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 +411,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 +438,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 +459,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 +476,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 +489,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 +502,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 +514,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 +526,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 +538,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 +585,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 +618,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 +641,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 +654,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 +665,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 +676,7 @@ var itemEncodingTests = []struct { }{ TTL: time.Time{}, }, - out: map[string]*dynamodb.AttributeValue{}, + out: map[string]types.AttributeValue{}, }, { name: "*time.Time (unixtime encoding)", @@ -682,8 +685,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 +696,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")}, + in: customItemMarshaler{Thing: (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")}, + in: &customItemMarshaler{Thing: (52)}, + out: map[string]types.AttributeValue{ + "thing": &types.AttributeValueMemberN{Value: "52"}, }, }, } @@ -721,14 +724,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 +774,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..7f9b00a 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,17 @@ 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 + github.com/niltonkummer/dynamo v1.12.0 + golang.org/x/net v0.0.0-20201110031124-69a78807bb2b ) go 1.13 diff --git a/go.sum b/go.sum index a9e5907..0744b3d 100644 --- a/go.sum +++ b/go.sum @@ -1,32 +1,72 @@ -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 v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY= +github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 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= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/niltonkummer/dynamo v1.12.0 h1:4cHzn5FA7/R+EsJYj+3nUCh9RXJ3e8peTYdfLkyWqVg= +github.com/niltonkummer/dynamo v1.12.0/go.mod h1:Mc3TooY/MCgp/veyxUnGKwfCG0y0/ZKMoM0UU2YYdxk= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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= -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/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/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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..45d0c49 100644 --- a/query.go +++ b/query.go @@ -1,11 +1,13 @@ 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/types" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb" ) // Query is a request to get one or more items in a table. @@ -14,21 +16,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 +70,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). @@ -156,7 +158,7 @@ func (q *Query) Consistent(on bool) *Query { // Limit specifies the maximum amount of results to return. func (q *Query) Limit(limit int64) *Query { - q.limit = limit + q.limit = int32(limit) return q } @@ -164,7 +166,7 @@ func (q *Query) Limit(limit int64) *Query { // 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 { - q.searchLimit = limit + q.searchLimit = int32(limit) return q } @@ -189,7 +191,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 +203,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 +228,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 } @@ -259,7 +261,7 @@ func (q *Query) Count() (int64, error) { return q.CountWithContext(ctx) } -func (q *Query) CountWithContext(ctx aws.Context) (int64, error) { +func (q *Query) CountWithContext(ctx context.Context) (int64, error) { if q.err != nil { return 0, q.err } @@ -272,14 +274,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 += int64(res.Count) return nil }) if err != nil { @@ -305,15 +307,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 +329,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 +375,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 +454,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 +473,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 +543,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 +577,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 +597,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 +607,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..302edac 100644 --- a/retry.go +++ b/retry.go @@ -5,8 +5,6 @@ import ( "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" ) @@ -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..6bc22b3 100644 --- a/scan.go +++ b/scan.go @@ -1,24 +1,26 @@ 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/types" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb" ) // 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 @@ -76,7 +78,7 @@ func (s *Scan) Consistent(on bool) *Scan { // Limit specifies the maximum amount of results to return. func (s *Scan) Limit(limit int64) *Scan { - s.limit = limit + s.limit = int32(limit) return s } @@ -84,7 +86,7 @@ func (s *Scan) Limit(limit int64) *Scan { // 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 { - s.searchLimit = limit + s.searchLimit = int32(limit) return s } @@ -112,7 +114,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 +135,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, @@ -156,35 +158,35 @@ 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) (int64, error) { if s.err != nil { return 0, s.err } var count, scanned int64 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 += int64(out.Count) + scanned += int64(out.ScannedCount) if s.cc != nil { addConsumedCapacity(s.cc, out.ConsumedCapacity) } - if s.limit > 0 && count >= s.limit { + if s.limit > 0 && count >= int64(s.limit) { break } - if s.searchLimit > 0 && scanned >= s.searchLimit { + if s.searchLimit > 0 && scanned >= int64(s.searchLimit) { break } if out.LastEvaluatedKey == nil { @@ -223,7 +225,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 } @@ -244,12 +246,12 @@ type scanIter struct { n int64 // 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 +265,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() @@ -273,7 +275,7 @@ func (itr *scanIter) NextWithContext(ctx aws.Context, out interface{}) bool { } // stop if exceed limit - if itr.scan.limit > 0 && itr.n == itr.scan.limit { + if itr.scan.limit > 0 && itr.n == int64(itr.scan.limit) { // proactively grab the keys for LEK inferral, but don't count it as a real error yet to keep backwards compat itr.keys, itr.keyErr = itr.scan.table.primaryKeys(ctx, itr.exLEK, itr.exESK, itr.scan.index) return false @@ -309,7 +311,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..4505386 100644 --- a/substitute.go +++ b/substitute.go @@ -8,31 +8,30 @@ 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 +131,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 +145,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..cfd17f7 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-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/dynamodb" ) // 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} } @@ -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..8e53660 100644 --- a/table_test.go +++ b/table_test.go @@ -7,8 +7,9 @@ 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/service/dynamodb/types" + + "github.com/aws/aws-sdk-go-v2/aws" ) func TestTableLifecycle(t *testing.T) { @@ -122,21 +123,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 +148,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 +164,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..851cbed 100644 --- a/ttl.go +++ b/ttl.go @@ -1,8 +1,12 @@ 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/types" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" ) // UpdateTTL is a request to enable or disable a table's time to live functionality. @@ -36,11 +40,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 +53,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 +78,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 +94,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..ff5b4ec 100644 --- a/tx.go +++ b/tx.go @@ -1,10 +1,13 @@ 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/service/dynamodb/types" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/gofrs/uuid" ) @@ -13,7 +16,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 +68,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 +76,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 +115,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 +123,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 +164,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 +260,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 +269,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 +290,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 +307,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/update.go b/update.go index a341ad0..4b2ca41 100644 --- a/update.go +++ b/update.go @@ -1,11 +1,13 @@ 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/types" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb" ) // Update represents changes to an existing item. @@ -16,10 +18,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 +204,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 +287,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 +303,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 +322,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 +341,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 +360,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 +369,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 +378,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 +394,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 +424,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 } From 4304bbfd0411820620e99f8291741fe7fb428ee2 Mon Sep 17 00:00:00 2001 From: Hiroki Yoshioka Date: Thu, 13 Oct 2022 09:07:58 +0900 Subject: [PATCH 02/31] Remove unnecessary parenthesis --- createtable_test.go | 2 +- encode_test.go | 2 +- encoding_test.go | 60 ++++++++++++++++++++++----------------------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/createtable_test.go b/createtable_test.go index 9e985ed..655f16e 100644 --- a/createtable_test.go +++ b/createtable_test.go @@ -102,7 +102,7 @@ func TestCreateTable(t *testing.T) { }}, Projection: &types.Projection{ ProjectionType: types.ProjectionTypeInclude, - NonKeyAttributes: []string{("UUID")}, + NonKeyAttributes: []string{"UUID"}, }, }}, ProvisionedThroughput: &types.ProvisionedThroughput{ diff --git a/encode_test.go b/encode_test.go index 71ce0b7..78b261b 100644 --- a/encode_test.go +++ b/encode_test.go @@ -29,7 +29,7 @@ var itemEncodeOnlyTests = []struct { out: map[string]types.AttributeValue{ "L": &types.AttributeValueMemberL{Value: []types.AttributeValue{}}, "M": &types.AttributeValueMemberM{Value: map[string]types.AttributeValue{}}, - "Other": &types.AttributeValueMemberBOOL{Value: (true)}, + "Other": &types.AttributeValueMemberBOOL{Value: true}, }, }, { diff --git a/encoding_test.go b/encoding_test.go index c32236f..7f29821 100644 --- a/encoding_test.go +++ b/encoding_test.go @@ -195,11 +195,11 @@ var encodingTests = []struct { name: "array with empty string", in: [...]string{"", "hello", "", "world", ""}, out: &types.AttributeValueMemberL{Value: []types.AttributeValue{ - &types.AttributeValueMemberS{Value: ("")}, - &types.AttributeValueMemberS{Value: ("hello")}, - &types.AttributeValueMemberS{Value: ("")}, - &types.AttributeValueMemberS{Value: ("world")}, - &types.AttributeValueMemberS{Value: ("")}, + &types.AttributeValueMemberS{Value: ""}, + &types.AttributeValueMemberS{Value: "hello"}, + &types.AttributeValueMemberS{Value: ""}, + &types.AttributeValueMemberS{Value: "world"}, + &types.AttributeValueMemberS{Value: ""}, }}, }, { @@ -207,10 +207,10 @@ var encodingTests = []struct { in: []*string{nil, aws.String("hello"), aws.String(""), aws.String("world"), nil}, 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)}, + &types.AttributeValueMemberS{Value: "hello"}, + &types.AttributeValueMemberS{Value: ""}, + &types.AttributeValueMemberS{Value: "world"}, + &types.AttributeValueMemberNULL{Value: true}, }}, }, { @@ -241,7 +241,7 @@ var encodingTests = []struct { out: &types.AttributeValueMemberL{Value: []types.AttributeValue{ &types.AttributeValueMemberB{Value: []byte{}}, &types.AttributeValueMemberB{Value: []byte{'h', 'e', 'l', 'l', 'o'}}, - &types.AttributeValueMemberNULL{Value: (true)}, + &types.AttributeValueMemberNULL{Value: true}, &types.AttributeValueMemberB{Value: []byte{'w', 'o', 'r', 'l', 'd'}}, &types.AttributeValueMemberB{Value: []byte{}}, }}, @@ -261,7 +261,7 @@ var itemEncodingTests = []struct { A: "hello", }, out: map[string]types.AttributeValue{ - "A": &types.AttributeValueMemberS{Value: ("hello")}, + "A": &types.AttributeValueMemberS{Value: "hello"}, }, }, { @@ -272,7 +272,7 @@ var itemEncodingTests = []struct { A: "hello", }, out: map[string]types.AttributeValue{ - "A": &types.AttributeValueMemberS{Value: ("hello")}, + "A": &types.AttributeValueMemberS{Value: "hello"}, }, }, { @@ -294,7 +294,7 @@ var itemEncodingTests = []struct { A: "hello", }, out: map[string]types.AttributeValue{ - "renamed": &types.AttributeValueMemberS{Value: ("hello")}, + "renamed": &types.AttributeValueMemberS{Value: "hello"}, }, }, { @@ -307,7 +307,7 @@ var itemEncodingTests = []struct { Other: true, }, out: map[string]types.AttributeValue{ - "Other": &types.AttributeValueMemberBOOL{Value: (true)}, + "Other": &types.AttributeValueMemberBOOL{Value: true}, }, }, { @@ -322,7 +322,7 @@ var itemEncodingTests = []struct { Other: true, }, out: map[string]types.AttributeValue{ - "Other": &types.AttributeValueMemberBOOL{Value: (true)}, + "Other": &types.AttributeValueMemberBOOL{Value: true}, }, }, { @@ -586,23 +586,23 @@ var itemEncodingTests = []struct { NS5: map[uint]bool{maxUint: true}, }, 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")}}, + "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")}}, + "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}}, }, @@ -700,14 +700,14 @@ var itemEncodingTests = []struct { }, { name: "dynamodb.ItemUnmarshaler", - in: customItemMarshaler{Thing: (52)}, + in: customItemMarshaler{Thing: 52}, out: map[string]types.AttributeValue{ "thing": &types.AttributeValueMemberN{Value: "52"}, }, }, { name: "*dynamodb.ItemUnmarshaler", - in: &customItemMarshaler{Thing: (52)}, + in: &customItemMarshaler{Thing: 52}, out: map[string]types.AttributeValue{ "thing": &types.AttributeValueMemberN{Value: "52"}, }, From 2b00a6bfcd8f12efe6526b73b85395983cc60695 Mon Sep 17 00:00:00 2001 From: Hiroki Yoshioka Date: Thu, 13 Oct 2022 10:33:30 +0900 Subject: [PATCH 03/31] Fix problem of decoding AttributeValueMemberM --- decode.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/decode.go b/decode.go index bcb0986..03c78bf 100644 --- a/decode.go +++ b/decode.go @@ -86,8 +86,8 @@ func unmarshalReflect(av types.AttributeValue, rv reflect.Value) error { *x.(*types.AttributeValueMemberL) = *res return nil case *types.AttributeValueMemberM: - res := av.(*types.AttributeValueMemberL) - *x.(*types.AttributeValueMemberL) = *res + res := av.(*types.AttributeValueMemberM) + *x.(*types.AttributeValueMemberM) = *res return nil case *types.AttributeValueMemberN: res := av.(*types.AttributeValueMemberN) From 20620086854030a2bf41e34b858cbca168348b6d Mon Sep 17 00:00:00 2001 From: Hiroki Yoshioka Date: Sat, 15 Oct 2022 11:49:23 +0900 Subject: [PATCH 04/31] Remove unnecessary code --- decode.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/decode.go b/decode.go index 03c78bf..8ef156a 100644 --- a/decode.go +++ b/decode.go @@ -212,9 +212,6 @@ func unmarshalReflect(av types.AttributeValue, rv reflect.Value) error { truthy = reflect.ValueOf(struct{}{}) default: _, valueIsM := av.(*types.AttributeValueMemberM) - if !valueIsM { - return fmt.Errorf("dynamo: cannot unmarshal %s data into struct", avTypeName(av)) - } if !valueIsM { return fmt.Errorf("dynamo: unmarshal map set: value type must be struct{} or bool, got %v", rv.Type()) } From 4da56f451c1640a327528d33becb9e68a851cb52 Mon Sep 17 00:00:00 2001 From: Hiroki Yoshioka Date: Wed, 19 Oct 2022 08:57:32 +0900 Subject: [PATCH 05/31] Remove unnecessary blank line --- batchget.go | 1 - 1 file changed, 1 deletion(-) diff --git a/batchget.go b/batchget.go index cc7fc75..6d17df0 100644 --- a/batchget.go +++ b/batchget.go @@ -275,7 +275,6 @@ redo: } if itr.bg.cc != nil { for _, cc := range itr.output.ConsumedCapacity { - addConsumedCapacity(itr.bg.cc, &cc) } } From 0e4bb5edd8c09a53042f6d3ef8931cbf11e6fa2b Mon Sep 17 00:00:00 2001 From: Hiroki Yoshioka Date: Wed, 26 Oct 2022 10:37:27 +0900 Subject: [PATCH 06/31] Kill awserr --- db.go | 6 +++--- table.go | 6 +++--- tx_test.go | 6 ++++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/db.go b/db.go index 7427927..3ccc870 100644 --- a/db.go +++ b/db.go @@ -8,7 +8,7 @@ import ( "os" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/smithy-go" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb" @@ -198,8 +198,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/table.go b/table.go index cfd17f7..db28ad8 100644 --- a/table.go +++ b/table.go @@ -8,7 +8,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/smithy-go" ) // Status is an enumeration of table and index statuses. @@ -75,9 +75,9 @@ func (table Table) WaitWithContext(ctx context.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 } diff --git a/tx_test.go b/tx_test.go index 6f99c9f..31217de 100644 --- a/tx_test.go +++ b/tx_test.go @@ -1,6 +1,7 @@ package dynamo import ( + "errors" "reflect" "sync" "testing" @@ -8,7 +9,7 @@ import ( "github.com/gofrs/uuid" - "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/smithy-go" ) func TestTx(t *testing.T) { @@ -158,7 +159,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) } } From d109af160d4d3bad519e33ee476ec9d0ba68673a Mon Sep 17 00:00:00 2001 From: Hiroki Yoshioka Date: Fri, 28 Oct 2022 10:02:19 +0900 Subject: [PATCH 07/31] Recover unwrapping AWSEncoding wrapper --- decode.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/decode.go b/decode.go index 8ef156a..81e1327 100644 --- a/decode.go +++ b/decode.go @@ -488,6 +488,10 @@ func unmarshalItem(item map[string]types.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) + } + rv := reflect.ValueOf(out) if rv.Kind() != reflect.Ptr || rv.Elem().Kind() != reflect.Slice { return fmt.Errorf("dynamo: unmarshal append: result argument must be a slice pointer") From a55302515a4afc821187f0a0fa65606e0ecf59f5 Mon Sep 17 00:00:00 2001 From: Hiroki Yoshioka Date: Mon, 31 Oct 2022 09:55:11 +0900 Subject: [PATCH 08/31] int32 Query/Scan limit The official API doesn't accept int64 limits. --- query.go | 16 ++++++++-------- scan.go | 26 +++++++++++++------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/query.go b/query.go index 45d0c49..1f3a2da 100644 --- a/query.go +++ b/query.go @@ -157,16 +157,16 @@ func (q *Query) Consistent(on bool) *Query { } // Limit specifies the maximum amount of results to return. -func (q *Query) Limit(limit int64) *Query { - q.limit = int32(limit) +func (q *Query) Limit(limit int32) *Query { + q.limit = limit return q } // 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 { - q.searchLimit = int32(limit) +func (q *Query) SearchLimit(limit int32) *Query { + q.searchLimit = limit return q } @@ -255,18 +255,18 @@ func (q *Query) OneWithContext(ctx context.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 context.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() @@ -281,7 +281,7 @@ func (q *Query) CountWithContext(ctx context.Context) (int64, error) { if res.Count == 0 { return errors.New("nil count") } - count += int64(res.Count) + count += res.Count return nil }) if err != nil { diff --git a/scan.go b/scan.go index 6bc22b3..2db391c 100644 --- a/scan.go +++ b/scan.go @@ -77,16 +77,16 @@ func (s *Scan) Consistent(on bool) *Scan { } // Limit specifies the maximum amount of results to return. -func (s *Scan) Limit(limit int64) *Scan { - s.limit = int32(limit) +func (s *Scan) Limit(limit int32) *Scan { + s.limit = limit return s } // 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 { - s.searchLimit = int32(limit) +func (s *Scan) SearchLimit(limit int32) *Scan { + s.searchLimit = limit return s } @@ -149,7 +149,7 @@ func (s *Scan) AllWithLastEvaluatedKeyContext(ctx context.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) @@ -158,11 +158,11 @@ 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 context.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 = types.SelectCount for { @@ -176,17 +176,17 @@ func (s *Scan) CountWithContext(ctx context.Context) (int64, error) { return count, err } - count += int64(out.Count) - scanned += int64(out.ScannedCount) + count += out.Count + scanned += out.ScannedCount if s.cc != nil { addConsumedCapacity(s.cc, out.ConsumedCapacity) } - if s.limit > 0 && count >= int64(s.limit) { + if s.limit > 0 && count >= s.limit { break } - if s.searchLimit > 0 && scanned >= int64(s.searchLimit) { + if s.searchLimit > 0 && scanned >= s.searchLimit { break } if out.LastEvaluatedKey == nil { @@ -243,7 +243,7 @@ type scanIter struct { output *dynamodb.ScanOutput err error idx int - n int64 + n int32 // last item evaluated last map[string]types.AttributeValue @@ -275,7 +275,7 @@ func (itr *scanIter) NextWithContext(ctx context.Context, out interface{}) bool } // stop if exceed limit - if itr.scan.limit > 0 && itr.n == int64(itr.scan.limit) { + if itr.scan.limit > 0 && itr.n == itr.scan.limit { // proactively grab the keys for LEK inferral, but don't count it as a real error yet to keep backwards compat itr.keys, itr.keyErr = itr.scan.table.primaryKeys(ctx, itr.exLEK, itr.exESK, itr.scan.index) return false From 6aadb24f75cce9c79679f09e80095e05afe79931 Mon Sep 17 00:00:00 2001 From: Hiroki Yoshioka Date: Tue, 1 Nov 2022 10:18:58 +0900 Subject: [PATCH 09/31] Improvement of init in test * Recover DYNAMO_TEST_REGION * Error handling * No extra type --- db_test.go | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/db_test.go b/db_test.go index e18dd07..03db273 100644 --- a/db_test.go +++ b/db_test.go @@ -2,6 +2,7 @@ package dynamo import ( "context" + "log" "os" "testing" "time" @@ -17,36 +18,27 @@ var ( const offlineSkipMsg = "DYNAMO_TEST_REGION not set" -type endpointResolver struct { - resolveEndpoint func(service, region string, options ...interface{}) (aws.Endpoint, error) -} - -func (e endpointResolver) ResolveEndpoint(service, region string, options ...interface{}) (aws.Endpoint, error) { - return e.resolveEndpoint(service, region, options...) -} - func init() { if region := os.Getenv("DYNAMO_TEST_REGION"); region != "" { var endpoint *string if dte := os.Getenv("DYNAMO_TEST_ENDPOINT"); dte != "" { endpoint = aws.String(dte) } - - resolver := endpointResolver{ - resolveEndpoint: func(service, region string, options ...interface{}) (aws.Endpoint, error) { - return aws.Endpoint{ - URL: *endpoint, - }, nil - }, - } - - conf, err := config.LoadDefaultConfig(context.TODO(), config.WithEndpointResolverWithOptions(resolver), - config.WithRegion(os.Getenv("AWS_REGION"))) - testDB = New(conf) - + 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 { - return + log.Fatal(err) } + testDB = New(cfg) } if table := os.Getenv("DYNAMO_TEST_TABLE"); table != "" { testTable = table From 8c2922600616bb563b6071cf4544a2549b87f527 Mon Sep 17 00:00:00 2001 From: Hiroki Yoshioka Date: Wed, 2 Nov 2022 08:56:37 +0900 Subject: [PATCH 10/31] Use standard context module --- createtable.go | 2 +- retry.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/createtable.go b/createtable.go index d32b1bf..6aaa24f 100644 --- a/createtable.go +++ b/createtable.go @@ -1,6 +1,7 @@ package dynamo import ( + "context" "encoding" "fmt" "reflect" @@ -10,7 +11,6 @@ import ( "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "golang.org/x/net/context" "github.com/aws/aws-sdk-go-v2/service/dynamodb" diff --git a/retry.go b/retry.go index 302edac..da96bde 100644 --- a/retry.go +++ b/retry.go @@ -1,12 +1,12 @@ package dynamo import ( + "context" "errors" "time" "github.com/aws/aws-sdk-go/aws" "github.com/cenkalti/backoff/v4" - "golang.org/x/net/context" ) // RetryTimeout defines the maximum amount of time that requests will From c2f91d7e4a4d139d2cc5db8de7d1fe13f99d7e2f Mon Sep 17 00:00:00 2001 From: Hiroki Yoshioka Date: Wed, 2 Nov 2022 09:08:14 +0900 Subject: [PATCH 11/31] Remove unnecessary blank lines in import block --- batchget.go | 6 ++---- batchwrite.go | 6 ++---- createtable.go | 7 ++----- createtable_test.go | 3 +-- db.go | 5 ++--- decode_test.go | 3 +-- describetable.go | 3 +-- encoding_test.go | 3 +-- go.mod | 2 +- go.sum | 26 ++++++++++++++++++++++++-- query.go | 3 +-- scan.go | 3 +-- substitute.go | 1 - table_test.go | 3 +-- ttl.go | 3 +-- tx.go | 3 +-- tx_test.go | 3 +-- update.go | 3 +-- 18 files changed, 44 insertions(+), 42 deletions(-) diff --git a/batchget.go b/batchget.go index 6d17df0..f38b98f 100644 --- a/batchget.go +++ b/batchget.go @@ -5,11 +5,9 @@ import ( "errors" "fmt" - "github.com/aws/smithy-go/time" - - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "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" ) diff --git a/batchwrite.go b/batchwrite.go index f94583e..5e15fec 100644 --- a/batchwrite.go +++ b/batchwrite.go @@ -4,11 +4,9 @@ import ( "context" "math" - "github.com/aws/smithy-go/time" - - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "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" ) diff --git a/createtable.go b/createtable.go index 6aaa24f..cdaf972 100644 --- a/createtable.go +++ b/createtable.go @@ -8,13 +8,10 @@ import ( "strconv" "strings" + "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" - "github.com/aws/aws-sdk-go-v2/service/dynamodb" - - "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) // StreamView determines what information is written to a table's stream. diff --git a/createtable_test.go b/createtable_test.go index 655f16e..248620e 100644 --- a/createtable_test.go +++ b/createtable_test.go @@ -5,11 +5,10 @@ import ( "testing" "time" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "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 { diff --git a/db.go b/db.go index 3ccc870..b39b429 100644 --- a/db.go +++ b/db.go @@ -7,11 +7,10 @@ import ( "fmt" "os" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/aws/smithy-go" - "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/niltonkummer/dynamo/dynamodbiface" ) diff --git a/decode_test.go b/decode_test.go index 07776cf..6227dbe 100644 --- a/decode_test.go +++ b/decode_test.go @@ -7,10 +7,9 @@ import ( "testing" "time" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) var itemDecodeOnlyTests = []struct { diff --git a/describetable.go b/describetable.go index a75e2e7..5d2dc95 100644 --- a/describetable.go +++ b/describetable.go @@ -4,9 +4,8 @@ import ( "context" "time" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "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. diff --git a/encoding_test.go b/encoding_test.go index 7f29821..79d4353 100644 --- a/encoding_test.go +++ b/encoding_test.go @@ -6,9 +6,8 @@ import ( "strconv" "time" - "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" - "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" ) diff --git a/go.mod b/go.mod index 7f9b00a..da66c45 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/gofrs/uuid v3.2.0+incompatible github.com/google/go-cmp v0.5.6 github.com/niltonkummer/dynamo v1.12.0 - golang.org/x/net v0.0.0-20201110031124-69a78807bb2b + golang.org/x/net v0.1.0 // indirect ) go 1.13 diff --git a/go.sum b/go.sum index 0744b3d..7be7f27 100644 --- a/go.sum +++ b/go.sum @@ -53,18 +53,40 @@ 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= +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-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= +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-20210615035016-665e8c7367d1/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/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 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 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= diff --git a/query.go b/query.go index 1f3a2da..51f6501 100644 --- a/query.go +++ b/query.go @@ -5,9 +5,8 @@ import ( "errors" "strings" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "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. diff --git a/scan.go b/scan.go index 2db391c..e39dbcc 100644 --- a/scan.go +++ b/scan.go @@ -4,9 +4,8 @@ import ( "context" "strings" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "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. diff --git a/substitute.go b/substitute.go index 4505386..2c9d415 100644 --- a/substitute.go +++ b/substitute.go @@ -9,7 +9,6 @@ import ( "strings" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/guregu/dynamo/internal/exprs" ) diff --git a/table_test.go b/table_test.go index 8e53660..9727769 100644 --- a/table_test.go +++ b/table_test.go @@ -7,9 +7,8 @@ import ( "testing" "time" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) func TestTableLifecycle(t *testing.T) { diff --git a/ttl.go b/ttl.go index 851cbed..48f21e8 100644 --- a/ttl.go +++ b/ttl.go @@ -3,10 +3,9 @@ package dynamo import ( "context" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "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. diff --git a/tx.go b/tx.go index ff5b4ec..afe1601 100644 --- a/tx.go +++ b/tx.go @@ -4,10 +4,9 @@ import ( "context" "errors" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "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" ) diff --git a/tx_test.go b/tx_test.go index 31217de..224f705 100644 --- a/tx_test.go +++ b/tx_test.go @@ -7,9 +7,8 @@ import ( "testing" "time" - "github.com/gofrs/uuid" - "github.com/aws/smithy-go" + "github.com/gofrs/uuid" ) func TestTx(t *testing.T) { diff --git a/update.go b/update.go index 4b2ca41..c59c825 100644 --- a/update.go +++ b/update.go @@ -5,9 +5,8 @@ import ( "fmt" "strings" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "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. From 1afde07f21d4a0eed3348f05ae7ba83cc25b6e95 Mon Sep 17 00:00:00 2001 From: Hiroki Yoshioka Date: Mon, 7 Nov 2022 09:58:05 +0900 Subject: [PATCH 12/31] Update expressions making blank slice --- table_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/table_test.go b/table_test.go index 9727769..a1bbd91 100644 --- a/table_test.go +++ b/table_test.go @@ -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), }, }, } From 9552dd01a896c2689aeba007c91fc161d03dc194 Mon Sep 17 00:00:00 2001 From: Hiroki Yoshioka Date: Wed, 9 Nov 2022 09:56:05 +0900 Subject: [PATCH 13/31] Change repo --- README.md | 12 ++++++------ db.go | 2 +- go.mod | 1 - go.sum | 4 ---- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 2834b7d..09df909 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -## dynamo [![GoDoc](https://godoc.org/github.com/niltonkummer/dynamo?status.svg)](https://godoc.org/github.com/niltonkummer/dynamo) -`import "github.com/niltonkummer/dynamo"` +## 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 v2](https://github.com/aws/aws-sdk-go-v2/). @@ -17,7 +17,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" - "github.com/niltonkummer/dynamo" + "github.com/guregu/dynamo" ) // Use struct tags much like the standard JSON library, @@ -90,7 +90,7 @@ table.Put(item{ID: 42}).If("attribute_not_exists(ID)").Run() dynamo automatically handles the following interfaces: -* [`dynamo.Marshaler`](https://godoc.org/github.com/niltonkummer/dynamo#Marshaler) and [`dynamo.Unmarshaler`](https://godoc.org/github.com/niltonkummer/dynamo#Unmarshaler) +* [`dynamo.Marshaler`](https://godoc.org/github.com/guregu/dynamo#Marshaler) and [`dynamo.Unmarshaler`](https://godoc.org/github.com/guregu/dynamo#Unmarshaler) * [`dynamodbattribute.Marshaler`](https://godoc.org/github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute#Marshaler) and [`dynamodbattribute.Unmarshaler`](https://godoc.org/github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute#Unmarshaler) * [`encoding.TextMarshaler`](https://godoc.org/encoding#TextMarshaler) and [`encoding.TextUnmarshaler`](https://godoc.org/encoding#TextUnmarshaler) @@ -185,7 +185,7 @@ This creates a table with the primary hash key ID and range key Time. It creates dynamo has been in development before the official AWS libraries were stable. We use a different encoder and decoder than the [dynamodbattribute](https://godoc.org/github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute) package. dynamo uses the `dynamo` struct tag instead of the `dynamodbav` struct tag, and we also prefer to automatically omit invalid values such as empty strings, whereas the dynamodbattribute package substitutes null values for them. Items that satisfy the [`dynamodbattribute.(Un)marshaler`](https://godoc.org/github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute#Marshaler) interfaces are compatibile with both libraries. -In order to use dynamodbattribute's encoding facilities, you must wrap objects passed to dynamo with [`dynamo.AWSEncoding`](https://godoc.org/github.com/niltonkummer/dynamo#AWSEncoding). Here is a quick example: +In order to use dynamodbattribute's encoding facilities, you must wrap objects passed to dynamo with [`dynamo.AWSEncoding`](https://godoc.org/github.com/guregu/dynamo#AWSEncoding). Here is a quick example: ```go // Notice the use of the dynamodbav struct tag @@ -211,7 +211,7 @@ Change the table name with the environment variable `DYNAMO_TEST_TABLE`. You mus ```bash -DYNAMO_TEST_REGION=us-west-2 go test github.com/niltonkummer/dynamo/... -cover +DYNAMO_TEST_REGION=us-west-2 go test github.com/guregu/dynamo/... -cover ``` If you want to use [DynamoDB Local](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html) to run local tests, specify `DYNAMO_TEST_ENDPOINT`. diff --git a/db.go b/db.go index b39b429..8743eb3 100644 --- a/db.go +++ b/db.go @@ -12,7 +12,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/aws/smithy-go" "github.com/aws/smithy-go/logging" - "github.com/niltonkummer/dynamo/dynamodbiface" + "github.com/guregu/dynamo/dynamodbiface" ) // DB is a DynamoDB client. diff --git a/go.mod b/go.mod index da66c45..f0c5c1e 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/cenkalti/backoff/v4 v4.1.2 github.com/gofrs/uuid v3.2.0+incompatible github.com/google/go-cmp v0.5.6 - github.com/niltonkummer/dynamo v1.12.0 golang.org/x/net v0.1.0 // indirect ) diff --git a/go.sum b/go.sum index 7be7f27..296020d 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,6 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.11.1 h1:QKR7wy5e650q70PFKMfGF9sTo0rZ 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 v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY= -github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 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= @@ -47,8 +45,6 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y 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= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/niltonkummer/dynamo v1.12.0 h1:4cHzn5FA7/R+EsJYj+3nUCh9RXJ3e8peTYdfLkyWqVg= -github.com/niltonkummer/dynamo v1.12.0/go.mod h1:Mc3TooY/MCgp/veyxUnGKwfCG0y0/ZKMoM0UU2YYdxk= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= From e74a21846a72cb882db6b1d28619464dbd6ca0ea Mon Sep 17 00:00:00 2001 From: Greg Date: Fri, 5 Jan 2024 19:00:19 +0900 Subject: [PATCH 14/31] fix endpoint for tests --- db_test.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/db_test.go b/db_test.go index 7e5a07e..dfdecd8 100644 --- a/db_test.go +++ b/db_test.go @@ -24,20 +24,18 @@ const offlineSkipMsg = "DYNAMO_TEST_REGION not set" func init() { // os.Setenv("DYNAMO_TEST_REGION", "us-west-2") if region := os.Getenv("DYNAMO_TEST_REGION"); region != "" { - var endpoint *string + var endpoint aws.EndpointResolverWithOptions if dte := os.Getenv("DYNAMO_TEST_ENDPOINT"); dte != "" { - endpoint = aws.String(dte) + endpoint = aws.EndpointResolverWithOptionsFunc( + func(service, region string, options ...interface{}) (aws.Endpoint, error) { + return aws.Endpoint{URL: dte}, nil + }, + ) } 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 - }, - ), - ), + config.WithEndpointResolverWithOptions(endpoint), ) if err != nil { log.Fatal(err) From 0064f3b641e0c2a4d09df314902aaa5f895eed52 Mon Sep 17 00:00:00 2001 From: Greg Date: Fri, 5 Jan 2024 19:53:46 +0900 Subject: [PATCH 15/31] hmm --- go.mod | 1 + retry.go | 39 ++++++++++++++++++++++++++++++++++++++- tx_test.go | 1 + 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 268eaab..4301411 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8 github.com/aws/smithy-go v1.19.0 github.com/cenkalti/backoff/v4 v4.1.2 + github.com/davecgh/go-spew v1.1.0 golang.org/x/sync v0.6.0 ) diff --git a/retry.go b/retry.go index 632ad8c..974686a 100644 --- a/retry.go +++ b/retry.go @@ -5,7 +5,9 @@ import ( "errors" "time" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" "github.com/cenkalti/backoff/v4" ) @@ -56,5 +58,40 @@ func (db *DB) retry(ctx context.Context, f func() error) error { var errRetry = errors.New("dynamo: retry") func canRetry(err error) bool { - return errors.Is(err, errRetry) + if errors.Is(err, errRetry) { + return true + } + + // TODO: seems to be broken? + var txe *types.TransactionCanceledException + if errors.As(err, &txe) { + 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 + } + + // TODO: fix + 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 } diff --git a/tx_test.go b/tx_test.go index 780bcb9..64a6743 100644 --- a/tx_test.go +++ b/tx_test.go @@ -205,6 +205,7 @@ func TestTxRetry(t *testing.T) { Range("Time", widget1.Time). Add("Count", 1)) if err := tx.Run(); err != nil { + // spew.Dump(err) panic(err) } }() From 03408e0454157b367b3f177d039dd0f5ee1e6ef0 Mon Sep 17 00:00:00 2001 From: Greg Date: Fri, 5 Jan 2024 19:54:12 +0900 Subject: [PATCH 16/31] remove spurious dep --- go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/go.mod b/go.mod index 4301411..268eaab 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8 github.com/aws/smithy-go v1.19.0 github.com/cenkalti/backoff/v4 v4.1.2 - github.com/davecgh/go-spew v1.1.0 golang.org/x/sync v0.6.0 ) From 801336f266e754510ea1126ee0b5d39022ceea66 Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 27 Jan 2024 17:32:02 +0900 Subject: [PATCH 17/31] fix retrying settings, remove old sdk references --- db.go | 44 ++++++++++---------------------------- db_test.go | 1 + put_test.go | 2 +- query_test.go | 5 +++-- retry.go | 53 ++++++++++++++++++++++++---------------------- scan_test.go | 4 +--- substitute_test.go | 2 +- update_test.go | 2 +- 8 files changed, 47 insertions(+), 66 deletions(-) diff --git a/db.go b/db.go index ab65d75..7305c3b 100644 --- a/db.go +++ b/db.go @@ -10,7 +10,6 @@ import ( "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/aws-sdk-go/aws/request" "github.com/aws/smithy-go" "github.com/aws/smithy-go/logging" "github.com/guregu/dynamo/dynamodbiface" @@ -20,7 +19,7 @@ import ( type DB struct { client dynamodbiface.DynamoDBAPI logger logging.Logger - retryer request.Retryer + retryer func() aws.Retryer retryMax int } @@ -28,37 +27,17 @@ type DB struct { // If Retryer is configured, retrying responsibility will be delegated to it. // If MaxRetries is configured, the maximum number of retry attempts will be limited to the specified value // (0 for no retrying, -1 for default behavior of unlimited retries). -// MaxRetries is ignored if Retryer is set. -// func New(p client.ConfigProvider, cfgs ...*aws.Config) *DB { -// cfg := p.ClientConfig(dynamodb.EndpointsID, cfgs...) -// return newDB(dynamodb.New(p, cfgs...), cfg.Config) -// client dynamodbiface.DynamoDBAPI -// logger logging.Logger -// } -// TODO ^^^^^ - -// New creates a new client with the given configuration. -func New(cfg aws.Config) *DB { - db := &DB{ - client: dynamodb.NewFromConfig(cfg), - logger: cfg.Logger, - } - if db.logger == nil { - db.logger = logging.NewStandardLogger(os.Stdout) - } - return db +func New(cfg aws.Config, options ...func(*dynamodb.Options)) *DB { + client := dynamodb.NewFromConfig(cfg, options...) + return newDB(client, cfg) } // NewFromIface creates a new client with the given interface. func NewFromIface(client dynamodbiface.DynamoDBAPI) *DB { - // TODO: FIX vvvv - // if c, ok := client.(*dynamodb.DynamoDB); ok { - // return newDB(c, &c.Config) - // } - return newDB(client, &aws.Config{}) + return newDB(client, aws.Config{}) } -func newDB(client dynamodbiface.DynamoDBAPI, cfg *aws.Config) *DB { +func newDB(client dynamodbiface.DynamoDBAPI, cfg aws.Config) *DB { db := &DB{ client: client, logger: cfg.Logger, @@ -69,12 +48,11 @@ func newDB(client dynamodbiface.DynamoDBAPI, cfg *aws.Config) *DB { db.logger = logging.NewStandardLogger(os.Stdout) } - // TODO: FIX vvvvvv - // if retryer, ok := cfg.Retryer.(request.Retryer); ok { - // db.retryer = retryer - // } else if cfg.MaxRetries != nil { - // db.retryMax = *cfg.MaxRetries - // } + if cfg.Retryer != nil { + db.retryer = cfg.Retryer + } else if cfg.RetryMaxAttempts > 0 { + db.retryMax = cfg.RetryMaxAttempts + } return db } diff --git a/db_test.go b/db_test.go index dfdecd8..0b757b8 100644 --- a/db_test.go +++ b/db_test.go @@ -36,6 +36,7 @@ func init() { context.Background(), config.WithRegion(region), config.WithEndpointResolverWithOptions(endpoint), + config.WithRetryer(nil), ) if err != nil { log.Fatal(err) diff --git a/put_test.go b/put_test.go index f0fd48a..50acd52 100644 --- a/put_test.go +++ b/put_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go-v2/aws" ) func TestPut(t *testing.T) { diff --git a/query_test.go b/query_test.go index 816418f..6897460 100644 --- a/query_test.go +++ b/query_test.go @@ -1,12 +1,13 @@ package dynamo import ( + "context" "reflect" "testing" "time" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/aws/aws-sdk-go/aws" ) func TestGetAllCount(t *testing.T) { @@ -239,7 +240,7 @@ func TestQueryMagicLEK(t *testing.T) { }) t.Run("table cache", func(t *testing.T) { - pk, err := table.primaryKeys(aws.BackgroundContext(), nil, nil, "") + pk, err := table.primaryKeys(context.Background(), nil, nil, "") if err != nil { t.Fatal(err) } diff --git a/retry.go b/retry.go index 974686a..b388298 100644 --- a/retry.go +++ b/retry.go @@ -5,24 +5,14 @@ import ( "errors" "time" + "github.com/aws/aws-sdk-go-v2/aws/transport/http" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/smithy-go" "github.com/cenkalti/backoff/v4" ) -// RetryTimeout defines the maximum amount of time that requests will -// attempt to automatically retry for. In other words, this is the maximum -// amount of time that dynamo operations will block. -// RetryTimeout is only considered by methods that do not take a context. -// Higher values are better when using tables with lower throughput. -var RetryTimeout = 1 * time.Minute - func defaultContext() (context.Context, context.CancelFunc) { - if RetryTimeout == 0 { - return aws.BackgroundContext(), (func() {}) - } - return context.WithDeadline(aws.BackgroundContext(), time.Now().Add(RetryTimeout)) + return context.Background(), func() {} } func (db *DB) retry(ctx context.Context, f func() error) error { @@ -46,8 +36,7 @@ func (db *DB) retry(ctx context.Context, f func() error) error { if next = b.NextBackOff(); next == backoff.Stop { return err } - - if err := aws.SleepWithContext(ctx, next); err != nil { + if err := sleep(ctx, next); err != nil { return err } } @@ -62,7 +51,6 @@ func canRetry(err error) bool { return true } - // TODO: seems to be broken? var txe *types.TransactionCanceledException if errors.As(err, &txe) { retry := false @@ -80,18 +68,33 @@ func canRetry(err error) bool { return retry } - // TODO: fix - if ae, ok := err.(awserr.RequestFailure); ok { - switch ae.StatusCode() { + var aerr smithy.APIError + if errors.As(err, &aerr) { + switch aerr.ErrorCode() { + case "ProvisionedThroughputExceededException", + "ThrottlingException": + return true + } + } + + var rerr *http.ResponseError + if errors.As(err, &rerr) { + switch rerr.HTTPStatusCode() { case 500, 503: return true - case 400: - switch ae.Code() { - case "ProvisionedThroughputExceededException", - "ThrottlingException": - return true - } } } + return false } + +func sleep(ctx context.Context, dur time.Duration) error { + timer := time.NewTimer(dur) + defer timer.Stop() + + select { + case <-ctx.Done(): + case <-timer.C: + } + return ctx.Err() +} diff --git a/scan_test.go b/scan_test.go index 75cccbf..2962a9e 100644 --- a/scan_test.go +++ b/scan_test.go @@ -5,8 +5,6 @@ import ( "reflect" "testing" "time" - - "github.com/aws/aws-sdk-go/aws" ) func TestScan(t *testing.T) { @@ -224,7 +222,7 @@ func TestScanMagicLEK(t *testing.T) { }) t.Run("table cache", func(t *testing.T) { - pk, err := table.primaryKeys(aws.BackgroundContext(), nil, nil, "") + pk, err := table.primaryKeys(context.Background(), nil, nil, "") if err != nil { t.Fatal(err) } diff --git a/substitute_test.go b/substitute_test.go index 61dd0d7..3fd73c5 100644 --- a/substitute_test.go +++ b/substitute_test.go @@ -5,8 +5,8 @@ import ( "reflect" "testing" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/aws/aws-sdk-go/aws" ) func TestSubExpr(t *testing.T) { diff --git a/update_test.go b/update_test.go index ee493e1..54fd962 100644 --- a/update_test.go +++ b/update_test.go @@ -5,8 +5,8 @@ import ( "testing" "time" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/aws/aws-sdk-go/aws" ) func TestUpdate(t *testing.T) { From 91db943563a81d2c65fbb1dab7066d91b359a63c Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 27 Jan 2024 18:05:18 +0900 Subject: [PATCH 18/31] fix retrying tests --- db.go | 15 ++++++ go.mod | 1 - go.sum | 13 ----- retry.go | 1 + retry_test.go | 138 ++++++++++++++++++++++---------------------------- 5 files changed, 77 insertions(+), 91 deletions(-) diff --git a/db.go b/db.go index 7305c3b..e07077c 100644 --- a/db.go +++ b/db.go @@ -48,12 +48,27 @@ func newDB(client dynamodbiface.DynamoDBAPI, cfg aws.Config) *DB { db.logger = logging.NewStandardLogger(os.Stdout) } + // TODO: replace all of this with AWS Retryer interface + /* + if real, ok := client.(*dynamodb.Client); ok { + if retryer := real.Options().Retryer; retryer != nil { + db.retryer = func() aws.Retryer { return retryer } + if cfg.Retryer != nil { + db.retryer = cfg.Retryer + } + } else if real.Options().RetryMaxAttempts > 0 { + db.retryMax = cfg.RetryMaxAttempts + } + } else { + */ if cfg.Retryer != nil { db.retryer = cfg.Retryer } else if cfg.RetryMaxAttempts > 0 { db.retryMax = cfg.RetryMaxAttempts } + // } + return db } diff --git a/go.mod b/go.mod index 268eaab..9f44979 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,6 @@ module github.com/guregu/dynamo require ( - github.com/aws/aws-sdk-go v1.38.0 github.com/aws/aws-sdk-go-v2 v1.24.1 github.com/aws/aws-sdk-go-v2/config v1.11.0 github.com/aws/aws-sdk-go-v2/credentials v1.6.4 diff --git a/go.sum b/go.sum index 7421aa7..dda0cfb 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -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/go.mod h1:SQfA+m2ltnu1cA0soUkj4dRSsmITiVQUJvBIZjzfPyQ= github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= @@ -50,22 +48,11 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y 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= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -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/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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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/retry.go b/retry.go index b388298..a8d7d4f 100644 --- a/retry.go +++ b/retry.go @@ -96,5 +96,6 @@ func sleep(ctx context.Context, dur time.Duration) error { case <-ctx.Done(): case <-timer.C: } + return ctx.Err() } diff --git a/retry_test.go b/retry_test.go index 0556072..6dd6880 100644 --- a/retry_test.go +++ b/retry_test.go @@ -1,85 +1,69 @@ package dynamo -// TODO: FIX +import ( + "context" + "fmt" + "testing" -// import ( -// "context" -// "fmt" -// "testing" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/retry" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" +) -// "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/aws/session" -// "github.com/aws/aws-sdk-go/service/dynamodb" -// ) +func TestRetryMax(t *testing.T) { + if testing.Short() { + t.SkipNow() + } -// func TestRetryMax(t *testing.T) { -// if testing.Short() { -// t.SkipNow() -// } + test := func(max int) (string, func(t *testing.T)) { + name := fmt.Sprintf("max(%d)", max) + return name, func(t *testing.T) { + t.Parallel() + t.Helper() + db := New(aws.Config{ + RetryMaxAttempts: max, + Credentials: dummyCreds, + }) -// test := func(max int) (string, func(t *testing.T)) { -// name := fmt.Sprintf("max(%d)", max) -// return name, func(t *testing.T) { -// t.Parallel() -// t.Helper() -// sesh, err := session.NewSession(&aws.Config{ -// MaxRetries: aws.Int(max), -// Credentials: dummyCreds, -// }) -// if err != nil { -// t.Fatal(err) -// } -// db := New(sesh) + var runs int + err := db.retry(context.Background(), func() error { + runs++ + return &types.ProvisionedThroughputExceededException{} + }) + if err == nil { + t.Fatal("expected error, got nil") + } + if want := max + 1; runs != want { + t.Error("wrong number of runs. want:", want, "got:", runs) + } + } + } + // t.Run(test(0)) // behavior changed from v1 + t.Run(test(1)) + t.Run(test(3)) +} -// var runs int -// err = db.retry(context.Background(), func() error { -// runs++ -// return awserr.NewRequestFailure( -// awserr.New(dynamodb.ErrCodeProvisionedThroughputExceededException, "dummy error", nil), -// 400, -// fmt.Sprintf("try-%d", runs), -// ) -// }) -// if err == nil { -// t.Fatal("expected error, got nil") -// } -// if want := max + 1; runs != want { -// t.Error("wrong number of runs. want:", want, "got:", runs) -// } -// } -// } -// t.Run(test(0)) -// t.Run(test(1)) -// t.Run(test(3)) -// } +func TestRetryCustom(t *testing.T) { + t.Parallel() + retryer := func() aws.Retryer { + return retry.NewStandard(func(so *retry.StandardOptions) { + so.MaxAttempts = 1 + }) + } + db := New(aws.Config{ + Retryer: retryer, + Credentials: dummyCreds, + }) -// func TestRetryCustom(t *testing.T) { -// t.Parallel() -// sesh, err := session.NewSession(&aws.Config{ -// Retryer: client.NoOpRetryer{}, -// MaxRetries: aws.Int(10), // should be ignored (superseded by Retryer) -// Credentials: dummyCreds, -// }) -// if err != nil { -// t.Fatal(err) -// } -// db := New(sesh) - -// var runs int -// err = db.retry(context.Background(), func() error { -// runs++ -// return awserr.NewRequestFailure( -// awserr.New(dynamodb.ErrCodeProvisionedThroughputExceededException, "dummy error", nil), -// 400, -// fmt.Sprintf("try-%d", runs), -// ) -// }) -// if err == nil { -// t.Fatal("expected error, got nil") -// } -// if want := 1; runs != want { -// t.Error("wrong number of runs. want:", want, "got:", runs) -// } -// } + var runs int + err := db.retry(context.Background(), func() error { + runs++ + return &types.ProvisionedThroughputExceededException{} + }) + if err == nil { + t.Fatal("expected error, got nil") + } + if want := 1; runs != want { + t.Error("wrong number of runs. want:", want, "got:", runs) + } +} From ef69352ae640404555c23c56dbb37014c5964a8a Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 27 Jan 2024 18:33:47 +0900 Subject: [PATCH 19/31] change LastEvaluatedKey signature --- db.go | 5 ++--- query.go | 23 ++++++++--------------- query_test.go | 18 +++++++++++++++--- scan.go | 49 ++++++++++++++++++++++++++----------------------- scan_test.go | 24 ++++++++++++++++++++---- 5 files changed, 71 insertions(+), 48 deletions(-) diff --git a/db.go b/db.go index e07077c..51d80c7 100644 --- a/db.go +++ b/db.go @@ -66,7 +66,6 @@ func newDB(client dynamodbiface.DynamoDBAPI, cfg aws.Config) *DB { } else if cfg.RetryMaxAttempts > 0 { db.retryMax = cfg.RetryMaxAttempts } - // } return db @@ -230,7 +229,7 @@ type PagingIter interface { Iter // LastEvaluatedKey returns a key that can be passed to StartFrom in Query or Scan. // Combined with SearchLimit, it is useful for paginating partial results. - LastEvaluatedKey() PagingKey + LastEvaluatedKey(context.Context) (PagingKey, error) } // PagingIter is an iterator of combined request results from multiple iterators running in parallel. @@ -238,7 +237,7 @@ type ParallelIter interface { Iter // LastEvaluatedKeys returns each parallel segment's last evaluated key in order of segment number. // The slice will be the same size as the number of segments, and the keys can be nil. - LastEvaluatedKeys() []PagingKey + LastEvaluatedKeys(context.Context) ([]PagingKey, error) } // PagingKey is a key used for splitting up partial results. diff --git a/query.go b/query.go index 8558a6a..14e2411 100644 --- a/query.go +++ b/query.go @@ -424,39 +424,32 @@ func (itr *queryIter) Err() error { return itr.err } -func (itr *queryIter) LastEvaluatedKey() PagingKey { +func (itr *queryIter) LastEvaluatedKey(ctx context.Context) (PagingKey, error) { if itr.output != nil { // if we've hit the end of our results, we can use the real LEK if itr.idx == len(itr.output.Items) { - return itr.output.LastEvaluatedKey + return itr.output.LastEvaluatedKey, nil } // figure out the primary keys if needed if itr.keys == nil && itr.keyErr == nil { - ctx, _ := defaultContext() // TODO(v2): take context instead of using the default itr.keys, itr.keyErr = itr.query.table.primaryKeys(ctx, itr.exLEK, itr.exESK, itr.query.index) } if itr.keyErr != nil { // primaryKeys can fail if the credentials lack DescribeTable permissions // in order to preserve backwards compatibility, we fall back to the old behavior and warn // see: https://github.com/guregu/dynamo/pull/187#issuecomment-1045183901 - // TODO(v2): rejigger this API. - itr.query.table.db.log("dynamo: Warning:", itr.keyErr, "Returning a later LastEvaluatedKey.") - return itr.output.LastEvaluatedKey + return itr.output.LastEvaluatedKey, fmt.Errorf("dynamo: failed to determine LastEvaluatedKey in query: %w", itr.keyErr) } // we can't use the real LEK, so we need to infer the LEK from the last item we saw lek, err := lekify(itr.last, itr.keys) - // unfortunately, this API can't return an error so a warning is the best we can do... - // this matches old behavior before the LEK was automatically generated - // TODO(v2): fix this. if err != nil { - itr.query.table.db.log("dynamo: Warning:", err, "Returning a later LastEvaluatedKey.") - return itr.output.LastEvaluatedKey + return itr.output.LastEvaluatedKey, fmt.Errorf("dynamo: failed to infer LastEvaluatedKey in query: %w", err) } - return lek + return lek, nil } - return nil + return nil, nil } // All executes this request and unmarshals all results to out, which must be a pointer to a slice. @@ -493,7 +486,8 @@ func (q *Query) AllWithLastEvaluatedKeyContext(ctx context.Context, out interfac } for iter.NextWithContext(ctx, out) { } - return iter.LastEvaluatedKey(), iter.Err() + lek, err := iter.LastEvaluatedKey(ctx) + return lek, errors.Join(iter.Err(), err) } // Iter returns a results iterator for this request. @@ -503,7 +497,6 @@ func (q *Query) Iter() PagingIter { unmarshal: unmarshalItem, err: q.err, } - return iter } diff --git a/query_test.go b/query_test.go index 6897460..2ffed0e 100644 --- a/query_test.go +++ b/query_test.go @@ -186,7 +186,11 @@ func TestQueryPaging(t *testing.T) { if more { t.Error("unexpected more", more) } - itr = table.Get("UserID", 1969).StartFrom(itr.LastEvaluatedKey()).SearchLimit(1).Iter() + lek, err := itr.LastEvaluatedKey(context.Background()) + if err != nil { + t.Error("LEK error", err) + } + itr = table.Get("UserID", 1969).StartFrom(lek).SearchLimit(1).Iter() } } @@ -235,7 +239,11 @@ func TestQueryMagicLEK(t *testing.T) { if more { t.Error("unexpected more", more) } - itr = table.Get("UserID", 1970).StartFrom(itr.LastEvaluatedKey()).Limit(1).Iter() + lek, err := itr.LastEvaluatedKey(context.Background()) + if err != nil { + t.Error("LEK error", lek) + } + itr = table.Get("UserID", 1970).StartFrom(lek).Limit(1).Iter() } }) @@ -268,7 +276,11 @@ func TestQueryMagicLEK(t *testing.T) { if more { t.Error("unexpected more", more) } - itr = table.Get("Msg", "TestQueryMagicLEK").Index("Msg-Time-index").Filter("UserID = ?", 1970).StartFrom(itr.LastEvaluatedKey()).Limit(1).Iter() + lek, err := itr.LastEvaluatedKey(context.Background()) + if err != nil { + t.Error("LEK error", err) + } + itr = table.Get("Msg", "TestQueryMagicLEK").Index("Msg-Time-index").Filter("UserID = ?", 1970).StartFrom(lek).Limit(1).Iter() } }) } diff --git a/scan.go b/scan.go index 61f7603..3dafa3b 100644 --- a/scan.go +++ b/scan.go @@ -2,6 +2,8 @@ package dynamo import ( "context" + "errors" + "fmt" "strings" "sync" @@ -198,7 +200,8 @@ func (s *Scan) AllWithLastEvaluatedKeyContext(ctx context.Context, out interface } for itr.NextWithContext(ctx, out) { } - return itr.LastEvaluatedKey(), itr.Err() + lek, err := itr.LastEvaluatedKey(ctx) + return lek, errors.Join(itr.Err(), err) } // AllParallel executes this request by running the given number of segments in parallel, then unmarshaling all results to out, which must be a pointer to a slice. @@ -219,7 +222,8 @@ func (s *Scan) AllParallelWithLastEvaluatedKeys(ctx context.Context, segments in go ps.run(ctx) for ps.NextWithContext(ctx, out) { } - return ps.LastEvaluatedKeys(), ps.Err() + leks, err := ps.LastEvaluatedKeys(ctx) + return leks, errors.Join(ps.Err(), err) } // AllParallelStartFrom executes this request by continuing parallel scans from the given LastEvaluatedKeys, then unmarshaling all results to out, which must be a pointer to a slice. @@ -230,7 +234,8 @@ func (s *Scan) AllParallelStartFrom(ctx context.Context, keys []PagingKey, out i go ps.run(ctx) for ps.NextWithContext(ctx, out) { } - return ps.LastEvaluatedKeys(), ps.Err() + leks, err := ps.LastEvaluatedKeys(ctx) + return leks, errors.Join(ps.Err(), err) } // Count executes this request and returns the number of items matching the scan. @@ -442,49 +447,44 @@ func (itr *scanIter) Err() error { // LastEvaluatedKey returns a key that can be used to continue this scan. // Use with SearchLimit for best results. -func (itr *scanIter) LastEvaluatedKey() PagingKey { +func (itr *scanIter) LastEvaluatedKey(ctx context.Context) (PagingKey, error) { if itr.output != nil { // if we've hit the end of our results, we can use the real LEK if itr.idx == len(itr.output.Items) { - return itr.output.LastEvaluatedKey + return itr.output.LastEvaluatedKey, nil } // figure out the primary keys if needed if itr.keys == nil && itr.keyErr == nil { - ctx, _ := defaultContext() // TODO(v2): take context instead of using the default itr.keys, itr.keyErr = itr.scan.table.primaryKeys(ctx, itr.exLEK, itr.exESK, itr.scan.index) } if itr.keyErr != nil { // primaryKeys can fail if the credentials lack DescribeTable permissions // in order to preserve backwards compatibility, we fall back to the old behavior and warn // see: https://github.com/guregu/dynamo/pull/187#issuecomment-1045183901 - // TODO(v2): rejigger this API. - itr.scan.table.db.log("dynamo: Warning:", itr.keyErr, "Returning a later LastEvaluatedKey.") - return itr.output.LastEvaluatedKey + return itr.output.LastEvaluatedKey, fmt.Errorf("dynamo: failed to determine LastEvaluatedKey in scan: %w", itr.keyErr) } // we can't use the real LEK, so we need to infer the LEK from the last item we saw lek, err := lekify(itr.last, itr.keys) - // unfortunately, this API can't return an error so a warning is the best we can do... - // this matches old behavior before the LEK was automatically generated - // TODO(v2): fix this. if err != nil { - itr.scan.table.db.log("dynamo: Warning:", err, "Returning a later LastEvaluatedKey.") - return itr.output.LastEvaluatedKey + return itr.output.LastEvaluatedKey, fmt.Errorf("dynamo: failed to infer LastEvaluatedKey in scan: %w", err) } - return lek + return lek, nil } - return nil + return nil, nil } type parallelScan struct { iters []*scanIter items chan Item - leks []PagingKey - cc *ConsumedCapacity - err error - mu *sync.Mutex + leks []PagingKey + lekErr error + + cc *ConsumedCapacity + err error + mu *sync.Mutex unmarshal unmarshalFunc } @@ -522,9 +522,12 @@ func (ps *parallelScan) run(ctx context.Context) { } if ps.leks != nil { - lek := iter.LastEvaluatedKey() + lek, err := iter.LastEvaluatedKey(ctx) ps.mu.Lock() ps.leks[i] = lek + if err != nil && ps.lekErr == nil { + ps.lekErr = err + } ps.mu.Unlock() } } @@ -582,10 +585,10 @@ func (ps *parallelScan) Err() error { return ps.err } -func (ps *parallelScan) LastEvaluatedKeys() []PagingKey { +func (ps *parallelScan) LastEvaluatedKeys(_ context.Context) ([]PagingKey, error) { keys := make([]PagingKey, len(ps.leks)) ps.mu.Lock() defer ps.mu.Unlock() copy(keys, ps.leks) - return keys + return keys, ps.lekErr } diff --git a/scan_test.go b/scan_test.go index 2962a9e..72ecc48 100644 --- a/scan_test.go +++ b/scan_test.go @@ -131,7 +131,11 @@ func TestScanPaging(t *testing.T) { if !more { break } - itr = table.Scan().StartFrom(itr.LastEvaluatedKey()).SearchLimit(1).Iter() + lek, err := itr.LastEvaluatedKey(context.Background()) + if err != nil { + t.Error("LEK error", err) + } + itr = table.Scan().StartFrom(lek).SearchLimit(1).Iter() } for i, w := range widgets { if w.UserID == 0 && w.Time.IsZero() { @@ -159,7 +163,11 @@ func TestScanPaging(t *testing.T) { if !more { break } - itr = table.Scan().SearchLimit(1).IterParallelStartFrom(ctx, itr.LastEvaluatedKeys()) + leks, err := itr.LastEvaluatedKeys(context.Background()) + if err != nil { + t.Error("LEK error", err) + } + itr = table.Scan().SearchLimit(1).IterParallelStartFrom(ctx, leks) } for i, w := range widgets { if w.UserID == 0 && w.Time.IsZero() { @@ -205,7 +213,11 @@ func TestScanMagicLEK(t *testing.T) { if itr.Err() != nil { t.Error("unexpected error", itr.Err()) } - itr = table.Scan().Filter("'Msg' = ?", "TestScanMagicLEK").StartFrom(itr.LastEvaluatedKey()).Limit(2).Iter() + lek, err := itr.LastEvaluatedKey(context.Background()) + if err != nil { + t.Error("LEK error", err) + } + itr = table.Scan().Filter("'Msg' = ?", "TestScanMagicLEK").StartFrom(lek).Limit(2).Iter() } }) @@ -217,7 +229,11 @@ func TestScanMagicLEK(t *testing.T) { if itr.Err() != nil { t.Error("unexpected error", itr.Err()) } - itr = table.Scan().Index("Msg-Time-index").Filter("UserID = ?", 2069).StartFrom(itr.LastEvaluatedKey()).Limit(2).Iter() + lek, err := itr.LastEvaluatedKey(context.Background()) + if err != nil { + t.Error("LEK error", err) + } + itr = table.Scan().Index("Msg-Time-index").Filter("UserID = ?", 2069).StartFrom(lek).Limit(2).Iter() } }) From 2b8cde02fb19fd6c8a342ef9a17b9db525cdff86 Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 27 Jan 2024 18:39:06 +0900 Subject: [PATCH 20/31] rename KMSMasterKeyArn -> KMSMasterKeyARN --- describetable.go | 2 +- sse.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/describetable.go b/describetable.go index d6d4892..e37cf4c 100644 --- a/describetable.go +++ b/describetable.go @@ -198,7 +198,7 @@ func newDescription(table *types.TableDescription) Description { sseDesc.InaccessibleEncryptionDateTime = *table.SSEDescription.InaccessibleEncryptionDateTime } if table.SSEDescription.KMSMasterKeyArn != nil { - sseDesc.KMSMasterKeyArn = *table.SSEDescription.KMSMasterKeyArn + sseDesc.KMSMasterKeyARN = *table.SSEDescription.KMSMasterKeyArn } if table.SSEDescription.SSEType != "" { sseDesc.SSEType = table.SSEDescription.SSEType diff --git a/sse.go b/sse.go index ec76536..9495455 100644 --- a/sse.go +++ b/sse.go @@ -18,7 +18,7 @@ const ( type SSEDescription struct { InaccessibleEncryptionDateTime time.Time - KMSMasterKeyArn string + KMSMasterKeyARN string SSEType types.SSEType Status types.SSEStatus } From 9b9e3cb1eaec443ba52fbc6e0ae9cc48bc9589b2 Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 27 Jan 2024 19:33:04 +0900 Subject: [PATCH 21/31] remove non-context methods --- batch_test.go | 28 ++++++++++++--------- batchget.go | 20 +++------------ batchwrite.go | 8 +----- createtable.go | 22 +++------------- db.go | 32 ++++++------------------ db_test.go | 2 +- decode_aux_test.go | 2 +- delete.go | 16 ++---------- delete_test.go | 8 +++--- describetable.go | 8 +----- describetable_test.go | 3 ++- go.mod | 2 +- put.go | 19 ++------------ put_test.go | 15 ++++++----- query.go | 46 ++++++---------------------------- query_test.go | 38 +++++++++++++++------------- retry.go | 19 ++------------ scan.go | 58 ++++++++----------------------------------- scan_test.go | 23 +++++++++-------- substitute.go | 2 +- table.go | 36 ++++++--------------------- table_test.go | 11 +++++--- ttl.go | 18 ++------------ ttl_test.go | 4 ++- tx.go | 27 +++----------------- tx_test.go | 38 +++++++++++++++------------- update.go | 49 ++++-------------------------------- update_test.go | 22 +++++++++------- updatetable.go | 8 +----- updatetable_test.go | 4 ++- 30 files changed, 177 insertions(+), 411 deletions(-) diff --git a/batch_test.go b/batch_test.go index 398e455..c49cf4a 100644 --- a/batch_test.go +++ b/batch_test.go @@ -1,6 +1,7 @@ package dynamo import ( + "context" "testing" "time" ) @@ -12,6 +13,7 @@ func TestBatchGetWrite(t *testing.T) { t.Skip(offlineSkipMsg) } table := testDB.Table(testTable) + ctx := context.TODO() items := make([]interface{}, batchSize) widgets := make(map[int]widget) @@ -29,7 +31,7 @@ func TestBatchGetWrite(t *testing.T) { } var wcc ConsumedCapacity - wrote, err := table.Batch().Write().Put(items...).ConsumedCapacity(&wcc).Run() + wrote, err := table.Batch().Write().Put(items...).ConsumedCapacity(&wcc).Run(ctx) if wrote != batchSize { t.Error("unexpected wrote:", wrote, "≠", batchSize) } @@ -48,7 +50,7 @@ func TestBatchGetWrite(t *testing.T) { Project("UserID", "Time"). Consistent(true). ConsumedCapacity(&cc). - All(&results) + All(ctx, &results) if err != nil { t.Error("unexpected error:", err) } @@ -73,7 +75,7 @@ func TestBatchGetWrite(t *testing.T) { // delete both wrote, err = table.Batch("UserID", "Time").Write(). - Delete(keys...).Run() + Delete(keys...).Run(ctx) if wrote != batchSize { t.Error("unexpected wrote:", wrote, "≠", batchSize) } @@ -86,7 +88,7 @@ func TestBatchGetWrite(t *testing.T) { err = table.Batch("UserID", "Time"). Get(keys...). Consistent(true). - All(&results) + All(ctx, &results) if err != ErrNotFound { t.Error("expected ErrNotFound, got", err) } @@ -100,15 +102,16 @@ func TestBatchGetEmptySets(t *testing.T) { t.Skip(offlineSkipMsg) } table := testDB.Table(testTable) + ctx := context.TODO() now := time.Now().UnixNano() / 1000000000 id := int(now) entry := widget{UserID: id, Time: time.Now()} - if err := table.Put(entry).Run(); err != nil { + if err := table.Put(entry).Run(ctx); err != nil { panic(err) } entry2 := widget{UserID: id + batchSize*2, Time: entry.Time} - if err := table.Put(entry2).Run(); err != nil { + if err := table.Put(entry2).Run(ctx); err != nil { panic(err) } @@ -118,7 +121,7 @@ func TestBatchGetEmptySets(t *testing.T) { } results := []widget{} - err := table.Batch("UserID", "Time").Get(keysToCheck...).Consistent(true).All(&results) + err := table.Batch("UserID", "Time").Get(keysToCheck...).Consistent(true).All(ctx, &results) if err != nil { t.Error(err) } @@ -126,12 +129,12 @@ func TestBatchGetEmptySets(t *testing.T) { t.Error("batch get empty set, unexpected length:", len(results), "want:", 2) } - if err := table.Delete("UserID", entry.UserID).Range("Time", entry.Time).Run(); err != nil { + if err := table.Delete("UserID", entry.UserID).Range("Time", entry.Time).Run(ctx); err != nil { panic(err) } results = []widget{} - err = table.Batch("UserID", "Time").Get(keysToCheck...).Consistent(true).All(&results) + err = table.Batch("UserID", "Time").Get(keysToCheck...).Consistent(true).All(ctx, &results) if err != nil { t.Error(err) } @@ -140,7 +143,7 @@ func TestBatchGetEmptySets(t *testing.T) { } results = []widget{} - err = table.Batch("UserID", "Time").Get(keysToCheck[:len(keysToCheck)-1]...).Consistent(true).All(&results) + err = table.Batch("UserID", "Time").Get(keysToCheck[:len(keysToCheck)-1]...).Consistent(true).All(ctx, &results) if err != ErrNotFound { t.Error(err) } @@ -150,14 +153,15 @@ func TestBatchGetEmptySets(t *testing.T) { } func TestBatchEmptyInput(t *testing.T) { + ctx := context.TODO() table := testDB.Table(testTable) var out []any - err := table.Batch("UserID", "Time").Get().All(&out) + err := table.Batch("UserID", "Time").Get().All(ctx, &out) if err != ErrNoInput { t.Error("unexpected error", err) } - _, err = table.Batch("UserID", "Time").Write().Run() + _, err = table.Batch("UserID", "Time").Write().Run(ctx) if err != ErrNoInput { t.Error("unexpected error", err) } diff --git a/batchget.go b/batchget.go index 8cd47a2..e5b8b03 100644 --- a/batchget.go +++ b/batchget.go @@ -118,17 +118,9 @@ func (bg *BatchGet) ConsumedCapacity(cc *ConsumedCapacity) *BatchGet { } // All executes this request and unmarshals all results to out, which must be a pointer to a slice. -func (bg *BatchGet) All(out interface{}) error { +func (bg *BatchGet) All(ctx context.Context, out interface{}) error { iter := newBGIter(bg, unmarshalAppendTo(out), bg.err) - for iter.Next(out) { - } - return iter.Err() -} - -// AllWithContext executes this request and unmarshals all results to out, which must be a pointer to a slice. -func (bg *BatchGet) AllWithContext(ctx context.Context, out interface{}) error { - iter := newBGIter(bg, unmarshalAppendTo(out), bg.err) - for iter.NextWithContext(ctx, out) { + for iter.Next(ctx, out) { } return iter.Err() } @@ -216,13 +208,7 @@ func newBGIter(bg *BatchGet, fn unmarshalFunc, err error) *bgIter { // Next tries to unmarshal the next result into out. // Returns false when it is complete or if it runs into an error. -func (itr *bgIter) Next(out interface{}) bool { - ctx, cancel := defaultContext() - defer cancel() - return itr.NextWithContext(ctx, out) -} - -func (itr *bgIter) NextWithContext(ctx context.Context, out interface{}) bool { +func (itr *bgIter) Next(ctx context.Context, out interface{}) bool { // stop if we have an error if ctx.Err() != nil { itr.err = ctx.Err() diff --git a/batchwrite.go b/batchwrite.go index 642b88b..93e84ba 100644 --- a/batchwrite.go +++ b/batchwrite.go @@ -67,13 +67,7 @@ func (bw *BatchWrite) ConsumedCapacity(cc *ConsumedCapacity) *BatchWrite { // For batches with more than 25 operations, an error could indicate that // some records have been written and some have not. Consult the wrote // return amount to figure out which operations have succeeded. -func (bw *BatchWrite) Run() (wrote int, err error) { - ctx, cancel := defaultContext() - defer cancel() - return bw.RunWithContext(ctx) -} - -func (bw *BatchWrite) RunWithContext(ctx context.Context) (wrote int, err error) { +func (bw *BatchWrite) Run(ctx context.Context) (wrote int, err error) { if bw.err != nil { return 0, bw.err } diff --git a/createtable.go b/createtable.go index caec3d3..168798d 100644 --- a/createtable.go +++ b/createtable.go @@ -235,14 +235,7 @@ func (ct *CreateTable) SSEEncryption(enabled bool, keyID string, sseType SSEType } // Run creates this table or returns an error. -func (ct *CreateTable) Run() error { - ctx, cancel := defaultContext() - defer cancel() - return ct.RunWithContext(ctx) -} - -// RunWithContext creates this table or returns an error. -func (ct *CreateTable) RunWithContext(ctx context.Context) error { +func (ct *CreateTable) Run(ctx context.Context) error { if ct.err != nil { return ct.err } @@ -255,18 +248,11 @@ func (ct *CreateTable) RunWithContext(ctx context.Context) error { } // Wait creates this table and blocks until it exists and is ready to use. -func (ct *CreateTable) Wait() error { - ctx, cancel := defaultContext() - defer cancel() - return ct.WaitWithContext(ctx) -} - -// WaitWithContext creates this table and blocks until it exists and is ready to use. -func (ct *CreateTable) WaitWithContext(ctx context.Context) error { - if err := ct.RunWithContext(ctx); err != nil { +func (ct *CreateTable) Wait(ctx context.Context) error { + if err := ct.Run(ctx); err != nil { return err } - return ct.db.Table(ct.tableName).WaitWithContext(ctx) + return ct.db.Table(ct.tableName).Wait(ctx) } func (ct *CreateTable) from(rv reflect.Value) error { diff --git a/db.go b/db.go index 51d80c7..22dc5a6 100644 --- a/db.go +++ b/db.go @@ -12,7 +12,7 @@ import ( "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" + "github.com/guregu/dynamo/v2/dynamodbiface" ) // DB is a DynamoDB client. @@ -99,9 +99,9 @@ func (db *DB) Client() dynamodbiface.DynamoDBAPI { // return db // } -func (db *DB) log(format string, v ...interface{}) { - db.logger.Logf(logging.Debug, format, v...) -} +// func (db *DB) log(format string, v ...interface{}) { +// db.logger.Logf(logging.Debug, format, v...) +// } // ListTables is a request to list tables. // See: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ListTables.html @@ -115,18 +115,11 @@ func (db *DB) ListTables() *ListTables { } // All returns every table or an error. -func (lt *ListTables) All() ([]string, error) { - ctx, cancel := defaultContext() - defer cancel() - return lt.AllWithContext(ctx) -} - -// AllWithContext returns every table or an error. -func (lt *ListTables) AllWithContext(ctx context.Context) ([]string, error) { +func (lt *ListTables) All(ctx context.Context) ([]string, error) { var tables []string itr := lt.Iter() var name string - for itr.NextWithContext(ctx, &name) { + for itr.Next(ctx, &name) { tables = append(tables, name) } return tables, itr.Err() @@ -145,13 +138,7 @@ func (lt *ListTables) Iter() Iter { return <Iter{lt: lt} } -func (itr *ltIter) Next(out interface{}) bool { - ctx, cancel := defaultContext() - defer cancel() - return itr.NextWithContext(ctx, out) -} - -func (itr *ltIter) NextWithContext(ctx context.Context, out interface{}) bool { +func (itr *ltIter) Next(ctx context.Context, out interface{}) bool { if ctx.Err() != nil { itr.err = ctx.Err() } @@ -214,10 +201,7 @@ func (itr *ltIter) input() *dynamodb.ListTablesInput { type Iter interface { // Next tries to unmarshal the next result into out. // Returns false when it is complete or if it runs into an error. - 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 context.Context, out interface{}) bool + Next(ctx context.Context, out interface{}) bool // Err returns the error encountered, if any. // You should check this after Next is finished. Err() error diff --git a/db_test.go b/db_test.go index 0b757b8..a83f1f1 100644 --- a/db_test.go +++ b/db_test.go @@ -63,7 +63,7 @@ func TestListTables(t *testing.T) { t.Skip(offlineSkipMsg) } - tables, err := testDB.ListTables().All() + tables, err := testDB.ListTables().All(context.TODO()) if err != nil { t.Error(err) return diff --git a/decode_aux_test.go b/decode_aux_test.go index 5e6105d..5e7c570 100644 --- a/decode_aux_test.go +++ b/decode_aux_test.go @@ -7,7 +7,7 @@ import ( "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/guregu/dynamo" + "github.com/guregu/dynamo/v2" ) type Coffee struct { diff --git a/delete.go b/delete.go index 90cc996..85ca119 100644 --- a/delete.go +++ b/delete.go @@ -78,13 +78,7 @@ func (d *Delete) ConsumedCapacity(cc *ConsumedCapacity) *Delete { } // Run executes this delete request. -func (d *Delete) Run() error { - ctx, cancel := defaultContext() - defer cancel() - return d.RunWithContext(ctx) -} - -func (d *Delete) RunWithContext(ctx context.Context) error { +func (d *Delete) Run(ctx context.Context) error { d.returnType = "NONE" _, err := d.run(ctx) return err @@ -92,13 +86,7 @@ func (d *Delete) RunWithContext(ctx context.Context) error { // OldValue executes this delete request, unmarshaling the previous value to out. // Returns ErrNotFound is there was no previous value. -func (d *Delete) OldValue(out interface{}) error { - ctx, cancel := defaultContext() - defer cancel() - return d.OldValueWithContext(ctx, out) -} - -func (d *Delete) OldValueWithContext(ctx context.Context, out interface{}) error { +func (d *Delete) OldValue(ctx context.Context, out interface{}) error { d.returnType = "ALL_OLD" output, err := d.run(ctx) switch { diff --git a/delete_test.go b/delete_test.go index 751565d..6858678 100644 --- a/delete_test.go +++ b/delete_test.go @@ -1,6 +1,7 @@ package dynamo import ( + "context" "reflect" "testing" "time" @@ -11,6 +12,7 @@ func TestDelete(t *testing.T) { t.Skip(offlineSkipMsg) } table := testDB.Table(testTable) + ctx := context.TODO() // first, add an item to delete later item := widget{ @@ -21,7 +23,7 @@ func TestDelete(t *testing.T) { "color": "octarine", }, } - err := table.Put(item).Run() + err := table.Put(item).Run(ctx) if err != nil { t.Error("unexpected error:", err) } @@ -31,7 +33,7 @@ func TestDelete(t *testing.T) { Range("Time", item.Time). If("Meta.'color' = ?", "octarine"). If("Msg = ?", "wrong msg"). - Run() + Run(ctx) if !IsCondCheckFailed(err) { t.Error("expected ConditionalCheckFailedException, not", err) } @@ -39,7 +41,7 @@ func TestDelete(t *testing.T) { // delete it var old widget var cc ConsumedCapacity - err = table.Delete("UserID", item.UserID).Range("Time", item.Time).ConsumedCapacity(&cc).OldValue(&old) + err = table.Delete("UserID", item.UserID).Range("Time", item.Time).ConsumedCapacity(&cc).OldValue(ctx, &old) if err != nil { t.Error("unexpected error:", err) } diff --git a/describetable.go b/describetable.go index e37cf4c..4c295ca 100644 --- a/describetable.go +++ b/describetable.go @@ -254,13 +254,7 @@ func (table Table) Describe() *DescribeTable { } // Run executes this request and describe the table. -func (dt *DescribeTable) Run() (Description, error) { - ctx, cancel := defaultContext() - defer cancel() - return dt.RunWithContext(ctx) -} - -func (dt *DescribeTable) RunWithContext(ctx context.Context) (Description, error) { +func (dt *DescribeTable) Run(ctx context.Context) (Description, error) { input := dt.input() var result *dynamodb.DescribeTableOutput diff --git a/describetable_test.go b/describetable_test.go index 34bc50e..894b951 100644 --- a/describetable_test.go +++ b/describetable_test.go @@ -1,6 +1,7 @@ package dynamo import ( + "context" "testing" ) @@ -10,7 +11,7 @@ func TestDescribeTable(t *testing.T) { } table := testDB.Table(testTable) - desc, err := table.Describe().Run() + desc, err := table.Describe().Run(context.TODO()) if err != nil { t.Error(err) return diff --git a/go.mod b/go.mod index 9f44979..35af853 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/guregu/dynamo +module github.com/guregu/dynamo/v2 require ( github.com/aws/aws-sdk-go-v2 v1.24.1 diff --git a/put.go b/put.go index f3ca6f3..4d43b71 100644 --- a/put.go +++ b/put.go @@ -53,14 +53,7 @@ func (p *Put) ConsumedCapacity(cc *ConsumedCapacity) *Put { } // Run executes this put. -func (p *Put) Run() error { - ctx, cancel := defaultContext() - defer cancel() - return p.RunWithContext(ctx) -} - -// Run executes this put. -func (p *Put) RunWithContext(ctx context.Context) error { +func (p *Put) Run(ctx context.Context) error { p.returnType = "NONE" _, err := p.run(ctx) return err @@ -68,15 +61,7 @@ func (p *Put) RunWithContext(ctx context.Context) error { // OldValue executes this put, unmarshaling the previous value into out. // Returns ErrNotFound is there was no previous value. -func (p *Put) OldValue(out interface{}) error { - ctx, cancel := defaultContext() - defer cancel() - return p.OldValueWithContext(ctx, out) -} - -// OldValueWithContext executes this put, unmarshaling the previous value into out. -// Returns ErrNotFound is there was no previous value. -func (p *Put) OldValueWithContext(ctx context.Context, out interface{}) error { +func (p *Put) OldValue(ctx context.Context, out interface{}) error { p.returnType = "ALL_OLD" output, err := p.run(ctx) switch { diff --git a/put_test.go b/put_test.go index 50acd52..3c37edc 100644 --- a/put_test.go +++ b/put_test.go @@ -1,6 +1,7 @@ package dynamo import ( + "context" "reflect" "testing" "time" @@ -13,6 +14,7 @@ func TestPut(t *testing.T) { t.Skip(offlineSkipMsg) } table := testDB.Table(testTable) + ctx := context.TODO() type widget2 struct { widget @@ -34,7 +36,7 @@ func TestPut(t *testing.T) { List: []*string{}, } - err := table.Put(item).Run() + err := table.Put(item).Run(ctx) if err != nil { t.Error("unexpected error:", err) } @@ -53,7 +55,7 @@ func TestPut(t *testing.T) { } var oldValue widget2 var cc ConsumedCapacity - err = table.Put(newItem).ConsumedCapacity(&cc).OldValue(&oldValue) + err = table.Put(newItem).ConsumedCapacity(&cc).OldValue(ctx, &oldValue) if err != nil { t.Error("unexpected error:", err) } @@ -67,7 +69,7 @@ func TestPut(t *testing.T) { } // putting the same item: this should fail - err = table.Put(newItem).If("attribute_not_exists(UserID)").If("attribute_not_exists('Time')").Run() + err = table.Put(newItem).If("attribute_not_exists(UserID)").If("attribute_not_exists('Time')").Run(ctx) if !IsCondCheckFailed(err) { t.Error("expected ConditionalCheckFailedException, not", err) } @@ -78,6 +80,7 @@ func TestPutAndQueryAWSEncoding(t *testing.T) { t.Skip(offlineSkipMsg) } table := testDB.Table(testTable) + ctx := context.TODO() type awsWidget struct { XUserID int `dynamodbav:"UserID"` @@ -98,13 +101,13 @@ func TestPutAndQueryAWSEncoding(t *testing.T) { XMsg: "hello world", } - err = table.Put(AWSEncoding(item)).Run() + err = table.Put(AWSEncoding(item)).Run(ctx) if err != nil { t.Error(err) } var result awsWidget - err = table.Get("UserID", item.XUserID).Range("Time", Equal, item.XTime).Consistent(true).One(AWSEncoding(&result)) + err = table.Get("UserID", item.XUserID).Range("Time", Equal, item.XTime).Consistent(true).One(ctx, AWSEncoding(&result)) if err != nil { t.Error(err) } @@ -113,7 +116,7 @@ func TestPutAndQueryAWSEncoding(t *testing.T) { } var list []awsWidget - err = table.Get("UserID", item.XUserID).Consistent(true).All(AWSEncoding(&list)) + err = table.Get("UserID", item.XUserID).Consistent(true).All(ctx, AWSEncoding(&list)) if err != nil { t.Error(err) } diff --git a/query.go b/query.go index 14e2411..b2eaccc 100644 --- a/query.go +++ b/query.go @@ -197,13 +197,7 @@ func (q *Query) ConsumedCapacity(cc *ConsumedCapacity) *Query { // One executes this query and retrieves a single result, // unmarshaling the result to out. -func (q *Query) One(out interface{}) error { - ctx, cancel := defaultContext() - defer cancel() - return q.OneWithContext(ctx, out) -} - -func (q *Query) OneWithContext(ctx context.Context, out interface{}) error { +func (q *Query) One(ctx context.Context, out interface{}) error { if q.err != nil { return q.err } @@ -267,13 +261,7 @@ func (q *Query) OneWithContext(ctx context.Context, out interface{}) error { } // Count executes this request, returning the number of results. -func (q *Query) Count() (int, error) { - ctx, cancel := defaultContext() - defer cancel() - return q.CountWithContext(ctx) -} - -func (q *Query) CountWithContext(ctx context.Context) (int, error) { +func (q *Query) Count(ctx context.Context) (int, error) { if q.err != nil { return 0, q.err } @@ -335,13 +323,7 @@ type queryIter struct { // Next tries to unmarshal the next result into out. // Returns false when it is complete or if it runs into an error. -func (itr *queryIter) Next(out interface{}) bool { - ctx, cancel := defaultContext() - defer cancel() - return itr.NextWithContext(ctx, out) -} - -func (itr *queryIter) NextWithContext(ctx context.Context, out interface{}) bool { +func (itr *queryIter) Next(ctx context.Context, out interface{}) bool { // stop if we have an error if ctx.Err() != nil { itr.err = ctx.Err() @@ -404,7 +386,7 @@ func (itr *queryIter) NextWithContext(ctx context.Context, out interface{}) bool if len(itr.output.Items) == 0 { if itr.output.LastEvaluatedKey != nil { // we need to retry until we get some data - return itr.NextWithContext(ctx, out) + return itr.Next(ctx, out) } // we're done return false @@ -453,38 +435,26 @@ func (itr *queryIter) LastEvaluatedKey(ctx context.Context) (PagingKey, error) { } // All executes this request and unmarshals all results to out, which must be a pointer to a slice. -func (q *Query) All(out interface{}) error { - ctx, cancel := defaultContext() - defer cancel() - return q.AllWithContext(ctx, out) -} - -func (q *Query) AllWithContext(ctx context.Context, out interface{}) error { +func (q *Query) All(ctx context.Context, out interface{}) error { iter := &queryIter{ query: q, unmarshal: unmarshalAppendTo(out), err: q.err, } - for iter.NextWithContext(ctx, out) { + for iter.Next(ctx, out) { } return iter.Err() } // AllWithLastEvaluatedKey executes this request and unmarshals all results to out, which must be a pointer to a slice. // This returns a PagingKey you can use with StartFrom to split up results. -func (q *Query) AllWithLastEvaluatedKey(out interface{}) (PagingKey, error) { - ctx, cancel := defaultContext() - defer cancel() - return q.AllWithLastEvaluatedKeyContext(ctx, out) -} - -func (q *Query) AllWithLastEvaluatedKeyContext(ctx context.Context, out interface{}) (PagingKey, error) { +func (q *Query) AllWithLastEvaluatedKey(ctx context.Context, out interface{}) (PagingKey, error) { iter := &queryIter{ query: q, unmarshal: unmarshalAppendTo(out), err: q.err, } - for iter.NextWithContext(ctx, out) { + for iter.Next(ctx, out) { } lek, err := iter.LastEvaluatedKey(ctx) return lek, errors.Join(iter.Err(), err) diff --git a/query_test.go b/query_test.go index 2ffed0e..a3d714f 100644 --- a/query_test.go +++ b/query_test.go @@ -14,6 +14,7 @@ func TestGetAllCount(t *testing.T) { if testDB == nil { t.Skip(offlineSkipMsg) } + ctx := context.TODO() table := testDB.Table(testTable) // first, add an item to make sure there is at least one @@ -27,7 +28,7 @@ func TestGetAllCount(t *testing.T) { }, StrPtr: new(string), } - err := table.Put(item).Run() + err := table.Put(item).Run(ctx) if err != nil { t.Error("unexpected error:", err) } @@ -52,7 +53,7 @@ func TestGetAllCount(t *testing.T) { Filter("StrPtr = ?", ""). Filter("?", lit). ConsumedCapacity(&cc1). - All(&result) + All(ctx, &result) if err != nil { t.Error("unexpected error:", err) } @@ -63,7 +64,7 @@ func TestGetAllCount(t *testing.T) { Filter("StrPtr = ?", ""). Filter("$", lit). // both $ and ? are OK for literals ConsumedCapacity(&cc2). - Count() + Count(ctx) if err != nil { t.Error("unexpected error:", err) } @@ -92,7 +93,7 @@ func TestGetAllCount(t *testing.T) { // query specifically against the inserted item (using GetItem) var one widget - err = table.Get("UserID", 42).Range("Time", Equal, item.Time).Consistent(true).One(&one) + err = table.Get("UserID", 42).Range("Time", Equal, item.Time).Consistent(true).One(ctx, &one) if err != nil { t.Error("unexpected error:", err) } @@ -102,7 +103,7 @@ func TestGetAllCount(t *testing.T) { // query specifically against the inserted item (using Query) one = widget{} - err = table.Get("UserID", 42).Range("Time", Equal, item.Time).Filter("Msg = ?", item.Msg).Filter("StrPtr = ?", "").Consistent(true).One(&one) + err = table.Get("UserID", 42).Range("Time", Equal, item.Time).Filter("Msg = ?", item.Msg).Filter("StrPtr = ?", "").Consistent(true).One(ctx, &one) if err != nil { t.Error("unexpected error:", err) } @@ -116,7 +117,7 @@ func TestGetAllCount(t *testing.T) { UserID: item.UserID, Time: item.Time, } - err = table.Get("UserID", 42).Range("Time", Equal, item.Time).Project("UserID", "Time").Consistent(true).One(&one) + err = table.Get("UserID", 42).Range("Time", Equal, item.Time).Project("UserID", "Time").Consistent(true).One(ctx, &one) if err != nil { t.Error("unexpected error:", err) } @@ -134,7 +135,7 @@ func TestGetAllCount(t *testing.T) { "animal.cow": "moo", }, } - err = table.Get("UserID", 42).Range("Time", Equal, item.Time).ProjectExpr("UserID, $, Meta.foo, Meta.$", "Time", "animal.cow").Consistent(true).One(&one) + err = table.Get("UserID", 42).Range("Time", Equal, item.Time).ProjectExpr("UserID, $, Meta.foo, Meta.$", "Time", "animal.cow").Consistent(true).One(ctx, &one) if err != nil { t.Error("unexpected error:", err) } @@ -147,6 +148,7 @@ func TestQueryPaging(t *testing.T) { if testDB == nil { t.Skip(offlineSkipMsg) } + ctx := context.TODO() table := testDB.Table(testTable) widgets := []interface{}{ @@ -167,7 +169,7 @@ func TestQueryPaging(t *testing.T) { }, } - if _, err := table.Batch().Write().Put(widgets...).Run(); err != nil { + if _, err := table.Batch().Write().Put(widgets...).Run(ctx); err != nil { t.Error("couldn't write paging prep data", err) return } @@ -175,14 +177,14 @@ func TestQueryPaging(t *testing.T) { itr := table.Get("UserID", 1969).SearchLimit(1).Iter() for i := 0; i < len(widgets); i++ { var w widget - itr.Next(&w) + itr.Next(ctx, &w) if !reflect.DeepEqual(w, widgets[i]) { t.Error("bad result:", w, "≠", widgets[i]) } if itr.Err() != nil { t.Error("unexpected error", itr.Err()) } - more := itr.Next(&w) + more := itr.Next(ctx, &w) if more { t.Error("unexpected more", more) } @@ -198,6 +200,7 @@ func TestQueryMagicLEK(t *testing.T) { if testDB == nil { t.Skip(offlineSkipMsg) } + ctx := context.Background() table := testDB.Table(testTable) widgets := []interface{}{ @@ -219,7 +222,7 @@ func TestQueryMagicLEK(t *testing.T) { } t.Run("prepare data", func(t *testing.T) { - if _, err := table.Batch().Write().Put(widgets...).Run(); err != nil { + if _, err := table.Batch().Write().Put(widgets...).Run(ctx); err != nil { t.Fatal(err) } }) @@ -228,14 +231,14 @@ func TestQueryMagicLEK(t *testing.T) { itr := table.Get("UserID", 1970).Filter("attribute_exists('Msg')").Limit(1).Iter() for i := 0; i < len(widgets); i++ { var w widget - itr.Next(&w) + itr.Next(ctx, &w) if !reflect.DeepEqual(w, widgets[i]) { t.Error("bad result:", w, "≠", widgets[i]) } if itr.Err() != nil { t.Error("unexpected error", itr.Err()) } - more := itr.Next(&w) + more := itr.Next(ctx, &w) if more { t.Error("unexpected more", more) } @@ -265,14 +268,14 @@ func TestQueryMagicLEK(t *testing.T) { itr := table.Get("Msg", "TestQueryMagicLEK").Index("Msg-Time-index").Filter("UserID = ?", 1970).Limit(1).Iter() for i := 0; i < len(widgets); i++ { var w widget - itr.Next(&w) + itr.Next(ctx, &w) if !reflect.DeepEqual(w, widgets[i]) { t.Error("bad result:", w, "≠", widgets[i]) } if itr.Err() != nil { t.Error("unexpected error", itr.Err()) } - more := itr.Next(&w) + more := itr.Next(ctx, &w) if more { t.Error("unexpected more", more) } @@ -290,10 +293,11 @@ func TestQueryBadKeys(t *testing.T) { t.Skip(offlineSkipMsg) } table := testDB.Table(testTable) + ctx := context.Background() t.Run("hash key", func(t *testing.T) { var v interface{} - err := table.Get("UserID", "").Range("Time", Equal, "123").One(&v) + err := table.Get("UserID", "").Range("Time", Equal, "123").One(ctx, &v) if err == nil { t.Error("want error, got", err) } @@ -301,7 +305,7 @@ func TestQueryBadKeys(t *testing.T) { t.Run("range key", func(t *testing.T) { var v interface{} - err := table.Get("UserID", 123).Range("Time", Equal, "").One(&v) + err := table.Get("UserID", 123).Range("Time", Equal, "").One(ctx, &v) if err == nil { t.Error("want error, got", err) } diff --git a/retry.go b/retry.go index a8d7d4f..43b8d6e 100644 --- a/retry.go +++ b/retry.go @@ -8,13 +8,10 @@ import ( "github.com/aws/aws-sdk-go-v2/aws/transport/http" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/aws/smithy-go" + awstime "github.com/aws/smithy-go/time" "github.com/cenkalti/backoff/v4" ) -func defaultContext() (context.Context, context.CancelFunc) { - return context.Background(), func() {} -} - func (db *DB) retry(ctx context.Context, f func() error) error { // if a custom retryer has been set, the SDK will retry for us if db.retryer != nil { @@ -36,7 +33,7 @@ func (db *DB) retry(ctx context.Context, f func() error) error { if next = b.NextBackOff(); next == backoff.Stop { return err } - if err := sleep(ctx, next); err != nil { + if err := awstime.SleepWithContext(ctx, next); err != nil { return err } } @@ -87,15 +84,3 @@ func canRetry(err error) bool { return false } - -func sleep(ctx context.Context, dur time.Duration) error { - timer := time.NewTimer(dur) - defer timer.Stop() - - select { - case <-ctx.Done(): - case <-timer.C: - } - - return ctx.Err() -} diff --git a/scan.go b/scan.go index 3dafa3b..ed4d940 100644 --- a/scan.go +++ b/scan.go @@ -164,41 +164,26 @@ func (s *Scan) IterParallelStartFrom(ctx context.Context, keys []PagingKey) Para } // All executes this request and unmarshals all results to out, which must be a pointer to a slice. -func (s *Scan) All(out interface{}) error { - ctx, cancel := defaultContext() - defer cancel() - return s.AllWithContext(ctx, out) -} - -// AllWithContext executes this request and unmarshals all results to out, which must be a pointer to a slice. -func (s *Scan) AllWithContext(ctx context.Context, out interface{}) error { +func (s *Scan) All(ctx context.Context, out interface{}) error { itr := &scanIter{ scan: s, unmarshal: unmarshalAppendTo(out), err: s.err, } - for itr.NextWithContext(ctx, out) { + for itr.Next(ctx, out) { } return itr.Err() } // AllWithLastEvaluatedKey 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) AllWithLastEvaluatedKey(out interface{}) (PagingKey, error) { - ctx, cancel := defaultContext() - defer cancel() - return s.AllWithLastEvaluatedKeyContext(ctx, out) -} - -// 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 context.Context, out interface{}) (PagingKey, error) { +func (s *Scan) AllWithLastEvaluatedKey(ctx context.Context, out interface{}) (PagingKey, error) { itr := &scanIter{ scan: s, unmarshal: unmarshalAppendTo(out), err: s.err, } - for itr.NextWithContext(ctx, out) { + for itr.Next(ctx, out) { } lek, err := itr.LastEvaluatedKey(ctx) return lek, errors.Join(itr.Err(), err) @@ -209,7 +194,7 @@ func (s *Scan) AllParallel(ctx context.Context, segments int, out interface{}) e iters := s.newSegments(segments, nil) ps := newParallelScan(iters, s.cc, true, unmarshalAppendTo(out)) go ps.run(ctx) - for ps.NextWithContext(ctx, out) { + for ps.Next(ctx, out) { } return ps.Err() } @@ -220,7 +205,7 @@ func (s *Scan) AllParallelWithLastEvaluatedKeys(ctx context.Context, segments in iters := s.newSegments(segments, nil) ps := newParallelScan(iters, s.cc, false, unmarshalAppendTo(out)) go ps.run(ctx) - for ps.NextWithContext(ctx, out) { + for ps.Next(ctx, out) { } leks, err := ps.LastEvaluatedKeys(ctx) return leks, errors.Join(ps.Err(), err) @@ -232,7 +217,7 @@ func (s *Scan) AllParallelStartFrom(ctx context.Context, keys []PagingKey, out i iters := s.newSegments(len(keys), keys) ps := newParallelScan(iters, s.cc, false, unmarshalAppendTo(out)) go ps.run(ctx) - for ps.NextWithContext(ctx, out) { + for ps.Next(ctx, out) { } leks, err := ps.LastEvaluatedKeys(ctx) return leks, errors.Join(ps.Err(), err) @@ -241,16 +226,7 @@ func (s *Scan) AllParallelStartFrom(ctx context.Context, keys []PagingKey, out i // 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() (int, error) { - ctx, cancel := defaultContext() - defer cancel() - return s.CountWithContext(ctx) -} - -// 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 context.Context) (int, error) { +func (s *Scan) Count(ctx context.Context) (int, error) { if s.err != nil { return 0, s.err } @@ -357,13 +333,7 @@ type scanIter struct { // Next tries to unmarshal the next result into out. // Returns false when it is complete or if it runs into an error. -func (itr *scanIter) Next(out interface{}) bool { - ctx, cancel := defaultContext() - defer cancel() - return itr.NextWithContext(ctx, out) -} - -func (itr *scanIter) NextWithContext(ctx context.Context, out interface{}) bool { +func (itr *scanIter) Next(ctx context.Context, out interface{}) bool { redo: // stop if we have an error if ctx.Err() != nil { @@ -512,7 +482,7 @@ func (ps *parallelScan) run(ctx context.Context) { } grp.Go(func() error { var item Item - for iter.NextWithContext(ctx, &item) { + for iter.Next(ctx, &item) { select { case <-ctx.Done(): return ctx.Err() @@ -548,13 +518,7 @@ func (ps *parallelScan) run(ctx context.Context) { close(ps.items) } -func (ps *parallelScan) Next(out interface{}) bool { - ctx, cancel := defaultContext() - defer cancel() - return ps.NextWithContext(ctx, out) -} - -func (ps *parallelScan) NextWithContext(ctx context.Context, out interface{}) bool { +func (ps *parallelScan) Next(ctx context.Context, out interface{}) bool { select { case <-ctx.Done(): ps.setError(ctx.Err()) diff --git a/scan_test.go b/scan_test.go index 72ecc48..b4d0ef3 100644 --- a/scan_test.go +++ b/scan_test.go @@ -12,6 +12,7 @@ func TestScan(t *testing.T) { t.Skip(offlineSkipMsg) } table := testDB.Table(testTable) + ctx := context.TODO() // first, add an item to make sure there is at least one item := widget{ @@ -19,13 +20,13 @@ func TestScan(t *testing.T) { Time: time.Now().UTC(), Msg: "hello", } - err := table.Put(item).Run() + err := table.Put(item).Run(ctx) if err != nil { t.Error("unexpected error:", err) } // count items via Query - ct, err := table.Get("UserID", 42).Consistent(true).Count() + ct, err := table.Get("UserID", 42).Consistent(true).Count(ctx) if err != nil { t.Error("unexpected error:", err) } @@ -34,7 +35,7 @@ func TestScan(t *testing.T) { t.Run("All", func(t *testing.T) { var result []widget var cc ConsumedCapacity - err = table.Scan().Filter("UserID = ?", 42).Consistent(true).ConsumedCapacity(&cc).All(&result) + err = table.Scan().Filter("UserID = ?", 42).Consistent(true).ConsumedCapacity(&cc).All(ctx, &result) if err != nil { t.Error("unexpected error:", err) } @@ -61,7 +62,7 @@ func TestScan(t *testing.T) { // check this against Scan's count, too t.Run("Count", func(t *testing.T) { var cc2 ConsumedCapacity - scanCt, err := table.Scan().Filter("UserID = ?", 42).Consistent(true).ConsumedCapacity(&cc2).Count() + scanCt, err := table.Scan().Filter("UserID = ?", 42).Consistent(true).ConsumedCapacity(&cc2).Count(ctx) if err != nil { t.Error("unexpected error:", err) } @@ -106,6 +107,7 @@ func TestScanPaging(t *testing.T) { t.Skip(offlineSkipMsg) } table := testDB.Table(testTable) + ctx := context.TODO() // prepare data insert := make([]interface{}, 10) @@ -116,7 +118,7 @@ func TestScanPaging(t *testing.T) { Msg: "garbage", } } - if _, err := table.Batch().Write().Put(insert...).Run(); err != nil { + if _, err := table.Batch().Write().Put(insert...).Run(ctx); err != nil { t.Fatal(err) } @@ -124,7 +126,7 @@ func TestScanPaging(t *testing.T) { widgets := [10]widget{} itr := table.Scan().Consistent(true).SearchLimit(1).Iter() for i := 0; i < len(widgets); i++ { - more := itr.Next(&widgets[i]) + more := itr.Next(ctx, &widgets[i]) if itr.Err() != nil { t.Error("unexpected error", itr.Err()) } @@ -152,7 +154,7 @@ func TestScanPaging(t *testing.T) { for i := 0; i < len(widgets)/segments; i++ { var more bool for j := 0; j < segments; j++ { - more = itr.Next(&widgets[i*segments+j]) + more = itr.Next(ctx, &widgets[i*segments+j]) if !more && j != segments-1 { t.Error("bad number of results from parallel scan") } @@ -182,6 +184,7 @@ func TestScanMagicLEK(t *testing.T) { t.Skip(offlineSkipMsg) } table := testDB.Table(testTable) + ctx := context.Background() widgets := []interface{}{ widget{ @@ -201,7 +204,7 @@ func TestScanMagicLEK(t *testing.T) { }, } // prepare data - if _, err := table.Batch().Write().Put(widgets...).Run(); err != nil { + if _, err := table.Batch().Write().Put(widgets...).Run(ctx); err != nil { t.Fatal(err) } @@ -209,7 +212,7 @@ func TestScanMagicLEK(t *testing.T) { itr := table.Scan().Filter("'Msg' = ?", "TestScanMagicLEK").Limit(2).Iter() for i := 0; i < len(widgets); i++ { var w widget - itr.Next(&w) + itr.Next(ctx, &w) if itr.Err() != nil { t.Error("unexpected error", itr.Err()) } @@ -225,7 +228,7 @@ func TestScanMagicLEK(t *testing.T) { itr := table.Scan().Index("Msg-Time-index").Filter("UserID = ?", 2069).Limit(2).Iter() for i := 0; i < len(widgets); i++ { var w widget - itr.Next(&w) + itr.Next(ctx, &w) if itr.Err() != nil { t.Error("unexpected error", itr.Err()) } diff --git a/substitute.go b/substitute.go index b81e78e..65c2631 100644 --- a/substitute.go +++ b/substitute.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - "github.com/guregu/dynamo/internal/exprs" + "github.com/guregu/dynamo/v2/internal/exprs" ) // subber is a "mixin" for operators for keep track of subtituted keys and values diff --git a/table.go b/table.go index 559e4bb..f3235f5 100644 --- a/table.go +++ b/table.go @@ -54,15 +54,7 @@ func (table Table) Name() string { // 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) Wait(want ...Status) error { - ctx, cancel := defaultContext() - defer cancel() - return table.WaitWithContext(ctx, want...) -} - -// 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 context.Context, want ...Status) error { +func (table Table) Wait(ctx context.Context, want ...Status) error { if len(want) == 0 { want = []Status{ActiveStatus} } @@ -74,7 +66,7 @@ func (table Table) WaitWithContext(ctx context.Context, want ...Status) error { } err := table.db.retry(ctx, func() error { - desc, err := table.Describe().RunWithContext(ctx) + desc, err := table.Describe().Run(ctx) var aerr smithy.APIError if errors.As(err, &aerr) { if aerr.ErrorCode() == "ResourceNotFoundException" { @@ -134,7 +126,7 @@ func (table Table) primaryKeys(ctx context.Context, lek, esk Item, index string) keys := make(map[string]struct{}) err := table.db.retry(ctx, func() error { - desc, err := table.Describe().RunWithContext(ctx) + desc, err := table.Describe().Run(ctx) if err != nil { return err } @@ -181,14 +173,7 @@ func (table Table) DeleteTable() *DeleteTable { } // Run executes this request and deletes the table. -func (dt *DeleteTable) Run() error { - ctx, cancel := defaultContext() - defer cancel() - return dt.RunWithContext(ctx) -} - -// RunWithContext executes this request and deletes the table. -func (dt *DeleteTable) RunWithContext(ctx context.Context) error { +func (dt *DeleteTable) Run(ctx context.Context) error { input := dt.input() return dt.table.db.retry(ctx, func() error { _, err := dt.table.db.client.DeleteTable(ctx, input) @@ -197,18 +182,11 @@ func (dt *DeleteTable) RunWithContext(ctx context.Context) error { } // Wait executes this request and blocks until the table is finished deleting. -func (dt *DeleteTable) Wait() error { - ctx, cancel := defaultContext() - defer cancel() - return dt.WaitWithContext(ctx) -} - -// WaitWithContext executes this request and blocks until the table is finished deleting. -func (dt *DeleteTable) WaitWithContext(ctx context.Context) error { - if err := dt.RunWithContext(ctx); err != nil { +func (dt *DeleteTable) Wait(ctx context.Context) error { + if err := dt.Run(ctx); err != nil { return err } - return dt.table.WaitWithContext(ctx, NotExistsStatus) + return dt.table.Wait(ctx, NotExistsStatus) } func (dt *DeleteTable) input() *dynamodb.DeleteTableInput { diff --git a/table_test.go b/table_test.go index 30d6195..ca356cc 100644 --- a/table_test.go +++ b/table_test.go @@ -1,6 +1,7 @@ package dynamo import ( + "context" "fmt" "reflect" "sort" @@ -19,6 +20,8 @@ func TestTableLifecycle(t *testing.T) { t.SkipNow() } + ctx := context.TODO() + now := time.Now().UTC() name := fmt.Sprintf("TestDB-%d", now.UnixNano()) @@ -37,11 +40,11 @@ func TestTableLifecycle(t *testing.T) { HashKeyType: StringType, RangeKey: "Bar", RangeKeyType: NumberType, - }).Wait(); err != nil { + }).Wait(ctx); err != nil { t.Fatal(err) } - desc, err := testDB.Table(name).Describe().Run() + desc, err := testDB.Table(name).Describe().Run(ctx) if err != nil { t.Fatal(err) } @@ -114,12 +117,12 @@ func TestTableLifecycle(t *testing.T) { // make sure it really works table := testDB.Table(name) - if err := table.Put(UserAction{UserID: "test", Time: now, Seq: 1, UUID: "42"}).Run(); err != nil { + if err := table.Put(UserAction{UserID: "test", Time: now, Seq: 1, UUID: "42"}).Run(ctx); err != nil { t.Fatal(err) } // delete & wait - if err := testDB.Table(name).DeleteTable().Wait(); err != nil { + if err := testDB.Table(name).DeleteTable().Wait(ctx); err != nil { t.Fatal(err) } } diff --git a/ttl.go b/ttl.go index 2e23795..10f9e65 100644 --- a/ttl.go +++ b/ttl.go @@ -32,14 +32,7 @@ func (table Table) UpdateTTL(attribute string, enabled bool) *UpdateTTL { } // Run executes this request. -func (ttl *UpdateTTL) Run() error { - ctx, cancel := defaultContext() - defer cancel() - return ttl.RunWithContext(ctx) -} - -// RunWithContext executes this request. -func (ttl *UpdateTTL) RunWithContext(ctx context.Context) error { +func (ttl *UpdateTTL) Run(ctx context.Context) error { input := ttl.input() err := ttl.table.db.retry(ctx, func() error { @@ -70,14 +63,7 @@ func (table Table) DescribeTTL() *DescribeTTL { } // Run executes this request and returns details about time to live, or an error. -func (d *DescribeTTL) Run() (TTLDescription, error) { - ctx, cancel := defaultContext() - defer cancel() - return d.RunWithContext(ctx) -} - -// RunWithContext executes this request and returns details about time to live, or an error. -func (d *DescribeTTL) RunWithContext(ctx context.Context) (TTLDescription, error) { +func (d *DescribeTTL) Run(ctx context.Context) (TTLDescription, error) { input := d.input() var result *dynamodb.DescribeTimeToLiveOutput diff --git a/ttl_test.go b/ttl_test.go index 9ded4e3..4076647 100644 --- a/ttl_test.go +++ b/ttl_test.go @@ -1,6 +1,7 @@ package dynamo import ( + "context" "testing" ) @@ -9,8 +10,9 @@ func TestDescribeTTL(t *testing.T) { t.Skip(offlineSkipMsg) } table := testDB.Table(testTable) + ctx := context.TODO() - desc, err := table.DescribeTTL().Run() + desc, err := table.DescribeTTL().Run(ctx) if err != nil { t.Error(err) return diff --git a/tx.go b/tx.go index c7f9027..1caecd5 100644 --- a/tx.go +++ b/tx.go @@ -61,14 +61,7 @@ func (tx *GetTx) ConsumedCapacity(cc *ConsumedCapacity) *GetTx { } // Run executes this transaction and unmarshals everything specified by GetOne. -func (tx *GetTx) Run() error { - ctx, cancel := defaultContext() - defer cancel() - return tx.RunWithContext(ctx) -} - -// RunWithContext executes this transaction and unmarshals everything specified by GetOne. -func (tx *GetTx) RunWithContext(ctx context.Context) error { +func (tx *GetTx) Run(ctx context.Context) error { input, err := tx.input() if err != nil { return err @@ -108,14 +101,7 @@ func (tx *GetTx) unmarshal(resp *dynamodb.TransactGetItemsOutput) error { } // All executes this transaction and unmarshals every value to out, which must be a pointer to a slice. -func (tx *GetTx) All(out interface{}) error { - ctx, cancel := defaultContext() - defer cancel() - return tx.AllWithContext(ctx, out) -} - -// AllWithContext executes this transaction and unmarshals every value to out, which must be a pointer to a slice. -func (tx *GetTx) AllWithContext(ctx context.Context, out interface{}) error { +func (tx *GetTx) All(ctx context.Context, out interface{}) error { input, err := tx.input() if err != nil { return err @@ -260,14 +246,7 @@ func (tx *WriteTx) ConsumedCapacity(cc *ConsumedCapacity) *WriteTx { } // Run executes this transaction. -func (tx *WriteTx) Run() error { - ctx, cancel := defaultContext() - defer cancel() - return tx.RunWithContext(ctx) -} - -// RunWithContext executes this transaction. -func (tx *WriteTx) RunWithContext(ctx context.Context) error { +func (tx *WriteTx) Run(ctx context.Context) error { if tx.err != nil { return tx.err } diff --git a/tx_test.go b/tx_test.go index 64a6743..eb09864 100644 --- a/tx_test.go +++ b/tx_test.go @@ -1,6 +1,7 @@ package dynamo import ( + "context" "errors" "reflect" "sync" @@ -15,6 +16,8 @@ func TestTx(t *testing.T) { t.Skip(offlineSkipMsg) } + ctx := context.TODO() + date1 := time.Date(1969, 1, 1, 1, 1, 1, 0, time.UTC) date2 := time.Date(1969, 2, 2, 2, 2, 2, 0, time.UTC) date3 := time.Date(1969, 3, 3, 3, 3, 3, 0, time.UTC) @@ -30,7 +33,7 @@ func TestTx(t *testing.T) { tx.Put(table.Put(widget2)) tx.Check(table.Check("UserID", 69).Range("Time", date3).IfNotExists()) tx.ConsumedCapacity(&cc) - err := tx.Run() + err := tx.Run(ctx) if err != nil { t.Error(err) } @@ -39,7 +42,7 @@ func TestTx(t *testing.T) { } ccold = cc - err = tx.Run() + err = tx.Run(ctx) if err != nil { t.Error(err) } @@ -64,7 +67,7 @@ func TestTx(t *testing.T) { tx.Put(table.Put(widget1)) tx.Put(table.Put(widget2)) tx.ConsumedCapacity(&cc) - err = tx.Run() + err = tx.Run(ctx) if err != nil { t.Error(err) } @@ -73,7 +76,7 @@ func TestTx(t *testing.T) { } ccold = cc - err = tx.Run() + err = tx.Run(ctx) if err != nil { t.Error(err) } @@ -95,7 +98,7 @@ func TestTx(t *testing.T) { getTx.GetOne(table.Get("UserID", 69).Range("Time", Equal, date2), &record2) getTx.GetOne(table.Get("UserID", 69).Range("Time", Equal, date3), &record3) getTx.ConsumedCapacity(&cc2) - err = getTx.Run() + err = getTx.Run(ctx) if err != nil { t.Error(err) } @@ -115,7 +118,7 @@ func TestTx(t *testing.T) { // All oldCC2 := cc2 var records []widget - err = getTx.All(&records) + err = getTx.All(ctx, &records) if err != nil { t.Error(err) } @@ -131,7 +134,7 @@ func TestTx(t *testing.T) { tx = testDB.WriteTx() tx.Check(table.Check("UserID", widget1.UserID).Range("Time", widget1.Time).If("Msg = ?", widget1.Msg)) tx.Update(table.Update("UserID", widget2.UserID).Range("Time", widget2.Time).Set("Msg", widget2.Msg)) - if err = tx.Run(); err != nil { + if err = tx.Run(ctx); err != nil { t.Error(err) } @@ -139,12 +142,12 @@ func TestTx(t *testing.T) { tx = testDB.WriteTx() tx.Delete(table.Delete("UserID", widget1.UserID).Range("Time", widget1.Time).If("Msg = ?", widget1.Msg)) tx.Delete(table.Delete("UserID", widget2.UserID).Range("Time", widget2.Time).If("Msg = ?", widget2.Msg)) - if err = tx.Run(); err != nil { + if err = tx.Run(ctx); err != nil { t.Error(err) } // zero results - if err = getTx.Run(); err != ErrNotFound { + if err = getTx.Run(ctx); err != ErrNotFound { t.Error("expected ErrNotFound, got:", err) } @@ -153,7 +156,7 @@ func TestTx(t *testing.T) { tx.Put(table.Put(widget{UserID: 69, Time: date1}).If("'Msg' = ?", "should not exist")) tx.Put(table.Put(widget{UserID: 69, Time: date2})) tx.Check(table.Check("UserID", 69).Range("Time", date3).IfExists().If("Msg = ?", "don't exist foo")) - err = tx.Run() + err = tx.Run(ctx) if err == nil { t.Error("expected error") } else { @@ -167,12 +170,12 @@ func TestTx(t *testing.T) { t.Logf("All: %+v (len: %d)", records, len(records)) // no input - err = testDB.GetTx().All(nil) + err = testDB.GetTx().All(ctx, nil) if err != ErrNoInput { t.Error("unexpected error", err) } - err = testDB.WriteTx().Run() + err = testDB.WriteTx().Run(ctx) if err != ErrNoInput { t.Error("unexpected error", err) } @@ -182,12 +185,13 @@ func TestTxRetry(t *testing.T) { if testDB == nil { t.Skip(offlineSkipMsg) } + ctx := context.TODO() date1 := time.Date(1999, 1, 1, 1, 1, 1, 0, time.UTC) widget1 := widget{UserID: 69, Time: date1, Msg: "dog", Count: 0} table := testDB.Table(testTable) - if err := table.Put(widget1).Run(); err != nil { + if err := table.Put(widget1).Run(ctx); err != nil { t.Fatal(err) } @@ -204,7 +208,7 @@ func TestTxRetry(t *testing.T) { tx.Update(table.Update("UserID", widget1.UserID). Range("Time", widget1.Time). Add("Count", 1)) - if err := tx.Run(); err != nil { + if err := tx.Run(ctx); err != nil { // spew.Dump(err) panic(err) } @@ -219,7 +223,7 @@ func TestTxRetry(t *testing.T) { tx.Update(table.Update("UserID", widget1.UserID). Range("Time", widget1.Time).Add("Count", 1). If("'Count' = ?", -1)) - if err := tx.Run(); err != nil && !IsCondCheckFailed(err) { + if err := tx.Run(ctx); err != nil && !IsCondCheckFailed(err) { panic(err) } }() @@ -230,13 +234,13 @@ func TestTxRetry(t *testing.T) { defer wg.Done() tx := testDB.WriteTx() tx.Update(table.Update("UserID", "\u0002").Set("Foo", "")) - _ = tx.Run() + _ = tx.Run(ctx) }() wg.Wait() var got widget - if err := table.Get("UserID", widget1.UserID).Range("Time", Equal, widget1.Time).One(&got); err != nil { + if err := table.Get("UserID", widget1.UserID).Range("Time", Equal, widget1.Time).One(ctx, &got); err != nil { t.Fatal(err) } diff --git a/update.go b/update.go index 96a67a1..f841769 100644 --- a/update.go +++ b/update.go @@ -287,14 +287,7 @@ func (u *Update) ConsumedCapacity(cc *ConsumedCapacity) *Update { } // Run executes this update. -func (u *Update) Run() error { - ctx, cancel := defaultContext() - defer cancel() - return u.RunWithContext(ctx) -} - -// RunWithContext executes this update. -func (u *Update) RunWithContext(ctx context.Context) error { +func (u *Update) Run(ctx context.Context) error { u.returnType = "NONE" _, err := u.run(ctx) return err @@ -302,15 +295,7 @@ func (u *Update) RunWithContext(ctx context.Context) error { // Value 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) Value(out interface{}) error { - ctx, cancel := defaultContext() - defer cancel() - return u.ValueWithContext(ctx, out) -} - -// 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 context.Context, out interface{}) error { +func (u *Update) Value(ctx context.Context, out interface{}) error { u.returnType = "ALL_NEW" output, err := u.run(ctx) if err != nil { @@ -321,15 +306,7 @@ func (u *Update) ValueWithContext(ctx context.Context, out interface{}) error { // OldValue 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) OldValue(out interface{}) error { - ctx, cancel := defaultContext() - defer cancel() - return u.OldValueWithContext(ctx, out) -} - -// 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 context.Context, out interface{}) error { +func (u *Update) OldValue(ctx context.Context, out interface{}) error { u.returnType = "ALL_OLD" output, err := u.run(ctx) if err != nil { @@ -340,15 +317,7 @@ func (u *Update) OldValueWithContext(ctx context.Context, out interface{}) error // OnlyUpdatedValue 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) OnlyUpdatedValue(out interface{}) error { - ctx, cancel := defaultContext() - defer cancel() - return u.OnlyUpdatedValueWithContext(ctx, out) -} - -// 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 context.Context, out interface{}) error { +func (u *Update) OnlyUpdatedValue(ctx context.Context, out interface{}) error { u.returnType = "UPDATED_NEW" output, err := u.run(ctx) if err != nil { @@ -359,15 +328,7 @@ func (u *Update) OnlyUpdatedValueWithContext(ctx context.Context, out interface{ // OnlyUpdatedOldValue 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) OnlyUpdatedOldValue(out interface{}) error { - ctx, cancel := defaultContext() - defer cancel() - return u.OnlyUpdatedOldValueWithContext(ctx, out) -} - -// 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 context.Context, out interface{}) error { +func (u *Update) OnlyUpdatedOldValue(ctx context.Context, out interface{}) error { u.returnType = "UPDATED_OLD" output, err := u.run(ctx) if err != nil { diff --git a/update_test.go b/update_test.go index 54fd962..c3a29e1 100644 --- a/update_test.go +++ b/update_test.go @@ -1,6 +1,7 @@ package dynamo import ( + "context" "reflect" "testing" "time" @@ -14,6 +15,7 @@ func TestUpdate(t *testing.T) { t.Skip(offlineSkipMsg) } table := testDB.Table(testTable) + ctx := context.TODO() type widget2 struct { widget @@ -40,7 +42,7 @@ func TestUpdate(t *testing.T) { MySet2: map[string]struct{}{"a": {}, "b": {}, "bad1": {}, "c": {}, "bad2": {}}, MySet3: map[int64]struct{}{1: {}, 999: {}, 2: {}, 3: {}, 555: {}}, } - err := table.Put(item).Run() + err := table.Put(item).Run(ctx) if err != nil { t.Error("unexpected error:", err) } @@ -90,7 +92,7 @@ func TestUpdate(t *testing.T) { DeleteFromSet("MySet2", []string{"bad1", "bad2"}). DeleteFromSet("MySet3", map[int64]struct{}{999: {}, 555: {}}). ConsumedCapacity(&cc). - Value(&result) + Value(ctx, &result) expected := widget2{ widget: widget{ @@ -130,7 +132,7 @@ func TestUpdate(t *testing.T) { Range("Time", item.Time). Set("Msg", expected2.Msg). Add("Count", 1). - OnlyUpdatedValue(&updated) + OnlyUpdatedValue(ctx, &updated) if err != nil { t.Error("unexpected error:", err) } @@ -143,7 +145,7 @@ func TestUpdate(t *testing.T) { Range("Time", item.Time). Set("Msg", "this shouldn't be seen"). Add("Count", 100). - OnlyUpdatedOldValue(&updatedOld) + OnlyUpdatedOldValue(ctx, &updatedOld) if err != nil { t.Error("unexpected error:", err) } @@ -158,7 +160,7 @@ func TestUpdate(t *testing.T) { Add("Count", 1). If("'Count' > ?", 100). If("(MeaningOfLife = ?)", 42). - Value(&result) + Value(ctx, &result) if !IsCondCheckFailed(err) { t.Error("expected ConditionalCheckFailedException, not", err) } @@ -169,6 +171,7 @@ func TestUpdateNil(t *testing.T) { t.Skip(offlineSkipMsg) } table := testDB.Table(testTable) + ctx := context.TODO() // first, add an item to make sure there is at least one item := widget{ @@ -180,7 +183,7 @@ func TestUpdateNil(t *testing.T) { }, Count: 100, } - err := table.Put(item).Run() + err := table.Put(item).Run(ctx) if err != nil { t.Error("unexpected error:", err) t.FailNow() @@ -199,7 +202,7 @@ func TestUpdateNil(t *testing.T) { Set("Meta.'ok'", (*ptrTextMarshaler)(nil)). SetExpr("'Count' = ?", (*textMarshaler)(nil)). SetExpr("MsgPtr = ?", ""). - Value(&result) + Value(ctx, &result) if err != nil { t.Error("unexpected error:", err) } @@ -224,6 +227,7 @@ func TestUpdateSetAutoOmit(t *testing.T) { t.Skip(offlineSkipMsg) } table := testDB.Table(testTable) + ctx := context.TODO() type widget2 struct { widget @@ -241,7 +245,7 @@ func TestUpdateSetAutoOmit(t *testing.T) { CStr: customString("delete me"), SPtr: &str, } - err := table.Put(item).Run() + err := table.Put(item).Run(ctx) if err != nil { t.Error("unexpected error:", err) t.FailNow() @@ -252,7 +256,7 @@ func TestUpdateSetAutoOmit(t *testing.T) { err = table.Update("UserID", item.UserID).Range("Time", item.Time). Set("CStr", customString("")). Set("SPtr", nil). - Value(&result) + Value(ctx, &result) if err != nil { t.Error("unexpected error:", err) } diff --git a/updatetable.go b/updatetable.go index bade515..1b6eae3 100644 --- a/updatetable.go +++ b/updatetable.go @@ -107,13 +107,7 @@ func (ut *UpdateTable) DisableStream() *UpdateTable { } // Run executes this request and describes the table. -func (ut *UpdateTable) Run() (Description, error) { - ctx, cancel := defaultContext() - defer cancel() - return ut.RunWithContext(ctx) -} - -func (ut *UpdateTable) RunWithContext(ctx context.Context) (Description, error) { +func (ut *UpdateTable) Run(ctx context.Context) (Description, error) { if ut.err != nil { return Description{}, ut.err } diff --git a/updatetable_test.go b/updatetable_test.go index 04c2c1b..5b1e863 100644 --- a/updatetable_test.go +++ b/updatetable_test.go @@ -1,6 +1,7 @@ package dynamo import ( + "context" "testing" ) @@ -10,6 +11,7 @@ func _TestUpdateTable(t *testing.T) { t.Skip(offlineSkipMsg) } table := testDB.Table(testTable) + ctx := context.TODO() desc, err := table.UpdateTable().CreateIndex(Index{ Name: "test123", @@ -23,7 +25,7 @@ func _TestUpdateTable(t *testing.T) { Read: 1, Write: 1, }, - }).Run() + }).Run(ctx) // desc, err := table.UpdateTable().DeleteIndex("test123").Run() From ec1651c49bd82ebbde825d242b42e71f3a05d722 Mon Sep 17 00:00:00 2001 From: Greg Date: Sun, 5 May 2024 05:05:11 +0900 Subject: [PATCH 22/31] remove custom retrying stuff in favor of aws impl --- db.go | 70 +++----------------------------------------- retry.go | 81 ++------------------------------------------------- retry_test.go | 34 --------------------- table.go | 46 ++++++++++++++++------------- 4 files changed, 32 insertions(+), 199 deletions(-) diff --git a/db.go b/db.go index 22dc5a6..b9fa812 100644 --- a/db.go +++ b/db.go @@ -5,22 +5,18 @@ import ( "context" "errors" "fmt" - "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/v2/dynamodbiface" ) // DB is a DynamoDB client. type DB struct { - client dynamodbiface.DynamoDBAPI - logger logging.Logger - retryer func() aws.Retryer - retryMax int + client dynamodbiface.DynamoDBAPI } // New creates a new client with the given configuration. @@ -29,45 +25,14 @@ type DB struct { // (0 for no retrying, -1 for default behavior of unlimited retries). func New(cfg aws.Config, options ...func(*dynamodb.Options)) *DB { client := dynamodb.NewFromConfig(cfg, options...) - return newDB(client, cfg) + return NewFromIface(client) } // NewFromIface creates a new client with the given interface. func NewFromIface(client dynamodbiface.DynamoDBAPI) *DB { - return newDB(client, aws.Config{}) -} - -func newDB(client dynamodbiface.DynamoDBAPI, cfg aws.Config) *DB { db := &DB{ - client: client, - logger: cfg.Logger, - retryMax: -1, - } - - if db.logger == nil { - db.logger = logging.NewStandardLogger(os.Stdout) + client: client, } - - // TODO: replace all of this with AWS Retryer interface - /* - if real, ok := client.(*dynamodb.Client); ok { - if retryer := real.Options().Retryer; retryer != nil { - db.retryer = func() aws.Retryer { return retryer } - if cfg.Retryer != nil { - db.retryer = cfg.Retryer - } - } else if real.Options().RetryMaxAttempts > 0 { - db.retryMax = cfg.RetryMaxAttempts - } - } else { - */ - if cfg.Retryer != nil { - db.retryer = cfg.Retryer - } else if cfg.RetryMaxAttempts > 0 { - db.retryMax = cfg.RetryMaxAttempts - } - // } - return db } @@ -76,33 +41,6 @@ func (db *DB) Client() dynamodbiface.DynamoDBAPI { return db.client } -// TODO: should we expose these, or come up with a better interface? -// They could be useful in conjunction with NewFromIface, but SetRetryer would be misleading; -// dynamo expects it to be called from within the dynamodbapi interface. -// Probably best to create a forward-compatible (v2-friendly) configuration API instead. - -// func (db *DB) SetRetryer(retryer request.Retryer) { -// db.retryer = retryer -// } - -// func (db *DB) SetMaxRetries(max int) *DB { -// db.retryMax = max -// return db -// } - -// func (db *DB) SetLogger(logger aws.Logger) *DB { -// if logger == nil { -// db.logger = noopLogger{} -// return db -// } -// db.logger = logger -// return db -// } - -// func (db *DB) log(format string, v ...interface{}) { -// db.logger.Logf(logging.Debug, format, v...) -// } - // ListTables is a request to list tables. // See: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ListTables.html type ListTables struct { diff --git a/retry.go b/retry.go index 43b8d6e..f4f0aa1 100644 --- a/retry.go +++ b/retry.go @@ -2,85 +2,10 @@ package dynamo import ( "context" - "errors" - "time" - - "github.com/aws/aws-sdk-go-v2/aws/transport/http" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/aws/smithy-go" - awstime "github.com/aws/smithy-go/time" - "github.com/cenkalti/backoff/v4" ) -func (db *DB) retry(ctx context.Context, f func() error) error { - // if a custom retryer has been set, the SDK will retry for us - if db.retryer != nil { - return f() - } - - var err error - var next time.Duration - b := backoff.WithContext(backoff.NewExponentialBackOff(), ctx) - for i := 0; db.retryMax < 0 || i <= db.retryMax; i++ { - if err = f(); err == nil { - return nil - } - - if !canRetry(err) { - return err - } - - if next = b.NextBackOff(); next == backoff.Stop { - return err - } - if err := awstime.SleepWithContext(ctx, next); err != nil { - return err - } - } - return err -} - -// errRetry is a sentinel error to retry, should never be returned to user -var errRetry = errors.New("dynamo: retry") - -func canRetry(err error) bool { - if errors.Is(err, errRetry) { - return true - } - - var txe *types.TransactionCanceledException - if errors.As(err, &txe) { - 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 - } - - var aerr smithy.APIError - if errors.As(err, &aerr) { - switch aerr.ErrorCode() { - case "ProvisionedThroughputExceededException", - "ThrottlingException": - return true - } - } - - var rerr *http.ResponseError - if errors.As(err, &rerr) { - switch rerr.HTTPStatusCode() { - case 500, 503: - return true - } - } +// TODO: delete this - return false +func (db *DB) retry(_ context.Context, f func() error) error { + return f() } diff --git a/retry_test.go b/retry_test.go index 6dd6880..48e90f9 100644 --- a/retry_test.go +++ b/retry_test.go @@ -2,7 +2,6 @@ package dynamo import ( "context" - "fmt" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -10,39 +9,6 @@ import ( "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) -func TestRetryMax(t *testing.T) { - if testing.Short() { - t.SkipNow() - } - - test := func(max int) (string, func(t *testing.T)) { - name := fmt.Sprintf("max(%d)", max) - return name, func(t *testing.T) { - t.Parallel() - t.Helper() - db := New(aws.Config{ - RetryMaxAttempts: max, - Credentials: dummyCreds, - }) - - var runs int - err := db.retry(context.Background(), func() error { - runs++ - return &types.ProvisionedThroughputExceededException{} - }) - if err == nil { - t.Fatal("expected error, got nil") - } - if want := max + 1; runs != want { - t.Error("wrong number of runs. want:", want, "got:", runs) - } - } - } - // t.Run(test(0)) // behavior changed from v1 - t.Run(test(1)) - t.Run(test(3)) -} - func TestRetryCustom(t *testing.T) { t.Parallel() retryer := func() aws.Retryer { diff --git a/table.go b/table.go index f3235f5..5faffdb 100644 --- a/table.go +++ b/table.go @@ -2,13 +2,12 @@ package dynamo import ( "context" - "errors" "fmt" "sync/atomic" + "time" "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. @@ -65,29 +64,34 @@ func (table Table) Wait(ctx context.Context, want ...Status) error { } } - err := table.db.retry(ctx, func() error { - desc, err := table.Describe().Run(ctx) - var aerr smithy.APIError - if errors.As(err, &aerr) { - if aerr.ErrorCode() == "ResourceNotFoundException" { - if wantGone { - return nil - } - return errRetry - } - } - if err != nil { - return err - } + // I don't know why AWS wants a context _and_ a duration param. + // Infer it from context; if it's indefinite then set it to something really high (1 day) + deadline, ok := ctx.Deadline() + if !ok { + deadline = time.Now().Add(24 * time.Hour) + } + maxDur := time.Until(deadline) - for _, status := range want { - if status == desc.Status { - return nil + if wantGone { + waiter := dynamodb.NewTableNotExistsWaiter(table.db.client) + return waiter.Wait(ctx, table.Describe().input(), maxDur) + } + + waiter := dynamodb.NewTableExistsWaiter(table.db.client, func(opts *dynamodb.TableExistsWaiterOptions) { + fallback := opts.Retryable + opts.Retryable = func(ctx context.Context, in *dynamodb.DescribeTableInput, out *dynamodb.DescribeTableOutput, err error) (bool, error) { + if err == nil && out != nil && out.Table != nil { + status := string(out.Table.TableStatus) + for _, wantStatus := range want { + if status == string(wantStatus) { + return false, nil + } + } } + return fallback(ctx, in, out, err) } - return errRetry }) - return err + return waiter.Wait(ctx, table.Describe().input(), maxDur) } // primaryKeys attempts to determine this table's primary keys. From 681c46ac5c1e5076e846b634cb9a24dc764c0fad Mon Sep 17 00:00:00 2001 From: Greg Date: Sun, 5 May 2024 05:56:26 +0900 Subject: [PATCH 23/31] use int instead of int32 in APIs Query/Scan.limit isn't bound by DynamoDB API limits, so it can exceed MaxInt32 Make scan segmentation > MaxInt32 an error --- go.mod | 2 +- query.go | 15 +++++++++------ scan.go | 9 +++++++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 35af853..e2044e5 100644 --- a/go.mod +++ b/go.mod @@ -25,4 +25,4 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect ) -go 1.20 +go 1.21 diff --git a/query.go b/query.go index 689bffc..b6a7099 100644 --- a/query.go +++ b/query.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math" "strings" "github.com/aws/aws-sdk-go-v2/service/dynamodb" @@ -29,7 +30,7 @@ type Query struct { projection string filters []string consistent bool - limit int32 + limit int searchLimit int32 order *Order @@ -169,7 +170,7 @@ func (q *Query) Consistent(on bool) *Query { } // Limit specifies the maximum amount of results to return. -func (q *Query) Limit(limit int32) *Query { +func (q *Query) Limit(limit int) *Query { q.limit = limit return q } @@ -177,8 +178,9 @@ func (q *Query) Limit(limit int32) *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 int32) *Query { - q.searchLimit = limit +// Note: limit will be capped to MaxInt32 as that is the maximum number the DynamoDB API will accept. +func (q *Query) SearchLimit(limit int) *Query { + q.searchLimit = int32(min(limit, math.MaxInt32)) return q } @@ -307,7 +309,7 @@ type queryIter struct { output *dynamodb.QueryOutput err error idx int - n int32 + n int // last item evaluated last Item @@ -498,7 +500,8 @@ func (q *Query) queryInput() *dynamodb.QueryInput { } if q.limit > 0 { if len(q.filters) == 0 { - req.Limit = &q.limit + limit := int32(min(math.MaxInt32, q.limit)) + req.Limit = &limit } } if q.searchLimit > 0 { diff --git a/scan.go b/scan.go index ed4d940..975e467 100644 --- a/scan.go +++ b/scan.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math" "strings" "sync" @@ -58,9 +59,13 @@ func (s *Scan) Index(name string) *Scan { // Segment specifies the Segment and Total Segments to operate on in a manual parallel scan. // This is useful if you want to control the parallel scans by yourself instead of using ParallelIter. // Ignored by ParallelIter and friends. +// totalSegments must be less than MaxInt32 due to API limits. func (s *Scan) Segment(segment int, totalSegments int) *Scan { s.segment = int32(segment) s.totalSegments = int32(totalSegments) + if totalSegments > math.MaxInt32 { + s.setError(fmt.Errorf("dynamo: total segments in Scan must be less than or equal to %d (got %d)", math.MaxInt32, totalSegments)) + } return s } @@ -126,7 +131,7 @@ func (s *Scan) Limit(limit int) *Scan { // 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 int) *Scan { - s.searchLimit = int32(limit) + s.searchLimit = int32(min(limit, math.MaxInt32)) return s } @@ -281,7 +286,7 @@ func (s *Scan) scanInput() *dynamodb.ScanInput { } if s.limit > 0 { if len(s.filters) == 0 { - limit := int32(s.limit) + limit := int32(min(s.limit, math.MaxInt32)) input.Limit = &limit } } From 8728b18e786ec47e02c6d01fc308afbe1696c485 Mon Sep 17 00:00:00 2001 From: Greg Date: Sun, 16 Jun 2024 00:27:49 +0900 Subject: [PATCH 24/31] move table description cache to *DB --- db.go | 15 +++++++++++++++ describetable.go | 2 +- scan_test.go | 8 +++++++- table.go | 7 ++----- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/db.go b/db.go index b9fa812..511e297 100644 --- a/db.go +++ b/db.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "sync" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb" @@ -17,6 +18,8 @@ import ( // DB is a DynamoDB client. type DB struct { client dynamodbiface.DynamoDBAPI + // table description cache for LEK inference + descs *sync.Map // table name → Description } // New creates a new client with the given configuration. @@ -32,6 +35,7 @@ func New(cfg aws.Config, options ...func(*dynamodb.Options)) *DB { func NewFromIface(client dynamodbiface.DynamoDBAPI) *DB { db := &DB{ client: client, + descs: new(sync.Map), } return db } @@ -41,6 +45,17 @@ func (db *DB) Client() dynamodbiface.DynamoDBAPI { return db.client } +func (db *DB) loadDesc(name string) (desc Description, ok bool) { + if descv, exists := db.descs.Load(name); exists { + desc, ok = descv.(Description) + } + return +} + +func (db *DB) storeDesc(desc Description) { + db.descs.Store(desc.Name, desc) +} + // ListTables is a request to list tables. // See: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ListTables.html type ListTables struct { diff --git a/describetable.go b/describetable.go index 4c295ca..afbdacf 100644 --- a/describetable.go +++ b/describetable.go @@ -268,7 +268,7 @@ func (dt *DescribeTable) Run(ctx context.Context) (Description, error) { } desc := newDescription(result.Table) - dt.table.desc.Store(desc) + dt.table.db.storeDesc(desc) return desc, nil } diff --git a/scan_test.go b/scan_test.go index de2dcc9..c25622b 100644 --- a/scan_test.go +++ b/scan_test.go @@ -3,6 +3,7 @@ package dynamo import ( "context" "reflect" + "sync" "testing" "time" ) @@ -176,7 +177,12 @@ func TestScanMagicLEK(t *testing.T) { if testDB == nil { t.Skip(offlineSkipMsg) } - table := testDB.Table(testTableWidgets) + + testDB0 := *testDB + testDB0.descs = new(sync.Map) + freshTestDB := &testDB0 + + table := freshTestDB.Table(testTableWidgets) ctx := context.Background() widgets := []interface{}{ diff --git a/table.go b/table.go index 5faffdb..056e991 100644 --- a/table.go +++ b/table.go @@ -3,7 +3,6 @@ package dynamo import ( "context" "fmt" - "sync/atomic" "time" "github.com/aws/aws-sdk-go-v2/service/dynamodb" @@ -33,8 +32,6 @@ const ( type Table struct { name string db *DB - // desc is this table's cached description, used for inferring keys - desc *atomic.Value // Description } // Table returns a Table handle specified by name. @@ -42,7 +39,6 @@ func (db *DB) Table(name string) Table { return Table{ name: name, db: db, - desc: new(atomic.Value), } } @@ -119,7 +115,8 @@ func (table Table) primaryKeys(ctx context.Context, lek, esk Item, index string) // now we're forced to call DescribeTable // do we have a description cached? - if desc, ok := table.desc.Load().(Description); ok { + + if desc, ok := table.db.loadDesc(table.name); ok { keys := desc.keys(index) if keys != nil { return keys, nil From 7ba9ed29453e79abe0bf4660add33492e2425021 Mon Sep 17 00:00:00 2001 From: Greg Date: Sun, 16 Jun 2024 00:37:01 +0900 Subject: [PATCH 25/31] update README for v2 --- README.md | 73 ++++++++++++++++++++++--------------------------------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index e4aa6d2..074c47d 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ -## dynamo [![GoDoc](https://godoc.org/github.com/guregu/dynamo?status.svg)](https://godoc.org/github.com/guregu/dynamo) -`import "github.com/guregu/dynamo"` +## dynamo [![GoDoc](https://godoc.org/github.com/guregu/dynamo/v2?status.svg)](https://godoc.org/github.com/guregu/dynamo/v2) +`import "github.com/guregu/dynamo/v2"` 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. +> [!TIP] +> dynamo v2 is finally released! See [**v2 Migration**](#migrating-from-v1) for tips on migrating from dynamo v1. +> +> If you're using an older version, see: [**dynamo v1 Documentation**](https://pkg.go.dev/github.com/guregu/dynamo). + ### Example ```go @@ -17,7 +22,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" - "github.com/guregu/dynamo" + "github.com/guregu/dynamo/v2" ) // Use struct tags much like the standard JSON library, @@ -45,21 +50,21 @@ func main() { // put item w := widget{UserID: 613, Time: time.Now(), Msg: "hello"} - err = table.Put(w).Run() + err = table.Put(w).Run(ctx) // get the same item var result widget err = table.Get("UserID", w.UserID). Range("Time", dynamo.Equal, w.Time). - One(&result) + One(ctx, &result) // get all items var results []widget - err = table.Scan().All(&results) + err = table.Scan().All(ctx, &results) // use placeholders in filter expressions (see Expressions section below) var filtered []widget - err = table.Scan().Filter("'Count' > ?", 10).All(&filtered) + err = table.Scan().Filter("'Count' > ?", 10).All(ctx, &filtered) } ``` @@ -76,14 +81,14 @@ Please see the [DynamoDB reference on expressions](http://docs.aws.amazon.com/am ```go // Using single quotes to escape a reserved word, and a question mark as a value placeholder. // Finds all items whose date is greater than or equal to lastUpdate. -table.Scan().Filter("'Date' >= ?", lastUpdate).All(&results) +table.Scan().Filter("'Date' >= ?", lastUpdate).All(ctx, &results) // Using dollar signs as a placeholder for attribute names. // Deletes the item with an ID of 42 if its score is at or below the cutoff, and its name starts with G. -table.Delete("ID", 42).If("Score <= ? AND begins_with($, ?)", cutoff, "Name", "G").Run() +table.Delete("ID", 42).If("Score <= ? AND begins_with($, ?)", cutoff, "Name", "G").Run(ctx) // Put a new item, only if it doesn't already exist. -table.Put(item{ID: 42}).If("attribute_not_exists(ID)").Run() +table.Put(item{ID: 42}).If("attribute_not_exists(ID)").Run(ctx) ``` ### Encoding support @@ -182,42 +187,13 @@ This creates a table with the primary hash key ID and range key Time. It creates ### Retrying -Requests that fail with certain errors (e.g. `ThrottlingException`) are [automatically retried](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Programming.Errors.html#Programming.Errors.RetryAndBackoff). -Methods that take a `context.Context` will retry until the context is canceled. -Methods without a context will use the `RetryTimeout` global variable, which can be changed; using context is recommended instead. - -#### Limiting or disabling retrying - -The maximum number of retries can be configured via the `MaxRetries` field in the `*aws.Config` passed to `dynamo.New()`. A value of `0` will disable retrying. A value of `-1` means unlimited and is the default (however, context or `RetryTimeout` will still apply). - -```go -db := dynamo.New(session, &aws.Config{ - MaxRetries: aws.Int(0), // disables automatic retrying -}) -``` - -#### Custom retrying logic - -If a custom [`request.Retryer`](https://pkg.go.dev/github.com/aws/aws-sdk-go/aws/request#Retryer) is set via the `Retryer` field in `*aws.Config`, dynamo will delegate retrying entirely to it, taking precedence over other retrying settings. This allows you to have full control over all aspects of retrying. - -Example using [`client.DefaultRetryer`](https://pkg.go.dev/github.com/aws/aws-sdk-go/aws/client#DefaultRetryer): - -```go -retryer := client.DefaultRetryer{ - NumMaxRetries: 10, - MinThrottleDelay: 500 * time.Millisecond, - MaxThrottleDelay: 30 * time.Second, -} -db := dynamo.New(session, &aws.Config{ - Retryer: retryer, -}) -``` +As of v2, dynamo relies on the AWS SDK for retrying. See: [**Retries and Timeouts documentation**](https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/retries-timeouts/) for information about how to configure its behavior. ### Compatibility with the official AWS library -dynamo has been in development before the official AWS libraries were stable. We use a different encoder and decoder than the [dynamodbattribute](https://godoc.org/github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute) package. dynamo uses the `dynamo` struct tag instead of the `dynamodbav` struct tag, and we also prefer to automatically omit invalid values such as empty strings, whereas the dynamodbattribute package substitutes null values for them. Items that satisfy the [`dynamodbattribute.(Un)marshaler`](https://godoc.org/github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute#Marshaler) interfaces are compatibile with both libraries. +dynamo has been in development before the official AWS libraries were stable. We use a different encoder and decoder than the [dynamodbattribute](https://pkg.go.dev/github.com/jviney/aws-sdk-go-v2/service/dynamodb/dynamodbattribute) package. dynamo uses the `dynamo` struct tag instead of the `dynamodbav` struct tag, and we also prefer to automatically omit invalid values such as empty strings, whereas the dynamodbattribute package substitutes null values for them. Items that satisfy the [`dynamodbattribute.(Un)marshaler`](https://pkg.go.dev/github.com/jviney/aws-sdk-go-v2/service/dynamodb/dynamodbattribute#Marshaler) interfaces are compatibile with both libraries. -In order to use dynamodbattribute's encoding facilities, you must wrap objects passed to dynamo with [`dynamo.AWSEncoding`](https://godoc.org/github.com/guregu/dynamo#AWSEncoding). Here is a quick example: +In order to use dynamodbattribute's encoding facilities, you must wrap objects passed to dynamo with [`dynamo.AWSEncoding`](https://godoc.org/github.com/guregu/dynamo/v2#AWSEncoding). Here is a quick example: ```go // Notice the use of the dynamodbav struct tag @@ -229,12 +205,21 @@ type book struct { err := db.Table("Books").Put(dynamo.AWSEncoding(book{ ID: 42, Title: "Principia Discordia", -})).Run() +})).Run(ctx) // When getting an item you MUST pass a pointer to AWSEncoding! var someBook book -err := db.Table("Books").Get("ID", 555).One(dynamo.AWSEncoding(&someBook)) +err := db.Table("Books").Get("ID", 555).One(ctx, dynamo.AWSEncoding(&someBook)) ``` +### Migrating from v1 + +The API hasn't changed much from v1 to v2. Here are some migration tips: + +- All request methods now take a [context](https://go.dev/blog/context) as their first argument. +- Retrying relies on the AWS SDK configuration, see: [Retrying](#retrying). + - Transactions won't retry TransactionCanceled responses by default anymore, make sure you configure that if you need it. +- [Compatibility with the official AWS library](#compatibility-with-the-official-aws-library) uses v2 interfaces instead of v1. + ### Integration tests By default, tests are run in offline mode. In order to run the integration tests, some environment variables need to be set. From ed12b7df018243e972cb6307e71cdde432bd8049 Mon Sep 17 00:00:00 2001 From: Greg Date: Sun, 16 Jun 2024 05:44:38 +0900 Subject: [PATCH 26/31] add RetryTx for old tx retrying behavior --- README.md | 25 +++++++++++++++++++++++++ db_test.go | 7 ++++++- retry.go | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 074c47d..8fe2b7f 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,31 @@ This creates a table with the primary hash key ID and range key Time. It creates As of v2, dynamo relies on the AWS SDK for retrying. See: [**Retries and Timeouts documentation**](https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/retries-timeouts/) for information about how to configure its behavior. +By default, canceled transactions (i.e. errors from conflicting transactions) will not be retried. To get automatic retrying behavior like in v1, use [`dynamo.RetryTx`](https://godoc.org/github.com/guregu/dynamo/v2#RetryTx). + +```go +import ( + "context" + "log" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/retry" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/guregu/dynamo/v2" +) + +func main() { + cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRetryer(func() aws.Retryer { + return retry.NewStandard(dynamo.RetryTx) + })) + if err != nil { + log.Fatal(err) + } + db := dynamo.New(cfg) + // use db +} +``` + ### Compatibility with the official AWS library dynamo has been in development before the official AWS libraries were stable. We use a different encoder and decoder than the [dynamodbattribute](https://pkg.go.dev/github.com/jviney/aws-sdk-go-v2/service/dynamodb/dynamodbattribute) package. dynamo uses the `dynamo` struct tag instead of the `dynamodbav` struct tag, and we also prefer to automatically omit invalid values such as empty strings, whereas the dynamodbattribute package substitutes null values for them. Items that satisfy the [`dynamodbattribute.(Un)marshaler`](https://pkg.go.dev/github.com/jviney/aws-sdk-go-v2/service/dynamodb/dynamodbattribute#Marshaler) interfaces are compatibile with both libraries. diff --git a/db_test.go b/db_test.go index 58ecbd9..e453aca 100644 --- a/db_test.go +++ b/db_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/retry" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/smithy-go" @@ -58,11 +59,15 @@ func TestMain(m *testing.M) { }, ) } + // TransactionCanceledException + cfg, err := config.LoadDefaultConfig( context.Background(), config.WithRegion(*region), config.WithEndpointResolverWithOptions(resolv), - config.WithRetryer(nil), + config.WithRetryer(func() aws.Retryer { + return retry.NewStandard(RetryTx) + }), ) if err != nil { log.Fatal(err) diff --git a/retry.go b/retry.go index f4f0aa1..b975ff8 100644 --- a/retry.go +++ b/retry.go @@ -2,6 +2,11 @@ package dynamo import ( "context" + "errors" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/retry" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" ) // TODO: delete this @@ -9,3 +14,30 @@ import ( func (db *DB) retry(_ context.Context, f func() error) error { return f() } + +// RetryTx is an option for [github.com/aws/aws-sdk-go-v2/aws/retry.NewStandard] +// that adds retrying behavior for TransactionCanceledException errors. +// See also: [github.com/aws/aws-sdk-go-v2/config.WithRetryer]. +func RetryTx(opts *retry.StandardOptions) { + opts.Retryables = append(opts.Retryables, retry.IsErrorRetryableFunc(shouldRetryTx)) +} + +func shouldRetryTx(err error) aws.Ternary { + var txe *types.TransactionCanceledException + if errors.As(err, &txe) { + retry := aws.FalseTernary + for _, reason := range txe.CancellationReasons { + if reason.Code == nil { + continue + } + switch *reason.Code { + case "ValidationError", "ConditionalCheckFailed", "ItemCollectionSizeLimitExceeded": + return aws.FalseTernary + case "ThrottlingError", "ProvisionedThroughputExceeded", "TransactionConflict": + retry = aws.TrueTernary + } + } + return retry + } + return aws.UnknownTernary +} From 35807f650720ce804f86d41bad22f432cd1f2a4c Mon Sep 17 00:00:00 2001 From: Greg Date: Sun, 16 Jun 2024 05:44:58 +0900 Subject: [PATCH 27/31] add some examples --- example_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 example_test.go diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..331de5d --- /dev/null +++ b/example_test.go @@ -0,0 +1,67 @@ +package dynamo_test + +import ( + "context" + "log" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/retry" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/guregu/dynamo/v2" +) + +func ExampleNew() { + // Basic setup example. + // See: https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config for more on configuration options. + const region = "us-west-2" + cfg, err := config.LoadDefaultConfig( + context.Background(), + config.WithRegion(region), + ) + if err != nil { + log.Fatal(err) + } + db := dynamo.New(cfg) + // use the db + _ = db +} + +func ExampleNew_local_endpoint() { + // Example of connecting to a DynamoDB local instance. + // See: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html + const endpoint = "http://localhost:8000" + resolver := aws.EndpointResolverWithOptionsFunc( + func(service, region string, options ...interface{}) (aws.Endpoint, error) { + return aws.Endpoint{URL: endpoint}, nil + }, + ) + // credentials can be anything, but must be set + creds := credentials.NewStaticCredentialsProvider("dummy", "dummy", "") + cfg, err := config.LoadDefaultConfig( + context.Background(), + config.WithRegion("local"), // region can also be anything + config.WithEndpointResolverWithOptions(resolver), + config.WithCredentialsProvider(creds), + ) + if err != nil { + log.Fatal(err) + } + db := dynamo.New(cfg) + // use the db + _ = db +} + +func ExampleRetryTx() { + // `dynamo.RetryTx` is an option you can pass to retry.NewStandard. + // It will automatically retry canceled transactions. + cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRetryer(func() aws.Retryer { + return retry.NewStandard(dynamo.RetryTx) + })) + if err != nil { + log.Fatal(err) + } + db := dynamo.New(cfg) + // use the db + _ = db +} From ae8d3cc399ffcd474644e15f62ed8a96c64df851 Mon Sep 17 00:00:00 2001 From: Greg Date: Sun, 16 Jun 2024 05:50:04 +0900 Subject: [PATCH 28/31] update README migration tips --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8fe2b7f..b592cb1 100644 --- a/README.md +++ b/README.md @@ -243,7 +243,9 @@ The API hasn't changed much from v1 to v2. Here are some migration tips: - All request methods now take a [context](https://go.dev/blog/context) as their first argument. - Retrying relies on the AWS SDK configuration, see: [Retrying](#retrying). - Transactions won't retry TransactionCanceled responses by default anymore, make sure you configure that if you need it. +- Arguments that took `int64` (such as in `Query.Limit`) now take `int` instead. - [Compatibility with the official AWS library](#compatibility-with-the-official-aws-library) uses v2 interfaces instead of v1. +- `KMSMasterKeyArn` renamed to `KMSMasterKeyARN`. ### Integration tests From b910e5f079f5f0b7f5d0f0f6ea2fe4fa9491b605 Mon Sep 17 00:00:00 2001 From: Greg Date: Sun, 16 Jun 2024 05:54:06 +0900 Subject: [PATCH 29/31] touch up the README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b592cb1..f59a245 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This library is stable and versioned with Go modules. > [!TIP] > dynamo v2 is finally released! See [**v2 Migration**](#migrating-from-v1) for tips on migrating from dynamo v1. > -> If you're using an older version, see: [**dynamo v1 Documentation**](https://pkg.go.dev/github.com/guregu/dynamo). +> For dynamo v1, which uses [aws-sdk-go v1](https://github.com/aws/aws-sdk-go/), see: [**dynamo v1 documentation**](https://pkg.go.dev/github.com/guregu/dynamo). ### Example From 840a2a6f4354125f46f1665fc52ce9c08bd198cd Mon Sep 17 00:00:00 2001 From: Greg Date: Sun, 16 Jun 2024 05:54:48 +0900 Subject: [PATCH 30/31] bump versions --- go.mod | 22 +++++++++++----------- go.sum | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index e2044e5..5f81167 100644 --- a/go.mod +++ b/go.mod @@ -1,24 +1,24 @@ module github.com/guregu/dynamo/v2 require ( - github.com/aws/aws-sdk-go-v2 v1.24.1 + github.com/aws/aws-sdk-go-v2 v1.27.2 github.com/aws/aws-sdk-go-v2/config v1.11.0 github.com/aws/aws-sdk-go-v2/credentials v1.6.4 - github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.4.4 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8 - github.com/aws/smithy-go v1.19.0 - github.com/cenkalti/backoff/v4 v4.1.2 - golang.org/x/sync v0.6.0 + github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.1 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.32.8 + github.com/aws/smithy-go v1.20.2 + github.com/cenkalti/backoff/v4 v4.3.0 + golang.org/x/sync v0.7.0 ) require ( github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.2 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.3.2 // indirect - github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.11 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.20.10 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.10 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.2 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.6.2 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.11.1 // indirect diff --git a/go.sum b/go.sum index dda0cfb..971c824 100644 --- a/go.sum +++ b/go.sum @@ -1,33 +1,49 @@ github.com/aws/aws-sdk-go-v2 v1.11.2/go.mod h1:SQfA+m2ltnu1cA0soUkj4dRSsmITiVQUJvBIZjzfPyQ= github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= +github.com/aws/aws-sdk-go-v2 v1.27.2 h1:pLsTXqX93rimAOZG2FIYraDQstZaaGVVN4tNw65v0h8= +github.com/aws/aws-sdk-go-v2 v1.27.2/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= 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/dynamodb/attributevalue v1.14.1 h1:Uhn/kOwwHAL4vI6LdgvV0cfaQbaLyvJbCCyrSZLNBm8= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.1/go.mod h1:fEjI/gFP0DXxz5c4tRWyYEQpcNCVvMzjh62t0uKFk8U= 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/go.mod h1:SgKKNBIoDC/E1ZCDhhMW3yalWjwuLjMcpLzsM/QQnWo= github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 h1:cy8ahBJuhtM8GTTSyOkfy6WVPV1IE+SS5/wfXUYuulw= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9/go.mod h1:CZBXGLaJnEZI6EVNcPd7a6B5IC5cA/GkRWtu9fp3S6Y= 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/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 h1:A4SYk07ef04+vxZToz9LWvAXl9LW0NClpPpMsi31cz0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9/go.mod h1:5jJcHuwDagxN+ErjQ3PU3ocf6Ylc/p9x+BLO/+X4iXw= 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/go.mod h1:ELltfl9ri0n4sZ/VjPZBgemNMd9mYIpCAuZhc7NP7l4= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8 h1:XKO0BswTDeZMLDBd/b5pCEZGttNXrzRUVtFvp2Ak/Vo= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8/go.mod h1:N5tqZcYMM0N1PN7UQYJNWuGyO886OfnMhf/3MAbqMcI= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.32.8 h1:yOosUCdI/P+gfBd8uXk6lvZmrp7z2Xs8s1caIDP33lo= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.32.8/go.mod h1:4sYs0Krug9vn4cfDly4ExdbXJRqqZZBVDJNtBHGxCpQ= 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/dynamodbstreams v1.20.10 h1:aK9uyT3Ua6UOmTMBYEM3sJHlnSO994eNZGagFlfLiOs= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.20.10/go.mod h1:S541uoWn3nWvo28EE8DnMbqZ5sZRAipVUPuL11V08Xw= 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/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= 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/endpoint-discovery v1.8.11 h1:e9AVb17H4x5FTE5KWIP5M1Du+9M86pS+Hw0lBUdN8EY= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.11/go.mod h1:B90ZQJa36xo0ph9HsoteI1+r8owgQH/U1QNfqZQkj1Q= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.10 h1:+ijk29Q2FlKCinEzG6GE3IcOyBsmPNUmFq/L82pSyhI= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.10/go.mod h1:D9WZXFWtJD76gmV2ZciWcY8BJBFdCblqdfF9OmkrwVU= 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= @@ -37,8 +53,12 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.11.1/go.mod h1:UV2N5HaPfdbDpkgkz4sRz github.com/aws/smithy-go v1.9.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= +github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= +github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -53,6 +73,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= From 756ca4a8e6af7d29cdca17dd5a68896ba1f14817 Mon Sep 17 00:00:00 2001 From: Greg Date: Sun, 16 Jun 2024 05:59:36 +0900 Subject: [PATCH 31/31] rename RetryTx to RetryTxConflicts --- README.md | 4 ++-- db_test.go | 2 +- example_test.go | 6 +++--- retry.go | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f59a245..f3494fd 100644 --- a/README.md +++ b/README.md @@ -189,7 +189,7 @@ This creates a table with the primary hash key ID and range key Time. It creates As of v2, dynamo relies on the AWS SDK for retrying. See: [**Retries and Timeouts documentation**](https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/retries-timeouts/) for information about how to configure its behavior. -By default, canceled transactions (i.e. errors from conflicting transactions) will not be retried. To get automatic retrying behavior like in v1, use [`dynamo.RetryTx`](https://godoc.org/github.com/guregu/dynamo/v2#RetryTx). +By default, canceled transactions (i.e. errors from conflicting transactions) will not be retried. To get automatic retrying behavior like in v1, use [`dynamo.RetryTxConflicts`](https://godoc.org/github.com/guregu/dynamo/v2#RetryTxConflicts). ```go import ( @@ -204,7 +204,7 @@ import ( func main() { cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRetryer(func() aws.Retryer { - return retry.NewStandard(dynamo.RetryTx) + return retry.NewStandard(dynamo.RetryTxConflicts) })) if err != nil { log.Fatal(err) diff --git a/db_test.go b/db_test.go index e453aca..e793842 100644 --- a/db_test.go +++ b/db_test.go @@ -66,7 +66,7 @@ func TestMain(m *testing.M) { config.WithRegion(*region), config.WithEndpointResolverWithOptions(resolv), config.WithRetryer(func() aws.Retryer { - return retry.NewStandard(RetryTx) + return retry.NewStandard(RetryTxConflicts) }), ) if err != nil { diff --git a/example_test.go b/example_test.go index 331de5d..9fc803f 100644 --- a/example_test.go +++ b/example_test.go @@ -52,11 +52,11 @@ func ExampleNew_local_endpoint() { _ = db } -func ExampleRetryTx() { - // `dynamo.RetryTx` is an option you can pass to retry.NewStandard. +func ExampleRetryTxConflicts() { + // `dynamo.RetryTxConflicts` is an option you can pass to retry.NewStandard. // It will automatically retry canceled transactions. cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRetryer(func() aws.Retryer { - return retry.NewStandard(dynamo.RetryTx) + return retry.NewStandard(dynamo.RetryTxConflicts) })) if err != nil { log.Fatal(err) diff --git a/retry.go b/retry.go index b975ff8..3be6e8a 100644 --- a/retry.go +++ b/retry.go @@ -15,10 +15,10 @@ func (db *DB) retry(_ context.Context, f func() error) error { return f() } -// RetryTx is an option for [github.com/aws/aws-sdk-go-v2/aws/retry.NewStandard] -// that adds retrying behavior for TransactionCanceledException errors. +// RetryTxConflicts is an option for [github.com/aws/aws-sdk-go-v2/aws/retry.NewStandard] +// that adds retrying behavior for TransactionConflict within TransactionCanceledException errors. // See also: [github.com/aws/aws-sdk-go-v2/config.WithRetryer]. -func RetryTx(opts *retry.StandardOptions) { +func RetryTxConflicts(opts *retry.StandardOptions) { opts.Retryables = append(opts.Retryables, retry.IsErrorRetryableFunc(shouldRetryTx)) }