Skip to content

Commit 30ce520

Browse files
committed
jsonpath: reuse evalComparison logic for all predicate expressions
This commit refactors `evalComparison` to be used for `like_regex` logic in addition to comparison operators. This allows for `like_regex` to have existence semantics when being evaluated on arrays of strings, rather than throwing an error. Epic: None Release note: None
1 parent 2a2adfe commit 30ce520

File tree

2 files changed

+45
-26
lines changed

2 files changed

+45
-26
lines changed

pkg/sql/logictest/testdata/logic_test/jsonb_path_query

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,16 @@ SELECT jsonb_path_query('"He said \"Hello\\World!\""', '$ ? (@ like_regex ".*\"H
914914
----
915915
"He said \"Hello\\World!\""
916916

917+
query T
918+
SELECT jsonb_path_query('["hello", "a"]', '$ like_regex "he"');
919+
----
920+
true
921+
922+
query T
923+
SELECT jsonb_path_query('["hello", "a"]', 'strict $ like_regex "he"');
924+
----
925+
null
926+
917927
query T rowsort
918928
SELECT jsonb_path_query('{"a": [1, 2], "b": "hello"}', '$.a ? ($.b == "hello") ');
919929
----

pkg/util/jsonpath/eval/operation.go

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ func (ctx *jsonpathCtx) evalBoolean(
8282
case jsonpath.OpCompEqual, jsonpath.OpCompNotEqual,
8383
jsonpath.OpCompLess, jsonpath.OpCompLessEqual,
8484
jsonpath.OpCompGreater, jsonpath.OpCompGreaterEqual:
85-
return ctx.evalComparison(op, jsonValue)
85+
return ctx.evalPredicate(op, jsonValue, evalComparisonFunc, true /* evalRight */, true /* unwrapRight */)
8686
case jsonpath.OpLikeRegex:
87-
return ctx.evalRegex(op, jsonValue)
87+
return ctx.evalPredicate(op, jsonValue, evalRegexFunc, false /* evalRight */, false /* unwrapRight */)
8888
case jsonpath.OpExists:
8989
return ctx.evalExists(op, jsonValue)
9090
case jsonpath.OpIsUnknown:
@@ -126,17 +126,13 @@ func (ctx *jsonpathCtx) evalExists(
126126
return jsonpathBoolTrue, nil
127127
}
128128

129-
func (ctx *jsonpathCtx) evalRegex(
130-
op jsonpath.Operation, jsonValue json.JSON,
131-
) (jsonpathBool, error) {
132-
l, err := ctx.evalAndUnwrapResult(op.Left, jsonValue, true /* unwrap */)
133-
if err != nil {
134-
return jsonpathBoolUnknown, err
135-
}
136-
if len(l) != 1 {
137-
return jsonpathBoolUnknown, errors.AssertionFailedf("left is not a single string")
129+
func evalRegexFunc(op jsonpath.Operation, l, _ json.JSON) (jsonpathBool, error) {
130+
regexOp, ok := op.Right.(jsonpath.Regex)
131+
if !ok {
132+
return jsonpathBoolUnknown, errors.AssertionFailedf("op.Right is not a regex")
138133
}
139-
if l[0].Type() != json.StringJSONType {
134+
135+
if l.Type() != json.StringJSONType {
140136
return jsonpathBoolUnknown, nil
141137
}
142138
// AsText() provides the correct string representation for regex pattern
@@ -150,16 +146,16 @@ func (ctx *jsonpathCtx) evalRegex(
150146
// - For a JSON string with a newline ("\n"): AsText() returns an actual
151147
// newline character ("\n"), while String() returns "\"\\n\"" (an escaped
152148
// backslash and 'n' enclosed in quotes)
153-
text, err := l[0].AsText()
149+
text, err := l.AsText()
154150
if err != nil {
155151
return jsonpathBoolUnknown, err
156152
}
157153

158-
regexOp := op.Right.(jsonpath.Regex)
159154
r, err := parser.ReCache.GetRegexp(regexOp)
160155
if err != nil {
161156
return jsonpathBoolUnknown, err
162157
}
158+
163159
res := r.MatchString(*text)
164160
if !res {
165161
return jsonpathBoolFalse, nil
@@ -223,28 +219,40 @@ func (ctx *jsonpathCtx) evalLogical(
223219
}
224220
}
225221

226-
// evalComparison evaluates a comparison operation predicate. Predicates have
227-
// existence semantics. True is returned if any pair of items from the left and
228-
// right paths satisfy the condition. In strict mode, even if a pair has been
229-
// found, all pairs need to be checked for errors.
230-
func (ctx *jsonpathCtx) evalComparison(
231-
op jsonpath.Operation, jsonValue json.JSON,
222+
// evalPredicate evaluates a predicate operation. Predicates have existence
223+
// semantics. True is returned if any pair of items from the left and right
224+
// paths satisfy the condition. In strict mode, even if a pair has been found,
225+
// all pairs need to be checked for errors.
226+
func (ctx *jsonpathCtx) evalPredicate(
227+
op jsonpath.Operation,
228+
jsonValue json.JSON,
229+
exec func(op jsonpath.Operation, l, r json.JSON) (jsonpathBool, error),
230+
evalRight, unwrapRight bool,
232231
) (jsonpathBool, error) {
233-
// The left and right argument results are always auto-unwrapped.
232+
// The left argument results are always auto-unwrapped.
234233
left, err := ctx.evalAndUnwrapResult(op.Left, jsonValue, true /* unwrap */)
235234
if err != nil {
236235
return jsonpathBoolUnknown, err
237236
}
238-
right, err := ctx.evalAndUnwrapResult(op.Right, jsonValue, true /* unwrap */)
239-
if err != nil {
240-
return jsonpathBoolUnknown, err
237+
var right []json.JSON
238+
if evalRight {
239+
// The right argument results are conditionally evaluated and unwrapped.
240+
right, err = ctx.evalAndUnwrapResult(op.Right, jsonValue, unwrapRight)
241+
if err != nil {
242+
return jsonpathBoolUnknown, err
243+
}
244+
} else {
245+
// If we don't want to evaluate the right argument, we need to call
246+
// the exec function once for each item in the left argument. Currently,
247+
// this only includes OpLikeRegex.
248+
right = append(right, nil)
241249
}
242250

243251
errored := false
244252
found := false
245253
for _, l := range left {
246254
for _, r := range right {
247-
res, err := execComparison(l, r, op.Type)
255+
res, err := exec(op, l, r)
248256
if err != nil {
249257
return jsonpathBoolUnknown, err
250258
}
@@ -271,7 +279,8 @@ func (ctx *jsonpathCtx) evalComparison(
271279
return jsonpathBoolFalse, nil
272280
}
273281

274-
func execComparison(l, r json.JSON, op jsonpath.OperationType) (jsonpathBool, error) {
282+
func evalComparisonFunc(operation jsonpath.Operation, l, r json.JSON) (jsonpathBool, error) {
283+
op := operation.Type
275284
if l.Type() != r.Type() && !(isBool(l) && isBool(r)) {
276285
// Inequality comparison of nulls to non-nulls is true. Everything else
277286
// is false.

0 commit comments

Comments
 (0)