diff --git a/internal/dynamo-browse/models/query.go b/internal/dynamo-browse/models/query.go index cecde72..c3f6704 100644 --- a/internal/dynamo-browse/models/query.go +++ b/internal/dynamo-browse/models/query.go @@ -1,8 +1,48 @@ package models -import "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" +import ( + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" + "github.com/lmika/audax/internal/dynamo-browse/models/itemrender" +) type QueryExecutionPlan struct { CanQuery bool Expression expression.Expression } + +func (qep QueryExecutionPlan) Describe(dp DescribingPrinter) { + if qep.CanQuery { + dp.Println(" execute as: query") + } else { + dp.Println(" execute as: scan") + } + + if keyCond := aws.ToString(qep.Expression.KeyCondition()); keyCond != "" { + dp.Printf(" key condition: %v", keyCond) + } + if cond := aws.ToString(qep.Expression.Condition()); cond != "" { + dp.Printf(" condition: %v", cond) + } + if filter := aws.ToString(qep.Expression.Filter()); filter != "" { + dp.Printf(" filter: %v", filter) + } + if names := qep.Expression.Names(); len(names) > 0 { + dp.Println(" names:") + for k, v := range names { + dp.Printf(" %v = %v", k, v) + } + } + if values := qep.Expression.Values(); len(values) > 0 { + dp.Println(" values:") + for k, v := range values { + r := itemrender.ToRenderer(v) + dp.Printf(" %v (%v) = %v", k, r.TypeName(), r.StringValue()) + } + } +} + +type DescribingPrinter interface { + Println(v ...any) + Printf(format string, v ...any) +} diff --git a/internal/dynamo-browse/models/queryexpr/ast.go b/internal/dynamo-browse/models/queryexpr/ast.go index fde7c46..d38b319 100644 --- a/internal/dynamo-browse/models/queryexpr/ast.go +++ b/internal/dynamo-browse/models/queryexpr/ast.go @@ -81,11 +81,15 @@ type astPlaceholder struct { } type astLiteralValue struct { - StringVal *string `parser:"@String"` - IntVal *int64 `parser:"| @Int"` + StringVal *string `parser:"@String"` + IntVal *int64 `parser:"| @Int"` + TrueBoolValue bool `parser:"| @KwdTrue"` + FalseBoolValue bool `parser:"| @KwdFalse"` } var scanner = lexer.MustSimple([]lexer.SimpleRule{ + {Name: "KwdTrue", Pattern: `true`}, + {Name: "KwdFalse", Pattern: `false`}, {Name: "Eq", Pattern: `=|[\\^]=|[!]=`}, {Name: "Cmp", Pattern: `<[=]?|>[=]?`}, {Name: "String", Pattern: `"(\\"|[^"])*"`}, diff --git a/internal/dynamo-browse/models/queryexpr/expr_test.go b/internal/dynamo-browse/models/queryexpr/expr_test.go index 953a0ff..a7864dd 100644 --- a/internal/dynamo-browse/models/queryexpr/expr_test.go +++ b/internal/dynamo-browse/models/queryexpr/expr_test.go @@ -158,6 +158,14 @@ func TestModExpr_Query(t *testing.T) { scanCase("greater or equal to value", `num >= 100`, `#0 >= :0`, exprNameIsNumber(0, 0, "num", "100"), ), + scanCase("is true", `bool = true`, `#0 = :0`, + exprName(0, "bool"), + exprValueIsBool(0, true), + ), + scanCase("is false", `bool = false`, `#0 = :0`, + exprName(0, "bool"), + exprValueIsBool(0, false), + ), scanCase("with disjunctions", `pk="prefix" or sk="another"`, `(#0 = :0) OR (#1 = :1)`, @@ -846,6 +854,12 @@ func exprValueIsNumber(valIdx int, expected string) func(ss *scanScenario) { } } +func exprValueIsBool(valIdx int, expected bool) func(ss *scanScenario) { + return func(ss *scanScenario) { + ss.expectedValues[fmt.Sprintf(":%d", valIdx)] = &types.AttributeValueMemberBOOL{Value: expected} + } +} + func exprNameIsString(idx, valIdx int, name string, expected string) func(ss *scanScenario) { return func(ss *scanScenario) { ss.expectedNames[fmt.Sprintf("#%d", idx)] = name diff --git a/internal/dynamo-browse/models/queryexpr/values.go b/internal/dynamo-browse/models/queryexpr/values.go index 387f29d..0b41442 100644 --- a/internal/dynamo-browse/models/queryexpr/values.go +++ b/internal/dynamo-browse/models/queryexpr/values.go @@ -51,6 +51,10 @@ func (a *astLiteralValue) goValue() (any, error) { return s, nil case a.IntVal != nil: return *a.IntVal, nil + case a.TrueBoolValue: + return true, nil + case a.FalseBoolValue: + return false, nil } return nil, errors.New("unrecognised type") } diff --git a/internal/dynamo-browse/services/tables/service.go b/internal/dynamo-browse/services/tables/service.go index 154a5ad..7cf6612 100644 --- a/internal/dynamo-browse/services/tables/service.go +++ b/internal/dynamo-browse/services/tables/service.go @@ -59,15 +59,18 @@ func (s *Service) doScan( runAsQuery = plan.CanQuery filterExpr = &plan.Expression + + log.Printf("Running query over '%v'", tableInfo.Name) + plan.Describe(log.Default()) + } else { + log.Printf("Performing scan over '%v'", tableInfo.Name) } var results []models.Item var lastEvalKey map[string]types.AttributeValue if runAsQuery { - log.Printf("executing query") results, lastEvalKey, err = s.provider.QueryItems(ctx, tableInfo.Name, filterExpr, exclusiveStartKey, limit) } else { - log.Printf("executing scan") results, lastEvalKey, err = s.provider.ScanItems(ctx, tableInfo.Name, filterExpr, exclusiveStartKey, limit) }