15
15
#include "postgres.h"
16
16
17
17
#include "executor/execExpr.h"
18
+ #include "nodes/makefuncs.h"
18
19
#include "nodes/nodeFuncs.h"
19
20
#include "nodes/subscripting.h"
20
21
#include "parser/parse_coerce.h"
21
22
#include "parser/parse_expr.h"
22
23
#include "utils/builtins.h"
23
24
#include "utils/jsonb.h"
25
+ #include "utils/jsonpath.h"
24
26
25
27
26
- /* SubscriptingRefState.workspace for jsonb subscripting execution */
28
+ /*
29
+ * SubscriptingRefState.workspace for generic jsonb subscripting execution.
30
+ *
31
+ * Stores state for both jsonb simple subscripting and dot notation access.
32
+ * Dot notation additionally uses `jsonpath` for JsonPath evaluation.
33
+ */
27
34
typedef struct JsonbSubWorkspace
28
35
{
29
36
bool expectArray ; /* jsonb root is expected to be an array */
30
37
Oid * indexOid ; /* OID of coerced subscript expression, could
31
38
* be only integer or text */
32
39
Datum * index ; /* Subscript values in Datum format */
40
+ JsonPath * jsonpath ; /* JsonPath for dot notation execution via
41
+ * JsonPathQuery() */
33
42
} JsonbSubWorkspace ;
34
43
35
44
static Oid
@@ -110,6 +119,228 @@ coerce_jsonpath_subscript(ParseState *pstate, Node *subExpr, Oid numtype)
110
119
return subExpr ;
111
120
}
112
121
122
+ /*
123
+ * During transformation, determine whether to build a JsonPath
124
+ * for JsonPathQuery() execution.
125
+ *
126
+ * JsonPath is needed if the indirection list includes:
127
+ * - String-based access (dot notation)
128
+ * - Wildcard (`*`)
129
+ * - Slice-based subscripting
130
+ *
131
+ * Otherwise, simple jsonb subscripting is sufficient.
132
+ */
133
+ static bool
134
+ jsonb_check_jsonpath_needed (List * indirection )
135
+ {
136
+ ListCell * lc ;
137
+
138
+ foreach (lc , indirection )
139
+ {
140
+ Node * accessor = lfirst (lc );
141
+
142
+ if (IsA (accessor , String ) ||
143
+ IsA (accessor , A_Star ))
144
+ return true;
145
+ else
146
+ {
147
+ A_Indices * ai ;
148
+
149
+ Assert (IsA (accessor , A_Indices ));
150
+ ai = castNode (A_Indices , accessor );
151
+
152
+ if (!ai -> uidx || ai -> lidx )
153
+ {
154
+ Assert (ai -> is_slice );
155
+ return true;
156
+ }
157
+ }
158
+ }
159
+
160
+ return false;
161
+ }
162
+
163
+ /*
164
+ * Helper functions for constructing JsonPath expressions.
165
+ *
166
+ * The following functions create various types of JsonPathParseItem nodes,
167
+ * which are used to build JsonPath expressions for jsonb simplified accessor.
168
+ */
169
+
170
+ static JsonPathParseItem *
171
+ make_jsonpath_item (JsonPathItemType type )
172
+ {
173
+ JsonPathParseItem * v = palloc (sizeof (* v ));
174
+
175
+ v -> type = type ;
176
+ v -> next = NULL ;
177
+
178
+ return v ;
179
+ }
180
+
181
+ static JsonPathParseItem *
182
+ make_jsonpath_item_int (int32 val , List * * exprs )
183
+ {
184
+ JsonPathParseItem * jpi = make_jsonpath_item (jpiNumeric );
185
+
186
+ jpi -> value .numeric =
187
+ DatumGetNumeric (DirectFunctionCall1 (int4_numeric , Int32GetDatum (val )));
188
+
189
+ * exprs = lappend (* exprs , makeConst (INT4OID , -1 , InvalidOid , 4 ,
190
+ Int32GetDatum (val ), false, true));
191
+
192
+ return jpi ;
193
+ }
194
+
195
+ /*
196
+ * Convert an expression into a JsonPathParseItem.
197
+ * If the expression is a constant integer, create a direct numeric item.
198
+ * Otherwise, create a variable reference and add it to the expression list.
199
+ */
200
+ static JsonPathParseItem *
201
+ make_jsonpath_item_expr (ParseState * pstate , Node * expr , List * * exprs )
202
+ {
203
+ Const * cnst ;
204
+
205
+ expr = transformExpr (pstate , expr , pstate -> p_expr_kind );
206
+
207
+ if (!IsA (expr , Const ))
208
+ ereport (ERROR ,
209
+ (errcode (ERRCODE_DATATYPE_MISMATCH ),
210
+ errmsg ("jsonb simplified accessor supports subscripting in const int4, got type: %s" ,
211
+ format_type_be (exprType (expr ))),
212
+ parser_errposition (pstate , exprLocation (expr ))));
213
+
214
+ cnst = (Const * ) expr ;
215
+
216
+ if (cnst -> consttype == INT4OID && !cnst -> constisnull )
217
+ {
218
+ int32 val = DatumGetInt32 (cnst -> constvalue );
219
+
220
+ return make_jsonpath_item_int (val , exprs );
221
+ }
222
+
223
+ ereport (ERROR ,
224
+ (errcode (ERRCODE_DATATYPE_MISMATCH ),
225
+ errmsg ("jsonb simplified accessor supports subscripting in type: INT4, got type: %s" ,
226
+ format_type_be (cnst -> consttype )),
227
+ parser_errposition (pstate , exprLocation (expr ))));
228
+ }
229
+
230
+ /*
231
+ * jsonb_subscript_make_jsonpath
232
+ *
233
+ * Constructs a JsonPath expression from a list of indirections.
234
+ * This function is used when jsonb subscripting involves dot notation,
235
+ * wildcards (*), or slice-based subscripting, requiring JsonPath-based
236
+ * evaluation.
237
+ *
238
+ * The function modifies the indirection list in place, removing processed
239
+ * elements as it converts them into JsonPath components, as follows:
240
+ * - String keys (dot notation) -> jpiKey items.
241
+ * - Wildcard (*) -> jpiAnyKey item.
242
+ * - Array indices and slices -> jpiIndexArray items.
243
+ *
244
+ * Parameters:
245
+ * - pstate: Parse state context.
246
+ * - indirection: List of subscripting expressions (modified in-place).
247
+ * - uexprs: Upper-bound expressions extracted from subscripts.
248
+ * - lexprs: Lower-bound expressions extracted from subscripts.
249
+ * Returns:
250
+ * - a Const node containing the transformed JsonPath expression.
251
+ */
252
+ static Node *
253
+ jsonb_subscript_make_jsonpath (ParseState * pstate , List * * indirection ,
254
+ List * * uexprs , List * * lexprs )
255
+ {
256
+ JsonPathParseResult jpres ;
257
+ JsonPathParseItem * path = make_jsonpath_item (jpiRoot );
258
+ ListCell * lc ;
259
+ Datum jsp ;
260
+ int pathlen = 0 ;
261
+
262
+ * uexprs = NIL ;
263
+ * lexprs = NIL ;
264
+
265
+ jpres .expr = path ;
266
+ jpres .lax = true;
267
+
268
+ foreach (lc , * indirection )
269
+ {
270
+ Node * accessor = lfirst (lc );
271
+ JsonPathParseItem * jpi ;
272
+
273
+ if (IsA (accessor , String ))
274
+ {
275
+ char * field = strVal (accessor );
276
+
277
+ jpi = make_jsonpath_item (jpiKey );
278
+ jpi -> value .string .val = field ;
279
+ jpi -> value .string .len = strlen (field );
280
+
281
+ * uexprs = lappend (* uexprs , accessor );
282
+ }
283
+ else if (IsA (accessor , A_Star ))
284
+ {
285
+ jpi = make_jsonpath_item (jpiAnyKey );
286
+
287
+ * uexprs = lappend (* uexprs , NULL );
288
+ }
289
+ else if (IsA (accessor , A_Indices ))
290
+ {
291
+ A_Indices * ai = castNode (A_Indices , accessor );
292
+
293
+ jpi = make_jsonpath_item (jpiIndexArray );
294
+ jpi -> value .array .nelems = 1 ;
295
+ jpi -> value .array .elems = palloc (sizeof (jpi -> value .array .elems [0 ]));
296
+
297
+ if (ai -> is_slice )
298
+ {
299
+ while (list_length (* lexprs ) < list_length (* uexprs ))
300
+ * lexprs = lappend (* lexprs , NULL );
301
+
302
+ if (ai -> lidx )
303
+ jpi -> value .array .elems [0 ].from = make_jsonpath_item_expr (pstate , ai -> lidx , lexprs );
304
+ else
305
+ jpi -> value .array .elems [0 ].from = make_jsonpath_item_int (0 , lexprs );
306
+
307
+ if (ai -> uidx )
308
+ jpi -> value .array .elems [0 ].to = make_jsonpath_item_expr (pstate , ai -> uidx , uexprs );
309
+ else
310
+ {
311
+ jpi -> value .array .elems [0 ].to = make_jsonpath_item (jpiLast );
312
+ * uexprs = lappend (* uexprs , NULL );
313
+ }
314
+ }
315
+ else
316
+ {
317
+ Assert (ai -> uidx && !ai -> lidx );
318
+ jpi -> value .array .elems [0 ].from = make_jsonpath_item_expr (pstate , ai -> uidx , uexprs );
319
+ jpi -> value .array .elems [0 ].to = NULL ;
320
+ }
321
+ }
322
+ else
323
+ break ;
324
+
325
+ /* append path item */
326
+ path -> next = jpi ;
327
+ path = jpi ;
328
+ pathlen ++ ;
329
+ }
330
+
331
+ if (* lexprs )
332
+ {
333
+ while (list_length (* lexprs ) < list_length (* uexprs ))
334
+ * lexprs = lappend (* lexprs , NULL );
335
+ }
336
+
337
+ * indirection = list_delete_first_n (* indirection , pathlen );
338
+
339
+ jsp = jsonPathFromParseResult (& jpres , 0 , NULL );
340
+
341
+ return (Node * ) makeConst (JSONPATHOID , -1 , InvalidOid , -1 , jsp , false, false);
342
+ }
343
+
113
344
/*
114
345
* Finish parse analysis of a SubscriptingRef expression for a jsonb.
115
346
*
@@ -126,19 +357,32 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
126
357
List * upperIndexpr = NIL ;
127
358
ListCell * idx ;
128
359
360
+ /* Determine the result type of the subscripting operation; always jsonb */
361
+ sbsref -> refrestype = JSONBOID ;
362
+ sbsref -> reftypmod = -1 ;
363
+
364
+ if (jsonb_check_jsonpath_needed (* indirection ))
365
+ {
366
+ sbsref -> refjsonbpath =
367
+ jsonb_subscript_make_jsonpath (pstate , indirection ,
368
+ & sbsref -> refupperindexpr ,
369
+ & sbsref -> reflowerindexpr );
370
+ return ;
371
+ }
372
+
129
373
/*
130
374
* Transform and convert the subscript expressions. Jsonb subscripting
131
375
* does not support slices, look only and the upper index.
132
376
*/
133
377
foreach (idx , * indirection )
134
378
{
379
+ Node * i = lfirst (idx );
135
380
A_Indices * ai ;
136
381
Node * subExpr ;
137
382
138
- if (!IsA (lfirst (idx ), A_Indices ))
139
- break ;
383
+ Assert (IsA (i , A_Indices ));
140
384
141
- ai = lfirst_node (A_Indices , idx );
385
+ ai = castNode (A_Indices , i );
142
386
143
387
if (isSlice )
144
388
{
@@ -175,10 +419,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
175
419
sbsref -> refupperindexpr = upperIndexpr ;
176
420
sbsref -> reflowerindexpr = NIL ;
177
421
178
- /* Determine the result type of the subscripting operation; always jsonb */
179
- sbsref -> refrestype = JSONBOID ;
180
- sbsref -> reftypmod = -1 ;
181
-
182
422
/* Remove processed elements */
183
423
if (upperIndexpr )
184
424
* indirection = list_delete_first_n (* indirection , list_length (upperIndexpr ));
@@ -233,7 +473,7 @@ jsonb_subscript_check_subscripts(ExprState *state,
233
473
* For jsonb fetch and assign functions we need to provide path in
234
474
* text format. Convert if it's not already text.
235
475
*/
236
- if (workspace -> indexOid [i ] == INT4OID )
476
+ if (! workspace -> jsonpath && workspace -> indexOid [i ] == INT4OID )
237
477
{
238
478
Datum datum = sbsrefstate -> upperindex [i ];
239
479
char * cs = DatumGetCString (DirectFunctionCall1 (int4out , datum ));
@@ -261,17 +501,32 @@ jsonb_subscript_fetch(ExprState *state,
261
501
{
262
502
SubscriptingRefState * sbsrefstate = op -> d .sbsref .state ;
263
503
JsonbSubWorkspace * workspace = (JsonbSubWorkspace * ) sbsrefstate -> workspace ;
264
- Jsonb * jsonbSource ;
265
504
266
505
/* Should not get here if source jsonb (or any subscript) is null */
267
506
Assert (!(* op -> resnull ));
268
507
269
- jsonbSource = DatumGetJsonbP (* op -> resvalue );
270
- * op -> resvalue = jsonb_get_element (jsonbSource ,
271
- workspace -> index ,
272
- sbsrefstate -> numupper ,
273
- op -> resnull ,
274
- false);
508
+ if (workspace -> jsonpath )
509
+ {
510
+ bool empty = false;
511
+ bool error = false;
512
+
513
+ * op -> resvalue = JsonPathQuery (* op -> resvalue , workspace -> jsonpath ,
514
+ JSW_CONDITIONAL ,
515
+ & empty , & error , NULL ,
516
+ NULL );
517
+
518
+ * op -> resnull = empty || error ;
519
+ }
520
+ else
521
+ {
522
+ Jsonb * jsonbSource = DatumGetJsonbP (* op -> resvalue );
523
+
524
+ * op -> resvalue = jsonb_get_element (jsonbSource ,
525
+ workspace -> index ,
526
+ sbsrefstate -> numupper ,
527
+ op -> resnull ,
528
+ false);
529
+ }
275
530
}
276
531
277
532
/*
@@ -381,13 +636,17 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
381
636
ListCell * lc ;
382
637
int nupper = sbsref -> refupperindexpr -> length ;
383
638
char * ptr ;
639
+ bool useJsonpath = sbsref -> refjsonbpath != NULL ;
384
640
385
641
/* Allocate type-specific workspace with space for per-subscript data */
386
642
workspace = palloc0 (MAXALIGN (sizeof (JsonbSubWorkspace )) +
387
643
nupper * (sizeof (Datum ) + sizeof (Oid )));
388
644
workspace -> expectArray = false;
389
645
ptr = ((char * ) workspace ) + MAXALIGN (sizeof (JsonbSubWorkspace ));
390
646
647
+ if (useJsonpath )
648
+ workspace -> jsonpath = DatumGetJsonPathP (castNode (Const , sbsref -> refjsonbpath )-> constvalue );
649
+
391
650
/*
392
651
* This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
393
652
* misalign the indexOid pointer
@@ -404,7 +663,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref,
404
663
Node * expr = lfirst (lc );
405
664
int i = foreach_current_index (lc );
406
665
407
- workspace -> indexOid [i ] = exprType (expr );
666
+ workspace -> indexOid [i ] = jsonb_subscript_type (expr );
408
667
}
409
668
410
669
/*
0 commit comments