Skip to content

Commit 24e135f

Browse files
craig[bot]normanchenn
andcommitted
Merge #143923
143923: jsonpath: add @ and LAST semantic checks over AST during parsing r=normanchenn a=normanchenn This commit adds some semantic validation during parsing to enforce some Postgres-compatible restrictions: - The @ (current) operator is not allowed in root expressions - The LAST keyword is only allowed within array accessors Previously, we would accept these as valid jsonpath types, (ex. `SELECT '`@'::JSONPATH;`)` would work, but now we don't. Informs: #143730 Release note: None Co-authored-by: Norman Chen <[email protected]>
2 parents 92f6aa5 + af9753b commit 24e135f

File tree

4 files changed

+97
-10
lines changed

4 files changed

+97
-10
lines changed

pkg/sql/logictest/testdata/logic_test/jsonb_path_query

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,8 +1047,11 @@ SELECT jsonb_path_query('[1, 2, 3, 4]', '$[*] ? (@ > +2)');
10471047
3
10481048
4
10491049

1050-
statement error pgcode 42601 pq: LAST is allowed only in array subscripts
1051-
SELECT jsonb_path_query('{}', 'last');
1050+
statement error pgcode 42601 pq: jsonb_path_query\(\): could not parse "last" as type jsonpath: LAST is allowed only in array subscripts
1051+
SELECT jsonb_path_query('{}', 'last'::JSONPATH);
1052+
1053+
statement error pgcode 42601 pq: jsonb_path_query\(\): could not parse "@" as type jsonpath: @ is not allowed in root expressions
1054+
SELECT jsonb_path_query('{}', '@'::JSONPATH);
10521055

10531056
query T
10541057
SELECT jsonb_path_query('[1, 2, 3, 4]', '$[last]');

pkg/sql/logictest/testdata/logic_test/jsonpath

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@ $."a"?((@."b" == 1))."c"?((@."d" == 2))
153153
statement error pgcode 2201B pq: could not parse .* invalid regular expression: error parsing regexp: missing closing \)
154154
SELECT '$ ? (@ like_regex "(invalid pattern")'::JSONPATH
155155

156+
statement error pgcode 42601 pq: could not parse "last" as type jsonpath: LAST is allowed only in array subscripts
157+
SELECT 'last'::JSONPATH
158+
159+
statement error pgcode 42601 pq: could not parse "@" as type jsonpath: @ is not allowed in root expressions
160+
SELECT '@'::JSONPATH
161+
156162
## When we allow table creation
157163

158164
# statement ok

pkg/util/jsonpath/parser/parse.go

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ package parser
77

88
import (
99
"github.com/cockroachdb/cockroach/pkg/sql/parser/statements"
10+
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
11+
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
1012
"github.com/cockroachdb/cockroach/pkg/sql/scanner"
1113
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
14+
"github.com/cockroachdb/cockroach/pkg/util/jsonpath"
1215
"github.com/cockroachdb/errors"
1316
)
1417

@@ -24,6 +27,11 @@ func init() {
2427

2528
var (
2629
ReCache = tree.NewRegexpCache(64)
30+
31+
errCurrentInRoot = pgerror.Newf(pgcode.Syntax,
32+
"@ is not allowed in root expressions")
33+
errLastInNonArray = pgerror.Newf(pgcode.Syntax,
34+
"LAST is allowed only in array subscripts")
2735
)
2836

2937
type Parser struct {
@@ -101,5 +109,78 @@ func (p *Parser) Parse(jsonpath string) (statements.JsonpathStatement, error) {
101109
// Parse parses a jsonpath string and returns a jsonpath.Jsonpath object.
102110
func Parse(jsonpath string) (statements.JsonpathStatement, error) {
103111
var p Parser
104-
return p.Parse(jsonpath)
112+
stmt, err := p.Parse(jsonpath)
113+
if err != nil {
114+
return statements.JsonpathStatement{}, err
115+
}
116+
// Similar to flattenJsonPathParseItem in postgres, we do a pass over the AST
117+
// to perform some semantic checks.
118+
if err := walkAST(stmt.AST.Path); err != nil {
119+
return statements.JsonpathStatement{}, err
120+
}
121+
return stmt, nil
122+
}
123+
124+
// TODO(normanchenn): Similarly to flattenJsonPathParseItem, we could use this to
125+
// generate a normalized jsonpath string, rather than calling stmt.AST.String().
126+
func walkAST(path jsonpath.Path) error {
127+
return walk(path, 0 /* nestingLevel */, false /* insideArraySubscript */)
128+
}
129+
130+
func walk(path jsonpath.Path, nestingLevel int, insideArraySubscript bool) error {
131+
switch path := path.(type) {
132+
case jsonpath.Paths:
133+
for _, p := range path {
134+
if err := walk(p, nestingLevel, insideArraySubscript); err != nil {
135+
return err
136+
}
137+
}
138+
return nil
139+
case jsonpath.ArrayList:
140+
for _, p := range path {
141+
if err := walk(p, nestingLevel, true /* insideArraySubscript */); err != nil {
142+
return err
143+
}
144+
}
145+
return nil
146+
case jsonpath.ArrayIndexRange:
147+
if err := walk(path.Start, nestingLevel, insideArraySubscript); err != nil {
148+
return err
149+
}
150+
if err := walk(path.End, nestingLevel, insideArraySubscript); err != nil {
151+
return err
152+
}
153+
return nil
154+
case jsonpath.Operation:
155+
if err := walk(path.Left, nestingLevel, insideArraySubscript); err != nil {
156+
return err
157+
}
158+
if path.Right != nil {
159+
if err := walk(path.Right, nestingLevel, insideArraySubscript); err != nil {
160+
return err
161+
}
162+
}
163+
return nil
164+
case jsonpath.Filter:
165+
if err := walk(path.Condition, nestingLevel+1, insideArraySubscript); err != nil {
166+
return err
167+
}
168+
return nil
169+
case jsonpath.Current:
170+
if nestingLevel <= 0 {
171+
return errCurrentInRoot
172+
}
173+
return nil
174+
case jsonpath.Last:
175+
if !insideArraySubscript {
176+
return errLastInNonArray
177+
}
178+
return nil
179+
case jsonpath.Root, jsonpath.Key, jsonpath.Wildcard, jsonpath.Regex,
180+
jsonpath.AnyKey, jsonpath.Scalar:
181+
// These are leaf nodes that don't require any further checks.
182+
return nil
183+
default:
184+
panic(errors.AssertionFailedf("unhandled path type: %T", path))
185+
}
105186
}

pkg/util/jsonpath/parser/testdata/jsonpath

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -347,11 +347,10 @@ $.abc ? ($.a[1] > 2)
347347
----
348348
$."abc"?(($."a"[1] > 2)) -- normalized!
349349

350-
# TODO(normanchenn): this should be not allowed
351-
parse
350+
error
352351
@
353352
----
354-
@
353+
@ is not allowed in root expressions
355354

356355
parse
357356
$.a[*] ? (@.b > 100)
@@ -502,12 +501,10 @@ DETAIL: source SQL:
502501
$ ? (@ like_regex "(invalid pattern")
503502
^
504503

505-
# TODO(normanchenn): This shouldn't be parsed, as the last keyword isn't within
506-
# array subscripts.
507-
parse
504+
error
508505
last
509506
----
510-
last
507+
LAST is allowed only in array subscripts
511508

512509
parse
513510
$[last]

0 commit comments

Comments
 (0)