Added async:query
Some checks failed
ci / Build (push) Has been cancelled

This commit is contained in:
Leon Mika 2025-11-04 14:24:19 +11:00
parent c11560e6cd
commit 08a3c162a2
12 changed files with 270 additions and 32 deletions

View file

@ -0,0 +1,96 @@
package cmdpacks
import (
"context"
"time"
"github.com/go-co-op/gocron/v2"
"lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/controllers"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/tables"
"ucl.lmika.dev/ucl"
)
type asyncModule struct {
tableService *tables.Service
state *controllers.State
}
func (m asyncModule) asyncDo(ctx context.Context, args ucl.CallArgs) (any, error) {
var block ucl.Invokable
if err := args.Bind(&block); err != nil {
return nil, err
}
return nil, commandctrl.ScheduleTask(ctx, func(ctx context.Context) error {
_, err := block.Invoke(ctx)
return err
})
}
func (m asyncModule) asyncIn(ctx context.Context, args ucl.CallArgs) (any, error) {
var (
duration int
block ucl.Invokable
)
if err := args.Bind(&duration, &block); err != nil {
return nil, err
}
_, err := commandctrl.CronScheduler(ctx).NewJob(
gocron.OneTimeJob(
gocron.OneTimeJobStartDateTime(time.Now().Add(time.Duration(duration)*time.Second)),
),
gocron.NewTask(func(ctx context.Context) {
commandctrl.ScheduleTask(ctx, func(ctx context.Context) error {
_, err := block.Invoke(ctx)
return err
})
}),
gocron.WithContext(ctx),
)
return nil, err
}
func (m asyncModule) asyncQuery(ctx context.Context, args ucl.CallArgs) (any, error) {
var (
block ucl.Invokable
)
args, q, tableInfo, err := parseQuery(ctx, args, m.state.ResultSet(), m.tableService, 1)
if err != nil {
return nil, err
}
if err := args.Bind(&block); err != nil {
return nil, err
}
return nil, commandctrl.ScheduleAuxTask(ctx, "query: "+q.String(), func(ctx context.Context) error {
newResultSet, err := m.tableService.ScanOrQuery(context.Background(), tableInfo, q, nil)
if err != nil {
return err
}
return commandctrl.ScheduleTask(ctx, func(ctx context.Context) error {
_, err := block.Invoke(ctx, newResultSetProxy(newResultSet))
return err
})
})
}
func moduleAsync(tableService *tables.Service, state *controllers.State) ucl.Module {
m := asyncModule{
state: state,
tableService: tableService,
}
return ucl.Module{
Name: "async",
Builtins: map[string]ucl.BuiltinHandler{
"do": m.asyncDo,
"in": m.asyncIn,
"query": m.asyncQuery,
},
}
}

View file

@ -2,6 +2,7 @@ package cmdpacks
import (
"context"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"ucl.lmika.dev/ucl"
)

View file

@ -2,6 +2,8 @@ package cmdpacks
import (
"context"
"time"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/pkg/errors"
"lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl"
@ -9,7 +11,6 @@ import (
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models/queryexpr"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/tables"
"time"
"ucl.lmika.dev/repl"
"ucl.lmika.dev/ucl"
)
@ -71,21 +72,22 @@ func parseQuery(
args ucl.CallArgs,
currentRS *models.ResultSet,
tablesService *tables.Service,
) (*queryexpr.QueryExpr, *models.TableInfo, error) {
extraArgs int,
) (ucl.CallArgs, *queryexpr.QueryExpr, *models.TableInfo, error) {
var expr string
if err := args.Bind(&expr); err != nil {
return nil, nil, err
return args, nil, nil, err
}
q, err := queryexpr.Parse(expr)
if err != nil {
return nil, nil, err
return args, nil, nil, err
}
if args.NArgs() > 0 {
if args.NArgs() > extraArgs {
var queryArgs ucl.Hashable
if err := args.Bind(&queryArgs); err != nil {
return nil, nil, err
return args, nil, nil, err
}
queryNames := map[string]string{}
@ -97,12 +99,15 @@ func parseQuery(
queryNames[k] = v.String()
switch v.(type) {
switch t := v.(type) {
case ucl.StringObject:
queryValues[k] = &types.AttributeValueMemberS{Value: v.String()}
case ucl.IntObject:
queryValues[k] = &types.AttributeValueMemberN{Value: v.String()}
// TODO: other types
case ucl.BoolObject:
queryValues[k] = &types.AttributeValueMemberBOOL{Value: t.Truthy()}
case attributeValueProxy:
queryValues[k] = t.value
}
return nil
})
@ -114,24 +119,24 @@ func parseQuery(
if args.HasSwitch("table") {
var tblName string
if err := args.BindSwitch("table", &tblName); err != nil {
return nil, nil, err
return args, nil, nil, err
}
tableInfo, err = tablesService.Describe(ctx, tblName)
if err != nil {
return nil, nil, err
return args, nil, nil, err
}
} else if currentRS != nil && currentRS.TableInfo != nil {
tableInfo = currentRS.TableInfo
} else {
return nil, nil, errors.New("no table specified")
return args, nil, nil, errors.New("no table specified")
}
return q, tableInfo, nil
return args, q, tableInfo, nil
}
func (rs *rsModule) rsQuery(ctx context.Context, args ucl.CallArgs) (any, error) {
q, tableInfo, err := parseQuery(ctx, args, rs.state.ResultSet(), rs.tableService)
_, q, tableInfo, err := parseQuery(ctx, args, rs.state.ResultSet(), rs.tableService, 0)
if err != nil {
return nil, err
}

View file

@ -214,6 +214,14 @@ func TestModRS_First(t *testing.T) {
rs = rs:query 'pk="zzz"' -table service-test-data
assert (eq $rs.First ()) "expected First to be nil"
`,
}, {
descr: "returns the first item using placeholders",
cmd: `
rs = rs:query 'pk=$v and sk=$u' [v:"abc" u:"222"] -table service-test-data
assert (eq $rs.First.pk "abc") "expected First.pk == abc"
assert (eq $rs.First.sk "222") "expected First.sk == 222"
assert (eq $rs.First.beta 1231) "expected First.beta == 1231"
`,
},
}
for _, tt := range tests {

View file

@ -175,7 +175,7 @@ func (m *uiModule) uiBind(ctx context.Context, args ucl.CallArgs) (any, error) {
}
func (m *uiModule) uiQuery(ctx context.Context, args ucl.CallArgs) (any, error) {
q, tableInfo, err := parseQuery(ctx, args, m.state.ResultSet(), m.tableService)
_, q, tableInfo, err := parseQuery(ctx, args, m.state.ResultSet(), m.tableService, 0)
if err != nil {
return nil, err
}
@ -210,6 +210,7 @@ func (m *uiModule) uiSetItemAnnotator(ctx context.Context, args ucl.CallArgs) (a
}
return fmt.Sprint(v)
}))
commandctrl.QueueRefresh(ctx)
return nil, nil
}

View file

@ -404,6 +404,7 @@ func (sc StandardCommands) InstOptions() []ucl.InstOption {
ucl.WithModule(modulePB(sc.PBProvider)),
ucl.WithModule(moduleOpt(sc.SettingsController)),
ucl.WithModule(moduleAttrValue()),
ucl.WithModule(moduleAsync(sc.TableService, sc.State)),
}
}
@ -453,4 +454,30 @@ ui:bind "view.toggle-marked-items" "M" {
mark all
}
}
proc _prep_officeCount {
openedOffices = 0
closedOffices = 0
async:query 'officeOpened=$v' [v:(av:true)] { |rs|
openedOffices = len $rs
async:query 'officeOpened=$u' [u:(av:false)] { |rs|
closedOffices = len $rs
ui:set-item-annotator { |rs item path|
if (eq $path.(-1) "officeOpened") {
if $item.officeOpened {
"Count = ${openedOffices}"
} else {
"Count = ${closedOffices}"
}
} else {
""
}
}
} -table business-addresses
} -table business-addresses
}
_prep_officeCount
`