Skip to content

Commit 7c6ffa8

Browse files
craig[bot]normanchenn
andcommitted
Merge #144643
144643: jsonpath: add support for numeric JSONPath methods r=normanchenn a=normanchenn This commit adds support for the following JSONPath numeric methods: - `.abs()` - `.floor()` - `.ceiling()` These methods can be applied to numeric values in JSONPath expressions. Epic: None Release note (sql change): Add support for numeric JSONPath methods `.abs()`, `.floor()`, `.ceiling()`. For example, `SELECT jsonb_path_query('-0.5', '$.abs()');`. Co-authored-by: Norman Chen <[email protected]>
2 parents 143eef3 + 2a9fcdf commit 7c6ffa8

File tree

7 files changed

+230
-19
lines changed

7 files changed

+230
-19
lines changed

pkg/sql/logictest/testdata/logic_test/jsonb_path_query

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1640,3 +1640,164 @@ query T
16401640
SELECT jsonb_path_query('{"a": 10}', '$ ? ((@.a < 12) || (@.a < $value))');
16411641
----
16421642
{"a": 10}
1643+
1644+
statement error pgcode 22036 pq: jsonpath item method .abs\(\) can only be applied to a numeric value
1645+
SELECT jsonb_path_query('{}', '$.abs()');
1646+
1647+
statement error pgcode 22036 pq: jsonpath item method .abs\(\) can only be applied to a numeric value
1648+
SELECT jsonb_path_query('"a"', '$.abs()');
1649+
1650+
statement error pgcode 22036 pq: jsonpath item method .abs\(\) can only be applied to a numeric value
1651+
SELECT jsonb_path_query('true', '$.abs()');
1652+
1653+
statement error pgcode 22036 pq: jsonpath item method .abs\(\) can only be applied to a numeric value
1654+
SELECT jsonb_path_query('null', '$.abs()');
1655+
1656+
query T
1657+
SELECT jsonb_path_query('{}', '(1).abs()');
1658+
----
1659+
1
1660+
1661+
query T
1662+
SELECT jsonb_path_query('{}', '(-1).abs()');
1663+
----
1664+
1
1665+
1666+
query T
1667+
SELECT jsonb_path_query('{}', '(0.5).abs()');
1668+
----
1669+
0.5
1670+
1671+
query T
1672+
SELECT jsonb_path_query('{}', '(-0.5).abs()');
1673+
----
1674+
0.5
1675+
1676+
query T rowsort
1677+
SELECT jsonb_path_query('[1, -1, 0.5, -0.5]', '$.abs()');
1678+
----
1679+
1
1680+
1
1681+
0.5
1682+
0.5
1683+
1684+
query T
1685+
SELECT jsonb_path_query('{}', '(1).ceiling()');
1686+
----
1687+
1
1688+
1689+
query T
1690+
SELECT jsonb_path_query('{}', '(-1).ceiling()');
1691+
----
1692+
-1
1693+
1694+
query T
1695+
SELECT jsonb_path_query('{}', '(0.5).ceiling()');
1696+
----
1697+
1
1698+
1699+
query T
1700+
SELECT jsonb_path_query('{}', '(-0.5).ceiling()');
1701+
----
1702+
0
1703+
1704+
query T
1705+
SELECT jsonb_path_query('{}', '(0.1).ceiling()');
1706+
----
1707+
1
1708+
1709+
query T
1710+
SELECT jsonb_path_query('{}', '(-0.1).ceiling()');
1711+
----
1712+
0
1713+
1714+
query T
1715+
SELECT jsonb_path_query('{}', '(0.9).ceiling()');
1716+
----
1717+
1
1718+
1719+
query T
1720+
SELECT jsonb_path_query('{}', '(-0.9).ceiling()');
1721+
----
1722+
0
1723+
1724+
query T rowsort
1725+
SELECT jsonb_path_query('[1, -1, 0.5, -0.5, 0.1, -0.1, 0.9, -0.9]', '$.ceiling()');
1726+
----
1727+
1
1728+
-1
1729+
1
1730+
0
1731+
1
1732+
0
1733+
1
1734+
0
1735+
1736+
query T
1737+
SELECT jsonb_path_query('{}', '(1).floor()');
1738+
----
1739+
1
1740+
1741+
query T
1742+
SELECT jsonb_path_query('{}', '(-1).floor()');
1743+
----
1744+
-1
1745+
1746+
query T
1747+
SELECT jsonb_path_query('{}', '(0.5).floor()');
1748+
----
1749+
0
1750+
1751+
query T
1752+
SELECT jsonb_path_query('{}', '(-0.5).floor()');
1753+
----
1754+
-1
1755+
1756+
query T
1757+
SELECT jsonb_path_query('{}', '(0.1).floor()');
1758+
----
1759+
0
1760+
1761+
query T
1762+
SELECT jsonb_path_query('{}', '(-0.1).floor()');
1763+
----
1764+
-1
1765+
1766+
query T
1767+
SELECT jsonb_path_query('{}', '(0.9).floor()');
1768+
----
1769+
0
1770+
1771+
query T
1772+
SELECT jsonb_path_query('{}', '(-0.9).floor()');
1773+
----
1774+
-1
1775+
1776+
query T rowsort
1777+
SELECT jsonb_path_query('[1, -1, 0.5, -0.5, 0.1, -0.1, 0.9, -0.9]', '$.floor()');
1778+
----
1779+
1
1780+
-1
1781+
0
1782+
-1
1783+
0
1784+
-1
1785+
0
1786+
-1
1787+
1788+
statement error pgcode 22036 pq: jsonpath item method .abs\(\) can only be applied to a numeric value
1789+
SELECT jsonb_path_query('[[1, 2]]', '$.abs()');
1790+
1791+
statement error pgcode 22036 pq: jsonpath item method .abs\(\) can only be applied to a numeric value
1792+
SELECT jsonb_path_query('[1, 2, [3, 4]]', '$.abs()');
1793+
1794+
query T
1795+
SELECT jsonb_path_query('{"a": -0.5}', '$.a.abs()')
1796+
----
1797+
0.5
1798+
1799+
statement error pgcode 22036 pq: jsonpath item method .abs\(\) can only be applied to a numeric value
1800+
SELECT jsonb_path_query('"1"', '$.abs()');
1801+
1802+
statement error pgcode 22036 pq: jsonpath item method .floor\(\) can only be applied to a numeric value
1803+
SELECT jsonb_path_query('{}', '(null).floor()');

pkg/sql/logictest/testdata/logic_test/jsonpath

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,6 @@ SELECT '$ ? (@ like_regex ".*" flag "i")'::JSONPATH;
165165
statement error unimplemented
166166
SELECT '$.keyvalue()'::JSONPATH;
167167

168-
statement error unimplemented
169-
SELECT '$.abs()'::JSONPATH;
170-
171-
statement error unimplemented
172-
SELECT '$.ceiling()'::JSONPATH;
173-
174-
statement error unimplemented
175-
SELECT '$.floor()'::JSONPATH;
176-
177168
statement error unimplemented
178169
SELECT '$.bigint()'::JSONPATH;
179170

pkg/util/jsonpath/eval/eval.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ func (ctx *jsonpathCtx) eval(
183183
case jsonpath.Last:
184184
return ctx.evalLast()
185185
case jsonpath.Method:
186-
return ctx.evalMethod(path, jsonValue)
186+
return ctx.evalMethod(path, jsonValue, unwrap)
187187
default:
188188
return nil, errUnimplemented
189189
}

pkg/util/jsonpath/eval/method.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ package eval
88
import (
99
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
1010
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
11+
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
1112
"github.com/cockroachdb/cockroach/pkg/util/json"
1213
"github.com/cockroachdb/cockroach/pkg/util/jsonpath"
14+
"github.com/cockroachdb/errors"
1315
)
1416

1517
var (
1618
errEvalSizeNotArray = pgerror.Newf(pgcode.SQLJSONArrayNotFound, "jsonpath item method .size() can only be applied to an array")
1719
)
1820

1921
func (ctx *jsonpathCtx) evalMethod(
20-
method jsonpath.Method, jsonValue json.JSON,
22+
method jsonpath.Method, jsonValue json.JSON, unwrap bool,
2123
) ([]json.JSON, error) {
2224
switch method.Type {
2325
case jsonpath.SizeMethod:
@@ -29,6 +31,8 @@ func (ctx *jsonpathCtx) evalMethod(
2931
case jsonpath.TypeMethod:
3032
t := ctx.evalType(jsonValue)
3133
return []json.JSON{json.FromString(t)}, nil
34+
case jsonpath.AbsMethod, jsonpath.FloorMethod, jsonpath.CeilingMethod:
35+
return ctx.evalNumericMethod(method, jsonValue, unwrap)
3236
default:
3337
return nil, errUnimplemented
3438
}
@@ -52,3 +56,37 @@ func (ctx *jsonpathCtx) evalType(jsonValue json.JSON) string {
5256
}
5357
return jsonValue.Type().String()
5458
}
59+
60+
func (ctx *jsonpathCtx) evalNumericMethod(
61+
jsonPath jsonpath.Path, jsonValue json.JSON, unwrap bool,
62+
) ([]json.JSON, error) {
63+
if unwrap && jsonValue.Type() == json.ArrayJSONType {
64+
return ctx.unwrapCurrentTargetAndEval(jsonPath, jsonValue, false /* unwrap */)
65+
}
66+
method, _ := jsonPath.(jsonpath.Method)
67+
// AsDecimal allocates a new Decimal object, so we can modify it below.
68+
dec, ok := jsonValue.AsDecimal()
69+
if !ok {
70+
return nil, maybeThrowError(ctx, pgerror.Newf(pgcode.NonNumericSQLJSONItem,
71+
"jsonpath item method .%s() can only be applied to a numeric value",
72+
jsonpath.MethodTypeStrings[method.Type]))
73+
}
74+
var err error
75+
switch method.Type {
76+
case jsonpath.AbsMethod:
77+
dec = dec.Abs(dec)
78+
case jsonpath.FloorMethod:
79+
_, err = tree.ExactCtx.Floor(dec, dec)
80+
case jsonpath.CeilingMethod:
81+
_, err = tree.ExactCtx.Ceil(dec, dec)
82+
if dec.IsZero() {
83+
dec.Negative = false
84+
}
85+
default:
86+
panic(errors.Newf("unimplemented method: %s", method.Type))
87+
}
88+
if err != nil {
89+
return nil, err
90+
}
91+
return []json.JSON{json.FromDecimal(*dec)}, nil
92+
}

pkg/util/jsonpath/method.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@ const (
1818
InvalidMethod MethodType = iota
1919
SizeMethod
2020
TypeMethod
21+
AbsMethod
22+
FloorMethod
23+
CeilingMethod
2124
)
2225

23-
var methodTypeStrings = [...]string{
24-
SizeMethod: "size",
25-
TypeMethod: "type",
26+
var MethodTypeStrings = [...]string{
27+
SizeMethod: "size",
28+
TypeMethod: "type",
29+
AbsMethod: "abs",
30+
FloorMethod: "floor",
31+
CeilingMethod: "ceiling",
2632
}
2733

2834
type Method struct {
@@ -33,8 +39,8 @@ var _ Path = Method{}
3339

3440
func (m Method) ToString(sb *strings.Builder, _, _ bool) {
3541
switch m.Type {
36-
case SizeMethod, TypeMethod:
37-
sb.WriteString(fmt.Sprintf(".%s()", methodTypeStrings[m.Type]))
42+
case SizeMethod, TypeMethod, AbsMethod, FloorMethod, CeilingMethod:
43+
sb.WriteString(fmt.Sprintf(".%s()", MethodTypeStrings[m.Type]))
3844
default:
3945
panic(errors.AssertionFailedf("unhandled method type: %d", m.Type))
4046
}

pkg/util/jsonpath/parser/jsonpath.y

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -569,15 +569,15 @@ method:
569569
}
570570
| ABS
571571
{
572-
return unimplemented(jsonpathlex, ".abs()")
572+
$$.val = jsonpath.Method{Type: jsonpath.AbsMethod}
573573
}
574574
| CEILING
575575
{
576-
return unimplemented(jsonpathlex, ".ceiling()")
576+
$$.val = jsonpath.Method{Type: jsonpath.CeilingMethod}
577577
}
578578
| FLOOR
579579
{
580-
return unimplemented(jsonpathlex, ".floor()")
580+
$$.val = jsonpath.Method{Type: jsonpath.FloorMethod}
581581
}
582582
| BIGINT
583583
{

pkg/util/jsonpath/parser/testdata/jsonpath

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,21 @@ parse
682682
----
683683
(1 + 123).type() -- normalized!
684684

685+
parse
686+
$.a.abs()
687+
----
688+
$."a".abs() -- normalized!
689+
690+
parse
691+
$.a.floor()
692+
----
693+
$."a".floor() -- normalized!
694+
695+
parse
696+
$.a.ceiling()
697+
----
698+
$."a".ceiling() -- normalized!
699+
685700
# parse
686701
# $.1a
687702
# ----

0 commit comments

Comments
 (0)