Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/sahil-4555-SSZ-QL: Access n-th element in List.m.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- refactor the ParsePath function to improve performance
85 changes: 41 additions & 44 deletions encoding/ssz/query/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,48 +15,45 @@ type PathElement struct {
}

func ParsePath(rawPath string) ([]PathElement, error) {
// We use dot notation, so we split the path by '.'.
rawElements := strings.Split(rawPath, ".")
if len(rawElements) == 0 {
return nil, errors.New("empty path provided")
}

if rawElements[0] == "" {
// Remove leading dot if present
rawElements = rawElements[1:]
}

var path []PathElement
for _, elem := range rawElements {
if elem == "" {
return nil, errors.New("invalid path: consecutive dots or trailing dot")
}

fieldName := elem
var index *uint64

// Check for index notation, e.g., "field[0]"
if strings.Contains(elem, "[") {
parts := strings.SplitN(elem, "[", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("invalid index notation in path element %s", elem)
}

fieldName = parts[0]
indexPart := strings.TrimSuffix(parts[1], "]")
if indexPart == "" {
return nil, errors.New("index cannot be empty")
}

indexValue, err := strconv.ParseUint(indexPart, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid index in path element %s: %w", elem, err)
}
index = &indexValue
}

path = append(path, PathElement{Name: fieldName, Index: index})
}

return path, nil
if rawPath == "" {
return nil, errors.New("empty path provided")
}

rawElements := strings.Split(rawPath, ".")
if rawElements[0] == "" {
rawElements = rawElements[1:]
if len(rawElements) == 0 {
return nil, errors.New("invalid path: only a dot")
}
}

path := make([]PathElement, 0, len(rawElements))
for _, elem := range rawElements {
if elem == "" {
return nil, errors.New("invalid path: consecutive dots or trailing dot")
}

i := strings.IndexByte(elem, '[')
if i < 0 {
path = append(path, PathElement{Name: elem})
continue
}

if elem[len(elem)-1] != ']' || len(elem)-1 <= i {
return nil, fmt.Errorf("invalid index notation in path element %s", elem)
}
fieldName := elem[:i]
indexPart := elem[i+1 : len(elem)-1]
if indexPart == "" {
return nil, errors.New("index cannot be empty")
}

val, err := strconv.ParseUint(indexPart, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid index in path element %s: %w", elem, err)
}
path = append(path, PathElement{Name: fieldName, Index: &val})
}
return path, nil
}

22 changes: 22 additions & 0 deletions encoding/ssz/query/path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,25 @@ func TestParsePath(t *testing.T) {
})
}
}

func BenchmarkParsePath(b *testing.B) {
benchmarks := []struct {
name string
path string
}{
{name: "simple path", path: "data.target.root"},
{name: "path with leading dot", path: ".data.target.root"},
{name: "nested arrays", path: "data.targets[15].values[42].leaf"},
{name: "deep nested", path: "root.level1.level2.level3.level4.level5"},
{name: "path with multiple indices", path: "root.array1[1].array2[2].array3[3].end"},
}

for _, bm := range benchmarks {
b.Run(bm.name, func(b *testing.B) {
for b.Loop() {
_, _ = query.ParsePath(bm.path)
}
})
}
}