Modified put to put all dirty or marked items
This commit is contained in:
parent
9fee17a6a6
commit
2dbd664dd2
9
internal/common/sliceutils/map.go
Normal file
9
internal/common/sliceutils/map.go
Normal 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
|
||||
}
|
|
@ -23,22 +23,3 @@ func (ps *promptSequence) next() tea.Msg {
|
|||
}
|
||||
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
|
||||
//}
|
||||
|
|
|
@ -49,4 +49,10 @@ type PromptForTableMsg struct {
|
|||
OnSelected func(tableName string) tea.Cmd
|
||||
}
|
||||
|
||||
type ResultSetUpdated struct{}
|
||||
type ResultSetUpdated struct {
|
||||
statusMessage string
|
||||
}
|
||||
|
||||
func (rs ResultSetUpdated) StatusMessage() string {
|
||||
return rs.statusMessage
|
||||
}
|
||||
|
|
|
@ -15,8 +15,7 @@ import (
|
|||
)
|
||||
|
||||
func TestTableReadController_InitTable(t *testing.T) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
|
||||
provider := dynamo.NewProvider(client)
|
||||
service := tables.NewService(provider)
|
||||
|
@ -41,8 +40,7 @@ func TestTableReadController_InitTable(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTableReadController_ListTables(t *testing.T) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
|
||||
provider := dynamo.NewProvider(client)
|
||||
service := tables.NewService(provider)
|
||||
|
@ -65,8 +63,7 @@ func TestTableReadController_ListTables(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTableReadController_ExportCSV(t *testing.T) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
|
||||
provider := dynamo.NewProvider(client)
|
||||
service := tables.NewService(provider)
|
||||
|
@ -101,8 +98,7 @@ func TestTableReadController_ExportCSV(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTableReadController_Query(t *testing.T) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
|
||||
provider := dynamo.NewProvider(client)
|
||||
service := tables.NewService(provider)
|
||||
|
|
|
@ -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 {
|
||||
return func() tea.Msg {
|
||||
resultSet := twc.state.ResultSet()
|
||||
|
@ -325,7 +387,7 @@ func (twc *TableWriteController) DeleteMarked() tea.Cmd {
|
|||
}
|
||||
|
||||
return events.PromptForInputMsg{
|
||||
Prompt: fmt.Sprintf("delete %d items? ", len(markedItems)),
|
||||
Prompt: applyToN("delete ", len(markedItems), "item", "items", "? "),
|
||||
OnDone: func(value string) tea.Cmd {
|
||||
if value != "y" {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -14,8 +14,7 @@ import (
|
|||
|
||||
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) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
|
||||
provider := dynamo.NewProvider(client)
|
||||
service := tables.NewService(provider)
|
||||
|
@ -45,8 +44,7 @@ func TestTableWriteController_NewItem(t *testing.T) {
|
|||
|
||||
func TestTableWriteController_SetAttributeValue(t *testing.T) {
|
||||
t.Run("should preserve the type of the field if unspecified", func(t *testing.T) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
t.Cleanup(cleanupFn)
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
|
||||
provider := dynamo.NewProvider(client)
|
||||
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) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
t.Cleanup(cleanupFn)
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
|
||||
provider := dynamo.NewProvider(client)
|
||||
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) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
|
||||
provider := dynamo.NewProvider(client)
|
||||
service := tables.NewService(provider)
|
||||
|
@ -331,8 +231,7 @@ func TestTableWriteController_DeleteAttribute(t *testing.T) {
|
|||
|
||||
func TestTableWriteController_PutItem(t *testing.T) {
|
||||
t.Run("should put the selected item if dirty", func(t *testing.T) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
|
||||
provider := dynamo.NewProvider(client)
|
||||
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) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
|
||||
provider := dynamo.NewProvider(client)
|
||||
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) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
|
||||
provider := dynamo.NewProvider(client)
|
||||
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) {
|
||||
t.Run("should put the selected item if unmodified", func(t *testing.T) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
|
||||
provider := dynamo.NewProvider(client)
|
||||
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) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
|
||||
provider := dynamo.NewProvider(client)
|
||||
service := tables.NewService(provider)
|
||||
|
@ -464,8 +461,7 @@ func TestTableWriteController_TouchItem(t *testing.T) {
|
|||
|
||||
func TestTableWriteController_NoisyTouchItem(t *testing.T) {
|
||||
t.Run("should delete and put the selected item if unmodified", func(t *testing.T) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
|
||||
provider := dynamo.NewProvider(client)
|
||||
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) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
|
||||
provider := dynamo.NewProvider(client)
|
||||
service := tables.NewService(provider)
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"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/types"
|
||||
"github.com/lmika/awstools/internal/common/sliceutils"
|
||||
"github.com/lmika/awstools/internal/dynamo-browse/models"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -14,6 +15,10 @@ type Provider struct {
|
|||
client *dynamodb.Client
|
||||
}
|
||||
|
||||
func NewProvider(client *dynamodb.Client) *Provider {
|
||||
return &Provider{client: client}
|
||||
}
|
||||
|
||||
func (p *Provider) ListTables(ctx context.Context) ([]string, error) {
|
||||
out, err := p.client.ListTables(ctx, &dynamodb.ListTablesInput{})
|
||||
if err != nil {
|
||||
|
@ -60,8 +65,33 @@ func (p *Provider) PutItem(ctx context.Context, name string, item models.Item) e
|
|||
return nil
|
||||
}
|
||||
|
||||
func NewProvider(client *dynamodb.Client) *Provider {
|
||||
return &Provider{client: client}
|
||||
func (p *Provider) PutItems(ctx context.Context, name string, items []models.Item) error {
|
||||
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) {
|
||||
|
|
|
@ -2,6 +2,8 @@ package dynamo_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/lmika/awstools/internal/dynamo-browse/models"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
||||
|
@ -13,8 +15,7 @@ import (
|
|||
func TestProvider_ScanItems(t *testing.T) {
|
||||
tableName := "test-table"
|
||||
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
provider := dynamo.NewProvider(client)
|
||||
|
||||
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) {
|
||||
tableName := "test-table"
|
||||
|
||||
t.Run("should delete item if exists in table", func(t *testing.T) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
provider := dynamo.NewProvider(client)
|
||||
|
||||
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) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
provider := dynamo.NewProvider(client)
|
||||
|
||||
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) {
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
provider := dynamo.NewProvider(client)
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
|
@ -14,4 +14,5 @@ type TableProvider interface {
|
|||
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
|
||||
PutItem(ctx context.Context, name string, item models.Item) error
|
||||
PutItems(ctx context.Context, name string, items []models.Item) error
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package tables
|
|||
import (
|
||||
"context"
|
||||
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression"
|
||||
"github.com/lmika/awstools/internal/common/sliceutils"
|
||||
"strings"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
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 {
|
||||
for _, item := range items {
|
||||
if err := s.provider.DeleteItem(ctx, tableInfo.Name, item.KeyValue(tableInfo)); err != nil {
|
||||
|
|
|
@ -13,8 +13,7 @@ import (
|
|||
func TestService_Describe(t *testing.T) {
|
||||
tableName := "service-test-data"
|
||||
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
provider := dynamo.NewProvider(client)
|
||||
|
||||
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) {
|
||||
tableName := "service-test-data"
|
||||
|
||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||
defer cleanupFn()
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
provider := dynamo.NewProvider(client)
|
||||
|
||||
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
|
||||
assert.Equal(t, rs.TableInfo, ti)
|
||||
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]))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ func NewModel(rc *controllers.TableReadController, wc *controllers.TableWriteCon
|
|||
},
|
||||
|
||||
"put": func(args []string) tea.Cmd {
|
||||
return wc.PutItem(dtv.SelectedItemIndex())
|
||||
return wc.PutItems()
|
||||
},
|
||||
"touch": func(args []string) tea.Cmd {
|
||||
return wc.TouchItem(dtv.SelectedItemIndex())
|
||||
|
|
|
@ -18,7 +18,7 @@ type TestData struct {
|
|||
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()
|
||||
ctx := context.Background()
|
||||
|
||||
|
@ -68,5 +68,5 @@ func SetupTestTable(t *testing.T, testData []TestData) (*dynamodb.Client, func()
|
|||
}
|
||||
})
|
||||
|
||||
return dynamoClient, func() {}
|
||||
return dynamoClient
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue