Skip to content

Commit ae7412b

Browse files
author
Nikita Glukhov
committed
Add planner support functions for jsonb_path_match() and jsonb_path_exists()
1 parent 445253c commit ae7412b

File tree

6 files changed

+309
-6
lines changed

6 files changed

+309
-6
lines changed

src/backend/catalog/system_views.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,6 +1328,7 @@ CREATE OR REPLACE FUNCTION
13281328
RETURNS boolean
13291329
LANGUAGE INTERNAL
13301330
STRICT IMMUTABLE PARALLEL SAFE
1331+
SUPPORT jsonb_path_exists_support
13311332
AS 'jsonb_path_exists';
13321333

13331334
CREATE OR REPLACE FUNCTION
@@ -1336,6 +1337,7 @@ CREATE OR REPLACE FUNCTION
13361337
RETURNS boolean
13371338
LANGUAGE INTERNAL
13381339
STRICT IMMUTABLE PARALLEL SAFE
1340+
SUPPORT jsonb_path_match_support
13391341
AS 'jsonb_path_match';
13401342

13411343
CREATE OR REPLACE FUNCTION

src/backend/utils/adt/jsonpath.c

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,15 @@
6363

6464
#include "postgres.h"
6565

66+
#include "catalog/pg_type_d.h"
67+
#include "catalog/pg_operator_d.h"
6668
#include "funcapi.h"
6769
#include "lib/stringinfo.h"
6870
#include "libpq/pqformat.h"
6971
#include "miscadmin.h"
72+
#include "nodes/makefuncs.h"
73+
#include "nodes/nodeFuncs.h"
74+
#include "nodes/supportnodes.h"
7075
#include "utils/builtins.h"
7176
#include "utils/fmgroids.h"
7277
#include "utils/json.h"
@@ -96,6 +101,7 @@ static int operationPriority(JsonPathItemType op);
96101
static bool replaceVariableReference(JsonPathContext *cxt, JsonPathItem *var,
97102
int32 pos);
98103
static JsonPath *substituteVariables(JsonPath *jsp, Jsonb *vars);
104+
static Node *jsonb_path_support(Node *rawreq, bool exists);
99105

100106
/**************************** INPUT/OUTPUT ********************************/
101107

@@ -184,6 +190,24 @@ jsonpath_embed_vars(PG_FUNCTION_ARGS)
184190
PG_RETURN_JSONPATH_P(jsp);
185191
}
186192

193+
/* Planner support for jsonb_path_match() */
194+
Datum
195+
jsonb_path_match_support(PG_FUNCTION_ARGS)
196+
{
197+
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
198+
199+
PG_RETURN_POINTER(jsonb_path_support(rawreq, false));
200+
}
201+
202+
/* Planner support for jsonb_path_exists() */
203+
Datum
204+
jsonb_path_exists_support(PG_FUNCTION_ARGS)
205+
{
206+
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
207+
208+
PG_RETURN_POINTER(jsonb_path_support(rawreq, true));
209+
}
210+
187211
/*
188212
* Converts C-string to a jsonpath value.
189213
*
@@ -1456,3 +1480,104 @@ substituteVariables(JsonPath *jsp, Jsonb *vars)
14561480
return encodeJsonPath(&item, !!(jsp->header & JSONPATH_LAX),
14571481
VARSIZE(jsp) + VARSIZE(vars), vars);
14581482
}
1483+
1484+
static Const *
1485+
getConstExpr(Expr *expr, Oid typid)
1486+
{
1487+
if (!IsA(expr, Const) ||
1488+
((Const *) expr)->constisnull ||
1489+
((Const *) expr)->consttype != typid)
1490+
return NULL;
1491+
1492+
return (Const *) expr;
1493+
}
1494+
1495+
/* Planner support for jsonb_path_match() and jsonb_path_exists() */
1496+
static Node *
1497+
jsonb_path_support(Node *rawreq, bool exists)
1498+
{
1499+
Node *ret = NULL;
1500+
1501+
if (IsA(rawreq, SupportRequestIndexCondition))
1502+
{
1503+
/* Try to convert operator/function call to index conditions */
1504+
SupportRequestIndexCondition *req = (SupportRequestIndexCondition *) rawreq;
1505+
1506+
/*
1507+
* Currently we have no "reverse" match operators with the pattern on
1508+
* the left, so we only need consider cases with the indexkey on the
1509+
* left.
1510+
*/
1511+
if (req->indexarg != 0)
1512+
return NULL;
1513+
1514+
if (is_funcclause(req->node))
1515+
{
1516+
FuncExpr *clause = (FuncExpr *) req->node;
1517+
Expr *opexpr;
1518+
Expr *jspexpr;
1519+
Expr *jsonexpr;
1520+
Const *pathexpr;
1521+
Const *varsexpr;
1522+
Const *silentexpr;
1523+
Jsonb *vars;
1524+
Oid oproid;
1525+
1526+
if (list_length(clause->args) < 4)
1527+
return NULL;
1528+
1529+
if (!(pathexpr = getConstExpr(lsecond(clause->args), JSONPATHOID)))
1530+
return NULL;
1531+
1532+
if (!(silentexpr = getConstExpr(lfourth(clause->args), BOOLOID)) ||
1533+
!DatumGetBool(silentexpr->constvalue))
1534+
return NULL;
1535+
1536+
if ((varsexpr = getConstExpr(lthird(clause->args), JSONBOID)))
1537+
{
1538+
vars = DatumGetJsonbP(varsexpr->constvalue);
1539+
1540+
if (!JsonContainerIsObject(&vars->root))
1541+
return NULL;
1542+
1543+
if (JsonContainerSize(&vars->root) <= 0)
1544+
jspexpr = (Expr *) pathexpr;
1545+
else
1546+
{
1547+
JsonPath *jsp = DatumGetJsonPathP(pathexpr->constvalue);
1548+
1549+
jsp = substituteVariables(jsp, vars);
1550+
1551+
if (!jsp)
1552+
return NULL;
1553+
1554+
jspexpr = (Expr *) makeConst(JSONPATHOID, -1, InvalidOid,
1555+
-1, PointerGetDatum(jsp),
1556+
false, false);
1557+
}
1558+
}
1559+
else
1560+
{
1561+
List *args = list_make2(pathexpr, lthird(clause->args));
1562+
1563+
jspexpr = (Expr *) makeFuncExpr(F_JSONPATH_EMBED_VARS,
1564+
JSONPATHOID, args,
1565+
InvalidOid, InvalidOid,
1566+
COERCE_EXPLICIT_CALL);
1567+
}
1568+
1569+
jsonexpr = linitial(clause->args);
1570+
1571+
oproid = exists ? JsonbPathExistsOperator : JsonbPathMatchOperator;
1572+
opexpr = make_opclause(oproid, BOOLOID, false,
1573+
jsonexpr, jspexpr,
1574+
InvalidOid, req->indexcollation);
1575+
1576+
req->lossy = false;
1577+
1578+
return (Node *) list_make1(opexpr);
1579+
}
1580+
}
1581+
1582+
return ret;
1583+
}

src/include/catalog/pg_operator.dat

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3319,11 +3319,12 @@
33193319
{ oid => '3287', descr => 'delete path',
33203320
oprname => '#-', oprleft => 'jsonb', oprright => '_text',
33213321
oprresult => 'jsonb', oprcode => 'jsonb_delete_path' },
3322-
{ oid => '4012', descr => 'jsonpath exists',
3322+
{ oid => '4012', oid_symbol => 'JsonbPathExistsOperator',
3323+
descr => 'jsonpath exists',
33233324
oprname => '@?', oprleft => 'jsonb', oprright => 'jsonpath',
33243325
oprresult => 'bool', oprcode => 'jsonb_path_exists_opr(jsonb,jsonpath)',
33253326
oprrest => 'matchingsel', oprjoin => 'matchingjoinsel' },
3326-
{ oid => '4013', descr => 'jsonpath match',
3327+
{ oid => '4013', oid_symbol => 'JsonbPathMatchOperator', descr => 'jsonpath match',
33273328
oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
33283329
oprresult => 'bool', oprcode => 'jsonb_path_match_opr(jsonb,jsonpath)',
33293330
oprrest => 'matchingsel', oprjoin => 'matchingjoinsel' },

src/include/catalog/pg_proc.dat

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9458,8 +9458,9 @@
94589458
prosrc => 'jsonpath_send' },
94599459

94609460
{ oid => '4005', descr => 'jsonpath exists test',
9461-
proname => 'jsonb_path_exists', prorettype => 'bool',
9462-
proargtypes => 'jsonb jsonpath jsonb bool', prosrc => 'jsonb_path_exists' },
9461+
proname => 'jsonb_path_exists', prosupport => 'jsonb_path_exists_support',
9462+
prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool',
9463+
prosrc => 'jsonb_path_exists' },
94639464
{ oid => '4006', descr => 'jsonpath query',
94649465
proname => 'jsonb_path_query', prorows => '1000', proretset => 't',
94659466
prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool',
@@ -9472,10 +9473,17 @@
94729473
proname => 'jsonb_path_query_first', prorettype => 'jsonb',
94739474
proargtypes => 'jsonb jsonpath jsonb bool',
94749475
prosrc => 'jsonb_path_query_first' },
9475-
{ oid => '4009', descr => 'jsonpath match',
9476-
proname => 'jsonb_path_match', prorettype => 'bool',
9476+
{ oid => '4009', descr => 'jsonpath match', proname => 'jsonb_path_match',
9477+
prosupport => 'jsonb_path_match_support', prorettype => 'bool',
94779478
proargtypes => 'jsonb jsonpath jsonb bool', prosrc => 'jsonb_path_match' },
94789479

9480+
{ oid => '6103', descr => 'planner support for jsonb_path_match',
9481+
proname => 'jsonb_path_match_support', prorettype => 'internal',
9482+
proargtypes => 'internal', prosrc => 'jsonb_path_match_support' },
9483+
{ oid => '6105', descr => 'planner support for jsonb_path_exists',
9484+
proname => 'jsonb_path_exists_support', prorettype => 'internal',
9485+
proargtypes => 'internal', prosrc => 'jsonb_path_exists_support' },
9486+
94799487
{ oid => '1177', descr => 'jsonpath exists test with timezone',
94809488
proname => 'jsonb_path_exists_tz', provolatile => 's', prorettype => 'bool',
94819489
proargtypes => 'jsonb jsonpath jsonb bool',

src/test/regress/expected/jsonb.out

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3167,6 +3167,142 @@ SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
31673167
0
31683168
(1 row)
31693169

3170+
EXPLAIN (COSTS OFF)
3171+
SELECT * FROM testjsonb WHERE jsonb_path_match(j, '$.age == 25');
3172+
QUERY PLAN
3173+
--------------------------------------------------------------------------------
3174+
Seq Scan on testjsonb
3175+
Filter: jsonb_path_match(j, '($."age" == 25)'::jsonpath, '{}'::jsonb, false)
3176+
(2 rows)
3177+
3178+
EXPLAIN (COSTS OFF)
3179+
SELECT * FROM testjsonb WHERE jsonb_path_match(j, '$.age == 25', silent => true);
3180+
QUERY PLAN
3181+
-------------------------------------------------------------------------------
3182+
Bitmap Heap Scan on testjsonb
3183+
Filter: jsonb_path_match(j, '($."age" == 25)'::jsonpath, '{}'::jsonb, true)
3184+
-> Bitmap Index Scan on jidx
3185+
Index Cond: (j @@ '($."age" == 25)'::jsonpath)
3186+
(4 rows)
3187+
3188+
EXPLAIN (COSTS OFF)
3189+
SELECT * FROM testjsonb WHERE jsonb_path_match(j, '$.age == 25', vars => '{"age": 34 }', silent => true);
3190+
QUERY PLAN
3191+
----------------------------------------------------------------------------------------
3192+
Bitmap Heap Scan on testjsonb
3193+
Filter: jsonb_path_match(j, '($."age" == 25)'::jsonpath, '{"age": 34}'::jsonb, true)
3194+
-> Bitmap Index Scan on jidx
3195+
Index Cond: (j @@ '($."age" == 25)'::jsonpath)
3196+
(4 rows)
3197+
3198+
EXPLAIN (COSTS OFF)
3199+
SELECT * FROM testjsonb WHERE jsonb_path_match(j, '$.age == $age', vars => '{"age": 25 }', silent => true);
3200+
QUERY PLAN
3201+
--------------------------------------------------------------------------------------------
3202+
Bitmap Heap Scan on testjsonb
3203+
Filter: jsonb_path_match(j, '($."age" == $"age")'::jsonpath, '{"age": 25}'::jsonb, true)
3204+
-> Bitmap Index Scan on jidx
3205+
Index Cond: (j @@ '($."age" == 25)'::jsonpath)
3206+
(4 rows)
3207+
3208+
EXPLAIN (COSTS OFF)
3209+
SELECT * FROM testjsonb WHERE jsonb_path_match(j, '$.age == $age', vars => '{"age": [25] }', silent => true);
3210+
QUERY PLAN
3211+
----------------------------------------------------------------------------------------------
3212+
Seq Scan on testjsonb
3213+
Filter: jsonb_path_match(j, '($."age" == $"age")'::jsonpath, '{"age": [25]}'::jsonb, true)
3214+
(2 rows)
3215+
3216+
EXPLAIN (COSTS OFF)
3217+
SELECT * FROM testjsonb WHERE jsonb_path_match(j, '$.age == $x || $.age == $y', vars => '{"x": 25, "y": 34}', silent => true);
3218+
QUERY PLAN
3219+
--------------------------------------------------------------------------------------------------------------------
3220+
Bitmap Heap Scan on testjsonb
3221+
Filter: jsonb_path_match(j, '($."age" == $"x" || $."age" == $"y")'::jsonpath, '{"x": 25, "y": 34}'::jsonb, true)
3222+
-> Bitmap Index Scan on jidx
3223+
Index Cond: (j @@ '($."age" == 25 || $."age" == 34)'::jsonpath)
3224+
(4 rows)
3225+
3226+
EXPLAIN (COSTS OFF)
3227+
SELECT * FROM testjsonb t1, testjsonb t2 WHERE jsonb_path_match(t1.j, '$.age == $age', vars => t2.j, silent => true);
3228+
QUERY PLAN
3229+
---------------------------------------------------------------------------------------------
3230+
Nested Loop
3231+
-> Seq Scan on testjsonb t2
3232+
-> Bitmap Heap Scan on testjsonb t1
3233+
Filter: jsonb_path_match(j, '($."age" == $"age")'::jsonpath, t2.j, true)
3234+
-> Bitmap Index Scan on jidx
3235+
Index Cond: (j @@ jsonpath_embed_vars('($."age" == $"age")'::jsonpath, t2.j))
3236+
(6 rows)
3237+
3238+
EXPLAIN (COSTS OFF)
3239+
SELECT * FROM testjsonb WHERE jsonb_path_exists(j, '$ ? (@.age == 25)');
3240+
QUERY PLAN
3241+
-----------------------------------------------------------------------------------
3242+
Seq Scan on testjsonb
3243+
Filter: jsonb_path_exists(j, '$?(@."age" == 25)'::jsonpath, '{}'::jsonb, false)
3244+
(2 rows)
3245+
3246+
EXPLAIN (COSTS OFF)
3247+
SELECT * FROM testjsonb WHERE jsonb_path_exists(j, '$ ? (@.age == 25)', silent => true);
3248+
QUERY PLAN
3249+
----------------------------------------------------------------------------------
3250+
Bitmap Heap Scan on testjsonb
3251+
Filter: jsonb_path_exists(j, '$?(@."age" == 25)'::jsonpath, '{}'::jsonb, true)
3252+
-> Bitmap Index Scan on jidx
3253+
Index Cond: (j @? '$?(@."age" == 25)'::jsonpath)
3254+
(4 rows)
3255+
3256+
EXPLAIN (COSTS OFF)
3257+
SELECT * FROM testjsonb WHERE jsonb_path_exists(j, '$ ? (@.age == 25)', vars => '{"age": 34 }', silent => true);
3258+
QUERY PLAN
3259+
-------------------------------------------------------------------------------------------
3260+
Bitmap Heap Scan on testjsonb
3261+
Filter: jsonb_path_exists(j, '$?(@."age" == 25)'::jsonpath, '{"age": 34}'::jsonb, true)
3262+
-> Bitmap Index Scan on jidx
3263+
Index Cond: (j @? '$?(@."age" == 25)'::jsonpath)
3264+
(4 rows)
3265+
3266+
EXPLAIN (COSTS OFF)
3267+
SELECT * FROM testjsonb WHERE jsonb_path_exists(j, '$ ? (@.age == $age)', vars => '{"age": 25 }', silent => true);
3268+
QUERY PLAN
3269+
-----------------------------------------------------------------------------------------------
3270+
Bitmap Heap Scan on testjsonb
3271+
Filter: jsonb_path_exists(j, '$?(@."age" == $"age")'::jsonpath, '{"age": 25}'::jsonb, true)
3272+
-> Bitmap Index Scan on jidx
3273+
Index Cond: (j @? '$?(@."age" == 25)'::jsonpath)
3274+
(4 rows)
3275+
3276+
EXPLAIN (COSTS OFF)
3277+
SELECT * FROM testjsonb WHERE jsonb_path_exists(j, '$ ? (@.age == $age)', vars => '{"age": [25] }', silent => true);
3278+
QUERY PLAN
3279+
-------------------------------------------------------------------------------------------------
3280+
Seq Scan on testjsonb
3281+
Filter: jsonb_path_exists(j, '$?(@."age" == $"age")'::jsonpath, '{"age": [25]}'::jsonb, true)
3282+
(2 rows)
3283+
3284+
EXPLAIN (COSTS OFF)
3285+
SELECT * FROM testjsonb WHERE jsonb_path_exists(j, '$ ? (@.age == $x || $.age == $y)', vars => '{"x": 25, "y": 34}', silent => true);
3286+
QUERY PLAN
3287+
-----------------------------------------------------------------------------------------------------------------------
3288+
Bitmap Heap Scan on testjsonb
3289+
Filter: jsonb_path_exists(j, '$?(@."age" == $"x" || $."age" == $"y")'::jsonpath, '{"x": 25, "y": 34}'::jsonb, true)
3290+
-> Bitmap Index Scan on jidx
3291+
Index Cond: (j @? '$?(@."age" == 25 || $."age" == 34)'::jsonpath)
3292+
(4 rows)
3293+
3294+
EXPLAIN (COSTS OFF)
3295+
SELECT * FROM testjsonb t1, testjsonb t2 WHERE jsonb_path_exists(t1.j, '$ ? (@.age == $age)', vars => t2.j, silent => true);
3296+
QUERY PLAN
3297+
-----------------------------------------------------------------------------------------------
3298+
Nested Loop
3299+
-> Seq Scan on testjsonb t2
3300+
-> Bitmap Heap Scan on testjsonb t1
3301+
Filter: jsonb_path_exists(j, '$?(@."age" == $"age")'::jsonpath, t2.j, true)
3302+
-> Bitmap Index Scan on jidx
3303+
Index Cond: (j @? jsonpath_embed_vars('$?(@."age" == $"age")'::jsonpath, t2.j))
3304+
(6 rows)
3305+
31703306
-- array exists - array elements should behave as keys (for GIN index scans too)
31713307
CREATE INDEX jidx_array ON testjsonb USING gin((j->'array'));
31723308
SELECT count(*) from testjsonb WHERE j->'array' ? 'bar';

src/test/regress/sql/jsonb.sql

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,37 @@ SELECT count(*) FROM testjsonb WHERE j @? '$';
826826
SELECT count(*) FROM testjsonb WHERE j @? '$.public';
827827
SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
828828

829+
EXPLAIN (COSTS OFF)
830+
SELECT * FROM testjsonb WHERE jsonb_path_match(j, '$.age == 25');
831+
EXPLAIN (COSTS OFF)
832+
SELECT * FROM testjsonb WHERE jsonb_path_match(j, '$.age == 25', silent => true);
833+
EXPLAIN (COSTS OFF)
834+
SELECT * FROM testjsonb WHERE jsonb_path_match(j, '$.age == 25', vars => '{"age": 34 }', silent => true);
835+
EXPLAIN (COSTS OFF)
836+
SELECT * FROM testjsonb WHERE jsonb_path_match(j, '$.age == $age', vars => '{"age": 25 }', silent => true);
837+
EXPLAIN (COSTS OFF)
838+
SELECT * FROM testjsonb WHERE jsonb_path_match(j, '$.age == $age', vars => '{"age": [25] }', silent => true);
839+
EXPLAIN (COSTS OFF)
840+
SELECT * FROM testjsonb WHERE jsonb_path_match(j, '$.age == $x || $.age == $y', vars => '{"x": 25, "y": 34}', silent => true);
841+
EXPLAIN (COSTS OFF)
842+
SELECT * FROM testjsonb t1, testjsonb t2 WHERE jsonb_path_match(t1.j, '$.age == $age', vars => t2.j, silent => true);
843+
844+
EXPLAIN (COSTS OFF)
845+
SELECT * FROM testjsonb WHERE jsonb_path_exists(j, '$ ? (@.age == 25)');
846+
EXPLAIN (COSTS OFF)
847+
SELECT * FROM testjsonb WHERE jsonb_path_exists(j, '$ ? (@.age == 25)', silent => true);
848+
EXPLAIN (COSTS OFF)
849+
SELECT * FROM testjsonb WHERE jsonb_path_exists(j, '$ ? (@.age == 25)', vars => '{"age": 34 }', silent => true);
850+
EXPLAIN (COSTS OFF)
851+
SELECT * FROM testjsonb WHERE jsonb_path_exists(j, '$ ? (@.age == $age)', vars => '{"age": 25 }', silent => true);
852+
EXPLAIN (COSTS OFF)
853+
SELECT * FROM testjsonb WHERE jsonb_path_exists(j, '$ ? (@.age == $age)', vars => '{"age": [25] }', silent => true);
854+
EXPLAIN (COSTS OFF)
855+
SELECT * FROM testjsonb WHERE jsonb_path_exists(j, '$ ? (@.age == $x || $.age == $y)', vars => '{"x": 25, "y": 34}', silent => true);
856+
EXPLAIN (COSTS OFF)
857+
SELECT * FROM testjsonb t1, testjsonb t2 WHERE jsonb_path_exists(t1.j, '$ ? (@.age == $age)', vars => t2.j, silent => true);
858+
859+
829860
-- array exists - array elements should behave as keys (for GIN index scans too)
830861
CREATE INDEX jidx_array ON testjsonb USING gin((j->'array'));
831862
SELECT count(*) from testjsonb WHERE j->'array' ? 'bar';

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy