issue-28: added default limit as a setting (#29)

This commit is contained in:
Leon Mika 2022-09-30 22:28:59 +10:00 committed by GitHub
parent 93ec519127
commit efdc7f9e25
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 194 additions and 148 deletions

View file

@ -34,6 +34,7 @@ func main() {
var flagLocal = flag.String("local", "", "local endpoint")
var flagDebug = flag.String("debug", "", "file to log debug messages")
var flagRO = flag.Bool("ro", false, "enable readonly mode")
var flagDefaultLimit = flag.Int("default-limit", 0, "default limit for queries and scans")
var flagWorkspace = flag.String("w", "", "workspace file")
flag.Parse()
@ -82,6 +83,11 @@ func main() {
cli.Fatalf("unable to set read-only mode: %v", err)
}
}
if *flagDefaultLimit > 0 {
if err := settingStore.SetDefaultLimit(*flagDefaultLimit); err != nil {
cli.Fatalf("unable to set default limit: %v", err)
}
}
tableService := tables.NewService(dynamoProvider, settingStore)
workspaceService := workspaces_service.NewService(resultSetSnapshotStore)

View file

@ -16,4 +16,6 @@ type TableReadService interface {
type SettingsProvider interface {
IsReadOnly() (bool, error)
SetReadOnly(ro bool) error
DefaultLimit() (limit int)
SetDefaultLimit(limit int) error
}

View file

@ -1,10 +1,12 @@
package controllers
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/audax/internal/common/ui/events"
"github.com/pkg/errors"
"log"
"strconv"
)
type SettingsController struct {
@ -35,7 +37,21 @@ func (sc *SettingsController) SetSetting(name string, value string) tea.Msg {
Message: "In read-write mode",
Next: SettingsUpdated{},
}
case "default-limit":
newLimit, err := strconv.Atoi(value)
if err != nil {
return errors.Wrapf(err, "bad value: %v", value)
}
if err := sc.settings.SetDefaultLimit(newLimit); err != nil {
return events.Error(err)
}
return events.WrappedStatusMsg{
Message: events.StatusMsg(fmt.Sprintf("Default query limit now %v", newLimit)),
Next: SettingsUpdated{},
}
}
return events.Error(errors.Errorf("unrecognised setting: %v", name))
}

View file

@ -9,7 +9,7 @@ import (
func TestSettingsController_SetSetting(t *testing.T) {
t.Run("read-only setting", func(t *testing.T) {
srv := newService(t, false)
srv := newService(t, serviceConfig{})
msg := invokeCommand(t, srv.settingsController.SetSetting("ro", ""))
@ -19,7 +19,7 @@ func TestSettingsController_SetSetting(t *testing.T) {
})
t.Run("read-write setting", func(t *testing.T) {
srv := newService(t, true)
srv := newService(t, serviceConfig{isReadOnly: true})
msg := invokeCommand(t, srv.settingsController.SetSetting("rw", ""))
@ -27,4 +27,13 @@ func TestSettingsController_SetSetting(t *testing.T) {
assert.IsType(t, events.WrappedStatusMsg{}, msg)
assert.IsType(t, controllers.SettingsUpdated{}, msg.(events.WrappedStatusMsg).Next)
})
t.Run("set default limit", func(t *testing.T) {
srv := newService(t, serviceConfig{})
assert.Equal(t, 1000, srv.settingProvider.DefaultLimit())
invokeCommand(t, srv.settingsController.SetSetting("default-limit", "20"))
assert.Equal(t, 20, srv.settingProvider.DefaultLimit())
})
}

View file

@ -6,11 +6,6 @@ import (
"github.com/lmika/audax/internal/common/ui/events"
"github.com/lmika/audax/internal/common/workspaces"
"github.com/lmika/audax/internal/dynamo-browse/controllers"
"github.com/lmika/audax/internal/dynamo-browse/providers/dynamo"
"github.com/lmika/audax/internal/dynamo-browse/providers/workspacestore"
"github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer"
"github.com/lmika/audax/internal/dynamo-browse/services/tables"
workspaces_service "github.com/lmika/audax/internal/dynamo-browse/services/workspaces"
"github.com/lmika/audax/test/testdynamo"
"github.com/stretchr/testify/assert"
"os"
@ -19,45 +14,28 @@ import (
)
func TestTableReadController_InitTable(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t))
workspaceService := workspaces_service.NewService(resultSetSnapshotStore)
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
provider := dynamo.NewProvider(client)
service := tables.NewService(provider, &mockedSetting{})
t.Run("should prompt for table if no table name provided", func(t *testing.T) {
readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, itemRendererService, "", false)
srv := newService(t, serviceConfig{})
event := readController.Init()
event := srv.readController.Init()
assert.IsType(t, controllers.PromptForTableMsg{}, event)
})
t.Run("should scan table if table name provided", func(t *testing.T) {
readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, itemRendererService, "", false)
srv := newService(t, serviceConfig{})
event := readController.Init()
event := srv.readController.Init()
assert.IsType(t, controllers.PromptForTableMsg{}, event)
})
}
func TestTableReadController_ListTables(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t))
workspaceService := workspaces_service.NewService(resultSetSnapshotStore)
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
provider := dynamo.NewProvider(client)
service := tables.NewService(provider, &mockedSetting{})
readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, itemRendererService, "", false)
t.Run("returns a list of tables", func(t *testing.T) {
event := readController.ListTables().(controllers.PromptForTableMsg)
srv := newService(t, serviceConfig{})
event := srv.readController.ListTables().(controllers.PromptForTableMsg)
assert.Equal(t, []string{"alpha-table", "bravo-table"}, event.Tables)
@ -71,59 +49,46 @@ func TestTableReadController_ListTables(t *testing.T) {
}
func TestTableReadController_Rescan(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t))
workspaceService := workspaces_service.NewService(resultSetSnapshotStore)
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
provider := dynamo.NewProvider(client)
service := tables.NewService(provider, &mockedSetting{})
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "bravo-table", false)
t.Run("should perform a rescan", func(t *testing.T) {
invokeCommand(t, readController.Init())
invokeCommand(t, readController.Rescan())
srv := newService(t, serviceConfig{tableName: "bravo-table"})
invokeCommand(t, srv.readController.Init())
invokeCommand(t, srv.readController.Rescan())
})
t.Run("should prompt to rescan if any dirty rows", func(t *testing.T) {
invokeCommand(t, readController.Init())
srv := newService(t, serviceConfig{tableName: "bravo-table"})
state.ResultSet().SetDirty(0, true)
invokeCommand(t, srv.readController.Init())
invokeCommandWithPrompt(t, readController.Rescan(), "y")
srv.state.ResultSet().SetDirty(0, true)
assert.False(t, state.ResultSet().IsDirty(0))
invokeCommandWithPrompt(t, srv.readController.Rescan(), "y")
assert.False(t, srv.state.ResultSet().IsDirty(0))
})
t.Run("should not rescan if any dirty rows", func(t *testing.T) {
invokeCommand(t, readController.Init())
srv := newService(t, serviceConfig{tableName: "bravo-table"})
state.ResultSet().SetDirty(0, true)
invokeCommand(t, srv.readController.Init())
invokeCommandWithPrompt(t, readController.Rescan(), "n")
srv.state.ResultSet().SetDirty(0, true)
assert.True(t, state.ResultSet().IsDirty(0))
invokeCommandWithPrompt(t, srv.readController.Rescan(), "n")
assert.True(t, srv.state.ResultSet().IsDirty(0))
})
}
func TestTableReadController_ExportCSV(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t))
workspaceService := workspaces_service.NewService(resultSetSnapshotStore)
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
provider := dynamo.NewProvider(client)
service := tables.NewService(provider, &mockedSetting{})
readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, itemRendererService, "bravo-table", false)
t.Run("should export result set to CSV file", func(t *testing.T) {
srv := newService(t, serviceConfig{tableName: "bravo-table"})
tempFile := tempFile(t)
invokeCommand(t, readController.Init())
invokeCommand(t, readController.ExportCSV(tempFile))
invokeCommand(t, srv.readController.Init())
invokeCommand(t, srv.readController.ExportCSV(tempFile))
bts, err := os.ReadFile(tempFile)
assert.NoError(t, err)
@ -137,33 +102,26 @@ func TestTableReadController_ExportCSV(t *testing.T) {
})
t.Run("should return error if result set is not set", func(t *testing.T) {
tempFile := tempFile(t)
readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, itemRendererService, "non-existant-table", false)
srv := newService(t, serviceConfig{tableName: "non-existant-table"})
invokeCommandExpectingError(t, readController.Init())
invokeCommandExpectingError(t, readController.ExportCSV(tempFile))
tempFile := tempFile(t)
invokeCommandExpectingError(t, srv.readController.Init())
invokeCommandExpectingError(t, srv.readController.ExportCSV(tempFile))
})
// Hidden items?
}
func TestTableReadController_Query(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t))
workspaceService := workspaces_service.NewService(resultSetSnapshotStore)
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
provider := dynamo.NewProvider(client)
service := tables.NewService(provider, &mockedSetting{})
readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, itemRendererService, "bravo-table", false)
t.Run("should run scan with filter based on user query", func(t *testing.T) {
srv := newService(t, serviceConfig{tableName: "bravo-table"})
tempFile := tempFile(t)
invokeCommand(t, readController.Init())
invokeCommandWithPrompts(t, readController.PromptForQuery(), `pk ^= "abc"`)
invokeCommand(t, readController.ExportCSV(tempFile))
invokeCommand(t, srv.readController.Init())
invokeCommandWithPrompts(t, srv.readController.PromptForQuery(), `pk ^= "abc"`)
invokeCommand(t, srv.readController.ExportCSV(tempFile))
bts, err := os.ReadFile(tempFile)
assert.NoError(t, err)
@ -175,11 +133,12 @@ func TestTableReadController_Query(t *testing.T) {
})
t.Run("should return error if result set is not set", func(t *testing.T) {
tempFile := tempFile(t)
readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, itemRendererService, "non-existant-table", false)
srv := newService(t, serviceConfig{tableName: "non-existant-table"})
invokeCommandExpectingError(t, readController.Init())
invokeCommandExpectingError(t, readController.ExportCSV(tempFile))
tempFile := tempFile(t)
invokeCommandExpectingError(t, srv.readController.Init())
invokeCommandExpectingError(t, srv.readController.ExportCSV(tempFile))
})
}

View file

@ -6,6 +6,7 @@ import (
"github.com/lmika/audax/internal/dynamo-browse/controllers"
"github.com/lmika/audax/internal/dynamo-browse/models"
"github.com/lmika/audax/internal/dynamo-browse/providers/dynamo"
"github.com/lmika/audax/internal/dynamo-browse/providers/settingstore"
"github.com/lmika/audax/internal/dynamo-browse/providers/workspacestore"
"github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer"
"github.com/lmika/audax/internal/dynamo-browse/services/tables"
@ -17,7 +18,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) {
srv := newService(t, false)
srv := newService(t, serviceConfig{tableName: "alpha-table"})
invokeCommand(t, srv.readController.Init())
assert.Len(t, srv.state.ResultSet().Items(), 3)
@ -38,7 +39,7 @@ func TestTableWriteController_NewItem(t *testing.T) {
})
t.Run("should do nothing when in read-only mode", func(t *testing.T) {
srv := newService(t, true)
srv := newService(t, serviceConfig{tableName: "alpha-table", isReadOnly: true})
invokeCommand(t, srv.readController.Init())
assert.Len(t, srv.state.ResultSet().Items(), 3)
@ -86,7 +87,7 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) {
for _, scenario := range scenarios {
t.Run(fmt.Sprintf("should set value of field: %v", scenario.attrKey), func(t *testing.T) {
srv := newService(t, false)
srv := newService(t, serviceConfig{tableName: "alpha-table"})
invokeCommand(t, srv.readController.Init())
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(0, models.UnsetItemType, scenario.attrKey), scenario.attrValue)
@ -99,7 +100,7 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) {
})
t.Run("should use type of selected item for marked fields if unspecified", func(t *testing.T) {
srv := newService(t, false)
srv := newService(t, serviceConfig{tableName: "alpha-table"})
invokeCommand(t, srv.readController.Init())
invokeCommand(t, srv.writeController.ToggleMark(0))
@ -146,7 +147,7 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) {
for _, scenario := range scenarios {
t.Run(fmt.Sprintf("should change the value of a field to type %v", scenario.attrType), func(t *testing.T) {
srv := newService(t, false)
srv := newService(t, serviceConfig{tableName: "alpha-table"})
invokeCommand(t, srv.readController.Init())
before, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
@ -165,7 +166,7 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) {
})
t.Run(fmt.Sprintf("should change value of nested field to type %v", scenario.attrType), func(t *testing.T) {
srv := newService(t, false)
srv := newService(t, serviceConfig{tableName: "alpha-table"})
invokeCommand(t, srv.readController.Init())
@ -193,7 +194,7 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) {
func TestTableWriteController_DeleteAttribute(t *testing.T) {
t.Run("should delete top level attribute", func(t *testing.T) {
srv := newService(t, false)
srv := newService(t, serviceConfig{tableName: "alpha-table"})
invokeCommand(t, srv.readController.Init())
before, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("age")
@ -207,7 +208,7 @@ func TestTableWriteController_DeleteAttribute(t *testing.T) {
})
t.Run("should delete attribute of map", func(t *testing.T) {
srv := newService(t, false)
srv := newService(t, serviceConfig{tableName: "alpha-table"})
invokeCommand(t, srv.readController.Init())
@ -228,7 +229,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) {
srv := newService(t, false)
srv := newService(t, serviceConfig{tableName: "alpha-table"})
// Read the table
invokeCommand(t, srv.readController.Init())
@ -248,7 +249,7 @@ func TestTableWriteController_PutItem(t *testing.T) {
})
t.Run("should not put the selected item if user does not confirm", func(t *testing.T) {
srv := newService(t, false)
srv := newService(t, serviceConfig{tableName: "alpha-table"})
// Read the table
invokeCommand(t, srv.readController.Init())
@ -272,7 +273,7 @@ func TestTableWriteController_PutItem(t *testing.T) {
})
t.Run("should not put the selected item if not dirty", func(t *testing.T) {
srv := newService(t, false)
srv := newService(t, serviceConfig{tableName: "alpha-table"})
// Read the table
invokeCommand(t, srv.readController.Init())
@ -284,7 +285,7 @@ func TestTableWriteController_PutItem(t *testing.T) {
})
t.Run("should not put the selected item if in read-only mode", func(t *testing.T) {
srv := newService(t, true)
srv := newService(t, serviceConfig{tableName: "alpha-table", isReadOnly: true})
// Read the table
invokeCommand(t, srv.readController.Init())
@ -310,7 +311,7 @@ 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) {
srv := newService(t, false)
srv := newService(t, serviceConfig{tableName: "alpha-table"})
invokeCommand(t, srv.readController.Init())
@ -331,7 +332,7 @@ func TestTableWriteController_PutItems(t *testing.T) {
})
t.Run("only put marked items", func(t *testing.T) {
srv := newService(t, false)
srv := newService(t, serviceConfig{tableName: "alpha-table"})
invokeCommand(t, srv.readController.Init())
@ -360,7 +361,7 @@ func TestTableWriteController_PutItems(t *testing.T) {
})
t.Run("do not put marked items which are not dirty", func(t *testing.T) {
srv := newService(t, false)
srv := newService(t, serviceConfig{tableName: "alpha-table"})
invokeCommand(t, srv.readController.Init())
@ -389,7 +390,7 @@ func TestTableWriteController_PutItems(t *testing.T) {
})
t.Run("do nothing if in read-only mode", func(t *testing.T) {
srv := newService(t, true)
srv := newService(t, serviceConfig{tableName: "alpha-table", isReadOnly: true})
invokeCommand(t, srv.readController.Init())
@ -420,7 +421,7 @@ func TestTableWriteController_PutItems(t *testing.T) {
func TestTableWriteController_TouchItem(t *testing.T) {
t.Run("should put the selected item if unmodified", func(t *testing.T) {
srv := newService(t, false)
srv := newService(t, serviceConfig{tableName: "alpha-table"})
// Read the table
invokeCommand(t, srv.readController.Init())
@ -439,7 +440,7 @@ func TestTableWriteController_TouchItem(t *testing.T) {
})
t.Run("should not put the selected item if modified", func(t *testing.T) {
srv := newService(t, false)
srv := newService(t, serviceConfig{tableName: "alpha-table"})
// Read the table
invokeCommand(t, srv.readController.Init())
@ -453,7 +454,7 @@ func TestTableWriteController_TouchItem(t *testing.T) {
})
t.Run("should not put the selected item if in read-only mode", func(t *testing.T) {
srv := newService(t, true)
srv := newService(t, serviceConfig{tableName: "alpha-table", isReadOnly: true})
// Read the table
invokeCommand(t, srv.readController.Init())
@ -474,7 +475,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) {
srv := newService(t, false)
srv := newService(t, serviceConfig{tableName: "alpha-table"})
// Read the table
invokeCommand(t, srv.readController.Init())
@ -493,7 +494,7 @@ func TestTableWriteController_NoisyTouchItem(t *testing.T) {
})
t.Run("should not put the selected item if modified", func(t *testing.T) {
srv := newService(t, false)
srv := newService(t, serviceConfig{tableName: "alpha-table"})
// Read the table
invokeCommand(t, srv.readController.Init())
@ -507,7 +508,7 @@ func TestTableWriteController_NoisyTouchItem(t *testing.T) {
})
t.Run("should not put the selected item if in read-only mode", func(t *testing.T) {
srv := newService(t, true)
srv := newService(t, serviceConfig{tableName: "alpha-table", isReadOnly: true})
// Read the table
invokeCommand(t, srv.readController.Init())
@ -522,7 +523,7 @@ func TestTableWriteController_NoisyTouchItem(t *testing.T) {
func TestTableWriteController_DeleteMarked(t *testing.T) {
t.Run("should delete marked items", func(t *testing.T) {
srv := newService(t, false)
srv := newService(t, serviceConfig{tableName: "alpha-table"})
// Read the table
invokeCommand(t, srv.readController.Init())
@ -543,7 +544,7 @@ func TestTableWriteController_DeleteMarked(t *testing.T) {
})
t.Run("should not delete marked items if in read-only mode", func(t *testing.T) {
srv := newService(t, true)
srv := newService(t, serviceConfig{tableName: "alpha-table", isReadOnly: true})
// Read the table
invokeCommand(t, srv.readController.Init())
@ -566,44 +567,46 @@ func TestTableWriteController_DeleteMarked(t *testing.T) {
type services struct {
state *controllers.State
settingProvider controllers.SettingsProvider
readController *controllers.TableReadController
writeController *controllers.TableWriteController
settingsController *controllers.SettingsController
}
func newService(t *testing.T, isReadOnly bool) *services {
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t))
type serviceConfig struct {
tableName string
isReadOnly bool
}
func newService(t *testing.T, cfg serviceConfig) *services {
ws := testWorkspace(t)
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(ws)
settingStore := settingstore.New(ws)
workspaceService := workspaces_service.NewService(resultSetSnapshotStore)
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
client := testdynamo.SetupTestTable(t, testData)
settingProvider := &mockedSetting{isReadOnly: isReadOnly}
provider := dynamo.NewProvider(client)
service := tables.NewService(provider, settingProvider)
service := tables.NewService(provider, settingStore)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController, settingProvider)
settingsController := controllers.NewSettingsController(settingProvider)
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, cfg.tableName, false)
writeController := controllers.NewTableWriteController(state, service, readController, settingStore)
settingsController := controllers.NewSettingsController(settingStore)
if cfg.isReadOnly {
if err := settingStore.SetReadOnly(cfg.isReadOnly); err != nil {
t.Errorf("cannot set ro: %v", err)
}
}
return &services{
state: state,
settingProvider: settingStore,
readController: readController,
writeController: writeController,
settingsController: settingsController,
}
}
type mockedSetting struct {
isReadOnly bool
}
func (ms *mockedSetting) SetReadOnly(ro bool) error {
ms.isReadOnly = ro
return nil
}
func (ms *mockedSetting) IsReadOnly() (bool, error) {
return ms.isReadOnly, nil
}

View file

@ -3,12 +3,17 @@ package settingstore
import (
"github.com/asdine/storm"
"github.com/lmika/audax/internal/common/workspaces"
"github.com/pkg/errors"
"log"
)
const settingBucket = "Settings"
const (
keyTableReadOnly = "table_ro"
keyTableReadOnly = "ro"
keyTableDefaultLimit = "default_limit"
defaultsDefaultLimit = 1000
)
type SettingStore struct {
@ -22,10 +27,30 @@ func New(ws *workspaces.Workspace) *SettingStore {
}
func (c *SettingStore) IsReadOnly() (b bool, err error) {
err = c.ws.Get(settingBucket, keyTableReadOnly, &b)
if err := c.ws.Get(settingBucket, keyTableReadOnly, &b); err != nil {
if errors.Is(err, storm.ErrNotFound) {
return false, nil
}
return false, err
}
return b, err
}
func (c *SettingStore) SetReadOnly(ro bool) error {
return c.ws.Set(settingBucket, keyTableReadOnly, ro)
}
func (c *SettingStore) DefaultLimit() (limit int) {
err := c.ws.Get(settingBucket, keyTableDefaultLimit, &limit)
if err != nil {
if !errors.Is(err, storm.ErrNotFound) {
log.Printf("warn: cannot get default limit from workspace, using default value: %v", err)
}
return defaultsDefaultLimit
}
return limit
}
func (c *SettingStore) SetDefaultLimit(limit int) error {
return errors.Wrapf(c.ws.Set(settingBucket, keyTableDefaultLimit, &limit), "cannot set default limit to %v", limit)
}

View file

@ -18,6 +18,7 @@ type TableProvider interface {
PutItems(ctx context.Context, name string, items []models.Item) error
}
type ROProvider interface {
type ConfigProvider interface {
IsReadOnly() (bool, error)
DefaultLimit() int
}

View file

@ -13,13 +13,13 @@ import (
type Service struct {
provider TableProvider
roProvider ROProvider
configProvider ConfigProvider
}
func NewService(provider TableProvider, roProvider ROProvider) *Service {
func NewService(provider TableProvider, roProvider ConfigProvider) *Service {
return &Service{
provider: provider,
roProvider: roProvider,
configProvider: roProvider,
}
}
@ -32,10 +32,10 @@ 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)
return s.doScan(ctx, tableInfo, nil, s.configProvider.DefaultLimit())
}
func (s *Service) doScan(ctx context.Context, tableInfo *models.TableInfo, expr models.Queryable) (*models.ResultSet, error) {
func (s *Service) doScan(ctx context.Context, tableInfo *models.TableInfo, expr models.Queryable, limit int) (*models.ResultSet, error) {
var (
filterExpr *expression.Expression
runAsQuery bool
@ -54,10 +54,10 @@ func (s *Service) doScan(ctx context.Context, tableInfo *models.TableInfo, expr
var results []models.Item
if runAsQuery {
log.Printf("executing query")
results, err = s.provider.QueryItems(ctx, tableInfo.Name, filterExpr, 1000)
results, err = s.provider.QueryItems(ctx, tableInfo.Name, filterExpr, limit)
} else {
log.Printf("executing scan")
results, err = s.provider.ScanItems(ctx, tableInfo.Name, filterExpr, 1000)
results, err = s.provider.ScanItems(ctx, tableInfo.Name, filterExpr, limit)
}
if err != nil {
@ -135,11 +135,11 @@ func (s *Service) Delete(ctx context.Context, tableInfo *models.TableInfo, items
}
func (s *Service) ScanOrQuery(ctx context.Context, tableInfo *models.TableInfo, expr models.Queryable) (*models.ResultSet, error) {
return s.doScan(ctx, tableInfo, expr)
return s.doScan(ctx, tableInfo, expr, s.configProvider.DefaultLimit())
}
func (s *Service) assertReadWrite() error {
b, err := s.roProvider.IsReadOnly()
b, err := s.configProvider.IsReadOnly()
if err != nil {
return err
} else if b {

View file

@ -19,7 +19,7 @@ func TestService_Describe(t *testing.T) {
t.Run("return details of the table", func(t *testing.T) {
ctx := context.Background()
service := tables.NewService(provider, mockedReadOnlyProvider{readOnly: false})
service := tables.NewService(provider, mockedConfigProvider{readOnly: false})
ti, err := service.Describe(ctx, tableName)
assert.NoError(t, err)
@ -40,17 +40,30 @@ func TestService_Scan(t *testing.T) {
t.Run("return all columns and fields in sorted order", func(t *testing.T) {
ctx := context.Background()
service := tables.NewService(provider, mockedReadOnlyProvider{readOnly: false})
service := tables.NewService(provider, mockedConfigProvider{readOnly: false})
ti, err := service.Describe(ctx, tableName)
assert.NoError(t, err)
rs, err := service.Scan(ctx, ti)
assert.NoError(t, err)
assert.Len(t, rs.Items(), 3)
// 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"})
})
t.Run("should honour default limits", func(t *testing.T) {
ctx := context.Background()
service := tables.NewService(provider, mockedConfigProvider{readOnly: false, defaultLimit: 2})
ti, err := service.Describe(ctx, tableName)
assert.NoError(t, err)
rs, err := service.Scan(ctx, ti)
assert.NoError(t, err)
assert.Len(t, rs.Items(), 2)
})
}
var testData = []testdynamo.TestData{
@ -78,10 +91,18 @@ var testData = []testdynamo.TestData{
},
}
type mockedReadOnlyProvider struct {
type mockedConfigProvider struct {
readOnly bool
defaultLimit int
}
func (m mockedReadOnlyProvider) IsReadOnly() (bool, error) {
func (m mockedConfigProvider) IsReadOnly() (bool, error) {
return m.readOnly, nil
}
func (m mockedConfigProvider) DefaultLimit() int {
if m.defaultLimit == 0 {
return 1000
}
return m.defaultLimit
}

View file

@ -106,6 +106,10 @@ func createTable(ctx context.Context, dynamoClient *dynamodb.Client, tableName s
type notROService struct{}
func (n notROService) DefaultLimit() int {
return 1000
}
func (n notROService) IsReadOnly() (bool, error) {
return false, nil
}