Modified put to put all dirty or marked items

This commit is contained in:
Leon Mika 2022-07-16 11:35:53 +10:00
parent 9fee17a6a6
commit 2dbd664dd2
13 changed files with 329 additions and 167 deletions

View file

@ -0,0 +1,9 @@
package sliceutils
func Map[T, U any](ts []T, fn func(t T) U) []U {
us := make([]U, len(ts))
for i, t := range ts {
us[i] = fn(t)
}
return us
}

View file

@ -23,22 +23,3 @@ func (ps *promptSequence) next() tea.Msg {
} }
return ps.onAllDone(ps.receivedValues) return ps.onAllDone(ps.receivedValues)
} }
//type SetAttributeArg struct {
// attrType models.ItemType
// attrName string
//}
//
//func ParseSetAttributeArgs(args []string) (attrArgs []SetAttributeArg, err error) {
// var currArg SetAttributeArg
// for _, arg := range args {
// if arg[0] == '-' {
// currArg.attrType = models.ItemType(arg[1:])
// } else {
// currArg.attrName = arg
// attrArgs = append(attrArgs, currArg)
// currArg = SetAttributeArg{}
// }
// }
// return attrArgs, nil
//}

View file

@ -49,4 +49,10 @@ type PromptForTableMsg struct {
OnSelected func(tableName string) tea.Cmd OnSelected func(tableName string) tea.Cmd
} }
type ResultSetUpdated struct{} type ResultSetUpdated struct {
statusMessage string
}
func (rs ResultSetUpdated) StatusMessage() string {
return rs.statusMessage
}

View file

@ -15,8 +15,7 @@ import (
) )
func TestTableReadController_InitTable(t *testing.T) { func TestTableReadController_InitTable(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
service := tables.NewService(provider) service := tables.NewService(provider)
@ -41,8 +40,7 @@ func TestTableReadController_InitTable(t *testing.T) {
} }
func TestTableReadController_ListTables(t *testing.T) { func TestTableReadController_ListTables(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
service := tables.NewService(provider) service := tables.NewService(provider)
@ -65,8 +63,7 @@ func TestTableReadController_ListTables(t *testing.T) {
} }
func TestTableReadController_ExportCSV(t *testing.T) { func TestTableReadController_ExportCSV(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
service := tables.NewService(provider) service := tables.NewService(provider)
@ -101,8 +98,7 @@ func TestTableReadController_ExportCSV(t *testing.T) {
} }
func TestTableReadController_Query(t *testing.T) { func TestTableReadController_Query(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
service := tables.NewService(provider) service := tables.NewService(provider)

View file

@ -257,6 +257,68 @@ func (twc *TableWriteController) PutItem(idx int) tea.Cmd {
} }
} }
func (twc *TableWriteController) PutItems() tea.Cmd {
return func() tea.Msg {
var (
expectedPuts int
markedItems int
)
twc.state.withResultSet(func(rs *models.ResultSet) {
markedItems = len(rs.MarkedItems())
for i := range rs.Items() {
if rs.IsDirty(i) && (markedItems == 0 || rs.Marked(i)) {
expectedPuts++
}
}
})
if expectedPuts == 0 {
if markedItems > 0 {
return events.StatusMsg("no marked items are modified")
} else {
return events.StatusMsg("no items are modified")
}
}
var promptMessage string
if markedItems > 0 {
promptMessage = applyToN("put ", expectedPuts, "marked item", "marked items", "? ")
} else {
promptMessage = applyToN("put ", expectedPuts, "item", "items", "? ")
}
return events.PromptForInputMsg{
Prompt: promptMessage,
OnDone: func(value string) tea.Cmd {
if value != "y" {
return events.SetStatus("operation aborted")
}
return func() tea.Msg {
if err := twc.state.withResultSetReturningError(func(rs *models.ResultSet) error {
updated, err := twc.tableService.PutSelectedItems(context.Background(), rs, func(idx int) bool {
return rs.IsDirty(idx) && (markedItems == 0 || rs.Marked(idx))
})
if err != nil {
return err
} else if updated != expectedPuts {
return errors.Errorf("expected %d updates but only %d were applied", expectedPuts, updated)
}
return nil
}); err != nil {
return events.Error(err)
}
return ResultSetUpdated{
statusMessage: applyToN("", expectedPuts, "item", "item", " put to table"),
}
}
},
}
}
}
func (twc *TableWriteController) TouchItem(idx int) tea.Cmd { func (twc *TableWriteController) TouchItem(idx int) tea.Cmd {
return func() tea.Msg { return func() tea.Msg {
resultSet := twc.state.ResultSet() resultSet := twc.state.ResultSet()
@ -325,7 +387,7 @@ func (twc *TableWriteController) DeleteMarked() tea.Cmd {
} }
return events.PromptForInputMsg{ return events.PromptForInputMsg{
Prompt: fmt.Sprintf("delete %d items? ", len(markedItems)), Prompt: applyToN("delete ", len(markedItems), "item", "items", "? "),
OnDone: func(value string) tea.Cmd { OnDone: func(value string) tea.Cmd {
if value != "y" { if value != "y" {
return events.SetStatus("operation aborted") return events.SetStatus("operation aborted")
@ -343,3 +405,10 @@ func (twc *TableWriteController) DeleteMarked() tea.Cmd {
} }
} }
} }
func applyToN(prefix string, n int, singular, plural, suffix string) string {
if n == 1 {
return fmt.Sprintf("%v%v %v%v", prefix, n, singular, suffix)
}
return fmt.Sprintf("%v%v %v%v", prefix, n, plural, suffix)
}

View file

@ -14,8 +14,7 @@ import (
func TestTableWriteController_NewItem(t *testing.T) { func TestTableWriteController_NewItem(t *testing.T) {
t.Run("should add an item with pk and sk set at the end of the result set", func(t *testing.T) { t.Run("should add an item with pk and sk set at the end of the result set", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
service := tables.NewService(provider) service := tables.NewService(provider)
@ -45,8 +44,7 @@ func TestTableWriteController_NewItem(t *testing.T) {
func TestTableWriteController_SetAttributeValue(t *testing.T) { func TestTableWriteController_SetAttributeValue(t *testing.T) {
t.Run("should preserve the type of the field if unspecified", func(t *testing.T) { t.Run("should preserve the type of the field if unspecified", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
t.Cleanup(cleanupFn)
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
service := tables.NewService(provider) service := tables.NewService(provider)
@ -99,8 +97,7 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) {
}) })
t.Run("should change the value to a particular field if already present", func(t *testing.T) { t.Run("should change the value to a particular field if already present", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
t.Cleanup(cleanupFn)
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
service := tables.NewService(provider) service := tables.NewService(provider)
@ -188,105 +185,8 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) {
}) })
} }
/*
func TestTableWriteController_SetStringValue(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
t.Run("should change the value of a string field if already present", func(t *testing.T) {
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, "alpha-table")
writeController := controllers.NewTableWriteController(state, service, readController)
invokeCommand(t, readController.Init())
before, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", before)
assert.False(t, state.ResultSet().IsDirty(0))
invokeCommandWithPrompt(t, writeController.SetStringValue(0, "alpha"), "a new value")
after, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "a new value", after)
assert.True(t, state.ResultSet().IsDirty(0))
})
t.Run("should change the value of a string field within a map if already present", func(t *testing.T) {
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, "alpha-table")
writeController := controllers.NewTableWriteController(state, service, readController)
invokeCommand(t, readController.Init())
beforeAddress := state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM)
beforeStreet := beforeAddress.Value["street"].(*types.AttributeValueMemberS).Value
assert.Equal(t, "Fake st.", beforeStreet)
assert.False(t, state.ResultSet().IsDirty(0))
invokeCommandWithPrompt(t, writeController.SetStringValue(0, "address.street"), "Fiction rd.")
afterAddress := state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM)
afterStreet := afterAddress.Value["street"].(*types.AttributeValueMemberS).Value
assert.Equal(t, "Fiction rd.", afterStreet)
assert.True(t, state.ResultSet().IsDirty(0))
})
}
func TestTableWriteController_SetNumberValue(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
t.Run("should change the value of a number field if already present", func(t *testing.T) {
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, "alpha-table")
writeController := controllers.NewTableWriteController(state, service, readController)
invokeCommand(t, readController.Init())
before, _ := state.ResultSet().Items()[0].AttributeValueAsString("age")
assert.Equal(t, "23", before)
assert.False(t, state.ResultSet().IsDirty(0))
invokeCommandWithPrompt(t, writeController.SetNumberValue(0, "age"), "46")
after, _ := state.ResultSet().Items()[0].AttributeValueAsString("age")
assert.Equal(t, "46", after)
assert.True(t, state.ResultSet().IsDirty(0))
})
t.Run("should change the value of a number field within a map if already present", func(t *testing.T) {
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, "alpha-table")
writeController := controllers.NewTableWriteController(state, service, readController)
invokeCommand(t, readController.Init())
beforeAddress := state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM)
beforeStreet := beforeAddress.Value["no"].(*types.AttributeValueMemberN).Value
assert.Equal(t, "123", beforeStreet)
assert.False(t, state.ResultSet().IsDirty(0))
invokeCommandWithPrompt(t, writeController.SetNumberValue(0, "address.no"), "456")
afterAddress := state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM)
afterStreet := afterAddress.Value["no"].(*types.AttributeValueMemberN).Value
assert.Equal(t, "456", afterStreet)
assert.True(t, state.ResultSet().IsDirty(0))
})
}
*/
func TestTableWriteController_DeleteAttribute(t *testing.T) { func TestTableWriteController_DeleteAttribute(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
service := tables.NewService(provider) service := tables.NewService(provider)
@ -331,8 +231,7 @@ func TestTableWriteController_DeleteAttribute(t *testing.T) {
func TestTableWriteController_PutItem(t *testing.T) { func TestTableWriteController_PutItem(t *testing.T) {
t.Run("should put the selected item if dirty", func(t *testing.T) { t.Run("should put the selected item if dirty", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
service := tables.NewService(provider) service := tables.NewService(provider)
@ -359,8 +258,7 @@ func TestTableWriteController_PutItem(t *testing.T) {
}) })
t.Run("should not put the selected item if user does not confirm", func(t *testing.T) { t.Run("should not put the selected item if user does not confirm", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
service := tables.NewService(provider) service := tables.NewService(provider)
@ -391,8 +289,7 @@ func TestTableWriteController_PutItem(t *testing.T) {
}) })
t.Run("should not put the selected item if not dirty", func(t *testing.T) { t.Run("should not put the selected item if not dirty", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
service := tables.NewService(provider) service := tables.NewService(provider)
@ -411,10 +308,111 @@ func TestTableWriteController_PutItem(t *testing.T) {
}) })
} }
func TestTableWriteController_PutItems(t *testing.T) {
t.Run("should put all dirty items if none are marked", func(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, "alpha-table")
writeController := controllers.NewTableWriteController(state, service, readController)
invokeCommand(t, readController.Init())
// Modify the item and put it
invokeCommandWithPrompt(t, writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandWithPrompt(t, writeController.SetAttributeValue(2, models.StringItemType, "alpha"), "another new value")
invokeCommandWithPrompt(t, writeController.PutItems(), "y")
// Rescan the table
invokeCommand(t, readController.Rescan())
assert.Equal(t, "a new value", state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Equal(t, "another new value", state.ResultSet().Items()[2]["alpha"].(*types.AttributeValueMemberS).Value)
assert.False(t, state.ResultSet().IsDirty(0))
assert.False(t, state.ResultSet().IsDirty(2))
})
t.Run("only put marked items", func(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, "alpha-table")
writeController := controllers.NewTableWriteController(state, service, readController)
invokeCommand(t, readController.Init())
// Modify the item and put it
invokeCommandWithPrompt(t, writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandWithPrompt(t, writeController.SetAttributeValue(2, models.StringItemType, "alpha"), "another new value")
invokeCommand(t, writeController.ToggleMark(0))
invokeCommandWithPrompt(t, writeController.PutItems(), "y")
// Verify dirty items are unchanged
assert.Equal(t, "a new value", state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Equal(t, "another new value", state.ResultSet().Items()[2]["alpha"].(*types.AttributeValueMemberS).Value)
assert.False(t, state.ResultSet().IsDirty(0))
assert.True(t, state.ResultSet().IsDirty(2))
// Rescan the table and verify dirty items were not written
invokeCommand(t, readController.Rescan())
assert.Equal(t, "a new value", state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Nil(t, state.ResultSet().Items()[2]["alpha"])
assert.False(t, state.ResultSet().IsDirty(0))
assert.False(t, state.ResultSet().IsDirty(2))
})
t.Run("do not put marked items which are not diry", func(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, "alpha-table")
writeController := controllers.NewTableWriteController(state, service, readController)
invokeCommand(t, readController.Init())
// Modify the item and put it
invokeCommandWithPrompt(t, writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandWithPrompt(t, writeController.SetAttributeValue(2, models.StringItemType, "alpha"), "another new value")
invokeCommand(t, writeController.ToggleMark(1))
invokeCommand(t, writeController.PutItems())
// Verify dirty items are unchanged
assert.Equal(t, "a new value", state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Equal(t, "another new value", state.ResultSet().Items()[2]["alpha"].(*types.AttributeValueMemberS).Value)
assert.True(t, state.ResultSet().IsDirty(0))
assert.True(t, state.ResultSet().IsDirty(2))
// Rescan the table and verify dirty items were not written
invokeCommand(t, readController.Rescan())
assert.Equal(t, "This is some value", state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Nil(t, state.ResultSet().Items()[2]["alpha"])
assert.False(t, state.ResultSet().IsDirty(0))
assert.False(t, state.ResultSet().IsDirty(2))
})
}
func TestTableWriteController_TouchItem(t *testing.T) { func TestTableWriteController_TouchItem(t *testing.T) {
t.Run("should put the selected item if unmodified", func(t *testing.T) { t.Run("should put the selected item if unmodified", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
service := tables.NewService(provider) service := tables.NewService(provider)
@ -440,8 +438,7 @@ func TestTableWriteController_TouchItem(t *testing.T) {
}) })
t.Run("should not put the selected item if modified", func(t *testing.T) { t.Run("should not put the selected item if modified", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
service := tables.NewService(provider) service := tables.NewService(provider)
@ -464,8 +461,7 @@ func TestTableWriteController_TouchItem(t *testing.T) {
func TestTableWriteController_NoisyTouchItem(t *testing.T) { func TestTableWriteController_NoisyTouchItem(t *testing.T) {
t.Run("should delete and put the selected item if unmodified", func(t *testing.T) { t.Run("should delete and put the selected item if unmodified", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
service := tables.NewService(provider) service := tables.NewService(provider)
@ -491,8 +487,7 @@ func TestTableWriteController_NoisyTouchItem(t *testing.T) {
}) })
t.Run("should not put the selected item if modified", func(t *testing.T) { t.Run("should not put the selected item if modified", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
service := tables.NewService(provider) service := tables.NewService(provider)

View file

@ -6,6 +6,7 @@ import (
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression"
"github.com/aws/aws-sdk-go-v2/service/dynamodb" "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-v2/service/dynamodb/types"
"github.com/lmika/awstools/internal/common/sliceutils"
"github.com/lmika/awstools/internal/dynamo-browse/models" "github.com/lmika/awstools/internal/dynamo-browse/models"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -14,6 +15,10 @@ type Provider struct {
client *dynamodb.Client client *dynamodb.Client
} }
func NewProvider(client *dynamodb.Client) *Provider {
return &Provider{client: client}
}
func (p *Provider) ListTables(ctx context.Context) ([]string, error) { func (p *Provider) ListTables(ctx context.Context) ([]string, error) {
out, err := p.client.ListTables(ctx, &dynamodb.ListTablesInput{}) out, err := p.client.ListTables(ctx, &dynamodb.ListTablesInput{})
if err != nil { if err != nil {
@ -60,8 +65,33 @@ func (p *Provider) PutItem(ctx context.Context, name string, item models.Item) e
return nil return nil
} }
func NewProvider(client *dynamodb.Client) *Provider { func (p *Provider) PutItems(ctx context.Context, name string, items []models.Item) error {
return &Provider{client: client} return p.batchPutItems(ctx, name, items)
}
func (p *Provider) batchPutItems(ctx context.Context, name string, items []models.Item) error {
reqs := len(items)/25 + 1
for rn := 0; rn < reqs; rn++ {
s, f := rn*25, (rn+1)*25
if f > len(items) {
f = len(items)
}
itemsInThisRequest := items[s:f]
writeRequests := sliceutils.Map(itemsInThisRequest, func(item models.Item) types.WriteRequest {
return types.WriteRequest{PutRequest: &types.PutRequest{Item: item}}
})
_, err := p.client.BatchWriteItem(ctx, &dynamodb.BatchWriteItemInput{
RequestItems: map[string][]types.WriteRequest{
name: writeRequests,
},
})
if err != nil {
errors.Wrapf(err, "unable to put page %v of back puts", rn)
}
}
return nil
} }
func (p *Provider) ScanItems(ctx context.Context, tableName string, filterExpr *expression.Expression, maxItems int) ([]models.Item, error) { func (p *Provider) ScanItems(ctx context.Context, tableName string, filterExpr *expression.Expression, maxItems int) ([]models.Item, error) {

View file

@ -2,6 +2,8 @@ package dynamo_test
import ( import (
"context" "context"
"fmt"
"github.com/lmika/awstools/internal/dynamo-browse/models"
"testing" "testing"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
@ -13,8 +15,7 @@ import (
func TestProvider_ScanItems(t *testing.T) { func TestProvider_ScanItems(t *testing.T) {
tableName := "test-table" tableName := "test-table"
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
t.Run("should return scanned items from the table", func(t *testing.T) { t.Run("should return scanned items from the table", func(t *testing.T) {
@ -38,12 +39,62 @@ func TestProvider_ScanItems(t *testing.T) {
}) })
} }
func TestProvider_PutItems(t *testing.T) {
tableName := "test-table"
scenarios := []struct {
maxItems int
}{
{maxItems: 3},
{maxItems: 13},
{maxItems: 25},
{maxItems: 48},
{maxItems: 73},
{maxItems: 103},
{maxItems: 291},
}
for _, scenario := range scenarios {
t.Run(fmt.Sprintf("should put items in batches: size %v", scenario.maxItems), func(t *testing.T) {
ctx := context.Background()
client := testdynamo.SetupTestTable(t, []testdynamo.TestData{
{
TableName: tableName,
},
})
provider := dynamo.NewProvider(client)
items := make([]models.Item, scenario.maxItems)
for i := 0; i < scenario.maxItems; i++ {
items[i] = models.Item{
"pk": &types.AttributeValueMemberS{Value: fmt.Sprintf("K#%v", i)},
"sk": &types.AttributeValueMemberS{Value: fmt.Sprintf("K#%v", i)},
"a": &types.AttributeValueMemberN{Value: fmt.Sprintf("%v", i)},
}
}
// Write the data
err := provider.PutItems(ctx, tableName, items)
assert.NoError(t, err)
// Verify the data
readItems, err := provider.ScanItems(ctx, tableName, nil, scenario.maxItems+5)
assert.NoError(t, err)
assert.Len(t, readItems, scenario.maxItems)
for i := 0; i < scenario.maxItems; i++ {
assert.Contains(t, readItems, items[i])
}
})
}
}
func TestProvider_DeleteItem(t *testing.T) { func TestProvider_DeleteItem(t *testing.T) {
tableName := "test-table" tableName := "test-table"
t.Run("should delete item if exists in table", func(t *testing.T) { t.Run("should delete item if exists in table", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
ctx := context.Background() ctx := context.Background()
@ -64,8 +115,7 @@ func TestProvider_DeleteItem(t *testing.T) {
}) })
t.Run("should do nothing if key does not exist", func(t *testing.T) { t.Run("should do nothing if key does not exist", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
ctx := context.Background() ctx := context.Background()
@ -85,8 +135,7 @@ func TestProvider_DeleteItem(t *testing.T) {
}) })
t.Run("should return error if table name does not exist", func(t *testing.T) { t.Run("should return error if table name does not exist", func(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
ctx := context.Background() ctx := context.Background()

View file

@ -14,4 +14,5 @@ type TableProvider interface {
ScanItems(ctx context.Context, tableName string, filterExpr *expression.Expression, maxItems int) ([]models.Item, error) ScanItems(ctx context.Context, tableName string, filterExpr *expression.Expression, maxItems int) ([]models.Item, error)
DeleteItem(ctx context.Context, tableName string, key map[string]types.AttributeValue) error DeleteItem(ctx context.Context, tableName string, key map[string]types.AttributeValue) error
PutItem(ctx context.Context, name string, item models.Item) error PutItem(ctx context.Context, name string, item models.Item) error
PutItems(ctx context.Context, name string, items []models.Item) error
} }

View file

@ -3,6 +3,7 @@ package tables
import ( import (
"context" "context"
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression"
"github.com/lmika/awstools/internal/common/sliceutils"
"strings" "strings"
"github.com/lmika/awstools/internal/dynamo-browse/models" "github.com/lmika/awstools/internal/dynamo-browse/models"
@ -114,6 +115,36 @@ func (s *Service) PutItemAt(ctx context.Context, resultSet *models.ResultSet, in
return nil return nil
} }
func (s *Service) PutSelectedItems(ctx context.Context, resultSet *models.ResultSet, shouldPut func(idx int) bool) (int, error) {
type dirtyItem struct {
item models.Item
idx int
}
dirtyItems := make([]dirtyItem, 0)
for i, item := range resultSet.Items() {
if shouldPut(i) {
dirtyItems = append(dirtyItems, dirtyItem{item, i})
}
}
if len(dirtyItems) == 0 {
return 0, nil
}
if err := s.provider.PutItems(ctx, resultSet.TableInfo.Name, sliceutils.Map(dirtyItems, func(t dirtyItem) models.Item {
return t.item
})); err != nil {
return 0, err
}
for _, di := range dirtyItems {
resultSet.SetDirty(di.idx, false)
resultSet.SetNew(di.idx, false)
}
return len(dirtyItems), nil
}
func (s *Service) Delete(ctx context.Context, tableInfo *models.TableInfo, items []models.Item) error { func (s *Service) Delete(ctx context.Context, tableInfo *models.TableInfo, items []models.Item) error {
for _, item := range items { for _, item := range items {
if err := s.provider.DeleteItem(ctx, tableInfo.Name, item.KeyValue(tableInfo)); err != nil { if err := s.provider.DeleteItem(ctx, tableInfo.Name, item.KeyValue(tableInfo)); err != nil {

View file

@ -13,8 +13,7 @@ import (
func TestService_Describe(t *testing.T) { func TestService_Describe(t *testing.T) {
tableName := "service-test-data" tableName := "service-test-data"
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
t.Run("return details of the table", func(t *testing.T) { t.Run("return details of the table", func(t *testing.T) {
@ -35,8 +34,7 @@ func TestService_Describe(t *testing.T) {
func TestService_Scan(t *testing.T) { func TestService_Scan(t *testing.T) {
tableName := "service-test-data" tableName := "service-test-data"
client, cleanupFn := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client) provider := dynamo.NewProvider(client)
t.Run("return all columns and fields in sorted order", func(t *testing.T) { t.Run("return all columns and fields in sorted order", func(t *testing.T) {
@ -52,9 +50,6 @@ func TestService_Scan(t *testing.T) {
// Hash first, then range, then columns in alphabetic order // Hash first, then range, then columns in alphabetic order
assert.Equal(t, rs.TableInfo, ti) assert.Equal(t, rs.TableInfo, ti)
assert.Equal(t, rs.Columns(), []string{"pk", "sk", "alpha", "beta", "gamma"}) assert.Equal(t, rs.Columns(), []string{"pk", "sk", "alpha", "beta", "gamma"})
//assert.Equal(t, rs.Items[0], testdynamo.TestRecordAsItem(t, testData[1]))
//assert.Equal(t, rs.Items[1], testdynamo.TestRecordAsItem(t, testData[0]))
//assert.Equal(t, rs.Items[2], testdynamo.TestRecordAsItem(t, testData[2]))
}) })
} }

View file

@ -95,7 +95,7 @@ func NewModel(rc *controllers.TableReadController, wc *controllers.TableWriteCon
}, },
"put": func(args []string) tea.Cmd { "put": func(args []string) tea.Cmd {
return wc.PutItem(dtv.SelectedItemIndex()) return wc.PutItems()
}, },
"touch": func(args []string) tea.Cmd { "touch": func(args []string) tea.Cmd {
return wc.TouchItem(dtv.SelectedItemIndex()) return wc.TouchItem(dtv.SelectedItemIndex())

View file

@ -18,7 +18,7 @@ type TestData struct {
Data []map[string]interface{} Data []map[string]interface{}
} }
func SetupTestTable(t *testing.T, testData []TestData) (*dynamodb.Client, func()) { func SetupTestTable(t *testing.T, testData []TestData) *dynamodb.Client {
t.Helper() t.Helper()
ctx := context.Background() ctx := context.Background()
@ -68,5 +68,5 @@ func SetupTestTable(t *testing.T, testData []TestData) (*dynamodb.Client, func()
} }
}) })
return dynamoClient, func() {} return dynamoClient
} }