Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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
51 changes: 42 additions & 9 deletions go/mysql/json/json_path.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const (
jpMemberAny
jpArrayLocation
jpArrayLocationAny
jpArrayLocationRange
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to add a new json path kind here. Previously offset0 and offset1 were used to distinguish between an array offset and an array range, but this does not work for things like $[0 to 0] or $[last to last]. So I figured adding a separate kind is simpler and cleaner.

jpAny
)

Expand Down Expand Up @@ -71,6 +72,15 @@ func (jp *Path) format(b *strings.Builder) {
case jpMemberAny:
b.WriteString(".*")
case jpArrayLocation:
switch {
case jp.offset0 == -1:
b.WriteString("[last]")
case jp.offset0 >= 0:
_, _ = fmt.Fprintf(b, "[%d]", jp.offset0)
case jp.offset0 < 0:
_, _ = fmt.Fprintf(b, "[last%d]", jp.offset0+1)
}
case jpArrayLocationRange:
switch {
case jp.offset0 == -1:
b.WriteString("[last")
Expand All @@ -82,7 +92,7 @@ func (jp *Path) format(b *strings.Builder) {
switch {
case jp.offset1 == -1:
b.WriteString(" to last")
case jp.offset1 > 0:
case jp.offset1 >= 0:
_, _ = fmt.Fprintf(b, " to %d", jp.offset1)
case jp.offset1 < 0:
_, _ = fmt.Fprintf(b, " to last%d", jp.offset1+1)
Expand All @@ -107,12 +117,8 @@ func (jp *Path) String() string {
func (jp *Path) ContainsWildcards() bool {
for jp != nil {
switch jp.kind {
case jpAny, jpArrayLocationAny, jpMemberAny:
case jpAny, jpArrayLocationAny, jpArrayLocationRange, jpMemberAny:
return true
case jpArrayLocation:
if jp.offset1 != 0 {
return true
}
}
jp = jp.next
}
Expand Down Expand Up @@ -186,6 +192,15 @@ func (m *matcher) value(p *Path, v *Value) {
})
}
case jpArrayLocation:
if ary, ok := v.Array(); ok {
from, _ := p.arrayOffsets(ary)
if from >= 0 && from < len(ary) {
m.value(p.next, ary[from])
}
} else if m.wrap && (p.offset0 == 0 || p.offset0 == -1) {
m.value(p.next, v)
}
case jpArrayLocationRange:
if ary, ok := v.Array(); ok {
from, to := p.arrayOffsets(ary)
if from >= 0 && from < len(ary) {
Expand All @@ -196,7 +211,7 @@ func (m *matcher) value(p *Path, v *Value) {
m.value(p.next, ary[n])
}
}
} else if m.wrap && (p.offset0 == 0 || p.offset0 == -1) {
} else if m.wrap && (p.offset0 == 0 || p.offset1 == -1) {
m.value(p.next, v)
}
case jpArrayLocationAny:
Expand Down Expand Up @@ -233,6 +248,20 @@ func (jp *Path) transform(v *Value, t func(pp *Path, vv *Value)) {
jp.next.transform(obj.Get(jp.name), t)
}
case jpArrayLocation:
if ary, ok := v.Array(); ok {
from, _ := jp.arrayOffsets(ary)
if from >= 0 && from < len(ary) {
jp.next.transform(ary[from], t)
}
} else if jp.offset0 == 0 || jp.offset0 == -1 {
/*
If the path is evaluated against a value that is not an array,
the result of the evaluation is the same as if the value had been
wrapped in a single-element array:
*/
jp.next.transform(v, t)
}
case jpArrayLocationRange:
if ary, ok := v.Array(); ok {
from, to := jp.arrayOffsets(ary)
if from != to {
Expand All @@ -241,7 +270,7 @@ func (jp *Path) transform(v *Value, t func(pp *Path, vv *Value)) {
if from >= 0 && from < len(ary) {
jp.next.transform(ary[from], t)
}
} else if jp.offset0 == 0 || jp.offset0 == -1 {
} else if jp.offset0 == 0 || jp.offset1 == -1 {
/*
If the path is evaluated against a value that is not an array,
the result of the evaluation is the same as if the value had been
Expand Down Expand Up @@ -532,13 +561,17 @@ func stepArrayLocationTo(p *PathParser, in []byte) ([]byte, error) {
if in == nil || skip == 0 {
return nil, errInvalid
}

// Upgrade to range
p.path.kind = jpArrayLocationRange

if in[0] >= '0' && in[0] <= '9' {
p.step = stepArrayLocationClose
offset, in2, err := p.lexNumeric(in)
if err != nil {
return nil, err
}
if offset <= p.path.offset0 {
if offset < p.path.offset0 {
return nil, fmt.Errorf("range %d should be >= %d", offset, p.path.offset0)
}
p.path.offset1 = offset
Expand Down
8 changes: 8 additions & 0 deletions go/mysql/json/json_path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ func TestParseJSONPath(t *testing.T) {
{P: `$.c[last-23 to last-444]`},
{P: `$.c[last-23 to last]`},
{P: `$.c[last]`},
{P: `$.c[last to last]`},
{P: `$.c[0 to 0]`},
{P: `$.c[last - 23 to last]`, Want: `$.c[last-23 to last]`},
{P: `$ . c [ last - 23 to last - 444 ]`, Want: `$.c[last-23 to last-444]`},
{P: `$. "a fish" `, Want: `$."a fish"`},
Expand Down Expand Up @@ -88,6 +90,12 @@ func TestJSONExtract(t *testing.T) {
{`{"a": 1, "b": 2, "c": [3, 4, 5]}`, `$.*`, []string{"1", "2", "[3, 4, 5]"}},
{`true`, `$[0]`, []string{"true"}},
{`true`, `$[last]`, []string{"true"}},
{`true`, `$[0 to 0]`, []string{"true"}},
{`true`, `$[0 to 1]`, []string{"true"}},
{`true`, `$[1 to 2]`, []string{}},
{`true`, `$[last to last]`, []string{"true"}},
{`true`, `$[last-4 to last]`, []string{"true"}},
{`true`, `$[last-4 to last-1]`, []string{}},
{`true`, `$[1]`, []string{}},
{`true`, `$[last-1]`, []string{}},
{`[ { "a": 1 }, { "a": 2 } ]`, `$**[0]`, []string{`{"a": 1}`, `1`, `{"a": 2}`, `2`}},
Expand Down
Loading