ucl: added more resultset functions
This commit is contained in:
parent
40f8dd76e2
commit
5088009672
2
go.mod
2
go.mod
|
@ -117,5 +117,5 @@ require (
|
|||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
ucl.lmika.dev v0.0.0-20250518033831-f79e91e26d78 // indirect
|
||||
ucl.lmika.dev v0.0.0-20250519120409-53b05b5ba6f8 // indirect
|
||||
)
|
||||
|
|
6
go.sum
6
go.sum
|
@ -454,3 +454,9 @@ ucl.lmika.dev v0.0.0-20250518024533-f4be44fcbc94 h1:x3IRtT1jbedblimi2hesKGBihg24
|
|||
ucl.lmika.dev v0.0.0-20250518024533-f4be44fcbc94/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||
ucl.lmika.dev v0.0.0-20250518033831-f79e91e26d78 h1:lbOZUb6whYMLI4win5QL+eLSgqc3N9TtTgT8hTipNl8=
|
||||
ucl.lmika.dev v0.0.0-20250518033831-f79e91e26d78/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||
ucl.lmika.dev v0.0.0-20250519111943-1173d163f5e3 h1:ZMQ1rkcAWa///c3bVvlXbtuqjfAWxDm01abQl3g/YVw=
|
||||
ucl.lmika.dev v0.0.0-20250519111943-1173d163f5e3/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||
ucl.lmika.dev v0.0.0-20250519114239-7ca821016e9a h1:dzBBFCY50+MQcJaQ90swdDyjzag5oIhwdfqbmZkvX3Q=
|
||||
ucl.lmika.dev v0.0.0-20250519114239-7ca821016e9a/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||
ucl.lmika.dev v0.0.0-20250519120409-53b05b5ba6f8 h1:h32JQi0d1MI86RaAMaEU7kvti4uSLX5XYe/nk2abApg=
|
||||
ucl.lmika.dev v0.0.0-20250519120409-53b05b5ba6f8/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||
|
|
|
@ -129,6 +129,84 @@ func (rs *rsModule) rsQuery(ctx context.Context, args ucl.CallArgs) (any, error)
|
|||
return newResultSetProxy(newResultSet), nil
|
||||
}
|
||||
|
||||
var rsScanDoc = repl.Doc{
|
||||
Brief: "Performs a scan of the table and returns the results as a result-set",
|
||||
Usage: "[-table NAME]",
|
||||
Args: []repl.ArgDoc{
|
||||
{Name: "-table", Brief: "Optional table name to use for the query"},
|
||||
},
|
||||
Detailed: `
|
||||
If no table is specified, then the value of @table will be used. If this is unavailable,
|
||||
the command will return an error.
|
||||
`,
|
||||
}
|
||||
|
||||
func (rs *rsModule) rsScan(ctx context.Context, args ucl.CallArgs) (_ any, err error) {
|
||||
var tableInfo *models.TableInfo
|
||||
if args.HasSwitch("table") {
|
||||
var tblName string
|
||||
if err := args.BindSwitch("table", &tblName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tableInfo, err = rs.tableService.Describe(ctx, tblName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if currRs := rs.state.ResultSet(); currRs != nil && currRs.TableInfo != nil {
|
||||
tableInfo = currRs.TableInfo
|
||||
} else {
|
||||
return nil, errors.New("no table specified")
|
||||
}
|
||||
|
||||
newResultSet, err := rs.tableService.Scan(context.Background(), tableInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newResultSetProxy(newResultSet), nil
|
||||
}
|
||||
|
||||
var rsNextPageDoc = repl.Doc{
|
||||
Brief: "Returns the next page of the passed in result-set",
|
||||
Usage: "RESULT_SET",
|
||||
Args: []repl.ArgDoc{
|
||||
{Name: "result-set", Brief: "Result set to fetch the next page of"},
|
||||
},
|
||||
Detailed: `
|
||||
If no next page exists, the command will return nil.
|
||||
`,
|
||||
}
|
||||
|
||||
func (rs *rsModule) rsNextPage(ctx context.Context, args ucl.CallArgs) (_ any, err error) {
|
||||
var rsProxy SimpleProxy[*models.ResultSet]
|
||||
|
||||
if err := args.Bind(&rsProxy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !rsProxy.value.HasNextPage() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
nextPage, err := rs.tableService.NextPage(ctx, rsProxy.value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newResultSetProxy(nextPage), nil
|
||||
}
|
||||
|
||||
func (rs *rsModule) rsUnion(ctx context.Context, args ucl.CallArgs) (_ any, err error) {
|
||||
var rsProxy1, rsProxy2 SimpleProxy[*models.ResultSet]
|
||||
|
||||
if err := args.Bind(&rsProxy1, &rsProxy2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newResultSetProxy(rsProxy1.ProxyValue().MergeWith(rsProxy2.ProxyValue())), nil
|
||||
}
|
||||
|
||||
func moduleRS(tableService *tables.Service, state *controllers.State) ucl.Module {
|
||||
m := &rsModule{
|
||||
tableService: tableService,
|
||||
|
@ -138,8 +216,11 @@ func moduleRS(tableService *tables.Service, state *controllers.State) ucl.Module
|
|||
return ucl.Module{
|
||||
Name: "rs",
|
||||
Builtins: map[string]ucl.BuiltinHandler{
|
||||
"new": m.rsNew,
|
||||
"query": m.rsQuery,
|
||||
"new": m.rsNew,
|
||||
"query": m.rsQuery,
|
||||
"scan": m.rsScan,
|
||||
"next-page": m.rsNextPage,
|
||||
"union": m.rsUnion,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package cmdpacks_test
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/lmika/dynamo-browse/internal/common/ui/commandctrl/cmdpacks"
|
||||
"github.com/lmika/dynamo-browse/internal/dynamo-browse/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
@ -13,7 +14,80 @@ func TestModRS_New(t *testing.T) {
|
|||
rsProxy, err := svc.CommandController.ExecuteAndWait(t.Context(), `rs:new`)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.IsType(t, rsProxy, &cmdpacks.ResultSetProxy{})
|
||||
assert.IsType(t, rsProxy, cmdpacks.SimpleProxy[*models.ResultSet]{})
|
||||
}
|
||||
|
||||
func TestModRS_NextPage(t *testing.T) {
|
||||
t.Run("multiple pages", func(t *testing.T) {
|
||||
svc := newService(t, withDataGenerator(largeTestData), withTable("large-table"), withDefaultLimit(20))
|
||||
|
||||
hasNextPage, err := svc.CommandController.ExecuteAndWait(t.Context(), `@resultset.HasNextPage`)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, hasNextPage.(bool))
|
||||
|
||||
// Page 2
|
||||
rsProxy, err := svc.CommandController.ExecuteAndWait(t.Context(), `rs:next-page @resultset`)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.IsType(t, cmdpacks.SimpleProxy[*models.ResultSet]{}, rsProxy)
|
||||
assert.Equal(t, 20, len(rsProxy.(cmdpacks.SimpleProxy[*models.ResultSet]).ProxyValue().Items()))
|
||||
|
||||
hasNextPage, err = svc.CommandController.ExecuteAndWait(t.Context(), `(rs:next-page @resultset).HasNextPage`)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, hasNextPage.(bool))
|
||||
|
||||
// Page 3
|
||||
rsProxy, err = svc.CommandController.ExecuteAndWait(t.Context(), `rs:next-page @resultset | rs:next-page`)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.IsType(t, cmdpacks.SimpleProxy[*models.ResultSet]{}, rsProxy)
|
||||
assert.Equal(t, 10, len(rsProxy.(cmdpacks.SimpleProxy[*models.ResultSet]).ProxyValue().Items()))
|
||||
|
||||
hasNextPage, err = svc.CommandController.ExecuteAndWait(t.Context(), `(rs:next-page @resultset | rs:next-page).HasNextPage`)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, hasNextPage.(bool))
|
||||
|
||||
// Last page
|
||||
rsProxy, err = svc.CommandController.ExecuteAndWait(t.Context(), `rs:next-page (rs:next-page @resultset | rs:next-page)`)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, rsProxy)
|
||||
})
|
||||
|
||||
t.Run("only one page", func(t *testing.T) {
|
||||
svc := newService(t)
|
||||
|
||||
hasNextPage, err := svc.CommandController.ExecuteAndWait(t.Context(), `@resultset.HasNextPage`)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, hasNextPage.(bool))
|
||||
|
||||
rsProxy, err := svc.CommandController.ExecuteAndWait(t.Context(), `rs:next-page @resultset`)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, rsProxy)
|
||||
})
|
||||
}
|
||||
|
||||
func TestModRS_Union(t *testing.T) {
|
||||
svc := newService(t, withDefaultLimit(2))
|
||||
|
||||
rsProxy, err := svc.CommandController.ExecuteAndWait(t.Context(), `
|
||||
$mr = rs:union @resultset (rs:next-page @resultset)
|
||||
|
||||
assert (eq (len $mr.Items) 3) "expected len == 3"
|
||||
assert (eq $mr.Items.(0).pk "abc") "expected 0.pk"
|
||||
assert (eq $mr.Items.(0).sk "111") "expected 0.sk"
|
||||
assert (eq $mr.Items.(1).pk "abc") "expected 1.pk"
|
||||
assert (eq $mr.Items.(1).sk "222") "expected 1.sk"
|
||||
assert (eq $mr.Items.(2).pk "bbb") "expected 2.pk"
|
||||
assert (eq $mr.Items.(2).sk "131") "expected 2.sk"
|
||||
|
||||
$mr
|
||||
`)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.IsType(t, cmdpacks.SimpleProxy[*models.ResultSet]{}, rsProxy)
|
||||
|
||||
rs := rsProxy.(cmdpacks.SimpleProxy[*models.ResultSet]).ProxyValue()
|
||||
assert.Equal(t, 3, len(rs.Items()))
|
||||
}
|
||||
|
||||
func TestModRS_Query(t *testing.T) {
|
||||
|
@ -65,11 +139,11 @@ func TestModRS_Query(t *testing.T) {
|
|||
res, err := svc.CommandController.ExecuteAndWait(t.Context(), tt.cmd)
|
||||
assert.NoError(t, err)
|
||||
|
||||
rs := res.(*cmdpacks.ResultSetProxy).RS
|
||||
rs := res.(cmdpacks.SimpleProxy[*models.ResultSet]).ProxyValue()
|
||||
assert.Len(t, rs.Items(), len(tt.wantRows))
|
||||
|
||||
for i, rowIndex := range tt.wantRows {
|
||||
for key, want := range testData[0].Data[rowIndex] {
|
||||
for key, want := range svc.testData[0].Data[rowIndex] {
|
||||
have, ok := rs.Items()[i].AttributeValueAsString(key)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, fmt.Sprint(want), have)
|
||||
|
|
|
@ -9,36 +9,50 @@ import (
|
|||
"ucl.lmika.dev/ucl"
|
||||
)
|
||||
|
||||
type proxyFields[T any] map[string]func(t T) ucl.Object
|
||||
|
||||
type simpleProxy[T comparable] struct {
|
||||
value T
|
||||
fields proxyFields[T]
|
||||
type proxyInfo[T comparable] struct {
|
||||
fields map[string]func(t T) ucl.Object
|
||||
lenFunc func(t T) int
|
||||
strFunc func(t T) string
|
||||
}
|
||||
|
||||
func (tp simpleProxy[T]) String() string {
|
||||
type SimpleProxy[T comparable] struct {
|
||||
value T
|
||||
proxyInfo *proxyInfo[T]
|
||||
}
|
||||
|
||||
func (tp SimpleProxy[T]) ProxyValue() T {
|
||||
return tp.value
|
||||
}
|
||||
|
||||
func (tp SimpleProxy[T]) String() string {
|
||||
if tp.proxyInfo.strFunc != nil {
|
||||
return tp.proxyInfo.strFunc(tp.value)
|
||||
}
|
||||
return fmt.Sprint(tp.value)
|
||||
}
|
||||
|
||||
func (tp simpleProxy[T]) Truthy() bool {
|
||||
func (tp SimpleProxy[T]) Truthy() bool {
|
||||
var zeroT T
|
||||
return tp.value != zeroT
|
||||
}
|
||||
|
||||
func (tp simpleProxy[T]) Len() int {
|
||||
return len(tp.fields)
|
||||
func (tp SimpleProxy[T]) Len() int {
|
||||
if tp.proxyInfo.lenFunc != nil {
|
||||
return tp.proxyInfo.lenFunc(tp.value)
|
||||
}
|
||||
return len(tp.proxyInfo.fields)
|
||||
}
|
||||
|
||||
func (tp simpleProxy[T]) Value(k string) ucl.Object {
|
||||
f, ok := tp.fields[k]
|
||||
func (tp SimpleProxy[T]) Value(k string) ucl.Object {
|
||||
f, ok := tp.proxyInfo.fields[k]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return f(tp.value)
|
||||
}
|
||||
|
||||
func (tp simpleProxy[T]) Each(fn func(k string, v ucl.Object) error) error {
|
||||
for key := range maps.Keys(tp.fields) {
|
||||
func (tp SimpleProxy[T]) Each(fn func(k string, v ucl.Object) error) error {
|
||||
for key := range maps.Keys(tp.proxyInfo.fields) {
|
||||
if err := fn(key, tp.Value(key)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -72,41 +86,63 @@ func (tp simpleProxyList[T]) Index(k int) ucl.Object {
|
|||
}
|
||||
|
||||
func newResultSetProxy(rs *models.ResultSet) ucl.Object {
|
||||
return simpleProxy[*models.ResultSet]{value: rs, fields: resultSetProxyFields}
|
||||
return SimpleProxy[*models.ResultSet]{value: rs, proxyInfo: resultSetProxyFields}
|
||||
}
|
||||
|
||||
var resultSetProxyFields = proxyFields[*models.ResultSet]{
|
||||
"Table": func(t *models.ResultSet) ucl.Object { return newTableProxy(t.TableInfo) },
|
||||
"Items": func(t *models.ResultSet) ucl.Object { return resultSetItemsProxy{t} },
|
||||
var resultSetProxyFields = &proxyInfo[*models.ResultSet]{
|
||||
lenFunc: func(t *models.ResultSet) int { return len(t.Items()) },
|
||||
strFunc: func(t *models.ResultSet) string {
|
||||
return fmt.Sprintf("ResultSet(%v:%d)", t.TableInfo.Name, len(t.Items()))
|
||||
},
|
||||
fields: map[string]func(t *models.ResultSet) ucl.Object{
|
||||
"Table": func(t *models.ResultSet) ucl.Object { return newTableProxy(t.TableInfo) },
|
||||
"Items": func(t *models.ResultSet) ucl.Object { return resultSetItemsProxy{t} },
|
||||
"HasNextPage": func(t *models.ResultSet) ucl.Object { return ucl.BoolObject(t.HasNextPage()) },
|
||||
},
|
||||
}
|
||||
|
||||
func newTableProxy(table *models.TableInfo) ucl.Object {
|
||||
return simpleProxy[*models.TableInfo]{value: table, fields: tableProxyFields}
|
||||
return SimpleProxy[*models.TableInfo]{value: table, proxyInfo: tableProxyFields}
|
||||
}
|
||||
|
||||
var tableProxyFields = proxyFields[*models.TableInfo]{
|
||||
"Name": func(t *models.TableInfo) ucl.Object { return ucl.StringObject(t.Name) },
|
||||
"Keys": func(t *models.TableInfo) ucl.Object { return newKeyAttributeProxy(t.Keys) },
|
||||
"DefinedAttributes": func(t *models.TableInfo) ucl.Object { return ucl.StringListObject(t.DefinedAttributes) },
|
||||
"GSIs": func(t *models.TableInfo) ucl.Object { return newSimpleProxyList(t.GSIs, newGSIProxy) },
|
||||
var tableProxyFields = &proxyInfo[*models.TableInfo]{
|
||||
strFunc: func(t *models.TableInfo) string {
|
||||
return fmt.Sprintf("Table(%v)", t.Name)
|
||||
},
|
||||
fields: map[string]func(t *models.TableInfo) ucl.Object{
|
||||
"Name": func(t *models.TableInfo) ucl.Object { return ucl.StringObject(t.Name) },
|
||||
"Keys": func(t *models.TableInfo) ucl.Object { return newKeyAttributeProxy(t.Keys) },
|
||||
"DefinedAttributes": func(t *models.TableInfo) ucl.Object { return ucl.StringListObject(t.DefinedAttributes) },
|
||||
"GSIs": func(t *models.TableInfo) ucl.Object { return newSimpleProxyList(t.GSIs, newGSIProxy) },
|
||||
},
|
||||
}
|
||||
|
||||
func newKeyAttributeProxy(keyAttrs models.KeyAttribute) ucl.Object {
|
||||
return simpleProxy[models.KeyAttribute]{value: keyAttrs, fields: keyAttributeProxyFields}
|
||||
return SimpleProxy[models.KeyAttribute]{value: keyAttrs, proxyInfo: keyAttributeProxyFields}
|
||||
}
|
||||
|
||||
var keyAttributeProxyFields = proxyFields[models.KeyAttribute]{
|
||||
"PartitionKey": func(t models.KeyAttribute) ucl.Object { return ucl.StringObject(t.PartitionKey) },
|
||||
"SortKey": func(t models.KeyAttribute) ucl.Object { return ucl.StringObject(t.SortKey) },
|
||||
var keyAttributeProxyFields = &proxyInfo[models.KeyAttribute]{
|
||||
strFunc: func(t models.KeyAttribute) string {
|
||||
return fmt.Sprintf("KeyAttribute(%v,%v)", t.PartitionKey, t.SortKey)
|
||||
},
|
||||
fields: map[string]func(t models.KeyAttribute) ucl.Object{
|
||||
"PartitionKey": func(t models.KeyAttribute) ucl.Object { return ucl.StringObject(t.PartitionKey) },
|
||||
"SortKey": func(t models.KeyAttribute) ucl.Object { return ucl.StringObject(t.SortKey) },
|
||||
},
|
||||
}
|
||||
|
||||
func newGSIProxy(gsi models.TableGSI) ucl.Object {
|
||||
return simpleProxy[models.TableGSI]{value: gsi, fields: gsiProxyFields}
|
||||
return SimpleProxy[models.TableGSI]{value: gsi, proxyInfo: gsiProxyFields}
|
||||
}
|
||||
|
||||
var gsiProxyFields = proxyFields[models.TableGSI]{
|
||||
"Name": func(t models.TableGSI) ucl.Object { return ucl.StringObject(t.Name) },
|
||||
"Keys": func(t models.TableGSI) ucl.Object { return newKeyAttributeProxy(t.Keys) },
|
||||
var gsiProxyFields = &proxyInfo[models.TableGSI]{
|
||||
strFunc: func(t models.TableGSI) string {
|
||||
return fmt.Sprintf("TableGSI(%v,(%v,%v))", t.Name, t.Keys.PartitionKey, t.Keys.SortKey)
|
||||
},
|
||||
fields: map[string]func(t models.TableGSI) ucl.Object{
|
||||
"Name": func(t models.TableGSI) ucl.Object { return ucl.StringObject(t.Name) },
|
||||
"Keys": func(t models.TableGSI) ucl.Object { return newKeyAttributeProxy(t.Keys) },
|
||||
},
|
||||
}
|
||||
|
||||
type resultSetItemsProxy struct {
|
||||
|
@ -114,7 +150,7 @@ type resultSetItemsProxy struct {
|
|||
}
|
||||
|
||||
func (ip resultSetItemsProxy) String() string {
|
||||
return "items"
|
||||
return "RSItem()"
|
||||
}
|
||||
|
||||
func (ip resultSetItemsProxy) Truthy() bool {
|
||||
|
@ -136,7 +172,7 @@ type itemProxy struct {
|
|||
}
|
||||
|
||||
func (ip itemProxy) String() string {
|
||||
return "item"
|
||||
return fmt.Sprintf("RSItems(%v)", len(ip.item))
|
||||
}
|
||||
|
||||
func (ip itemProxy) Truthy() bool {
|
||||
|
|
|
@ -26,9 +26,9 @@ func (rs resultSetPVar) Get(ctx context.Context) (any, error) {
|
|||
}
|
||||
|
||||
func (rs resultSetPVar) Set(ctx context.Context, value any) error {
|
||||
rsVal, ok := value.(simpleProxy[*models.ResultSet])
|
||||
rsVal, ok := value.(SimpleProxy[*models.ResultSet])
|
||||
if !ok {
|
||||
return errors.New("new value to @resultset is not a result set")
|
||||
return errors.New("new value to @resultset is nil or not a result set")
|
||||
}
|
||||
|
||||
msg := rs.readController.SetResultSet(rsVal.value)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package cmdpacks_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/lmika/dynamo-browse/internal/common/ui/commandctrl"
|
||||
"github.com/lmika/dynamo-browse/internal/common/ui/commandctrl/cmdpacks"
|
||||
"github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers"
|
||||
|
@ -48,14 +50,41 @@ func TestStdCmds_Mark(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type testDataGenerator func() []testdynamo.TestData
|
||||
type services struct {
|
||||
CommandController *commandctrl.CommandController
|
||||
SelItemIndex int
|
||||
|
||||
State *controllers.State
|
||||
|
||||
settingStore *settingstore.SettingStore
|
||||
table string
|
||||
|
||||
testDataGenerator testDataGenerator
|
||||
testData []testdynamo.TestData
|
||||
}
|
||||
|
||||
func newService(t *testing.T) *services {
|
||||
type serviceOpt func(*services)
|
||||
|
||||
func withDataGenerator(tg testDataGenerator) serviceOpt {
|
||||
return func(s *services) {
|
||||
s.testDataGenerator = tg
|
||||
}
|
||||
}
|
||||
|
||||
func withTable(table string) serviceOpt {
|
||||
return func(s *services) {
|
||||
s.table = table
|
||||
}
|
||||
}
|
||||
|
||||
func withDefaultLimit(limit int) serviceOpt {
|
||||
return func(s *services) {
|
||||
s.settingStore.SetDefaultLimit(limit)
|
||||
}
|
||||
}
|
||||
|
||||
func newService(t *testing.T, opts ...serviceOpt) *services {
|
||||
ws := testworkspace.New(t)
|
||||
|
||||
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(ws)
|
||||
|
@ -66,7 +95,18 @@ func newService(t *testing.T) *services {
|
|||
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
|
||||
inputHistoryService := inputhistory.New(inputHistoryStore)
|
||||
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
s := &services{
|
||||
table: "service-test-data",
|
||||
settingStore: settingStore,
|
||||
testDataGenerator: normalTestData,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(s)
|
||||
}
|
||||
|
||||
s.testData = s.testDataGenerator()
|
||||
client := testdynamo.SetupTestTable(t, s.testData)
|
||||
|
||||
provider := dynamo.NewProvider(client)
|
||||
service := tables.NewService(provider, settingStore)
|
||||
|
@ -74,6 +114,7 @@ func newService(t *testing.T) *services {
|
|||
|
||||
state := controllers.NewState()
|
||||
jobsController := controllers.NewJobsController(jobs.NewService(eventBus), eventBus, true)
|
||||
|
||||
readController := controllers.NewTableReadController(
|
||||
state,
|
||||
service,
|
||||
|
@ -84,7 +125,7 @@ func newService(t *testing.T) *services {
|
|||
eventBus,
|
||||
pasteboardprovider.NilProvider{},
|
||||
nil,
|
||||
"service-test-data",
|
||||
s.table,
|
||||
)
|
||||
writeController := controllers.NewTableWriteController(state, service, jobsController, readController, settingStore)
|
||||
settingsController := controllers.NewSettingsController(settingStore, eventBus)
|
||||
|
@ -102,10 +143,8 @@ func newService(t *testing.T) *services {
|
|||
},
|
||||
)
|
||||
|
||||
s := &services{
|
||||
State: state,
|
||||
CommandController: commandController,
|
||||
}
|
||||
s.State = state
|
||||
s.CommandController = commandController
|
||||
|
||||
commandController.SetUIStateProvider(s)
|
||||
readController.Init()
|
||||
|
@ -117,27 +156,57 @@ func (s *services) SelectedItemIndex() int {
|
|||
return s.SelItemIndex
|
||||
}
|
||||
|
||||
var testData = []testdynamo.TestData{
|
||||
{
|
||||
TableName: "service-test-data",
|
||||
Data: []map[string]interface{}{
|
||||
{
|
||||
"pk": "abc",
|
||||
"sk": "111",
|
||||
"alpha": "This is some value",
|
||||
},
|
||||
{
|
||||
"pk": "abc",
|
||||
"sk": "222",
|
||||
"alpha": "This is another some value",
|
||||
"beta": 1231,
|
||||
},
|
||||
{
|
||||
"pk": "bbb",
|
||||
"sk": "131",
|
||||
"beta": 2468,
|
||||
"gamma": "foobar",
|
||||
func (s *services) SetSelectedItemIndex(newIdx int) tea.Msg {
|
||||
s.SelItemIndex = newIdx
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalTestData() []testdynamo.TestData {
|
||||
return []testdynamo.TestData{
|
||||
{
|
||||
TableName: "service-test-data",
|
||||
Data: []map[string]interface{}{
|
||||
{
|
||||
"pk": "abc",
|
||||
"sk": "111",
|
||||
"alpha": "This is some value",
|
||||
},
|
||||
{
|
||||
"pk": "abc",
|
||||
"sk": "222",
|
||||
"alpha": "This is another some value",
|
||||
"beta": 1231,
|
||||
},
|
||||
{
|
||||
"pk": "bbb",
|
||||
"sk": "131",
|
||||
"beta": 2468,
|
||||
"gamma": "foobar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func largeTestData() []testdynamo.TestData {
|
||||
return []testdynamo.TestData{
|
||||
{
|
||||
TableName: "large-table",
|
||||
Data: genRow(50, func(i int) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"pk": fmt.Sprint(i),
|
||||
"sk": fmt.Sprint(i),
|
||||
"alpha": fmt.Sprintf("row %v", i),
|
||||
}
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func genRow(count int, mapFn func(int) map[string]interface{}) []map[string]interface{} {
|
||||
result := make([]map[string]interface{}, count)
|
||||
for i := 0; i < count; i++ {
|
||||
result[i] = mapFn(i)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -151,3 +151,37 @@ func (rs *ResultSet) Sort(criteria SortCriteria) {
|
|||
rs.sortCriteria = criteria
|
||||
Sort(rs.items, criteria)
|
||||
}
|
||||
|
||||
func (rs *ResultSet) MergeWith(otherRS *ResultSet) *ResultSet {
|
||||
type pksk struct {
|
||||
pk types.AttributeValue
|
||||
sk types.AttributeValue
|
||||
}
|
||||
|
||||
if !rs.TableInfo.Equal(otherRS.TableInfo) {
|
||||
return nil
|
||||
}
|
||||
|
||||
itemsInI := make(map[pksk]Item)
|
||||
newItems := make([]Item, 0, len(rs.Items())+len(otherRS.Items()))
|
||||
for _, item := range rs.Items() {
|
||||
pk, sk := item.PKSK(rs.TableInfo)
|
||||
itemsInI[pksk{pk, sk}] = item
|
||||
newItems = append(newItems, item)
|
||||
}
|
||||
|
||||
for _, item := range otherRS.Items() {
|
||||
pk, sk := item.PKSK(rs.TableInfo)
|
||||
if _, hasItem := itemsInI[pksk{pk, sk}]; !hasItem {
|
||||
newItems = append(newItems, item)
|
||||
}
|
||||
}
|
||||
|
||||
newResultSet := &ResultSet{
|
||||
Created: time.Now(),
|
||||
TableInfo: rs.TableInfo,
|
||||
}
|
||||
newResultSet.SetItems(newItems)
|
||||
|
||||
return newResultSet
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue