Added sub references to the query expression (#46)

These are the `thing[subref]` construct. Subrefs can either be a string or an integer.

At the moment, multiple sub references, such as `thing[1][2][3]` doesn't work. This is because the SDK does not properly handle this when creating the actual expression.
This commit is contained in:
Leon Mika 2023-02-16 21:57:40 +11:00 committed by GitHub
parent 348251c1cf
commit 7caf905c82
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 270 additions and 61 deletions

20
go.mod
View file

@ -5,12 +5,12 @@ go 1.18
require (
github.com/alecthomas/participle/v2 v2.0.0-beta.5
github.com/asdine/storm v2.1.2+incompatible
github.com/aws/aws-sdk-go-v2 v1.16.5
github.com/aws/aws-sdk-go-v2 v1.17.4
github.com/aws/aws-sdk-go-v2/config v1.13.1
github.com/aws/aws-sdk-go-v2/credentials v1.8.0
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.9.4
github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.4.10
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.15.7
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.10.12
github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.4.39
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.18.3
github.com/aws/aws-sdk-go-v2/service/sqs v1.16.0
github.com/aws/aws-sdk-go-v2/service/ssm v1.24.0
github.com/brianvoe/gofakeit/v6 v6.15.0
@ -37,16 +37,16 @@ require (
github.com/Sereal/Sereal v0.0.0-20220220040404-e0d1e550e879 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.10.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.12 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.5 // indirect
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.13.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.6 // indirect
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.14.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.22 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.7.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.9.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.14.0 // indirect
github.com/aws/smithy-go v1.11.3 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/aymanbagabas/go-osc52 v1.0.3 // indirect
github.com/cloudcmds/tamarin v1.0.0 // indirect
github.com/containerd/console v1.0.3 // indirect

20
go.sum
View file

@ -29,34 +29,52 @@ github.com/aws/aws-sdk-go-v2 v1.13.0/go.mod h1:L6+ZpqHaLbAaxsqV0L4cvxZY7QupWJB4f
github.com/aws/aws-sdk-go-v2 v1.16.1/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU=
github.com/aws/aws-sdk-go-v2 v1.16.5 h1:Ah9h1TZD9E2S1LzHpViBO3Jz9FPL5+rmflmb8hXirtI=
github.com/aws/aws-sdk-go-v2 v1.16.5/go.mod h1:Wh7MEsmEApyL5hrWzpDkba4gwAPc5/piwLVLFnCxp48=
github.com/aws/aws-sdk-go-v2 v1.17.4 h1:wyC6p9Yfq6V2y98wfDsj6OnNQa4w2BLGCLIxzNhwOGY=
github.com/aws/aws-sdk-go-v2 v1.17.4/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2/config v1.13.1 h1:yLv8bfNoT4r+UvUKQKqRtdnvuWGMK5a82l4ru9Jvnuo=
github.com/aws/aws-sdk-go-v2/config v1.13.1/go.mod h1:Ba5Z4yL/UGbjQUzsiaN378YobhFo0MLfueXGiOsYtEs=
github.com/aws/aws-sdk-go-v2/credentials v1.8.0 h1:8Ow0WcyDesGNL0No11jcgb1JAtE+WtubqXjgxau+S0o=
github.com/aws/aws-sdk-go-v2/credentials v1.8.0/go.mod h1:gnMo58Vwx3Mu7hj1wpcG8DI0s57c9o42UQ6wgTQT5to=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.9.4 h1:EoyeSOfbSuKh+bQIDoZaVJjON6PF+dsSn5w1RhIpMD0=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.9.4/go.mod h1:bfCL7OwZS6owS06pahfGxhcgpLWj2W1sQASoYRuenag=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.10.12 h1:ama2cD4WaH6+8Gq/M/g+ZumPmmqCyanr+6Sm+iJVxfA=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.10.12/go.mod h1:tPnUO5mS3JThpwfq4Q8iPd745s7yh6fGPqDUEBw+Wv4=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.4.10 h1:IBIZfpnWCTTQhH/bMvDcCMw10BtLBPYO30Ev8MLXMTY=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.4.10/go.mod h1:RL7aJOwlWj2N6wkE4nKR1S5M4iGph+xSu7JovwNYpyU=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.4.39 h1:PhgfvgqwMFQKwOcxLV7V3lNDVnR3ZUWzoB6T9oCFpR4=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.4.39/go.mod h1:/GkvC7uHpK50ilKkKx9I2gZiI/ieZbKjS2aah1rT9uE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.10.0 h1:NITDuUZO34mqtOwFWZiXo7yAHj7kf+XPE+EiKuCBNUI=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.10.0/go.mod h1:I6/fHT/fH460v09eg2gVrd8B/IqskhNdpcLH0WNO3QI=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.4/go.mod h1:XHgQ7Hz2WY2GAn//UXHofLfPXWh+s62MbMOijrg12Lw=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.8/go.mod h1:LnTQMTqbKsbtt+UI5+wPsB7jedW+2ZgozoPG8k6cMxg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.12 h1:Zt7DDk5V7SyQULUUwIKzsROtVzp/kVvcz15uQx/Tkow=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.12/go.mod h1:Afj/U8svX6sJ77Q+FPWMzabJ9QjbwP32YlopgKALUpg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28 h1:r+XwaCLpIvCKjBIYy/HVZujQS9tsz5ohHG3ZIe0wKoE=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28/go.mod h1:3lwChorpIM/BhImY/hy+Z6jekmN92cXGPI1QJasVPYY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.2.0/go.mod h1:BsCSJHx5DnDXIrOcqB8KN1/B+hXLG/bi4Y6Vjcx/x9E=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.2/go.mod h1:1x4ZP3Z8odssdhuLI+/1Tqw6Pt/VAaP4Tr8EUxHvPXE=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.6 h1:eeXdGVtXEe+2Jc49+/vAzna3FAQnUD4AagAw8tzbmfc=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.6/go.mod h1:FwpAKI+FBPIELJIdmQzlLtRe8LQSOreMcM2wBsPMvvc=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22 h1:7AwGYXDdqRQYsluvKFmWoqpcOQJ4bH634SkYf3FNj/A=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22/go.mod h1:EqK7gVrIGAHyZItrD1D8B0ilgwMD1GiWAmbU4u/JHNk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.5 h1:ixotxbfTCFpqbuwFv/RcZwyzhkxPSYDYEMcj4niB5Uk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.5/go.mod h1:R3sWUqPcfXSiF/LSFJhjyJmpg9uV6yP2yv3YZZjldVI=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.15.7 h1:Ls6kDGWNr3wxE8JypXgTTonHpQ1eRVCGNqaFHY2UASw=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.15.7/go.mod h1:+v2jeT4/39fCXUQ0ZfHQHMMiJljnmiuj16F03uAd9DY=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.18.3 h1:MxOpCZ+o9+AIeQHi2ocW7H4D7p0LhEkmetETVvDnkvg=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.18.3/go.mod h1:nkpC9xkh+3vdxmhqN8Ac10pgV14DsJDLzUsV2CcS+44=
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.13.7 h1:o2HKntJx3vr3y11NK58RA6tYKZKQo5PWWt/bs0rWR0U=
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.13.7/go.mod h1:FAVtDKEl/8WxRDQ33e2fz16RO1t4zeEwWIU5kR29xXs=
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.14.3 h1:B+bkmCnNJi194pu9aTtYUe8f4EPXafC+xfU+zciVxdg=
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.14.3/go.mod h1:bRphLmXQD9Ux4jLcFEwyrWdmuPTj2Lh8VGl9wILuJII=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.2 h1:T/ywkX1ed+TsZVQccu/8rRJGxKZF/t0Ivgrb4MHTSeo=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.2/go.mod h1:RnloUnyZ4KN9JStGY1LuQ7Wzqh7V0f8FinmRdHYtuaA=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.6 h1:JGrc3+kkyr848/wpG2+kWuzHK3H4Fyxj2jnXj8ijQ/Y=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.6/go.mod h1:zwvTysbXES8GDwFcwCPB8NkC+bCdio1abH+E+BRe/xg=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.22 h1:6zEryIiJOSk5/OcVHzkPDwzNBQ2atYCTShyA7TqkuxA=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.22/go.mod h1:moeOz5SKfY0p6pNIChdPIQdfaUfWI67+OVe0/r6+aGY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.7.0 h1:4QAOB3KrvI1ApJK14sliGr3Ie2pjyvNypn/lfzDHfUw=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.7.0/go.mod h1:K/qPe6AP2TGYv4l6n7c88zh9jWBDf6nHhvg1fx/EWfU=
github.com/aws/aws-sdk-go-v2/service/sqs v1.16.0 h1:dzWS4r8E9bA0TesHM40FSAtedwpTVCuTsLI8EziSqyk=
@ -71,6 +89,8 @@ github.com/aws/smithy-go v1.10.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiA
github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
github.com/aws/smithy-go v1.11.3 h1:DQixirEFM9IaKxX1olZ3ke3nvxRS2xMDteKIDWxozW8=
github.com/aws/smithy-go v1.11.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg=
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
github.com/brianvoe/gofakeit/v6 v6.15.0 h1:lJPGJZ2/07TRGDazyTzD5b18N3y4tmmJpdhCUw18FlI=

View file

@ -55,8 +55,14 @@ type astIsOp struct {
}
type astSubRef struct {
Ref *astFunctionCall `parser:"@@"`
Quals []string `parser:"('.' @Ident)*"`
Ref *astFunctionCall `parser:"@@"`
SubRefs []*astSubRefType `parser:"@@*"`
//Quals []string `parser:"('.' @Ident)*"`
}
type astSubRefType struct {
DotQual string `parser:"'.' @Ident"`
SubIndex *astExpr `parser:"| '[' @@ ']'"`
}
type astFunctionCall struct {

View file

@ -1,9 +1,11 @@
package queryexpr
import (
"fmt"
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/lmika/audax/internal/dynamo-browse/models"
"strings"
)
func (dt *astRef) evalToIR(ctx *evalContext, info *models.TableInfo) (irAtom, error) {
@ -43,7 +45,7 @@ func (a *astRef) String() string {
type irNamePath struct {
name string
quals []string
quals []any
}
func (i irNamePath) calcQueryForScan(info *models.TableInfo) (expression.ConditionBuilder, error) {
@ -62,9 +64,16 @@ func (i irNamePath) keyName() string {
}
func (i irNamePath) calcName(info *models.TableInfo) expression.NameBuilder {
nb := expression.Name(i.name)
var fullName strings.Builder
fullName.WriteString(i.name)
for _, qual := range i.quals {
nb = nb.AppendName(expression.Name(qual))
switch v := qual.(type) {
case string:
fullName.WriteString("." + v)
case int:
fullName.WriteString(fmt.Sprintf("[%v]", qual))
}
}
return nb
return expression.NameNoDotSplit(fullName.String())
}

View file

@ -34,6 +34,13 @@ func (n ValueNotAMapError) Error() string {
return fmt.Sprintf("%v: name is not a map", strings.Join(n, "."))
}
// ValueNotAListError is return if the given name is not a map
type ValueNotAListError []string
func (n ValueNotAListError) Error() string {
return fmt.Sprintf("%v: name is not a list", strings.Join(n, "."))
}
// ValuesNotComparable indicates that two values are not comparable
type ValuesNotComparable struct {
Left, Right types.AttributeValue
@ -123,3 +130,10 @@ type MissingPlaceholderError struct {
func (e MissingPlaceholderError) Error() string {
return "undefined placeholder '" + e.Placeholder + "'"
}
type ValueNotUsableAsASubref struct {
}
func (e ValueNotUsableAsASubref) Error() string {
return "value cannot be used as a subref"
}

View file

@ -111,6 +111,13 @@ func TestModExpr_Query(t *testing.T) {
exprNameIsString(0, 0, "pk", "prefix"),
exprNameIsString(1, 1, "sk", "another"),
),
// Querying the index
scanCase("when request pk is fixed",
`pk="prefix"`,
`#0 = :0`,
exprNameIsString(0, 0, "pk", "prefix"),
),
}
for _, scenario := range scenarios {
@ -271,17 +278,27 @@ func TestModExpr_Query(t *testing.T) {
exprValueIsNumber(0, "131"),
),
// Dots
scanCase("with the dot", `this.value = "something"`, `#0.#1 = :0`,
// Sub refs
scanCase("with index", `this[2] = "something"`, `#0[2] = :0`,
exprName(0, "this"),
exprName(1, "value"),
exprValueIsString(0, "something"),
),
scanCase("with multiple dots", `this.that.other.value = "else"`, `#0.#1.#2.#3 = :0`,
exprName(0, "this"),
exprName(1, "that"),
exprName(2, "other"),
exprName(3, "value"),
scanCase("with the dot", `this.value = "something"`, `#0 = :0`,
exprName(0, "this.value"),
exprValueIsString(0, "something"),
),
/*
scanCase("with multiple indices", `this[2][3] = "something"`, `#0[2][3] = :0`,
exprName(0, "this"),
exprValueIsString(0, "something"),
),
scanCase("with multiple indices with paren", `((this[2])[3])[4] = "something"`, `#0[2][3][4] = :0`,
exprName(0, "this"),
exprValueIsString(0, "something"),
),
*/
scanCase("with multiple dots", `this.that.other.value = "else"`, `#0 = :0`,
exprName(0, "this.that.other.value"),
exprValueIsString(0, "else"),
),

View file

@ -2,7 +2,9 @@ package queryexpr
import (
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/lmika/audax/internal/common/sliceutils"
"github.com/lmika/audax/internal/dynamo-browse/models"
"strconv"
"strings"
)
@ -11,7 +13,7 @@ func (r *astSubRef) evalToIR(ctx *evalContext, info *models.TableInfo) (irAtom,
if err != nil {
return nil, err
}
if len(r.Quals) == 0 {
if len(r.SubRefs) == 0 {
return refIR, nil
}
@ -21,9 +23,13 @@ func (r *astSubRef) evalToIR(ctx *evalContext, info *models.TableInfo) (irAtom,
return nil, OperandNotANameError(r.String())
}
quals := make([]string, 0)
for _, sr := range r.Quals {
quals = append(quals, sr)
quals := make([]any, 0)
for _, sr := range r.SubRefs {
sv, err := sr.evalToStrOrInt(ctx, nil)
if err != nil {
return nil, err
}
quals = append(quals, sv)
}
return irNamePath{name: namePath.name, quals: quals}, nil
}
@ -34,29 +40,52 @@ func (r *astSubRef) evalItem(ctx *evalContext, item models.Item) (types.Attribut
return nil, err
}
for i, qualName := range r.Quals {
var hasV bool
mapRes, isMapRes := res.(*types.AttributeValueMemberM)
if !isMapRes {
return nil, ValueNotAMapError(append([]string{r.Ref.String()}, r.Quals[:i+1]...))
}
res, hasV = mapRes.Value[qualName]
if !hasV {
return nil, nil
}
res, err = r.evalSubRefs(ctx, item, res, r.SubRefs)
if err != nil {
return nil, err
}
return res, nil
}
func (r *astSubRef) evalSubRefs(ctx *evalContext, item models.Item, res types.AttributeValue, subRefs []*astSubRefType) (types.AttributeValue, error) {
for i, sr := range subRefs {
sv, err := sr.evalToStrOrInt(ctx, nil)
if err != nil {
return nil, err
}
switch val := sv.(type) {
case string:
var hasV bool
mapRes, isMapRes := res.(*types.AttributeValueMemberM)
if !isMapRes {
return nil, newValueNotAMapError(r, subRefs[:i+1])
}
res, hasV = mapRes.Value[val]
if !hasV {
return nil, nil
}
case int:
listRes, isMapRes := res.(*types.AttributeValueMemberL)
if !isMapRes {
return nil, newValueNotAListError(r, subRefs[:i+1])
}
// TODO - deal with index properly
res = listRes.Value[val]
}
}
return res, nil
}
func (r *astSubRef) canModifyItem(ctx *evalContext, item models.Item) bool {
return r.Ref.canModifyItem(ctx, item)
}
func (r *astSubRef) setEvalItem(ctx *evalContext, item models.Item, value types.AttributeValue) error {
if len(r.Quals) == 0 {
if len(r.SubRefs) == 0 {
return r.Ref.setEvalItem(ctx, item, value)
}
@ -65,23 +94,40 @@ func (r *astSubRef) setEvalItem(ctx *evalContext, item models.Item, value types.
return err
}
for i, key := range r.Quals {
mapItem, isMapItem := parentItem.(*types.AttributeValueMemberM)
if !isMapItem {
return PathNotSettableError{}
if len(r.SubRefs) > 1 {
parentItem, err = r.evalSubRefs(ctx, item, parentItem, r.SubRefs[0:len(r.SubRefs)-1])
if err != nil {
return err
}
}
sv, err := r.SubRefs[len(r.SubRefs)-1].evalToStrOrInt(ctx, nil)
if err != nil {
return err
}
switch val := sv.(type) {
case string:
mapRes, isMapRes := parentItem.(*types.AttributeValueMemberM)
if !isMapRes {
return newValueNotAMapError(r, r.SubRefs)
}
if isLast := i == len(r.Quals)-1; isLast {
mapItem.Value[key] = value
} else {
parentItem = mapItem.Value[key]
mapRes.Value[val] = value
case int:
listRes, isMapRes := parentItem.(*types.AttributeValueMemberL)
if !isMapRes {
return newValueNotAListError(r, r.SubRefs)
}
// TODO: handle indexes
listRes.Value[val] = value
}
return nil
}
func (r *astSubRef) deleteAttribute(ctx *evalContext, item models.Item) error {
if len(r.Quals) == 0 {
if len(r.SubRefs) == 0 {
return r.Ref.deleteAttribute(ctx, item)
}
@ -90,17 +136,51 @@ func (r *astSubRef) deleteAttribute(ctx *evalContext, item models.Item) error {
return err
}
for i, key := range r.Quals {
mapItem, isMapItem := parentItem.(*types.AttributeValueMemberM)
if !isMapItem {
return PathNotSettableError{}
/*
for i, key := range r.Quals {
mapItem, isMapItem := parentItem.(*types.AttributeValueMemberM)
if !isMapItem {
return PathNotSettableError{}
}
if isLast := i == len(r.Quals)-1; isLast {
delete(mapItem.Value, key)
} else {
parentItem = mapItem.Value[key]
}
}
*/
if len(r.SubRefs) > 1 {
parentItem, err = r.evalSubRefs(ctx, item, parentItem, r.SubRefs[0:len(r.SubRefs)-1])
if err != nil {
return err
}
}
sv, err := r.SubRefs[len(r.SubRefs)-1].evalToStrOrInt(ctx, nil)
if err != nil {
return err
}
switch val := sv.(type) {
case string:
mapRes, isMapRes := parentItem.(*types.AttributeValueMemberM)
if !isMapRes {
return newValueNotAMapError(r, r.SubRefs)
}
if isLast := i == len(r.Quals)-1; isLast {
delete(mapItem.Value, key)
} else {
parentItem = mapItem.Value[key]
delete(mapRes.Value, val)
case int:
listRes, isMapRes := parentItem.(*types.AttributeValueMemberL)
if !isMapRes {
return newValueNotAListError(r, r.SubRefs)
}
// TODO: handle indexes out of bounds
oldList := listRes.Value
newList := append([]types.AttributeValue{}, oldList[:val]...)
newList = append(newList, oldList[val+1:]...)
listRes.Value = newList
}
return nil
}
@ -109,10 +189,67 @@ func (r *astSubRef) String() string {
var sb strings.Builder
sb.WriteString(r.Ref.String())
for _, q := range r.Quals {
sb.WriteRune('.')
sb.WriteString(q)
for _, q := range r.SubRefs {
switch {
case q.DotQual != "":
sb.WriteRune('.')
sb.WriteString(q.DotQual)
case q.SubIndex != nil:
sb.WriteRune('[')
sb.WriteString(q.SubIndex.String())
sb.WriteRune(']')
}
}
return sb.String()
}
func (sr *astSubRefType) evalToStrOrInt(ctx *evalContext, item models.Item) (any, error) {
if sr.DotQual != "" {
return sr.DotQual, nil
}
subEvalItem, err := sr.SubIndex.evalItem(ctx, item)
if err != nil {
return nil, err
}
switch v := subEvalItem.(type) {
case *types.AttributeValueMemberS:
return v.Value, nil
case *types.AttributeValueMemberN:
intVal, err := strconv.Atoi(v.Value)
if err == nil {
return intVal, nil
}
flVal, err := strconv.ParseFloat(v.Value, 64)
if err == nil {
return int(flVal), nil
}
return nil, err
}
return nil, ValueNotUsableAsASubref{}
}
func (sr *astSubRefType) string() string {
switch {
case sr.DotQual != "":
return sr.DotQual
case sr.SubIndex != nil:
return sr.SubIndex.String()
}
return ""
}
func newValueNotAMapError(r *astSubRef, subRefs []*astSubRefType) ValueNotAMapError {
subRefStrings := sliceutils.Map(subRefs, func(srt *astSubRefType) string {
return srt.string()
})
return ValueNotAMapError(append([]string{r.Ref.String()}, subRefStrings...))
}
func newValueNotAListError(r *astSubRef, subRefs []*astSubRefType) ValueNotAListError {
subRefStrings := sliceutils.Map(subRefs, func(srt *astSubRefType) string {
return srt.string()
})
return ValueNotAListError(append([]string{r.Ref.String()}, subRefStrings...))
}

View file

@ -4,6 +4,12 @@ type TableInfo struct {
Name string
Keys KeyAttribute
DefinedAttributes []string
GSIs []TableGSI
}
type TableGSI struct {
Name string
Keys KeyAttribute
}
func (ti *TableInfo) Equal(other *TableInfo) bool {