2022-03-23 00:56:33 +00:00
|
|
|
package tables
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-10-09 23:15:25 +00:00
|
|
|
"fmt"
|
2022-06-21 03:37:07 +00:00
|
|
|
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression"
|
2022-07-28 11:36:16 +00:00
|
|
|
"github.com/lmika/audax/internal/common/sliceutils"
|
2022-10-09 23:15:25 +00:00
|
|
|
"github.com/lmika/audax/internal/dynamo-browse/services/jobs"
|
2022-09-19 11:14:03 +00:00
|
|
|
"log"
|
2022-03-30 10:55:16 +00:00
|
|
|
"strings"
|
2022-10-09 23:15:25 +00:00
|
|
|
"time"
|
2022-03-24 21:17:52 +00:00
|
|
|
|
2022-07-28 11:36:16 +00:00
|
|
|
"github.com/lmika/audax/internal/dynamo-browse/models"
|
2022-03-23 00:56:33 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Service struct {
|
2022-09-30 12:28:59 +00:00
|
|
|
provider TableProvider
|
|
|
|
configProvider ConfigProvider
|
2022-03-23 00:56:33 +00:00
|
|
|
}
|
|
|
|
|
2022-09-30 12:28:59 +00:00
|
|
|
func NewService(provider TableProvider, roProvider ConfigProvider) *Service {
|
2022-03-23 00:56:33 +00:00
|
|
|
return &Service{
|
2022-09-30 12:28:59 +00:00
|
|
|
provider: provider,
|
|
|
|
configProvider: roProvider,
|
2022-03-23 00:56:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-27 23:19:38 +00:00
|
|
|
func (s *Service) ListTables(ctx context.Context) ([]string, error) {
|
|
|
|
return s.provider.ListTables(ctx)
|
|
|
|
}
|
|
|
|
|
2022-03-24 21:13:43 +00:00
|
|
|
func (s *Service) Describe(ctx context.Context, table string) (*models.TableInfo, error) {
|
|
|
|
return s.provider.DescribeTable(ctx, table)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) Scan(ctx context.Context, tableInfo *models.TableInfo) (*models.ResultSet, error) {
|
2022-09-30 12:28:59 +00:00
|
|
|
return s.doScan(ctx, tableInfo, nil, s.configProvider.DefaultLimit())
|
2022-06-21 03:37:07 +00:00
|
|
|
}
|
|
|
|
|
2022-09-30 12:28:59 +00:00
|
|
|
func (s *Service) doScan(ctx context.Context, tableInfo *models.TableInfo, expr models.Queryable, limit int) (*models.ResultSet, error) {
|
2022-09-19 11:14:03 +00:00
|
|
|
var (
|
|
|
|
filterExpr *expression.Expression
|
|
|
|
runAsQuery bool
|
|
|
|
err error
|
|
|
|
)
|
2022-06-22 01:57:12 +00:00
|
|
|
if expr != nil {
|
|
|
|
plan, err := expr.Plan(tableInfo)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-09-19 11:14:03 +00:00
|
|
|
runAsQuery = plan.CanQuery
|
2022-06-22 01:57:12 +00:00
|
|
|
filterExpr = &plan.Expression
|
|
|
|
}
|
|
|
|
|
2022-09-19 11:14:03 +00:00
|
|
|
var results []models.Item
|
|
|
|
if runAsQuery {
|
|
|
|
log.Printf("executing query")
|
2022-09-30 12:28:59 +00:00
|
|
|
results, err = s.provider.QueryItems(ctx, tableInfo.Name, filterExpr, limit)
|
2022-09-19 11:14:03 +00:00
|
|
|
} else {
|
|
|
|
log.Printf("executing scan")
|
2022-09-30 12:28:59 +00:00
|
|
|
results, err = s.provider.ScanItems(ctx, tableInfo.Name, filterExpr, limit)
|
2022-09-19 11:14:03 +00:00
|
|
|
}
|
|
|
|
|
2022-10-09 23:15:25 +00:00
|
|
|
if err != nil && len(results) == 0 {
|
2022-03-24 21:13:43 +00:00
|
|
|
return nil, errors.Wrapf(err, "unable to scan table %v", tableInfo.Name)
|
2022-03-23 00:56:33 +00:00
|
|
|
}
|
|
|
|
|
2022-03-24 21:13:43 +00:00
|
|
|
models.Sort(results, tableInfo)
|
2022-03-23 11:02:46 +00:00
|
|
|
|
2022-03-30 10:55:16 +00:00
|
|
|
resultSet := &models.ResultSet{
|
2022-03-24 21:17:52 +00:00
|
|
|
TableInfo: tableInfo,
|
2022-06-22 01:57:12 +00:00
|
|
|
Query: expr,
|
2022-03-30 10:55:16 +00:00
|
|
|
}
|
|
|
|
resultSet.SetItems(results)
|
2022-06-27 06:05:59 +00:00
|
|
|
resultSet.RefreshColumns()
|
2022-03-30 10:55:16 +00:00
|
|
|
|
2022-10-09 23:15:25 +00:00
|
|
|
return resultSet, err
|
2022-03-23 00:56:33 +00:00
|
|
|
}
|
2022-03-23 04:40:31 +00:00
|
|
|
|
2022-03-24 21:13:43 +00:00
|
|
|
func (s *Service) Put(ctx context.Context, tableInfo *models.TableInfo, item models.Item) error {
|
2022-09-29 12:10:18 +00:00
|
|
|
if err := s.assertReadWrite(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-03-24 21:13:43 +00:00
|
|
|
return s.provider.PutItem(ctx, tableInfo.Name, item)
|
2022-03-24 01:54:32 +00:00
|
|
|
}
|
|
|
|
|
2022-05-26 00:17:21 +00:00
|
|
|
func (s *Service) PutItemAt(ctx context.Context, resultSet *models.ResultSet, index int) error {
|
2022-09-29 12:10:18 +00:00
|
|
|
if err := s.assertReadWrite(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-05-26 00:17:21 +00:00
|
|
|
item := resultSet.Items()[index]
|
|
|
|
if err := s.provider.PutItem(ctx, resultSet.TableInfo.Name, item); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
resultSet.SetDirty(index, false)
|
|
|
|
resultSet.SetNew(index, false)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-07-17 12:01:25 +00:00
|
|
|
func (s *Service) PutSelectedItems(ctx context.Context, resultSet *models.ResultSet, markedItems []models.ItemIndex) error {
|
2022-09-29 12:10:18 +00:00
|
|
|
if err := s.assertReadWrite(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-07-17 12:01:25 +00:00
|
|
|
if len(markedItems) == 0 {
|
|
|
|
return nil
|
2022-07-16 01:35:53 +00:00
|
|
|
}
|
|
|
|
|
2022-07-17 12:01:25 +00:00
|
|
|
if err := s.provider.PutItems(ctx, resultSet.TableInfo.Name, sliceutils.Map(markedItems, func(t models.ItemIndex) models.Item {
|
|
|
|
return t.Item
|
2022-07-16 01:35:53 +00:00
|
|
|
})); err != nil {
|
2022-07-17 12:01:25 +00:00
|
|
|
return err
|
2022-07-16 01:35:53 +00:00
|
|
|
}
|
|
|
|
|
2022-07-17 12:01:25 +00:00
|
|
|
for _, di := range markedItems {
|
|
|
|
resultSet.SetDirty(di.Index, false)
|
|
|
|
resultSet.SetNew(di.Index, false)
|
2022-07-16 01:35:53 +00:00
|
|
|
}
|
2022-07-17 12:01:25 +00:00
|
|
|
return nil
|
2022-07-16 01:35:53 +00:00
|
|
|
}
|
|
|
|
|
2022-03-30 10:04:30 +00:00
|
|
|
func (s *Service) Delete(ctx context.Context, tableInfo *models.TableInfo, items []models.Item) error {
|
2022-09-29 12:10:18 +00:00
|
|
|
if err := s.assertReadWrite(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-10-09 23:15:25 +00:00
|
|
|
nextUpdate := time.Now().Add(1 * time.Second)
|
|
|
|
|
|
|
|
for i, item := range items {
|
2022-03-30 10:04:30 +00:00
|
|
|
if err := s.provider.DeleteItem(ctx, tableInfo.Name, item.KeyValue(tableInfo)); err != nil {
|
|
|
|
return errors.Wrapf(err, "cannot delete item")
|
|
|
|
}
|
2022-10-09 23:15:25 +00:00
|
|
|
|
|
|
|
if time.Now().After(nextUpdate) {
|
|
|
|
jobs.PostUpdate(ctx, fmt.Sprintf("delete %d items", i))
|
|
|
|
nextUpdate = time.Now().Add(1 * time.Second)
|
|
|
|
}
|
2022-03-30 10:04:30 +00:00
|
|
|
}
|
|
|
|
return nil
|
2022-03-23 04:40:31 +00:00
|
|
|
}
|
2022-03-30 10:55:16 +00:00
|
|
|
|
2022-06-22 01:57:12 +00:00
|
|
|
func (s *Service) ScanOrQuery(ctx context.Context, tableInfo *models.TableInfo, expr models.Queryable) (*models.ResultSet, error) {
|
2022-09-30 12:28:59 +00:00
|
|
|
return s.doScan(ctx, tableInfo, expr, s.configProvider.DefaultLimit())
|
2022-06-21 03:37:07 +00:00
|
|
|
}
|
|
|
|
|
2022-09-29 12:10:18 +00:00
|
|
|
func (s *Service) assertReadWrite() error {
|
2022-09-30 12:28:59 +00:00
|
|
|
b, err := s.configProvider.IsReadOnly()
|
2022-09-29 12:10:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
} else if b {
|
|
|
|
return models.ErrReadOnly
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-03-30 10:55:16 +00:00
|
|
|
// TODO: move into a new service
|
|
|
|
func (s *Service) Filter(resultSet *models.ResultSet, filter string) *models.ResultSet {
|
2022-10-09 23:15:25 +00:00
|
|
|
if resultSet == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-03-30 10:55:16 +00:00
|
|
|
for i, item := range resultSet.Items() {
|
|
|
|
if filter == "" {
|
|
|
|
resultSet.SetHidden(i, false)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var shouldHide = true
|
|
|
|
for k := range item {
|
|
|
|
str, ok := item.AttributeValueAsString(k)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if strings.Contains(str, filter) {
|
|
|
|
shouldHide = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
resultSet.SetHidden(i, shouldHide)
|
|
|
|
}
|
|
|
|
|
|
|
|
return resultSet
|
|
|
|
}
|