Skip to content

Commit b2af3fd

Browse files
committed
jsonpath/eval: add unwrap parameter to jsonpath.eval
Previously, jsonpath conditionals/filters would unwrap infinitely. For example, `SELECT jsonb_path_query('{"a": [[[[{"b": 1}]]]]}', '$.a ? (@.b == 1)');` would return an entry, when postgres would not. This commit adds an unwrap boolean parameter to `eval`, which allows for control over when to stop unwrapping json arrays. Additionally, this commit updates `eval` to take in a `json.JSON` rather than a `[]json.JSON`, simplifying the evaluation logic. Epic: None Release note: None
1 parent 1157d89 commit b2af3fd

File tree

7 files changed

+206
-179
lines changed

7 files changed

+206
-179
lines changed

pkg/sql/logictest/testdata/logic_test/jsonb_path_query

+9
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,15 @@ SELECT jsonb_path_query('{"a": [1,2,3]}', '$.a ? (@ > 10)');
692692
query empty
693693
SELECT jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.a ? (@.c > 100)');
694694

695+
query empty
696+
SELECT jsonb_path_query('{"a": [[[{"b": 1}], [{"b": 2}]], [[{"b": 2}], [{"b": 1}]]]}', '$.a ? (@.b == 1)');
697+
698+
query empty
699+
SELECT jsonb_path_query('{"a": [[[[[[{"b": 1}]]]]]]}', '$.a ? (@.b == 1)');
700+
701+
query empty
702+
SELECT jsonb_path_query('{"a": [[[{"b": 1}], [{"b": 2}]]]}', '$.a ? (@.b == 1)');
703+
695704
# when string literals are supported
696705
# query T rowsort
697706
# SELECT jsonb_path_query('{"data": [{"val": "a", "num": 1}, {"val": "b", "num": 2}, {"val": "a", "num": 3}]}'::jsonb, '$.data ? (@.val == "a")'::jsonpath);

pkg/util/jsonpath/eval/array.go

+47-66
Original file line numberDiff line numberDiff line change
@@ -13,75 +13,52 @@ import (
1313
"github.com/cockroachdb/errors"
1414
)
1515

16-
func (ctx *jsonpathCtx) evalArrayWildcard(current []json.JSON) ([]json.JSON, error) {
17-
var agg []json.JSON
18-
for _, j := range current {
19-
if j.Type() == json.ArrayJSONType {
20-
paths, err := json.AllPathsWithDepth(j, 1)
21-
if err != nil {
22-
return nil, err
23-
}
24-
for _, path := range paths {
25-
if path.Len() != 1 {
26-
return nil, errors.AssertionFailedf("unexpected path length")
27-
}
28-
unwrapped, err := path.FetchValIdx(0)
29-
if err != nil {
30-
return nil, err
31-
}
32-
if unwrapped == nil {
33-
return nil, errors.AssertionFailedf("unwrapping json element")
34-
}
35-
agg = append(agg, unwrapped)
36-
}
37-
} else if !ctx.strict {
38-
agg = append(agg, j)
39-
} else {
40-
return nil, pgerror.Newf(pgcode.SQLJSONArrayNotFound, "jsonpath wildcard array accessor can only be applied to an array")
41-
}
16+
func (ctx *jsonpathCtx) evalArrayWildcard(jsonValue json.JSON) ([]json.JSON, error) {
17+
if jsonValue.Type() == json.ArrayJSONType {
18+
// Do not evaluate any paths, just unwrap the current target.
19+
return ctx.unwrapCurrentTargetAndEval(nil, jsonValue, !ctx.strict)
20+
} else if !ctx.strict {
21+
return []json.JSON{jsonValue}, nil
22+
} else {
23+
return nil, pgerror.Newf(pgcode.SQLJSONArrayNotFound, "jsonpath wildcard array accessor can only be applied to an array")
4224
}
43-
return agg, nil
4425
}
4526

4627
func (ctx *jsonpathCtx) evalArrayList(
47-
a jsonpath.ArrayList, current []json.JSON,
28+
arrayList jsonpath.ArrayList, jsonValue json.JSON,
4829
) ([]json.JSON, error) {
49-
var agg []json.JSON
50-
for _, path := range a {
51-
var from, to int
52-
var err error
53-
if idxRange, ok := path.(jsonpath.ArrayIndexRange); ok {
54-
from, err = ctx.resolveArrayIndex(idxRange.Start, current)
55-
if err != nil {
56-
return nil, err
57-
}
58-
to, err = ctx.resolveArrayIndex(idxRange.End, current)
59-
if err != nil {
60-
return nil, err
61-
}
62-
} else {
63-
from, err = ctx.resolveArrayIndex(path, current)
64-
if err != nil {
65-
return nil, err
30+
if jsonValue.Type() == json.ArrayJSONType || !ctx.strict {
31+
var agg []json.JSON
32+
for _, idxAccessor := range arrayList {
33+
var from, to int
34+
var err error
35+
if idxRange, ok := idxAccessor.(jsonpath.ArrayIndexRange); ok {
36+
from, err = ctx.resolveArrayIndex(idxRange.Start, jsonValue)
37+
if err != nil {
38+
return nil, err
39+
}
40+
to, err = ctx.resolveArrayIndex(idxRange.End, jsonValue)
41+
if err != nil {
42+
return nil, err
43+
}
44+
} else {
45+
from, err = ctx.resolveArrayIndex(idxAccessor, jsonValue)
46+
if err != nil {
47+
return nil, err
48+
}
49+
to = from
6650
}
67-
to = from
68-
}
6951

70-
for _, j := range current {
71-
if ctx.strict && j.Type() != json.ArrayJSONType {
72-
return nil, pgerror.Newf(pgcode.SQLJSONArrayNotFound,
73-
"jsonpath array accessor can only be applied to an array")
74-
}
75-
length := j.Len()
76-
if j.Type() != json.ArrayJSONType {
52+
length := jsonValue.Len()
53+
if jsonValue.Type() != json.ArrayJSONType {
7754
length = 1
7855
}
7956
if ctx.strict && (from < 0 || from > to || to >= length) {
8057
return nil, pgerror.Newf(pgcode.InvalidSQLJSONSubscript,
8158
"jsonpath array subscript is out of bounds")
8259
}
8360
for i := max(from, 0); i <= min(to, length-1); i++ {
84-
v, err := jsonArrayValueAtIndex(ctx, j, i)
61+
v, err := jsonArrayValueAtIndex(ctx, jsonValue, i)
8562
if err != nil {
8663
return nil, err
8764
}
@@ -91,19 +68,23 @@ func (ctx *jsonpathCtx) evalArrayList(
9168
agg = append(agg, v)
9269
}
9370
}
71+
return agg, nil
72+
} else {
73+
return nil, pgerror.Newf(pgcode.SQLJSONArrayNotFound, "jsonpath array accessor can only be applied to an array")
9474
}
95-
return agg, nil
9675
}
9776

98-
func (ctx *jsonpathCtx) resolveArrayIndex(p jsonpath.Path, current []json.JSON) (int, error) {
99-
results, err := ctx.eval(p, current)
77+
func (ctx *jsonpathCtx) resolveArrayIndex(
78+
jsonPath jsonpath.Path, jsonValue json.JSON,
79+
) (int, error) {
80+
evalResults, err := ctx.eval(jsonPath, jsonValue, !ctx.strict)
10081
if err != nil {
10182
return 0, err
10283
}
103-
if len(results) != 1 || results[0].Type() != json.NumberJSONType {
84+
if len(evalResults) != 1 || evalResults[0].Type() != json.NumberJSONType {
10485
return -1, pgerror.Newf(pgcode.InvalidSQLJSONSubscript, "jsonpath array subscript is not a single numeric value")
10586
}
106-
i, err := asInt(results[0])
87+
i, err := asInt(evalResults[0])
10788
if err != nil {
10889
return -1, pgerror.Newf(pgcode.InvalidSQLJSONSubscript, "jsonpath array subscript is not a single numeric value")
10990
}
@@ -122,22 +103,22 @@ func asInt(j json.JSON) (int, error) {
122103
return int(i64), nil
123104
}
124105

125-
func jsonArrayValueAtIndex(ctx *jsonpathCtx, j json.JSON, index int) (json.JSON, error) {
126-
if ctx.strict && j.Type() != json.ArrayJSONType {
106+
func jsonArrayValueAtIndex(ctx *jsonpathCtx, jsonValue json.JSON, index int) (json.JSON, error) {
107+
if ctx.strict && jsonValue.Type() != json.ArrayJSONType {
127108
return nil, pgerror.Newf(pgcode.SQLJSONArrayNotFound, "jsonpath array accessor can only be applied to an array")
128-
} else if j.Type() != json.ArrayJSONType {
109+
} else if jsonValue.Type() != json.ArrayJSONType {
129110
if index == 0 {
130-
return j, nil
111+
return jsonValue, nil
131112
}
132113
return nil, nil
133114
}
134115

135-
if ctx.strict && index >= j.Len() {
116+
if ctx.strict && index >= jsonValue.Len() {
136117
return nil, pgerror.Newf(pgcode.InvalidSQLJSONSubscript, "jsonpath array subscript is out of bounds")
137118
}
138119
if index < 0 {
139120
// Shouldn't happen, not supported in parser.
140121
return nil, errors.AssertionFailedf("negative array index")
141122
}
142-
return j.FetchValIdx(index)
123+
return jsonValue.FetchValIdx(index)
143124
}

pkg/util/jsonpath/eval/eval.go

+97-34
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,12 @@ func JsonpathQuery(
3939
vars: vars.JSON,
4040
strict: expr.Strict,
4141
}
42-
4342
// When silent is true, overwrite the strict mode.
4443
if bool(silent) {
4544
ctx.strict = false
4645
}
4746

48-
j, err := ctx.eval(expr.Path, []json.JSON{ctx.root})
47+
j, err := ctx.eval(expr.Path, ctx.root, !ctx.strict)
4948
if err != nil {
5049
return nil, err
5150
}
@@ -56,68 +55,132 @@ func JsonpathQuery(
5655
return res, nil
5756
}
5857

59-
func (ctx *jsonpathCtx) eval(jp jsonpath.Path, current []json.JSON) ([]json.JSON, error) {
60-
switch p := jp.(type) {
58+
func (ctx *jsonpathCtx) eval(
59+
jsonPath jsonpath.Path, jsonValue json.JSON, unwrap bool,
60+
) ([]json.JSON, error) {
61+
switch path := jsonPath.(type) {
6162
case jsonpath.Paths:
62-
// Evaluate each path within the path list, update the current JSON
63-
// object after each evaluation.
64-
for _, path := range p {
65-
results, err := ctx.eval(path, current)
63+
results := []json.JSON{jsonValue}
64+
var err error
65+
for _, p := range path {
66+
results, err = ctx.evalArray(p, results, unwrap)
6667
if err != nil {
6768
return nil, err
6869
}
69-
current = results
7070
}
71-
return current, nil
71+
return results, nil
7272
case jsonpath.Root:
7373
return []json.JSON{ctx.root}, nil
7474
case jsonpath.Current:
75-
return current, nil
75+
return []json.JSON{jsonValue}, nil
7676
case jsonpath.Key:
77-
return ctx.evalKey(p, current)
77+
return ctx.evalKey(path, jsonValue, unwrap)
7878
case jsonpath.Wildcard:
79-
return ctx.evalArrayWildcard(current)
79+
return ctx.evalArrayWildcard(jsonValue)
8080
case jsonpath.ArrayList:
81-
return ctx.evalArrayList(p, current)
81+
return ctx.evalArrayList(path, jsonValue)
8282
case jsonpath.Scalar:
83-
resolved, err := ctx.resolveScalar(p)
83+
resolved, err := ctx.resolveScalar(path)
8484
if err != nil {
8585
return nil, err
8686
}
8787
return []json.JSON{resolved}, nil
8888
case jsonpath.Operation:
89-
return ctx.evalOperation(p, current)
89+
res, err := ctx.evalOperation(path, jsonValue)
90+
if err != nil {
91+
return nil, err
92+
}
93+
return convertFromBool(res), nil
9094
case jsonpath.Filter:
91-
return ctx.evalFilter(p, current)
95+
return ctx.evalFilter(path, jsonValue, unwrap)
9296
default:
9397
return nil, errUnimplemented
9498
}
9599
}
96100

97-
func (ctx *jsonpathCtx) unwrap(input json.JSON) []json.JSON {
98-
if !ctx.strict && input.Type() == json.ArrayJSONType {
99-
array, _ := input.AsArray()
100-
return array
101+
func (ctx *jsonpathCtx) evalArray(
102+
jsonPath jsonpath.Path, jsonValue []json.JSON, unwrap bool,
103+
) ([]json.JSON, error) {
104+
var agg []json.JSON
105+
for _, j := range jsonValue {
106+
arr, err := ctx.eval(jsonPath, j, unwrap)
107+
if err != nil {
108+
return nil, err
109+
}
110+
agg = append(agg, arr...)
111+
}
112+
return agg, nil
113+
}
114+
115+
// unwrapCurrentTargetAndEval is used to unwrap the current json array and evaluate
116+
// the jsonpath query on each element. It is similar to executeItemUnwrapTargetArray
117+
// in postgres/src/backend/utils/adt/jsonpath_exec.c.
118+
func (ctx *jsonpathCtx) unwrapCurrentTargetAndEval(
119+
jsonPath jsonpath.Path, jsonValue json.JSON, unwrapNext bool,
120+
) ([]json.JSON, error) {
121+
if jsonValue.Type() != json.ArrayJSONType {
122+
return nil, errors.Newf("unwrapCurrentTargetAndEval can only be applied to an array")
101123
}
102-
return []json.JSON{input}
124+
return ctx.executeAnyItem(jsonPath, jsonValue, unwrapNext)
103125
}
104126

105-
func (ctx *jsonpathCtx) evalAndUnwrap(path jsonpath.Path, inputs []json.JSON) ([]json.JSON, error) {
106-
results, err := ctx.eval(path, inputs)
127+
func (ctx *jsonpathCtx) executeAnyItem(
128+
jsonPath jsonpath.Path, jsonValue json.JSON, unwrapNext bool,
129+
) ([]json.JSON, error) {
130+
childItems, err := json.AllPathsWithDepth(jsonValue, 1)
107131
if err != nil {
108132
return nil, err
109133
}
110-
if ctx.strict {
111-
return results, nil
112-
}
113-
var unwrapped []json.JSON
114-
for _, result := range results {
115-
unwrapped = append(unwrapped, ctx.unwrap(result)...)
134+
var agg []json.JSON
135+
for _, item := range childItems {
136+
if item.Len() != 1 {
137+
return nil, errors.Newf("unexpected path length")
138+
}
139+
unwrappedItem, err := item.FetchValIdx(0)
140+
if err != nil {
141+
return nil, err
142+
}
143+
if unwrappedItem == nil {
144+
return nil, errors.Newf("unwrapping json element")
145+
}
146+
if jsonPath == nil {
147+
agg = append(agg, unwrappedItem)
148+
} else {
149+
evalResults, err := ctx.eval(jsonPath, unwrappedItem, unwrapNext)
150+
if err != nil {
151+
return nil, err
152+
}
153+
agg = append(agg, evalResults...)
154+
}
116155
}
117-
return unwrapped, nil
156+
return agg, nil
118157
}
119158

120-
func (ctx *jsonpathCtx) unwrapAndEval(path jsonpath.Path, input json.JSON) ([]json.JSON, error) {
121-
unwrapped := ctx.unwrap(input)
122-
return ctx.eval(path, unwrapped)
159+
// evalAndUnwrapResult is used to evaluate the jsonpath query and unwrap the result
160+
// if the unwrap flag is true. It is similar to executeItemOptUnwrapResult
161+
// in postgres/src/backend/utils/adt/jsonpath_exec.c.
162+
func (ctx *jsonpathCtx) evalAndUnwrapResult(
163+
jsonPath jsonpath.Path, jsonValue json.JSON, unwrap bool,
164+
) ([]json.JSON, error) {
165+
evalResults, err := ctx.eval(jsonPath, jsonValue, !ctx.strict)
166+
if err != nil {
167+
return nil, err
168+
}
169+
if unwrap && !ctx.strict {
170+
var agg []json.JSON
171+
for _, j := range evalResults {
172+
if j.Type() == json.ArrayJSONType {
173+
// Pass in nil to just unwrap the array.
174+
arr, err := ctx.unwrapCurrentTargetAndEval(nil, j, false)
175+
if err != nil {
176+
return nil, err
177+
}
178+
agg = append(agg, arr...)
179+
} else {
180+
agg = append(agg, j)
181+
}
182+
}
183+
return agg, nil
184+
}
185+
return evalResults, nil
123186
}

0 commit comments

Comments
 (0)