diff --git a/go.mod b/go.mod index 1578910..ada9d15 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 8921650..30ff8b7 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/dynamo-browse/models/queryexpr/ast.go b/internal/dynamo-browse/models/queryexpr/ast.go index d38b319..28c49ea 100644 --- a/internal/dynamo-browse/models/queryexpr/ast.go +++ b/internal/dynamo-browse/models/queryexpr/ast.go @@ -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 { diff --git a/internal/dynamo-browse/models/queryexpr/dot.go b/internal/dynamo-browse/models/queryexpr/dot.go index 8ab6ef6..d2c18fc 100644 --- a/internal/dynamo-browse/models/queryexpr/dot.go +++ b/internal/dynamo-browse/models/queryexpr/dot.go @@ -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()) } diff --git a/internal/dynamo-browse/models/queryexpr/errors.go b/internal/dynamo-browse/models/queryexpr/errors.go index 57fc0f4..59f1ddb 100644 --- a/internal/dynamo-browse/models/queryexpr/errors.go +++ b/internal/dynamo-browse/models/queryexpr/errors.go @@ -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" +} diff --git a/internal/dynamo-browse/models/queryexpr/expr_test.go b/internal/dynamo-browse/models/queryexpr/expr_test.go index a8e2093..e035664 100644 --- a/internal/dynamo-browse/models/queryexpr/expr_test.go +++ b/internal/dynamo-browse/models/queryexpr/expr_test.go @@ -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"), ), diff --git a/internal/dynamo-browse/models/queryexpr/subref.go b/internal/dynamo-browse/models/queryexpr/subref.go index 01c26ca..0674fcb 100644 --- a/internal/dynamo-browse/models/queryexpr/subref.go +++ b/internal/dynamo-browse/models/queryexpr/subref.go @@ -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...)) +} diff --git a/internal/dynamo-browse/models/tableinfo.go b/internal/dynamo-browse/models/tableinfo.go index 3cccf92..4a816c9 100644 --- a/internal/dynamo-browse/models/tableinfo.go +++ b/internal/dynamo-browse/models/tableinfo.go @@ -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 {