From e23ccada98ed9258193edf3d8ff4ae12175322d8 Mon Sep 17 00:00:00 2001 From: Radu Gribincea Date: Wed, 26 Feb 2025 22:42:10 +0200 Subject: [PATCH] allow nested list indices in expressions (#2078) --- .../2cca370d-5f01-451c-979b-b82c55dcf3ec.json | 9 ++ feature/dynamodb/expression/operand.go | 64 +++++++++-- feature/dynamodb/expression/operand_test.go | 102 ++++++++++++++++++ 3 files changed, 164 insertions(+), 11 deletions(-) create mode 100644 .changelog/2cca370d-5f01-451c-979b-b82c55dcf3ec.json diff --git a/.changelog/2cca370d-5f01-451c-979b-b82c55dcf3ec.json b/.changelog/2cca370d-5f01-451c-979b-b82c55dcf3ec.json new file mode 100644 index 00000000000..15fbfc07434 --- /dev/null +++ b/.changelog/2cca370d-5f01-451c-979b-b82c55dcf3ec.json @@ -0,0 +1,9 @@ +{ + "id": "2cca370d-5f01-451c-979b-b82c55dcf3ec", + "type": "bugfix", + "description": "allow nested list indices in expressions", + "collapse": false, + "modules": [ + "feature/dynamodb/expression" + ] +} diff --git a/feature/dynamodb/expression/operand.go b/feature/dynamodb/expression/operand.go index 01340e7cf64..c2cacf6b037 100644 --- a/feature/dynamodb/expression/operand.go +++ b/feature/dynamodb/expression/operand.go @@ -171,17 +171,25 @@ func (nb NameBuilder) AppendName(field NameBuilder) NameBuilder { if len(nb.names) != 0 && len(field.names) != 0 { lastLeftName := len(nb.names) - 1 firstRightName := lastLeftName + 1 - if v := names[firstRightName]; len(v) > 0 && v[0] == '[' { - if end := strings.Index(v, "]"); end != -1 { - names[lastLeftName] += v[0 : end+1] - names[firstRightName] = v[end+1:] - // Remove the name if it is empty after moving the index. - if len(names[firstRightName]) == 0 { - copy(names[firstRightName:], names[firstRightName+1:]) - names[len(names)-1] = "" - names = names[:len(names)-1] - } + + v := names[firstRightName] + end := strings.Index(v, "]") + + for len(v) > 0 && v[0] == '[' && end != -1 { + names[lastLeftName] += v[0 : end+1] + names[firstRightName] = v[end+1:] + + // Remove the name if it is empty after moving the index. + if len(names[firstRightName]) == 0 { + copy(names[firstRightName:], names[firstRightName+1:]) + names[len(names)-1] = "" + names = names[:len(names)-1] + + break } + + v = v[end+1:] + end = strings.Index(v, "]") } } @@ -603,10 +611,44 @@ func (nb NameBuilder) BuildOperand() (Operand, error) { return Operand{}, newInvalidParameterError("BuildOperand", "NameBuilder") } - if idx := strings.Index(word, "]"); idx != -1 && idx != len(word)-1 { + numOpenBrackets := strings.Count(word, "[") + numCloseBrackets := strings.Count(word, "]") + + // mismatched brackets + if numOpenBrackets != numCloseBrackets { return Operand{}, newInvalidParameterError("BuildOperand", "NameBuilder") } + openPositions := make([]int, 0, numOpenBrackets) + closePositions := make([]int, 0, numCloseBrackets) + + for i, char := range word { + if char == '[' { + openPositions = append(openPositions, i) + } else if char == ']' { + closePositions = append(closePositions, i) + } + } + + for idx := range closePositions { + openPosition := openPositions[idx] + closePosition := closePositions[idx] + if openPosition > closePosition { + return Operand{}, newInvalidParameterError("BuildOperand", "NameBuilder") + } + + part := word[openPosition+1 : closePosition] + if len(part) == 0 { + return Operand{}, newInvalidParameterError("BuildOperand", "NameBuilder") + } + + for _, c := range []byte(part) { + if c < '0' || c > '9' { + return Operand{}, newInvalidParameterError("BuildOperand", "NameBuilder") + } + } + } + if word[len(word)-1] == ']' { for j, char := range word { if char == '[' { diff --git a/feature/dynamodb/expression/operand_test.go b/feature/dynamodb/expression/operand_test.go index 30d67bda339..b12ba88b553 100644 --- a/feature/dynamodb/expression/operand_test.go +++ b/feature/dynamodb/expression/operand_test.go @@ -301,6 +301,108 @@ func TestBuildOperand(t *testing.T) { expected: exprNode{}, err: invalidName, }, + { + name: "no split name name with nested indices", + input: NameNoDotSplit("foo.bar[0][0]"), + expected: exprNode{ + names: []string{"foo.bar"}, + fmtExpr: "$n[0][0]", + }, + }, + { + name: "name with nested indices and property", + input: Name("foo[1][2].bar"), + expected: exprNode{ + names: []string{"foo", "bar"}, + fmtExpr: "$n[1][2].$n", + }, + }, + { + name: "names with nested indices", + input: Name("foo[1][2].bar[3][4]"), + expected: exprNode{ + names: []string{"foo", "bar"}, + fmtExpr: "$n[1][2].$n[3][4]", + }, + }, + { + name: "very log name with nested indices", + input: Name("foo[1][2][3][4][5][6][7][8][9][10].bar[11][12][13][14][15][16][17][18]"), + expected: exprNode{ + names: []string{"foo", "bar"}, + fmtExpr: "$n[1][2][3][4][5][6][7][8][9][10].$n[11][12][13][14][15][16][17][18]", + }, + }, + { + name: "very log name with nested indices", + input: Name("foo[1][2][3][4][5][6][7][8][9][10].bar[11][12][13][14][15][16][17][18]"), + expected: exprNode{ + names: []string{"foo", "bar"}, + fmtExpr: "$n[1][2][3][4][5][6][7][8][9][10].$n[11][12][13][14][15][16][17][18]", + }, + }, + { + name: "invalid name when bracket is missing", + input: Name("foo["), + expected: exprNode{}, + err: invalidName, + }, + { + name: "invalid name when bracket is missing", + input: Name("foo]"), + expected: exprNode{}, + err: invalidName, + }, + { + name: "invalid name when ending with dot", + input: Name("foo."), + expected: exprNode{}, + err: invalidName, + }, + { + name: "invalid name when alpha index", + input: Name("foo[a]"), + expected: exprNode{}, + err: invalidName, + }, + { + name: "invalid name when weird brackets", + input: Name("foo]1["), + expected: exprNode{}, + err: invalidName, + }, + { + name: "no split name append name with nested list index", + input: NameNoDotSplit("foo.bar"). + AppendName(Name("foo.bar")). + AppendName(Name("[0][1]")). + AppendName(Name("abc123")), + expected: exprNode{ + names: []string{"foo.bar", "foo", "bar", "abc123"}, + fmtExpr: "$n.$n.$n[0][1].$n", + }, + }, + { + name: "no split name append name with bad nested list index", + input: NameNoDotSplit("foo.bar"). + AppendName(Name("foo.bar")). + AppendName(Name("[0][a]")). + AppendName(Name("abc123")), + expected: exprNode{}, + err: invalidName, + }, + { + name: "bad input left bracket only", + input: Name("foo").AppendName(Name("[")), + expected: exprNode{}, + err: invalidName, + }, + { + name: "bad input right bracket only", + input: Name("foo").AppendName(Name("]")), + expected: exprNode{}, + err: invalidName, + }, } for _, c := range cases {