Added paging and the ability to specify the table in query script method (#44)

* paging: added controller for paging through results
* paging: added command and key binding for going to the next page
* paging: added the ability to specify the table in the query script method
* paging: have got exclusive start key written to backstack
This commit is contained in:
Leon Mika 2023-01-23 21:23:46 +11:00 committed by GitHub
parent 9e658b8619
commit ad1a77a257
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 444 additions and 95 deletions

View file

@ -31,6 +31,7 @@ type SessionService interface {
}
type QueryOptions struct {
TableName string
NamePlaceholders map[string]string
ValuePlaceholders map[string]types.AttributeValue
}

View file

@ -32,6 +32,11 @@ func (um *sessionModule) query(ctx context.Context, args ...object.Object) objec
return objErr
}
// Table name
if val, isVal := objMap.Get("table").(*object.String); isVal && val.Value() != "" {
options.TableName = val.Value()
}
// Placeholders
if argsVal, isArgsValMap := objMap.Get("args").(*object.Map); isArgsValMap {
options.NamePlaceholders = make(map[string]string)

View file

@ -79,6 +79,37 @@ func TestModSession_Query(t *testing.T) {
mockedSessionService.AssertExpectations(t)
})
t.Run("should successfully specify table name", func(t *testing.T) {
rs := &models.ResultSet{}
mockedSessionService := mocks.NewSessionService(t)
mockedSessionService.EXPECT().Query(mock.Anything, "some expr", scriptmanager.QueryOptions{
TableName: "some-table",
}).Return(rs, nil)
mockedUIService := mocks.NewUIService(t)
testFS := testScriptFile(t, "test.tm", `
res := session.query("some expr", {
table: "some-table",
})
assert(!res.is_err())
`)
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
srv.SetIFaces(scriptmanager.Ifaces{
UI: mockedUIService,
Session: mockedSessionService,
})
ctx := context.Background()
err := <-srv.RunAdHocScript(ctx, "test.tm")
assert.NoError(t, err)
mockedUIService.AssertExpectations(t)
mockedSessionService.AssertExpectations(t)
})
t.Run("should set placeholder values", func(t *testing.T) {
rs := &models.ResultSet{}

View file

@ -3,7 +3,6 @@ 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/audax/internal/dynamo-browse/models"
)
@ -11,11 +10,24 @@ import (
type TableProvider interface {
ListTables(ctx context.Context) ([]string, error)
DescribeTable(ctx context.Context, tableName string) (*models.TableInfo, error)
QueryItems(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
PutItem(ctx context.Context, name string, item models.Item) error
PutItems(ctx context.Context, name string, items []models.Item) error
QueryItems(
ctx context.Context,
tableName string,
filterExpr *expression.Expression,
exclusiveStartKey map[string]types.AttributeValue,
maxItems int,
) (items []models.Item, lastEvaluatedKey map[string]types.AttributeValue, err error)
ScanItems(
ctx context.Context,
tableName string,
filterExpr *expression.Expression,
exclusiveStartKey map[string]types.AttributeValue,
maxItems int,
) (item []models.Item, lastEvaluatedKey map[string]types.AttributeValue, err error)
}
type ConfigProvider interface {

View file

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/lmika/audax/internal/common/sliceutils"
"github.com/lmika/audax/internal/dynamo-browse/services/jobs"
"log"
@ -35,10 +36,16 @@ func (s *Service) Describe(ctx context.Context, table string) (*models.TableInfo
}
func (s *Service) Scan(ctx context.Context, tableInfo *models.TableInfo) (*models.ResultSet, error) {
return s.doScan(ctx, tableInfo, nil, s.configProvider.DefaultLimit())
return s.doScan(ctx, tableInfo, nil, nil, s.configProvider.DefaultLimit())
}
func (s *Service) doScan(ctx context.Context, tableInfo *models.TableInfo, expr models.Queryable, limit int) (*models.ResultSet, error) {
func (s *Service) doScan(
ctx context.Context,
tableInfo *models.TableInfo,
expr models.Queryable,
exclusiveStartKey map[string]types.AttributeValue,
limit int,
) (*models.ResultSet, error) {
var (
filterExpr *expression.Expression
runAsQuery bool
@ -55,26 +62,31 @@ func (s *Service) doScan(ctx context.Context, tableInfo *models.TableInfo, expr
}
var results []models.Item
var lastEvalKey map[string]types.AttributeValue
if runAsQuery {
log.Printf("executing query")
results, err = s.provider.QueryItems(ctx, tableInfo.Name, filterExpr, limit)
results, lastEvalKey, err = s.provider.QueryItems(ctx, tableInfo.Name, filterExpr, exclusiveStartKey, limit)
} else {
log.Printf("executing scan")
results, err = s.provider.ScanItems(ctx, tableInfo.Name, filterExpr, limit)
results, lastEvalKey, err = s.provider.ScanItems(ctx, tableInfo.Name, filterExpr, exclusiveStartKey, limit)
}
if err != nil && len(results) == 0 {
return &models.ResultSet{
TableInfo: tableInfo,
Query: expr,
TableInfo: tableInfo,
Query: expr,
ExclusiveStartKey: exclusiveStartKey,
LastEvaluatedKey: lastEvalKey,
}, errors.Wrapf(err, "unable to scan table %v", tableInfo.Name)
}
models.Sort(results, tableInfo)
resultSet := &models.ResultSet{
TableInfo: tableInfo,
Query: expr,
TableInfo: tableInfo,
Query: expr,
ExclusiveStartKey: exclusiveStartKey,
LastEvaluatedKey: lastEvalKey,
}
resultSet.SetItems(results)
resultSet.RefreshColumns()
@ -147,8 +159,12 @@ func (s *Service) Delete(ctx context.Context, tableInfo *models.TableInfo, items
return nil
}
func (s *Service) ScanOrQuery(ctx context.Context, tableInfo *models.TableInfo, expr models.Queryable) (*models.ResultSet, error) {
return s.doScan(ctx, tableInfo, expr, s.configProvider.DefaultLimit())
func (s *Service) ScanOrQuery(ctx context.Context, tableInfo *models.TableInfo, expr models.Queryable, exclusiveStartKey map[string]types.AttributeValue) (*models.ResultSet, error) {
return s.doScan(ctx, tableInfo, expr, exclusiveStartKey, s.configProvider.DefaultLimit())
}
func (s *Service) NextPage(ctx context.Context, resultSet *models.ResultSet) (*models.ResultSet, error) {
return s.doScan(ctx, resultSet.TableInfo, resultSet.Query, resultSet.LastEvaluatedKey, s.configProvider.DefaultLimit())
}
func (s *Service) assertReadWrite() error {