Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dynamo v2: featuring aws-sdk-go-v2 #232

Merged
merged 36 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0d038a9
Feat/dynamo sdk v2 (#2)
niltonkummer Dec 10, 2021
4304bbf
Remove unnecessary parenthesis
irohirokid Oct 13, 2022
2b00a6b
Fix problem of decoding AttributeValueMemberM
irohirokid Oct 13, 2022
2062008
Remove unnecessary code
irohirokid Oct 15, 2022
4da56f4
Remove unnecessary blank line
irohirokid Oct 18, 2022
0e4bb5e
Kill awserr
irohirokid Oct 26, 2022
d109af1
Recover unwrapping AWSEncoding wrapper
irohirokid Oct 28, 2022
a553025
int32 Query/Scan limit
irohirokid Oct 31, 2022
6aadb24
Improvement of init in test
irohirokid Nov 1, 2022
8c29226
Use standard context module
irohirokid Nov 1, 2022
c2f91d7
Remove unnecessary blank lines in import block
irohirokid Nov 2, 2022
1afde07
Update expressions making blank slice
irohirokid Nov 7, 2022
9552dd0
Change repo
irohirokid Nov 9, 2022
63eda64
Merge irohiroki:aws-sdk-v2-compliant into v2-dev
guregu Jan 5, 2024
e74a218
fix endpoint for tests
guregu Jan 5, 2024
0064f3b
hmm
guregu Jan 5, 2024
03408e0
remove spurious dep
guregu Jan 5, 2024
801336f
fix retrying settings, remove old sdk references
guregu Jan 27, 2024
91db943
fix retrying tests
guregu Jan 27, 2024
ef69352
change LastEvaluatedKey signature
guregu Jan 27, 2024
2b8cde0
rename KMSMasterKeyArn -> KMSMasterKeyARN
guregu Jan 27, 2024
9b9e3cb
remove non-context methods
guregu Jan 27, 2024
a749b0c
Merge branch 'master' into v2-dev
guregu May 4, 2024
ec1651c
remove custom retrying stuff in favor of aws impl
guregu May 4, 2024
681c46a
use int instead of int32 in APIs
guregu May 4, 2024
7e34253
Merge branch 'master' into v2-dev
guregu May 4, 2024
95d7bd1
Merge branch 'master' into v2-dev
guregu May 5, 2024
23cb3df
Merge branch 'master' into v2-dev
guregu May 20, 2024
8728b18
move table description cache to *DB
guregu Jun 15, 2024
7ba9ed2
update README for v2
guregu Jun 15, 2024
ed12b7d
add RetryTx for old tx retrying behavior
guregu Jun 15, 2024
35807f6
add some examples
guregu Jun 15, 2024
ae8d3cc
update README migration tips
guregu Jun 15, 2024
b910e5f
touch up the README
guregu Jun 15, 2024
840a2a6
bump versions
guregu Jun 15, 2024
756ca4a
rename RetryTx to RetryTxConflicts
guregu Jun 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
## dynamo [![GoDoc](https://godoc.org/github.com/guregu/dynamo?status.svg)](https://godoc.org/github.com/guregu/dynamo)
`import "github.com/guregu/dynamo"`

dynamo is an expressive [DynamoDB](https://aws.amazon.com/dynamodb/) client for Go, with an easy but powerful API. dynamo integrates with the official [AWS SDK](https://github.com/aws/aws-sdk-go/).
dynamo is an expressive [DynamoDB](https://aws.amazon.com/dynamodb/) client for Go, with an easy but powerful API. dynamo integrates with the official [AWS SDK v2](https://github.com/aws/aws-sdk-go-v2/).

This library is stable and versioned with Go modules.

Expand All @@ -12,9 +12,11 @@ package dynamo

import (
"time"
"context"
"log"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/guregu/dynamo"
)

Expand All @@ -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
Expand Down
108 changes: 52 additions & 56 deletions attr.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import (
"fmt"
"strconv"

"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)

// Item is a type alias for the raw DynamoDB item type.
type Item = map[string]*dynamodb.AttributeValue
type Item = map[string]types.AttributeValue

type shapeKey byte

Expand All @@ -31,114 +31,110 @@ const (
shapeInvalid shapeKey = 0
)

func shapeOf(av *dynamodb.AttributeValue) shapeKey {
func shapeOf(av types.AttributeValue) shapeKey {
if av == nil {
return shapeInvalid
}
switch {
case av.B != nil:
switch av.(type) {
case *types.AttributeValueMemberB:
return shapeB
case av.BS != nil:
case *types.AttributeValueMemberBS:
return shapeBS
case av.BOOL != nil:
case *types.AttributeValueMemberBOOL:
return shapeBOOL
case av.N != nil:
case *types.AttributeValueMemberN:
return shapeN
case av.S != nil:
case *types.AttributeValueMemberS:
return shapeS
case av.L != nil:
case *types.AttributeValueMemberL:
return shapeL
case av.NS != nil:
case *types.AttributeValueMemberNS:
return shapeNS
case av.SS != nil:
case *types.AttributeValueMemberSS:
return shapeSS
case av.M != nil:
case *types.AttributeValueMemberM:
return shapeM
case av.NULL != nil:
case *types.AttributeValueMemberNULL:
return shapeNULL
}
return shapeAny
}

// 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
}
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)
}
return set, nil
case av.M != nil:
m := make(map[string]interface{}, len(av.M))
for k, v := range av.M {
case *types.AttributeValueMemberSS:
return v.Value, nil
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
}
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 {
func avTypeName(av types.AttributeValue) string {
if av == nil {
return "<nil>"
}
switch {
case av.B != nil:
switch av.(type) {
case *types.AttributeValueMemberB:
return "binary"
case av.BS != nil:
case *types.AttributeValueMemberBS:
return "binary set"
case av.BOOL != nil:
case *types.AttributeValueMemberBOOL:
return "boolean"
case av.N != nil:
case *types.AttributeValueMemberN:
return "number"
case av.S != nil:
case *types.AttributeValueMemberS:
return "string"
case av.L != nil:
case *types.AttributeValueMemberL:
return "list"
case av.NS != nil:
case *types.AttributeValueMemberNS:
return "number set"
case av.SS != nil:
case *types.AttributeValueMemberSS:
return "string set"
case av.M != nil:
case *types.AttributeValueMemberM:
return "map"
case av.NULL != nil:
case *types.AttributeValueMemberNULL:
return "null"
}
return "<empty>"
Expand Down
28 changes: 16 additions & 12 deletions batch_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dynamo

import (
"context"
"testing"
"time"
)
Expand All @@ -15,6 +16,7 @@ func TestBatchGetWrite(t *testing.T) {
table2 := testDB.Table(testTableSprockets)
tables := []Table{table1, table2}
totalBatchSize := batchSize * len(tables)
ctx := context.TODO()

items := make([]interface{}, batchSize)
widgets := make(map[int]widget)
Expand All @@ -39,7 +41,7 @@ func TestBatchGetWrite(t *testing.T) {
batch1 := batches[0]
batch1.Merge(batches[1:]...)
var wcc ConsumedCapacity
wrote, err := batch1.ConsumedCapacity(&wcc).Run()
wrote, err := batch1.ConsumedCapacity(&wcc).Run(ctx)
if wrote != totalBatchSize {
t.Error("unexpected wrote:", wrote, "≠", totalBatchSize)
}
Expand All @@ -65,7 +67,7 @@ func TestBatchGetWrite(t *testing.T) {
get1.Merge(gets[1:]...)

var results []widget
err = get1.All(&results)
err = get1.All(ctx, &results)
if err != nil {
t.Error("unexpected error:", err)
}
Expand All @@ -92,7 +94,7 @@ func TestBatchGetWrite(t *testing.T) {
wrote, err = table1.Batch("UserID", "Time").Write().
Delete(keys...).
DeleteInRange(table2, "UserID", "Time", keys...).
Run()
Run(ctx)
if wrote != totalBatchSize {
t.Error("unexpected wrote:", wrote, "≠", totalBatchSize)
}
Expand All @@ -107,7 +109,7 @@ func TestBatchGetWrite(t *testing.T) {
Get(keys...).
FromRange(table2, "UserID", "Time", keys...).
Consistent(true).
All(&results)
All(ctx, &results)
if err != ErrNotFound {
t.Error("expected ErrNotFound, got", err)
}
Expand All @@ -122,15 +124,16 @@ func TestBatchGetEmptySets(t *testing.T) {
t.Skip(offlineSkipMsg)
}
table := testDB.Table(testTableWidgets)
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)
}

Expand All @@ -140,20 +143,20 @@ 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)
}
if len(results) != 2 {
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)
}
Expand All @@ -162,7 +165,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)
}
Expand All @@ -173,13 +176,14 @@ func TestBatchGetEmptySets(t *testing.T) {

func TestBatchEmptyInput(t *testing.T) {
table := testDB.Table(testTableWidgets)
ctx := context.TODO()
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)
}
Expand Down
Loading