dynamo-query: started working on queries

This commit is contained in:
Leon Mika 2022-06-21 13:37:07 +10:00
parent 41af399215
commit 54fab1b1c3
15 changed files with 305 additions and 16 deletions

19
go.mod
View file

@ -5,11 +5,11 @@ go 1.18
require (
github.com/alecthomas/participle/v2 v2.0.0-alpha7
github.com/asdine/storm v2.1.2+incompatible
github.com/aws/aws-sdk-go-v2 v1.16.1
github.com/aws/aws-sdk-go-v2 v1.16.5
github.com/aws/aws-sdk-go-v2/config v1.13.1
github.com/aws/aws-sdk-go-v2/credentials v1.8.0
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.8.0
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.15.0
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.9.4
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.15.7
github.com/aws/aws-sdk-go-v2/service/sqs v1.16.0
github.com/brianvoe/gofakeit/v6 v6.15.0
github.com/charmbracelet/bubbles v0.11.0
@ -25,18 +25,19 @@ require (
require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.4.10 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.10.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.8 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.12 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.5 // indirect
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.13.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.0 // indirect
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.13.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.6 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.7.0 // indirect
github.com/aws/aws-sdk-go-v2/service/ssm v1.24.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.9.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.14.0 // indirect
github.com/aws/smithy-go v1.11.2 // indirect
github.com/aws/smithy-go v1.11.3 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect

21
go.sum
View file

@ -12,12 +12,18 @@ github.com/aws/aws-sdk-go-v2 v1.15.0 h1:f9kWLNfyCzCB43eupDAk3/XgJ2EpgktiySD6leqs
github.com/aws/aws-sdk-go-v2 v1.15.0/go.mod h1:lJYcuZZEHWNIb6ugJjbQY1fykdoobWbOS7kJYb4APoI=
github.com/aws/aws-sdk-go-v2 v1.16.1 h1:udzee98w8H6ikRgtFdVN9JzzYEbi/quFfSvduZETJIU=
github.com/aws/aws-sdk-go-v2 v1.16.1/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU=
github.com/aws/aws-sdk-go-v2 v1.16.5 h1:Ah9h1TZD9E2S1LzHpViBO3Jz9FPL5+rmflmb8hXirtI=
github.com/aws/aws-sdk-go-v2 v1.16.5/go.mod h1:Wh7MEsmEApyL5hrWzpDkba4gwAPc5/piwLVLFnCxp48=
github.com/aws/aws-sdk-go-v2/config v1.13.1 h1:yLv8bfNoT4r+UvUKQKqRtdnvuWGMK5a82l4ru9Jvnuo=
github.com/aws/aws-sdk-go-v2/config v1.13.1/go.mod h1:Ba5Z4yL/UGbjQUzsiaN378YobhFo0MLfueXGiOsYtEs=
github.com/aws/aws-sdk-go-v2/credentials v1.8.0 h1:8Ow0WcyDesGNL0No11jcgb1JAtE+WtubqXjgxau+S0o=
github.com/aws/aws-sdk-go-v2/credentials v1.8.0/go.mod h1:gnMo58Vwx3Mu7hj1wpcG8DI0s57c9o42UQ6wgTQT5to=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.8.0 h1:XxTy21xVUkoCZOSGwf+AW22v8aK3eEbYMaGGQ3MbKKk=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.8.0/go.mod h1:6WkjzWenkrj3IgLPIPBBz4Qh99jNDF8L4Wj03vfMhAA=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.9.4 h1:EoyeSOfbSuKh+bQIDoZaVJjON6PF+dsSn5w1RhIpMD0=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.9.4/go.mod h1:bfCL7OwZS6owS06pahfGxhcgpLWj2W1sQASoYRuenag=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.4.10 h1:IBIZfpnWCTTQhH/bMvDcCMw10BtLBPYO30Ev8MLXMTY=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.4.10/go.mod h1:RL7aJOwlWj2N6wkE4nKR1S5M4iGph+xSu7JovwNYpyU=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.10.0 h1:NITDuUZO34mqtOwFWZiXo7yAHj7kf+XPE+EiKuCBNUI=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.10.0/go.mod h1:I6/fHT/fH460v09eg2gVrd8B/IqskhNdpcLH0WNO3QI=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.4 h1:CRiQJ4E2RhfDdqbie1ZYDo8QtIo75Mk7oTdJSfwJTMQ=
@ -26,22 +32,34 @@ github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.6 h1:xiGjGVQsem2cxoIX61
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.6/go.mod h1:SSPEdf9spsFgJyhjrXvawfpyzrXHBCUe+2eQ1CjC1Ak=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.8 h1:CDaO90VZVBAL1sK87S5oSPIrp7yZqORv1hPIi2UsTMk=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.8/go.mod h1:LnTQMTqbKsbtt+UI5+wPsB7jedW+2ZgozoPG8k6cMxg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.12 h1:Zt7DDk5V7SyQULUUwIKzsROtVzp/kVvcz15uQx/Tkow=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.12/go.mod h1:Afj/U8svX6sJ77Q+FPWMzabJ9QjbwP32YlopgKALUpg=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.2.0 h1:3ADoioDMOtF4uiK59vCpplpCwugEU+v4ZFD29jDL3RQ=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.2.0/go.mod h1:BsCSJHx5DnDXIrOcqB8KN1/B+hXLG/bi4Y6Vjcx/x9E=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.0 h1:bt3zw79tm209glISdMRCIVRCwvSDXxgAxh5KWe2qHkY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.0/go.mod h1:viTrxhAuejD+LszDahzAE2x40YjYWhMqzHxv2ZiWaME=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.2 h1:XXR3cdOcKRCTZf6ctcqpMf+go1BdzTm6+T9Ul5zxcMI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.2/go.mod h1:1x4ZP3Z8odssdhuLI+/1Tqw6Pt/VAaP4Tr8EUxHvPXE=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.6 h1:eeXdGVtXEe+2Jc49+/vAzna3FAQnUD4AagAw8tzbmfc=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.6/go.mod h1:FwpAKI+FBPIELJIdmQzlLtRe8LQSOreMcM2wBsPMvvc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.5 h1:ixotxbfTCFpqbuwFv/RcZwyzhkxPSYDYEMcj4niB5Uk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.5/go.mod h1:R3sWUqPcfXSiF/LSFJhjyJmpg9uV6yP2yv3YZZjldVI=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.15.0 h1:qnx+WyIH9/AD+wAxi05WCMNanO236ceqHg6hChCWs3M=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.15.0/go.mod h1:+Kc1UmbE37ijaAsb3KogW6FR8z0myjX6VtdcCkQEK0k=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.15.7 h1:Ls6kDGWNr3wxE8JypXgTTonHpQ1eRVCGNqaFHY2UASw=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.15.7/go.mod h1:+v2jeT4/39fCXUQ0ZfHQHMMiJljnmiuj16F03uAd9DY=
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.13.0 h1:s71pGCiLqqGRoUWtdJ2j4PazwEpZVwQc16na/4FfXdk=
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.13.0/go.mod h1:YGzTq/joAih4HRZZtMBWGP4bI8xVucOBQ9RvuanpclA=
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.13.7 h1:o2HKntJx3vr3y11NK58RA6tYKZKQo5PWWt/bs0rWR0U=
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.13.7/go.mod h1:FAVtDKEl/8WxRDQ33e2fz16RO1t4zeEwWIU5kR29xXs=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.0 h1:uhb7moM7VjqIEpWzTpCvceLDSwrWpaleXm39OnVjuLE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.0/go.mod h1:pA2St3Pu2Ldy6fBPY45Azoh1WBG4oS7eIKOd4XN7Meg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.2 h1:T/ywkX1ed+TsZVQccu/8rRJGxKZF/t0Ivgrb4MHTSeo=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.2/go.mod h1:RnloUnyZ4KN9JStGY1LuQ7Wzqh7V0f8FinmRdHYtuaA=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.0 h1:6Bc0KHhAyxGe15JUHrK+Udw7KhE5LN+5HKZjQGo4yDI=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.0/go.mod h1:0nXuX9UrkN4r0PX9TSKfcueGRfsdEYIKG4rjTeJ61X8=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.6 h1:JGrc3+kkyr848/wpG2+kWuzHK3H4Fyxj2jnXj8ijQ/Y=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.6/go.mod h1:zwvTysbXES8GDwFcwCPB8NkC+bCdio1abH+E+BRe/xg=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.7.0 h1:4QAOB3KrvI1ApJK14sliGr3Ie2pjyvNypn/lfzDHfUw=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.7.0/go.mod h1:K/qPe6AP2TGYv4l6n7c88zh9jWBDf6nHhvg1fx/EWfU=
github.com/aws/aws-sdk-go-v2/service/sqs v1.16.0 h1:dzWS4r8E9bA0TesHM40FSAtedwpTVCuTsLI8EziSqyk=
@ -58,6 +76,8 @@ github.com/aws/smithy-go v1.11.1 h1:IQ+lPZVkSM3FRtyaDox41R8YS6iwPMYIreejOgPW49g=
github.com/aws/smithy-go v1.11.1/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE=
github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
github.com/aws/smithy-go v1.11.3 h1:DQixirEFM9IaKxX1olZ3ke3nvxRS2xMDteKIDWxozW8=
github.com/aws/smithy-go v1.11.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/brianvoe/gofakeit/v6 v6.15.0 h1:lJPGJZ2/07TRGDazyTzD5b18N3y4tmmJpdhCUw18FlI=
github.com/brianvoe/gofakeit/v6 v6.15.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
github.com/calyptia/go-bubble-table v0.1.0 h1:mXpaaBlrHGH4K8v5PvM8YqBFT9jlysS1YOycU2u3gEQ=
@ -87,6 +107,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=

View file

@ -10,4 +10,5 @@ type TableReadService interface {
Describe(ctx context.Context, table string) (*models.TableInfo, error)
Scan(ctx context.Context, tableInfo *models.TableInfo) (*models.ResultSet, error)
Filter(resultSet *models.ResultSet, filter string) *models.ResultSet
ScanOrQuery(ctx context.Context, tableInfo *models.TableInfo, queryExpr string) (*models.ResultSet, error)
}

View file

@ -75,6 +75,29 @@ func (c *TableReadController) ScanTable(name string) tea.Cmd {
}
}
func (c *TableReadController) PromptForQuery() tea.Cmd {
return func() tea.Msg {
return events.PromptForInputMsg{
Prompt: "query: ",
OnDone: func(value string) tea.Cmd {
if value == "" {
return c.Rescan()
}
return func() tea.Msg {
resultSet := c.state.ResultSet()
newResultSet, err := c.tableService.ScanOrQuery(context.Background(), resultSet.TableInfo, value)
if err != nil {
return events.Error(err)
}
return c.setResultSetAndFilter(newResultSet, "")
}
},
}
}
}
func (c *TableReadController) Rescan() tea.Cmd {
return func() tea.Msg {
return c.doScan(context.Background(), c.state.ResultSet())

View file

@ -100,6 +100,40 @@ func TestTableReadController_ExportCSV(t *testing.T) {
// Hidden items?
}
func TestTableReadController_Query(t *testing.T) {
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
defer cleanupFn()
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
readController := controllers.NewTableReadController(controllers.NewState(), service, "alpha-table")
t.Run("should run scan with filter based on user query", func(t *testing.T) {
tempFile := tempFile(t)
invokeCommand(t, readController.Init())
invokeCommandWithPrompts(t, readController.PromptForQuery(), `pk ^= "abc"`)
invokeCommand(t, readController.ExportCSV(tempFile))
bts, err := os.ReadFile(tempFile)
assert.NoError(t, err)
assert.Equal(t, string(bts), strings.Join([]string{
"pk,sk,alpha,beta\n",
"abc,111,This is some value,\n",
"abc,222,This is another some value,1231\n",
}, ""))
})
t.Run("should return error if result set is not set", func(t *testing.T) {
tempFile := tempFile(t)
readController := controllers.NewTableReadController(controllers.NewState(), service, "non-existant-table")
invokeCommandExpectingError(t, readController.Init())
invokeCommandExpectingError(t, readController.ExportCSV(tempFile))
})
}
func tempFile(t *testing.T) string {
t.Helper()

View file

@ -0,0 +1,8 @@
package models
import "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression"
type QueryExecutionPlan struct {
CanQuery bool
Expression expression.Expression
}

View file

@ -0,0 +1,32 @@
package queryexpr
import (
"github.com/alecthomas/participle/v2"
"github.com/pkg/errors"
)
type astExpr struct {
Equality *astBinOp `parser:"@@"`
}
type astBinOp struct {
Name string `parser:"@Ident"`
Op string `parser:"@('^' '=' | '=')"`
Value *astLiteralValue `parser:"@@"`
}
type astLiteralValue struct {
String string `parser:"@String"`
}
var parser = participle.MustBuild(&astExpr{})
func Parse(expr string) (*QueryExpr, error) {
var ast astExpr
if err := parser.ParseString("expr", expr, &ast); err != nil {
return nil, errors.Wrapf(err, "cannot parse expression: '%v'", expr)
}
return &QueryExpr{ast: &ast}, nil
}

View file

@ -0,0 +1,52 @@
package queryexpr
import (
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression"
"github.com/lmika/awstools/internal/dynamo-browse/models"
"github.com/pkg/errors"
)
func (a *astExpr) calcQuery(tableInfo *models.TableInfo) (*models.QueryExecutionPlan, error) {
return a.Equality.calcQuery(tableInfo)
}
func (a *astBinOp) calcQuery(info *models.TableInfo) (*models.QueryExecutionPlan, error) {
// TODO: check if can be a query
cb, err := a.calcQueryForScan(info)
if err != nil {
return nil, err
}
builder := expression.NewBuilder()
builder = builder.WithFilter(cb)
expr, err := builder.Build()
if err != nil {
return nil, err
}
return &models.QueryExecutionPlan{
CanQuery: false,
Expression: expr,
}, nil
}
func (a *astBinOp) calcQueryForScan(info *models.TableInfo) (expression.ConditionBuilder, error) {
v, err := a.Value.goValue()
if err != nil {
return expression.ConditionBuilder{}, err
}
switch a.Op {
case "=":
return expression.Name(a.Name).Equal(expression.Value(v)), nil
case "^=":
strValue, isStrValue := v.(string)
if !isStrValue {
return expression.ConditionBuilder{}, errors.New("operand '^=' must be string")
}
return expression.Name(a.Name).BeginsWith(strValue), nil
}
return expression.ConditionBuilder{}, errors.Errorf("unrecognised operator: %v", a.Op)
}

View file

@ -0,0 +1,11 @@
package queryexpr
import "github.com/lmika/awstools/internal/dynamo-browse/models"
type QueryExpr struct {
ast *astExpr
}
func (md *QueryExpr) BuildQuery(tableInfo *models.TableInfo) (*models.QueryExecutionPlan, error) {
return md.ast.calcQuery(tableInfo)
}

View file

@ -0,0 +1,47 @@
package queryexpr_test
import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/lmika/awstools/internal/dynamo-browse/models/queryexpr"
"testing"
"github.com/lmika/awstools/internal/dynamo-browse/models"
"github.com/stretchr/testify/assert"
)
func TestModExpr_Query(t *testing.T) {
tableInfo := &models.TableInfo{
Name: "test",
Keys: models.KeyAttribute{
PartitionKey: "pk",
SortKey: "sk",
},
}
t.Run("perform query when request pk is fixed", func(t *testing.T) {
modExpr, err := queryexpr.Parse(`pk="prefix"`)
assert.NoError(t, err)
plan, err := modExpr.BuildQuery(tableInfo)
assert.NoError(t, err)
assert.False(t, plan.CanQuery)
assert.Equal(t, "#0 = :0", aws.ToString(plan.Expression.Filter()))
assert.Equal(t, "pk", plan.Expression.Names()["#0"])
assert.Equal(t, "prefix", plan.Expression.Values()[":0"].(*types.AttributeValueMemberS).Value)
})
t.Run("perform scan when request pk prefix", func(t *testing.T) {
modExpr, err := queryexpr.Parse(`pk^="prefix"`) // TODO: fix this so that '^ =' is invalid
assert.NoError(t, err)
plan, err := modExpr.BuildQuery(tableInfo)
assert.NoError(t, err)
assert.False(t, plan.CanQuery)
assert.Equal(t, "begins_with (#0, :0)", aws.ToString(plan.Expression.Filter()))
assert.Equal(t, "pk", plan.Expression.Names()["#0"])
assert.Equal(t, "prefix", plan.Expression.Values()[":0"].(*types.AttributeValueMemberS).Value)
})
}

View file

@ -0,0 +1,24 @@
package queryexpr
import (
"strconv"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/pkg/errors"
)
func (a *astLiteralValue) dynamoValue() (types.AttributeValue, error) {
s, err := strconv.Unquote(a.String)
if err != nil {
return nil, errors.Wrap(err, "cannot unquote string")
}
return &types.AttributeValueMemberS{Value: s}, nil
}
func (a *astLiteralValue) goValue() (any, error) {
s, err := strconv.Unquote(a.String)
if err != nil {
return nil, errors.Wrap(err, "cannot unquote string")
}
return s, nil
}

View file

@ -2,8 +2,8 @@ package dynamo
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"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/dynamo-browse/models"
@ -64,11 +64,18 @@ func NewProvider(client *dynamodb.Client) *Provider {
return &Provider{client: client}
}
func (p *Provider) ScanItems(ctx context.Context, tableName string, maxItems int) ([]models.Item, error) {
paginator := dynamodb.NewScanPaginator(p.client, &dynamodb.ScanInput{
func (p *Provider) ScanItems(ctx context.Context, tableName string, filterExpr *expression.Expression, maxItems int) ([]models.Item, error) {
input := &dynamodb.ScanInput{
TableName: aws.String(tableName),
Limit: aws.Int32(int32(maxItems)),
})
}
if filterExpr != nil {
input.FilterExpression = filterExpr.Filter()
input.ExpressionAttributeNames = filterExpr.Names()
input.ExpressionAttributeValues = filterExpr.Values()
}
paginator := dynamodb.NewScanPaginator(p.client, input)
items := make([]models.Item, 0)

View file

@ -2,6 +2,7 @@ package tables
import (
"context"
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/lmika/awstools/internal/dynamo-browse/models"
@ -10,7 +11,7 @@ import (
type TableProvider interface {
ListTables(ctx context.Context) ([]string, error)
DescribeTable(ctx context.Context, tableName string) (*models.TableInfo, error)
ScanItems(ctx context.Context, tableName string, 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
PutItem(ctx context.Context, name string, item models.Item) error
}

View file

@ -2,6 +2,8 @@ package tables
import (
"context"
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression"
"github.com/lmika/awstools/internal/dynamo-browse/models/queryexpr"
"sort"
"strings"
@ -28,7 +30,11 @@ func (s *Service) Describe(ctx context.Context, table string) (*models.TableInfo
}
func (s *Service) Scan(ctx context.Context, tableInfo *models.TableInfo) (*models.ResultSet, error) {
results, err := s.provider.ScanItems(ctx, tableInfo.Name, 1000)
return s.doScan(ctx, tableInfo, nil)
}
func (s *Service) doScan(ctx context.Context, tableInfo *models.TableInfo, filterExpr *expression.Expression) (*models.ResultSet, error) {
results, err := s.provider.ScanItems(ctx, tableInfo.Name, filterExpr, 1000)
if err != nil {
return nil, errors.Wrapf(err, "unable to scan table %v", tableInfo.Name)
}
@ -101,6 +107,25 @@ func (s *Service) Delete(ctx context.Context, tableInfo *models.TableInfo, items
return nil
}
func (s *Service) ScanOrQuery(ctx context.Context, tableInfo *models.TableInfo, queryExpr string) (*models.ResultSet, error) {
expr, err := queryexpr.Parse(queryExpr)
if err != nil {
return nil, err
}
plan, err := expr.BuildQuery(tableInfo)
if err != nil {
return nil, err
}
// TEMP
if plan.CanQuery {
return nil, errors.Errorf("queries not yet supported")
}
return s.doScan(ctx, tableInfo, &plan.Expression)
}
// TODO: move into a new service
func (s *Service) Filter(resultSet *models.ResultSet, filter string) *models.ResultSet {
for i, item := range resultSet.Items() {

View file

@ -98,8 +98,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if idx := m.tableView.SelectedItemIndex(); idx >= 0 {
return m, m.tableWriteController.ToggleMark(idx)
}
case "r":
case "R":
return m, m.tableReadController.Rescan()
case "?":
return m, m.tableReadController.PromptForQuery()
case "/":
return m, m.tableReadController.Filter()
case ":":