Skip to content

Commit 4bc4946

Browse files
craig[bot]normanchenn
craig[bot]
andcommitted
Merge #143735
143735: jsonpath: validate regular expression during parsing r=normanchenn a=normanchenn Previously, regular expressions in jsonpath queries were only validated during query execution. This could lead to queries with invalid regex patterns being accepted during parsing, only to fail later during execution. This change moves regex validation to parse time, ensuring that any jsonpath query containing an invalid regular expression will fail immediately during parsing. This matches what postgres does when they handle regex. Informs: #143730 Closes: #143693 Release note: None. Co-authored-by: Norman Chen <[email protected]>
2 parents bdbdbf1 + 26024c4 commit 4bc4946

File tree

9 files changed

+48
-27
lines changed

9 files changed

+48
-27
lines changed

pkg/sql/logictest/testdata/logic_test/jsonpath

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ SELECT '$ . a ? ( @ . b == 1 ) . c ? ( @ . d == 2 ) '::JSON
150150
----
151151
$."a"?((@."b" == 1))."c"?((@."d" == 2))
152152

153+
statement error pgcode 2201B pq: could not parse .* invalid regular expression: error parsing regexp: missing closing \)
154+
SELECT '$ ? (@ like_regex "(invalid pattern")'::JSONPATH
155+
153156
## When we allow table creation
154157

155158
# statement ok

pkg/sql/sem/builtins/builtins.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12184,9 +12184,7 @@ func makeTimestampStatementBuiltinOverload(withOutputTZ bool, withInputTZ bool)
1218412184
}
1218512185
}
1218612186

12187-
func makeJsonpathExists(
12188-
_ context.Context, evalCtx *eval.Context, args tree.Datums,
12189-
) (tree.Datum, error) {
12187+
func makeJsonpathExists(_ context.Context, _ *eval.Context, args tree.Datums) (tree.Datum, error) {
1219012188
target := tree.MustBeDJSON(args[0])
1219112189
path := tree.MustBeDJsonpath(args[1])
1219212190
vars := tree.EmptyDJSON
@@ -12197,7 +12195,7 @@ func makeJsonpathExists(
1219712195
if len(args) > 3 {
1219812196
silent = tree.MustBeDBool(args[3])
1219912197
}
12200-
exists, err := jsonpath.JsonpathExists(evalCtx, target, path, vars, silent)
12198+
exists, err := jsonpath.JsonpathExists(target, path, vars, silent)
1220112199
if err != nil {
1220212200
return nil, err
1220312201
}

pkg/sql/sem/builtins/generator_builtins.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1624,8 +1624,6 @@ var jsonObjectKeysImpl = makeGeneratorOverload(
16241624
var jsonPathQueryGeneratorType = types.Jsonb
16251625

16261626
type jsonPathQueryGenerator struct {
1627-
evalCtx *eval.Context
1628-
16291627
target tree.DJSON
16301628
path tree.DJsonpath
16311629
vars tree.DJSON
@@ -1636,7 +1634,7 @@ type jsonPathQueryGenerator struct {
16361634
}
16371635

16381636
func makeJsonpathQueryGenerator(
1639-
_ context.Context, evalCtx *eval.Context, args tree.Datums,
1637+
_ context.Context, _ *eval.Context, args tree.Datums,
16401638
) (eval.ValueGenerator, error) {
16411639
target := tree.MustBeDJSON(args[0])
16421640
path := tree.MustBeDJsonpath(args[1])
@@ -1652,11 +1650,10 @@ func makeJsonpathQueryGenerator(
16521650
silent = tree.MustBeDBool(args[3])
16531651
}
16541652
return &jsonPathQueryGenerator{
1655-
evalCtx: evalCtx,
1656-
target: target,
1657-
path: path,
1658-
vars: vars,
1659-
silent: silent,
1653+
target: target,
1654+
path: path,
1655+
vars: vars,
1656+
silent: silent,
16601657
}, nil
16611658
}
16621659

@@ -1667,7 +1664,7 @@ func (g *jsonPathQueryGenerator) ResolvedType() *types.T {
16671664

16681665
// Start implements the eval.ValueGenerator interface.
16691666
func (g *jsonPathQueryGenerator) Start(_ context.Context, _ *kv.Txn) error {
1670-
jsonb, err := jsonpath.JsonpathQuery(g.evalCtx, g.target, g.path, g.vars, g.silent)
1667+
jsonb, err := jsonpath.JsonpathQuery(g.target, g.path, g.vars, g.silent)
16711668
if err != nil {
16721669
return err
16731670
}

pkg/util/jsonpath/eval/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ go_library(
1515
deps = [
1616
"//pkg/sql/pgwire/pgcode",
1717
"//pkg/sql/pgwire/pgerror",
18-
"//pkg/sql/sem/eval",
1918
"//pkg/sql/sem/tree",
2019
"//pkg/util/errorutil/unimplemented",
2120
"//pkg/util/json",

pkg/util/jsonpath/eval/eval.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
package eval
77

88
import (
9-
"github.com/cockroachdb/cockroach/pkg/sql/sem/eval"
109
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
1110
"github.com/cockroachdb/cockroach/pkg/util/errorutil/unimplemented"
1211
"github.com/cockroachdb/cockroach/pkg/util/json"
@@ -21,8 +20,6 @@ var (
2120
)
2221

2322
type jsonpathCtx struct {
24-
evalCtx *eval.Context
25-
2623
// Root of the given JSON object ($). We store this because we will need to
2724
// support queries with multiple root elements (ex. $.a ? ($.b == "hello").
2825
root json.JSON
@@ -31,7 +28,7 @@ type jsonpathCtx struct {
3128
}
3229

3330
func JsonpathQuery(
34-
evalCtx *eval.Context, target tree.DJSON, path tree.DJsonpath, vars tree.DJSON, silent tree.DBool,
31+
target tree.DJSON, path tree.DJsonpath, vars tree.DJSON, silent tree.DBool,
3532
) ([]tree.DJSON, error) {
3633
parsedPath, err := parser.Parse(string(path))
3734
if err != nil {
@@ -40,10 +37,9 @@ func JsonpathQuery(
4037
expr := parsedPath.AST
4138

4239
ctx := &jsonpathCtx{
43-
evalCtx: evalCtx,
44-
root: target.JSON,
45-
vars: vars.JSON,
46-
strict: expr.Strict,
40+
root: target.JSON,
41+
vars: vars.JSON,
42+
strict: expr.Strict,
4743
}
4844
// When silent is true, overwrite the strict mode.
4945
if bool(silent) {
@@ -62,9 +58,9 @@ func JsonpathQuery(
6258
}
6359

6460
func JsonpathExists(
65-
evalCtx *eval.Context, target tree.DJSON, path tree.DJsonpath, vars tree.DJSON, silent tree.DBool,
61+
target tree.DJSON, path tree.DJsonpath, vars tree.DJSON, silent tree.DBool,
6662
) (tree.DBool, error) {
67-
j, err := JsonpathQuery(evalCtx, target, path, vars, silent)
63+
j, err := JsonpathQuery(target, path, vars, silent)
6864
if err != nil {
6965
return false, err
7066
}

pkg/util/jsonpath/eval/operation.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
1313
"github.com/cockroachdb/cockroach/pkg/util/json"
1414
"github.com/cockroachdb/cockroach/pkg/util/jsonpath"
15+
"github.com/cockroachdb/cockroach/pkg/util/jsonpath/parser"
1516
"github.com/cockroachdb/errors"
1617
)
1718

@@ -124,7 +125,7 @@ func (ctx *jsonpathCtx) evalRegex(
124125
}
125126

126127
regexOp := op.Right.(jsonpath.Regex)
127-
r, err := ctx.evalCtx.ReCache.GetRegexp(regexOp)
128+
r, err := parser.ReCache.GetRegexp(regexOp)
128129
if err != nil {
129130
return jsonpathBoolUnknown, err
130131
}

pkg/util/jsonpath/parser/jsonpath.y

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ package parser
44
import (
55
"strconv"
66

7+
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
8+
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
79
"github.com/cockroachdb/cockroach/pkg/sql/scanner"
810
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
911
"github.com/cockroachdb/cockroach/pkg/util/json"
@@ -124,6 +126,16 @@ func unaryOp(op jsonpath.OperationType, left jsonpath.Path) jsonpath.Operation {
124126
}
125127
}
126128

129+
func regexBinaryOp(left jsonpath.Path, regex string) (jsonpath.Operation, error) {
130+
r := jsonpath.Regex{Regex: regex}
131+
_, err := ReCache.GetRegexp(r)
132+
if err != nil {
133+
return jsonpath.Operation{}, pgerror.Wrapf(err, pgcode.InvalidRegularExpression,
134+
"invalid regular expression")
135+
}
136+
return binaryOp(jsonpath.OpLikeRegex, left, r), nil
137+
}
138+
127139
%}
128140

129141
%union{
@@ -398,8 +410,11 @@ predicate:
398410
}
399411
| expr LIKE_REGEX STRING
400412
{
401-
regex := jsonpath.Regex{Regex: $3}
402-
$$.val = binaryOp(jsonpath.OpLikeRegex, $1.path(), regex)
413+
regex, err := regexBinaryOp($1.path(), $3)
414+
if err != nil {
415+
return setErr(jsonpathlex, err)
416+
}
417+
$$.val = regex
403418
}
404419
| expr LIKE_REGEX STRING FLAG STRING
405420
{

pkg/util/jsonpath/parser/parse.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ func init() {
2222
}
2323
}
2424

25+
var (
26+
ReCache = tree.NewRegexpCache(64)
27+
)
28+
2529
type Parser struct {
2630
scanner scanner.JSONPathScanner
2731
lexer lexer

pkg/util/jsonpath/parser/testdata/jsonpath

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,14 @@ parse
494494
----
495495
(1 + (2 * (-4))) -- normalized!
496496

497+
error
498+
$ ? (@ like_regex "(invalid pattern")
499+
----
500+
at or near ")": syntax error: invalid regular expression: error parsing regexp: missing closing ): `(invalid pattern`
501+
DETAIL: source SQL:
502+
$ ? (@ like_regex "(invalid pattern")
503+
^
504+
497505
# postgres allows floats as array indexes
498506
# parse
499507
# $.abc[1.0]

0 commit comments

Comments
 (0)