- Most aspects of scans and queries can now be represented using the expression language - All constructs of the expression language can be used to evaluate items
492 lines
18 KiB
Go
492 lines
18 KiB
Go
package queryexpr_test
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
|
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
|
"github.com/lmika/audax/internal/dynamo-browse/models/queryexpr"
|
|
"testing"
|
|
|
|
"github.com/lmika/audax/internal/dynamo-browse/models"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestModExpr_Query(t *testing.T) {
|
|
tableInfo := &models.TableInfo{
|
|
Name: "test",
|
|
Keys: models.KeyAttribute{
|
|
PartitionKey: "pk",
|
|
SortKey: "sk",
|
|
},
|
|
}
|
|
|
|
t.Run("as queries", func(t *testing.T) {
|
|
scenarios := []scanScenario{
|
|
scanCase("when request pk is fixed",
|
|
`pk="prefix"`,
|
|
`#0 = :0`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
),
|
|
scanCase("when request pk is fixed in parens #1",
|
|
`(pk="prefix")`,
|
|
`#0 = :0`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
),
|
|
scanCase("when request pk is fixed in parens #2",
|
|
`(pk)="prefix"`,
|
|
`#0 = :0`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
),
|
|
scanCase("when request pk is fixed in parens #3",
|
|
`pk=("prefix")`,
|
|
`#0 = :0`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
),
|
|
scanCase("when request pk is in with a single value",
|
|
`pk in ("prefix")`,
|
|
`#0 = :0`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
),
|
|
scanCase("when request pk and sk is fixed",
|
|
`pk="prefix" and sk="another"`,
|
|
`(#0 = :0) AND (#1 = :1)`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
exprNameIsString(1, 1, "sk", "another"),
|
|
),
|
|
scanCase("when request pk and sk is fixed (using 'in')",
|
|
`pk in ("prefix") and sk in ("another")`,
|
|
`(#0 = :0) AND (#1 = :1)`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
exprNameIsString(1, 1, "sk", "another"),
|
|
),
|
|
scanCase("when request pk is equals and sk is prefix #1",
|
|
`pk="prefix" and sk^="another"`,
|
|
`(#0 = :0) AND (begins_with (#1, :1))`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
exprNameIsString(1, 1, "sk", "another"),
|
|
),
|
|
scanCase("when request pk is equals and sk is prefix #2",
|
|
`sk^="another" and pk="prefix"`,
|
|
`(#0 = :0) AND (begins_with (#1, :1))`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
exprNameIsString(1, 1, "sk", "another"),
|
|
),
|
|
scanCase("when request pk is equals and sk is less than",
|
|
`pk="prefix" and sk < 100`,
|
|
`(#0 = :0) AND (#1 < :1)`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
exprNameIsNumber(1, 1, "sk", "100"),
|
|
),
|
|
scanCase("when request pk is equals and sk is less or equal to",
|
|
`pk="prefix" and sk <= 100`,
|
|
`(#0 = :0) AND (#1 <= :1)`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
exprNameIsNumber(1, 1, "sk", "100"),
|
|
),
|
|
scanCase("when request pk is equals and sk is greater than",
|
|
`pk="prefix" and sk > 100`,
|
|
`(#0 = :0) AND (#1 > :1)`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
exprNameIsNumber(1, 1, "sk", "100"),
|
|
),
|
|
scanCase("when request pk is equals and sk is greater or equal to",
|
|
`pk="prefix" and sk >= 100`,
|
|
`(#0 = :0) AND (#1 >= :1)`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
exprNameIsNumber(1, 1, "sk", "100"),
|
|
),
|
|
}
|
|
|
|
for _, scenario := range scenarios {
|
|
t.Run(scenario.description, func(t *testing.T) {
|
|
modExpr, err := queryexpr.Parse(scenario.expression)
|
|
assert.NoError(t, err)
|
|
|
|
plan, err := modExpr.Plan(tableInfo)
|
|
assert.NoError(t, err)
|
|
|
|
assert.True(t, plan.CanQuery)
|
|
assert.Equal(t, scenario.expectedFilter, aws.ToString(plan.Expression.KeyCondition()))
|
|
for k, v := range scenario.expectedNames {
|
|
assert.Equal(t, v, plan.Expression.Names()[k])
|
|
}
|
|
for k, v := range scenario.expectedValues {
|
|
assert.Equal(t, v, plan.Expression.Values()[k])
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("as scans", func(t *testing.T) {
|
|
scenarios := []scanScenario{
|
|
scanCase("when request pk prefix", `pk^="prefix"`, `begins_with (#0, :0)`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
),
|
|
scanCase("when request sk equals something", `sk="something"`, `#0 = :0`,
|
|
exprNameIsString(0, 0, "sk", "something"),
|
|
),
|
|
scanCase("with not equal", `sk != "something"`, `#0 <> :0`,
|
|
exprNameIsString(0, 0, "sk", "something"),
|
|
),
|
|
scanCase("less than value", `num < 100`, `#0 < :0`,
|
|
exprNameIsNumber(0, 0, "num", "100"),
|
|
),
|
|
scanCase("less or equal to value", `num <= 100`, `#0 <= :0`,
|
|
exprNameIsNumber(0, 0, "num", "100"),
|
|
),
|
|
scanCase("greater than value", `num > 100`, `#0 > :0`,
|
|
exprNameIsNumber(0, 0, "num", "100"),
|
|
),
|
|
scanCase("greater or equal to value", `num >= 100`, `#0 >= :0`,
|
|
exprNameIsNumber(0, 0, "num", "100"),
|
|
),
|
|
scanCase("with disjunctions",
|
|
`pk="prefix" or sk="another"`,
|
|
`(#0 = :0) OR (#1 = :1)`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
exprNameIsString(1, 1, "sk", "another"),
|
|
),
|
|
scanCase("with disjunctions with numbers",
|
|
`pk="prefix" or num=123 and negnum=-131`,
|
|
`(#0 = :0) OR ((#1 = :1) AND (#2 = :2))`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
exprNameIsNumber(1, 1, "num", "123"),
|
|
exprNameIsNumber(2, 2, "negnum", "-131"),
|
|
),
|
|
scanCase("with disjunctions with numbers (different priority)",
|
|
`(pk="prefix" or num=123) and negnum=-131`,
|
|
`((#0 = :0) OR (#1 = :1)) AND (#2 = :2)`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
exprNameIsNumber(1, 1, "num", "123"),
|
|
exprNameIsNumber(2, 2, "negnum", "-131"),
|
|
),
|
|
scanCase("with disjunctions if pk is present twice in expression",
|
|
`pk="prefix" and pk="another"`,
|
|
`(#0 = :0) AND (#0 = :1)`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
exprNameIsString(0, 1, "pk", "another"),
|
|
),
|
|
scanCase("with not", `not pk="prefix"`, `NOT (#0 = :0)`,
|
|
exprNameIsString(0, 0, "pk", "prefix"),
|
|
),
|
|
|
|
scanCase("with in", `pk in ("alpha", "bravo", "charlie")`,
|
|
`#0 IN (:0, :1, :2)`,
|
|
exprName(0, "pk"),
|
|
exprValueIsString(0, "alpha"),
|
|
exprValueIsString(1, "bravo"),
|
|
exprValueIsString(2, "charlie"),
|
|
),
|
|
scanCase("with not in", `pk not in ("alpha", "bravo", "charlie")`,
|
|
`NOT (#0 IN (:0, :1, :2))`,
|
|
exprName(0, "pk"),
|
|
exprValueIsString(0, "alpha"),
|
|
exprValueIsString(1, "bravo"),
|
|
exprValueIsString(2, "charlie"),
|
|
),
|
|
scanCase("with in with single operand returning a sequence", `pk in range(1, 5)`,
|
|
`#0 IN (:0, :1, :2, :3, :4)`,
|
|
exprName(0, "pk"),
|
|
exprValueIsNumber(0, "1"),
|
|
exprValueIsNumber(1, "2"),
|
|
exprValueIsNumber(2, "3"),
|
|
exprValueIsNumber(3, "4"),
|
|
exprValueIsNumber(4, "5"),
|
|
),
|
|
scanCase("with in with single operand not returning a literal", `"foobar" in pk`,
|
|
`contains (#0, :0)`,
|
|
exprNameIsString(0, 0, "pk", "foobar"),
|
|
),
|
|
// TODO: in > 100 items ==> items OR items
|
|
|
|
scanCase("with is S", `pk is "S"`,
|
|
`attribute_type (#0, :0)`,
|
|
exprNameIsString(0, 0, "pk", "S"),
|
|
),
|
|
scanCase("with is N", `pk is "N"`,
|
|
`attribute_type (#0, :0)`,
|
|
exprNameIsString(0, 0, "pk", "N"),
|
|
),
|
|
scanCase("with is not N", `pk is not "SS"`,
|
|
`NOT (attribute_type (#0, :0))`,
|
|
exprNameIsString(0, 0, "pk", "SS"),
|
|
),
|
|
scanCase("with is any", `pk is "any"`,
|
|
`attribute_exists (#0)`,
|
|
exprName(0, "pk"),
|
|
),
|
|
scanCase("with is not any", `pk is not "any"`,
|
|
`attribute_not_exists (#0)`,
|
|
exprName(0, "pk"),
|
|
),
|
|
|
|
scanCase("the size function as a right-side operand", `ln=size(pk)`,
|
|
`#0 = size (#1)`,
|
|
exprName(0, "ln"),
|
|
exprName(1, "pk"),
|
|
),
|
|
scanCase("the size function as a left-side operand #1", `size(pk) = 123`,
|
|
`size (#0) = :0`,
|
|
exprNameIsNumber(0, 0, "pk", "123"),
|
|
),
|
|
scanCase("the size function as a left-side operand #2", `size(pk) > 123`,
|
|
`size (#0) > :0`,
|
|
exprNameIsNumber(0, 0, "pk", "123"),
|
|
),
|
|
scanCase("the size function on both sizes",
|
|
`size(pk) != size(sk) and size(pk) > size(third) and size(pk) = 131`,
|
|
`(size (#0) <> size (#1)) AND (size (#0) > size (#2)) AND (size (#0) = :0)`,
|
|
exprName(0, "pk"),
|
|
exprName(1, "sk"),
|
|
exprName(2, "third"),
|
|
exprValueIsNumber(0, "131"),
|
|
),
|
|
|
|
// TODO: the contains function
|
|
}
|
|
|
|
for _, scenario := range scenarios {
|
|
t.Run(scenario.description, func(t *testing.T) {
|
|
modExpr, err := queryexpr.Parse(scenario.expression)
|
|
assert.NoError(t, err)
|
|
|
|
plan, err := modExpr.Plan(tableInfo)
|
|
assert.NoError(t, err)
|
|
|
|
assert.False(t, plan.CanQuery)
|
|
assert.Equal(t, scenario.expectedFilter, aws.ToString(plan.Expression.Filter()))
|
|
for k, v := range scenario.expectedNames {
|
|
assert.Equal(t, v, plan.Expression.Names()[k])
|
|
}
|
|
for k, v := range scenario.expectedValues {
|
|
assert.Equal(t, v, plan.Expression.Values()[k])
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestQueryExpr_EvalItem(t *testing.T) {
|
|
var (
|
|
item = models.Item{
|
|
"alpha": &types.AttributeValueMemberS{Value: "alpha"},
|
|
"bravo": &types.AttributeValueMemberN{Value: "123"},
|
|
"charlie": &types.AttributeValueMemberM{
|
|
Value: map[string]types.AttributeValue{
|
|
"door": &types.AttributeValueMemberS{Value: "red"},
|
|
"tree": &types.AttributeValueMemberS{Value: "green"},
|
|
},
|
|
},
|
|
"prime": &types.AttributeValueMemberL{
|
|
Value: []types.AttributeValue{
|
|
&types.AttributeValueMemberN{Value: "2"},
|
|
&types.AttributeValueMemberN{Value: "3"},
|
|
&types.AttributeValueMemberN{Value: "5"},
|
|
&types.AttributeValueMemberN{Value: "7"},
|
|
},
|
|
},
|
|
"three": &types.AttributeValueMemberN{Value: "3"},
|
|
}
|
|
)
|
|
|
|
t.Run("simple values", func(t *testing.T) {
|
|
scenarios := []struct {
|
|
expr string
|
|
expected types.AttributeValue
|
|
}{
|
|
// Simple values
|
|
{expr: `alpha`, expected: &types.AttributeValueMemberS{Value: "alpha"}},
|
|
{expr: `bravo`, expected: &types.AttributeValueMemberN{Value: "123"}},
|
|
{expr: `charlie`, expected: item["charlie"]},
|
|
{expr: `missing`, expected: nil},
|
|
|
|
// Equality with literal
|
|
{expr: `alpha="alpha"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `alpha!="not alpha"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `bravo=123`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `charlie.tree="green"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `alpha^="al"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `alpha="foobar"`, expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
{expr: `alpha^="need-something"`, expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
|
|
// Comparison
|
|
{expr: "three > 4", expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
{expr: "three >= 4", expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
{expr: "three < 4", expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: "three <= 4", expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: "three > 3", expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
{expr: "three >= 3", expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: "three < 3", expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
{expr: "three <= 3", expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: "three > 2", expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: "three >= 2", expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: "three < 2", expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
{expr: "three <= 2", expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
|
|
// In
|
|
{expr: "three in (2, 3, 4, 5)", expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: "three in (20, 30, 40)", expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
{expr: `alpha in ("alpha", "beta", "gamma", "delta")`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `alpha in ("ey", "be", "see")`, expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
{expr: `three in prime`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `1 in prime`, expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
{expr: `"door" in charlie`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `"sky" in charlie`, expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
{expr: `"al" in alpha`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `"cent" in "percentage"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
|
|
// Is
|
|
{expr: `alpha is "S"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `alpha is not "N"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `three is "N"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `three is not "S"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `(three = 3) is "BOOL"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `prime is "L"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `charlie is "M"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
|
|
{expr: `alpha is "any"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `three is "any"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `(three = 3) is "any"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `charlie is "any"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `prime is "any"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `undef is not "any"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
|
|
// Size
|
|
{expr: `size(alpha)`, expected: &types.AttributeValueMemberN{Value: "5"}},
|
|
{expr: `size("This is a test")`, expected: &types.AttributeValueMemberN{Value: "14"}},
|
|
{expr: `size(charlie)`, expected: &types.AttributeValueMemberN{Value: "2"}},
|
|
{expr: `size(prime)`, expected: &types.AttributeValueMemberN{Value: "4"}},
|
|
|
|
// Dot values
|
|
{expr: `charlie.door`, expected: &types.AttributeValueMemberS{Value: "red"}},
|
|
{expr: `charlie.tree`, expected: &types.AttributeValueMemberS{Value: "green"}},
|
|
|
|
// Conjunction
|
|
{expr: `alpha="alpha" and bravo=123`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `alpha="alpha" and bravo=321`, expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
{expr: `alpha="bravo" and bravo=123`, expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
{expr: `alpha="bravo" and bravo=321`, expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
{expr: `alpha="alpha" and bravo=123 and charlie.door="red"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `alpha="alpha" and bravo=123 and charlie.door^="green"`, expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
|
|
// Disjunction
|
|
{expr: `alpha="alpha" or bravo=123`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `alpha="alpha" or bravo=321`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `alpha="bravo" or bravo=123`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `alpha="bravo" or bravo=321`, expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
{expr: `alpha="alpha" or bravo=123 or charlie.tree="green"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `alpha="bravo" or bravo=321 or charlie.tree^="red"`, expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
|
|
// Bool negation
|
|
{expr: `not alpha="alpha"`, expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
{expr: `not alpha!="alpha"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
|
|
// Order of operation
|
|
{expr: `alpha="alpha" and bravo=123 or charlie.door="green"`, expected: &types.AttributeValueMemberBOOL{Value: true}},
|
|
{expr: `alpha="bravo" or bravo=321 and charlie.door="green"`, expected: &types.AttributeValueMemberBOOL{Value: false}},
|
|
}
|
|
for _, scenario := range scenarios {
|
|
t.Run(scenario.expr, func(t *testing.T) {
|
|
modExpr, err := queryexpr.Parse(scenario.expr)
|
|
assert.NoError(t, err)
|
|
|
|
res, err := modExpr.EvalItem(item)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, scenario.expected, res)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("unparsed expression", func(t *testing.T) {
|
|
scenarios := []struct {
|
|
expr string
|
|
expectedError error
|
|
}{
|
|
{expr: `bla ^ = "something"`},
|
|
}
|
|
|
|
for _, scenario := range scenarios {
|
|
t.Run(scenario.expr, func(t *testing.T) {
|
|
_, err := queryexpr.Parse(scenario.expr)
|
|
assert.Error(t, err)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("expression errors", func(t *testing.T) {
|
|
scenarios := []struct {
|
|
expr string
|
|
expectedError error
|
|
}{
|
|
{expr: `alpha.bravo`, expectedError: queryexpr.ValueNotAMapError([]string{"alpha", "bravo"})},
|
|
{expr: `charlie.tree.bla`, expectedError: queryexpr.ValueNotAMapError([]string{"charlie", "tree", "bla"})},
|
|
}
|
|
|
|
for _, scenario := range scenarios {
|
|
t.Run(scenario.expr, func(t *testing.T) {
|
|
modExpr, err := queryexpr.Parse(scenario.expr)
|
|
assert.NoError(t, err)
|
|
|
|
res, err := modExpr.EvalItem(item)
|
|
assert.Nil(t, res)
|
|
assert.Equal(t, scenario.expectedError, err)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
type scanScenario struct {
|
|
description string
|
|
expression string
|
|
expectedFilter string
|
|
expectedNames map[string]string
|
|
expectedValues map[string]types.AttributeValue
|
|
}
|
|
|
|
func scanCase(description, expression, expectedFilter string, options ...func(ss *scanScenario)) scanScenario {
|
|
ss := scanScenario{
|
|
description: description,
|
|
expression: expression,
|
|
expectedFilter: expectedFilter,
|
|
expectedNames: map[string]string{},
|
|
expectedValues: map[string]types.AttributeValue{},
|
|
}
|
|
for _, opt := range options {
|
|
opt(&ss)
|
|
}
|
|
return ss
|
|
}
|
|
|
|
func exprName(idx int, name string) func(ss *scanScenario) {
|
|
return func(ss *scanScenario) {
|
|
ss.expectedNames[fmt.Sprintf("#%d", idx)] = name
|
|
}
|
|
}
|
|
|
|
func exprValueIsString(valIdx int, expected string) func(ss *scanScenario) {
|
|
return func(ss *scanScenario) {
|
|
ss.expectedValues[fmt.Sprintf(":%d", valIdx)] = &types.AttributeValueMemberS{Value: expected}
|
|
}
|
|
}
|
|
|
|
func exprValueIsNumber(valIdx int, expected string) func(ss *scanScenario) {
|
|
return func(ss *scanScenario) {
|
|
ss.expectedValues[fmt.Sprintf(":%d", valIdx)] = &types.AttributeValueMemberN{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
|
|
ss.expectedValues[fmt.Sprintf(":%d", valIdx)] = &types.AttributeValueMemberS{Value: expected}
|
|
}
|
|
}
|
|
|
|
func exprNameIsNumber(idx, valIdx int, name string, expected string) func(ss *scanScenario) {
|
|
return func(ss *scanScenario) {
|
|
ss.expectedNames[fmt.Sprintf("#%d", idx)] = name
|
|
ss.expectedValues[fmt.Sprintf(":%d", valIdx)] = &types.AttributeValueMemberN{Value: expected}
|
|
}
|
|
}
|