Skip to content

Commit 63113dc

Browse files
author
Nikita Glukhov
committed
Add jsonpath object subscripting
1 parent 6efb195 commit 63113dc

File tree

3 files changed

+231
-2
lines changed

3 files changed

+231
-2
lines changed

src/backend/utils/adt/jsonpath_exec.c

+119-2
Original file line numberDiff line numberDiff line change
@@ -1549,8 +1549,126 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
15491549

15501550
cxt->innermostArraySize = innermostArraySize;
15511551
}
1552+
else if (JsonbType(jb) == jbvObject)
1553+
{
1554+
int innermostArraySize = cxt->innermostArraySize;
1555+
int i;
1556+
JsonbValue bin;
1557+
JsonbValue *wrapped = NULL;
1558+
1559+
if (jb->type != jbvBinary)
1560+
jb = JsonbWrapInBinary(jb, &bin);
1561+
1562+
cxt->innermostArraySize = 1;
1563+
1564+
for (i = 0; i < jsp->content.array.nelems; i++)
1565+
{
1566+
JsonPathItem from;
1567+
JsonPathItem to;
1568+
JsonbValue *key;
1569+
JsonbValue tmp;
1570+
JsonValueList keys = { 0 };
1571+
bool range = jspGetArraySubscript(jsp, &from, &to, i);
1572+
1573+
if (range)
1574+
{
1575+
int index_from;
1576+
int index_to;
1577+
1578+
if (!cxt->lax)
1579+
return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
1580+
1581+
if (!wrapped)
1582+
wrapped = wrapItem(jb);
1583+
1584+
res = getArrayIndex(cxt, &from, wrapped, &index_from);
1585+
if (jperIsError(res))
1586+
return res;
1587+
1588+
res = getArrayIndex(cxt, &to, wrapped, &index_to);
1589+
if (jperIsError(res))
1590+
return res;
1591+
1592+
res = jperNotFound;
1593+
1594+
if (index_from <= 0 && index_to >= 0)
1595+
{
1596+
res = recursiveExecuteNext(cxt, jsp, NULL, jb,
1597+
found, true);
1598+
if (jperIsError(res))
1599+
return res;
1600+
1601+
}
1602+
1603+
if (res == jperOk && !found)
1604+
break;
1605+
1606+
continue;
1607+
}
1608+
1609+
res = recursiveExecute(cxt, &from, jb, &keys);
1610+
1611+
if (jperIsError(res))
1612+
return res;
1613+
1614+
if (JsonValueListLength(&keys) != 1)
1615+
return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
1616+
1617+
key = JsonValueListHead(&keys);
1618+
1619+
if (JsonbType(key) == jbvScalar)
1620+
key = JsonbExtractScalar(key->val.binary.data, &tmp);
1621+
1622+
res = jperNotFound;
1623+
1624+
if (key->type == jbvNumeric && cxt->lax)
1625+
{
1626+
int index = DatumGetInt32(
1627+
DirectFunctionCall1(numeric_int4,
1628+
DirectFunctionCall2(numeric_trunc,
1629+
NumericGetDatum(key->val.numeric),
1630+
Int32GetDatum(0))));
1631+
1632+
if (!index)
1633+
{
1634+
res = recursiveExecuteNext(cxt, jsp, NULL, jb,
1635+
found, true);
1636+
if (jperIsError(res))
1637+
return res;
1638+
}
1639+
}
1640+
else if (key->type == jbvString)
1641+
{
1642+
key = findJsonbValueFromContainer(jb->val.binary.data,
1643+
JB_FOBJECT, key);
1644+
1645+
if (key)
1646+
{
1647+
res = recursiveExecuteNext(cxt, jsp, NULL, key,
1648+
found, false);
1649+
if (jperIsError(res))
1650+
return res;
1651+
}
1652+
else if (!cxt->lax)
1653+
return jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
1654+
}
1655+
else
1656+
return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
1657+
1658+
if (res == jperOk && !found)
1659+
break;
1660+
}
1661+
1662+
cxt->innermostArraySize = innermostArraySize;
1663+
}
15521664
else
1553-
res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
1665+
{
1666+
if (cxt->lax)
1667+
res = recursiveExecuteNoUnwrap(cxt, jsp, wrapItem(jb),
1668+
found, false);
1669+
else
1670+
res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
1671+
}
15541672
break;
15551673

15561674
case jpiLast:
@@ -2594,7 +2712,6 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
25942712
return recursiveExecuteUnwrap(cxt, jsp, jb, found);
25952713

25962714
case jpiAnyArray:
2597-
case jpiIndexArray:
25982715
jb = wrapItem(jb);
25992716
break;
26002717

src/test/regress/expected/jsonb_jsonpath.out

+90
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,30 @@ select jsonb '[1]' @? '$.[1.2]';
110110

111111
select jsonb '[1]' @? 'strict $.[1.2]';
112112
ERROR: Invalid SQL/JSON subscript
113+
select jsonb '{}' @? 'strict $.[0.3]';
114+
ERROR: Invalid SQL/JSON subscript
115+
select jsonb '{}' @? 'lax $.[0.3]';
116+
?column?
117+
----------
118+
t
119+
(1 row)
120+
121+
select jsonb '{}' @? 'strict $.[1.2]';
122+
ERROR: Invalid SQL/JSON subscript
123+
select jsonb '{}' @? 'lax $.[1.2]';
124+
?column?
125+
----------
126+
f
127+
(1 row)
128+
129+
select jsonb '{}' @? 'strict $.[-2 to 3]';
130+
ERROR: Invalid SQL/JSON subscript
131+
select jsonb '{}' @? 'lax $.[-2 to 3]';
132+
?column?
133+
----------
134+
t
135+
(1 row)
136+
113137
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
114138
?column?
115139
----------
@@ -244,6 +268,12 @@ select jsonb '1' @* 'lax $[*]';
244268
1
245269
(1 row)
246270

271+
select jsonb '{}' @* 'lax $[0]';
272+
?column?
273+
----------
274+
{}
275+
(1 row)
276+
247277
select jsonb '[1]' @* 'lax $[0]';
248278
?column?
249279
----------
@@ -277,6 +307,12 @@ select jsonb '[1]' @* '$[last]';
277307
1
278308
(1 row)
279309

310+
select jsonb '{}' @* 'lax $[last]';
311+
?column?
312+
----------
313+
{}
314+
(1 row)
315+
280316
select jsonb '[1,2,3]' @* '$[last]';
281317
?column?
282318
----------
@@ -1938,3 +1974,57 @@ select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$.map({x: @, y: @ < 3})[*], {z: "f
19381974
{"a": 5, "b": [{"x": 1, "y": true}, {"x": 2, "y": true}, {"x": 3, "y": false}, {"z": "foo"}]}
19391975
(1 row)
19401976

1977+
-- extension: object subscripting
1978+
select jsonb '{"a": 1}' @? '$["a"]';
1979+
?column?
1980+
----------
1981+
t
1982+
(1 row)
1983+
1984+
select jsonb '{"a": 1}' @? '$["b"]';
1985+
?column?
1986+
----------
1987+
f
1988+
(1 row)
1989+
1990+
select jsonb '{"a": 1}' @? 'strict $["b"]';
1991+
ERROR: SQL/JSON member not found
1992+
select jsonb '{"a": 1}' @? '$["b", "a"]';
1993+
?column?
1994+
----------
1995+
t
1996+
(1 row)
1997+
1998+
select jsonb '{"a": 1}' @* '$["a"]';
1999+
?column?
2000+
----------
2001+
1
2002+
(1 row)
2003+
2004+
select jsonb '{"a": 1}' @* 'strict $["b"]';
2005+
ERROR: SQL/JSON member not found
2006+
select jsonb '{"a": 1}' @* 'lax $["b"]';
2007+
?column?
2008+
----------
2009+
(0 rows)
2010+
2011+
select jsonb '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
2012+
?column?
2013+
------------------
2014+
2
2015+
2
2016+
1
2017+
{"a": 1, "b": 2}
2018+
(4 rows)
2019+
2020+
select jsonb 'null' @* '{"a": 1}["a"]';
2021+
?column?
2022+
----------
2023+
1
2024+
(1 row)
2025+
2026+
select jsonb 'null' @* '{"a": 1}["b"]';
2027+
?column?
2028+
----------
2029+
(0 rows)
2030+

src/test/regress/sql/jsonb_jsonpath.sql

+22
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ select jsonb '[1]' @? '$.[0.5]';
1818
select jsonb '[1]' @? '$.[0.9]';
1919
select jsonb '[1]' @? '$.[1.2]';
2020
select jsonb '[1]' @? 'strict $.[1.2]';
21+
select jsonb '{}' @? 'strict $.[0.3]';
22+
select jsonb '{}' @? 'lax $.[0.3]';
23+
select jsonb '{}' @? 'strict $.[1.2]';
24+
select jsonb '{}' @? 'lax $.[1.2]';
25+
select jsonb '{}' @? 'strict $.[-2 to 3]';
26+
select jsonb '{}' @? 'lax $.[-2 to 3]';
2127
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
2228
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
2329
select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
@@ -41,12 +47,14 @@ select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0 to 10].a';
4147
select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$.[2.5 - 1 to @.size() - 2]';
4248
select jsonb '1' @* 'lax $[0]';
4349
select jsonb '1' @* 'lax $[*]';
50+
select jsonb '{}' @* 'lax $[0]';
4451
select jsonb '[1]' @* 'lax $[0]';
4552
select jsonb '[1]' @* 'lax $[*]';
4653
select jsonb '[1,2,3]' @* 'lax $[*]';
4754
select jsonb '[]' @* '$[last]';
4855
select jsonb '[]' @* 'strict $[last]';
4956
select jsonb '[1]' @* '$[last]';
57+
select jsonb '{}' @* 'lax $[last]';
5058
select jsonb '[1,2,3]' @* '$[last]';
5159
select jsonb '[1,2,3]' @* '$[last - 1]';
5260
select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]';
@@ -430,3 +438,17 @@ select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
430438
select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
431439
select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
432440
select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$.map({x: @, y: @ < 3})[*], {z: "foo"}]}';
441+
442+
-- extension: object subscripting
443+
select jsonb '{"a": 1}' @? '$["a"]';
444+
select jsonb '{"a": 1}' @? '$["b"]';
445+
select jsonb '{"a": 1}' @? 'strict $["b"]';
446+
select jsonb '{"a": 1}' @? '$["b", "a"]';
447+
448+
select jsonb '{"a": 1}' @* '$["a"]';
449+
select jsonb '{"a": 1}' @* 'strict $["b"]';
450+
select jsonb '{"a": 1}' @* 'lax $["b"]';
451+
select jsonb '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
452+
453+
select jsonb 'null' @* '{"a": 1}["a"]';
454+
select jsonb 'null' @* '{"a": 1}["b"]';

0 commit comments

Comments
 (0)