LCOV - code coverage report
Current view: top level - src/backend/utils/adt - jsonbsubs.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 88.8 % 107 95
Test Date: 2026-02-17 17:20:33 Functions: 85.7 % 7 6
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * jsonbsubs.c
       4              :  *    Subscripting support functions for jsonb.
       5              :  *
       6              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7              :  * Portions Copyright (c) 1994, Regents of the University of California
       8              :  *
       9              :  *
      10              :  * IDENTIFICATION
      11              :  *    src/backend/utils/adt/jsonbsubs.c
      12              :  *
      13              :  *-------------------------------------------------------------------------
      14              :  */
      15              : #include "postgres.h"
      16              : 
      17              : #include "executor/execExpr.h"
      18              : #include "nodes/nodeFuncs.h"
      19              : #include "nodes/subscripting.h"
      20              : #include "parser/parse_coerce.h"
      21              : #include "parser/parse_expr.h"
      22              : #include "utils/builtins.h"
      23              : #include "utils/jsonb.h"
      24              : 
      25              : 
      26              : /* SubscriptingRefState.workspace for jsonb subscripting execution */
      27              : typedef struct JsonbSubWorkspace
      28              : {
      29              :     bool        expectArray;    /* jsonb root is expected to be an array */
      30              :     Oid        *indexOid;       /* OID of coerced subscript expression, could
      31              :                                  * be only integer or text */
      32              :     Datum      *index;          /* Subscript values in Datum format */
      33              : } JsonbSubWorkspace;
      34              : 
      35              : 
      36              : /*
      37              :  * Finish parse analysis of a SubscriptingRef expression for a jsonb.
      38              :  *
      39              :  * Transform the subscript expressions, coerce them to text,
      40              :  * and determine the result type of the SubscriptingRef node.
      41              :  */
      42              : static void
      43          210 : jsonb_subscript_transform(SubscriptingRef *sbsref,
      44              :                           List *indirection,
      45              :                           ParseState *pstate,
      46              :                           bool isSlice,
      47              :                           bool isAssignment)
      48              : {
      49          210 :     List       *upperIndexpr = NIL;
      50              :     ListCell   *idx;
      51              : 
      52              :     /*
      53              :      * Transform and convert the subscript expressions. Jsonb subscripting
      54              :      * does not support slices, look only at the upper index.
      55              :      */
      56          567 :     foreach(idx, indirection)
      57              :     {
      58          375 :         A_Indices  *ai = lfirst_node(A_Indices, idx);
      59              :         Node       *subExpr;
      60              : 
      61          375 :         if (isSlice)
      62              :         {
      63           15 :             Node       *expr = ai->uidx ? ai->uidx : ai->lidx;
      64              : 
      65           15 :             ereport(ERROR,
      66              :                     (errcode(ERRCODE_DATATYPE_MISMATCH),
      67              :                      errmsg("jsonb subscript does not support slices"),
      68              :                      parser_errposition(pstate, exprLocation(expr))));
      69              :         }
      70              : 
      71          360 :         if (ai->uidx)
      72              :         {
      73          360 :             Oid         subExprType = InvalidOid,
      74          360 :                         targetType = UNKNOWNOID;
      75              : 
      76          360 :             subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
      77          360 :             subExprType = exprType(subExpr);
      78              : 
      79          360 :             if (subExprType != UNKNOWNOID)
      80              :             {
      81          156 :                 Oid         targets[2] = {INT4OID, TEXTOID};
      82              : 
      83              :                 /*
      84              :                  * Jsonb can handle multiple subscript types, but cases when a
      85              :                  * subscript could be coerced to multiple target types must be
      86              :                  * avoided, similar to overloaded functions. It could be
      87              :                  * possibly extend with jsonpath in the future.
      88              :                  */
      89          468 :                 for (int i = 0; i < 2; i++)
      90              :                 {
      91          312 :                     if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
      92              :                     {
      93              :                         /*
      94              :                          * One type has already succeeded, it means there are
      95              :                          * two coercion targets possible, failure.
      96              :                          */
      97          153 :                         if (targetType != UNKNOWNOID)
      98            0 :                             ereport(ERROR,
      99              :                                     (errcode(ERRCODE_DATATYPE_MISMATCH),
     100              :                                      errmsg("subscript type %s is not supported", format_type_be(subExprType)),
     101              :                                      errhint("jsonb subscript must be coercible to only one type, integer or text."),
     102              :                                      parser_errposition(pstate, exprLocation(subExpr))));
     103              : 
     104          153 :                         targetType = targets[i];
     105              :                     }
     106              :                 }
     107              : 
     108              :                 /*
     109              :                  * No suitable types were found, failure.
     110              :                  */
     111          156 :                 if (targetType == UNKNOWNOID)
     112            3 :                     ereport(ERROR,
     113              :                             (errcode(ERRCODE_DATATYPE_MISMATCH),
     114              :                              errmsg("subscript type %s is not supported", format_type_be(subExprType)),
     115              :                              errhint("jsonb subscript must be coercible to either integer or text."),
     116              :                              parser_errposition(pstate, exprLocation(subExpr))));
     117              :             }
     118              :             else
     119          204 :                 targetType = TEXTOID;
     120              : 
     121              :             /*
     122              :              * We known from can_coerce_type that coercion will succeed, so
     123              :              * coerce_type could be used. Note the implicit coercion context,
     124              :              * which is required to handle subscripts of different types,
     125              :              * similar to overloaded functions.
     126              :              */
     127          357 :             subExpr = coerce_type(pstate,
     128              :                                   subExpr, subExprType,
     129              :                                   targetType, -1,
     130              :                                   COERCION_IMPLICIT,
     131              :                                   COERCE_IMPLICIT_CAST,
     132              :                                   -1);
     133          357 :             if (subExpr == NULL)
     134            0 :                 ereport(ERROR,
     135              :                         (errcode(ERRCODE_DATATYPE_MISMATCH),
     136              :                          errmsg("jsonb subscript must have text type"),
     137              :                          parser_errposition(pstate, exprLocation(subExpr))));
     138              :         }
     139              :         else
     140              :         {
     141              :             /*
     142              :              * Slice with omitted upper bound. Should not happen as we already
     143              :              * errored out on slice earlier, but handle this just in case.
     144              :              */
     145              :             Assert(isSlice && ai->is_slice);
     146            0 :             ereport(ERROR,
     147              :                     (errcode(ERRCODE_DATATYPE_MISMATCH),
     148              :                      errmsg("jsonb subscript does not support slices"),
     149              :                      parser_errposition(pstate, exprLocation(ai->uidx))));
     150              :         }
     151              : 
     152          357 :         upperIndexpr = lappend(upperIndexpr, subExpr);
     153              :     }
     154              : 
     155              :     /* store the transformed lists into the SubscriptingRef node */
     156          192 :     sbsref->refupperindexpr = upperIndexpr;
     157          192 :     sbsref->reflowerindexpr = NIL;
     158              : 
     159              :     /* Determine the result type of the subscripting operation; always jsonb */
     160          192 :     sbsref->refrestype = JSONBOID;
     161          192 :     sbsref->reftypmod = -1;
     162          192 : }
     163              : 
     164              : /*
     165              :  * During execution, process the subscripts in a SubscriptingRef expression.
     166              :  *
     167              :  * The subscript expressions are already evaluated in Datum form in the
     168              :  * SubscriptingRefState's arrays.  Check and convert them as necessary.
     169              :  *
     170              :  * If any subscript is NULL, we throw error in assignment cases, or in fetch
     171              :  * cases set result to NULL and return false (instructing caller to skip the
     172              :  * rest of the SubscriptingRef sequence).
     173              :  */
     174              : static bool
     175          228 : jsonb_subscript_check_subscripts(ExprState *state,
     176              :                                  ExprEvalStep *op,
     177              :                                  ExprContext *econtext)
     178              : {
     179          228 :     SubscriptingRefState *sbsrefstate = op->d.sbsref_subscript.state;
     180          228 :     JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
     181              : 
     182              :     /*
     183              :      * In case if the first subscript is an integer, the source jsonb is
     184              :      * expected to be an array. This information is not used directly, all
     185              :      * such cases are handled within corresponding jsonb assign functions. But
     186              :      * if the source jsonb is NULL the expected type will be used to construct
     187              :      * an empty source.
     188              :      */
     189          228 :     if (sbsrefstate->numupper > 0 && sbsrefstate->upperprovided[0] &&
     190          228 :         !sbsrefstate->upperindexnull[0] && workspace->indexOid[0] == INT4OID)
     191           66 :         workspace->expectArray = true;
     192              : 
     193              :     /* Process upper subscripts */
     194          612 :     for (int i = 0; i < sbsrefstate->numupper; i++)
     195              :     {
     196          393 :         if (sbsrefstate->upperprovided[i])
     197              :         {
     198              :             /* If any index expr yields NULL, result is NULL or error */
     199          393 :             if (sbsrefstate->upperindexnull[i])
     200              :             {
     201            9 :                 if (sbsrefstate->isassignment)
     202            3 :                     ereport(ERROR,
     203              :                             (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
     204              :                              errmsg("jsonb subscript in assignment must not be null")));
     205            6 :                 *op->resnull = true;
     206            6 :                 return false;
     207              :             }
     208              : 
     209              :             /*
     210              :              * For jsonb fetch and assign functions we need to provide path in
     211              :              * text format. Convert if it's not already text.
     212              :              */
     213          384 :             if (workspace->indexOid[i] == INT4OID)
     214              :             {
     215          150 :                 Datum       datum = sbsrefstate->upperindex[i];
     216          150 :                 char       *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
     217              : 
     218          150 :                 workspace->index[i] = CStringGetTextDatum(cs);
     219              :             }
     220              :             else
     221          234 :                 workspace->index[i] = sbsrefstate->upperindex[i];
     222              :         }
     223              :     }
     224              : 
     225          219 :     return true;
     226              : }
     227              : 
     228              : /*
     229              :  * Evaluate SubscriptingRef fetch for a jsonb element.
     230              :  *
     231              :  * Source container is in step's result variable (it's known not NULL, since
     232              :  * we set fetch_strict to true).
     233              :  */
     234              : static void
     235           96 : jsonb_subscript_fetch(ExprState *state,
     236              :                       ExprEvalStep *op,
     237              :                       ExprContext *econtext)
     238              : {
     239           96 :     SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
     240           96 :     JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
     241              :     Jsonb      *jsonbSource;
     242              : 
     243              :     /* Should not get here if source jsonb (or any subscript) is null */
     244              :     Assert(!(*op->resnull));
     245              : 
     246           96 :     jsonbSource = DatumGetJsonbP(*op->resvalue);
     247          192 :     *op->resvalue = jsonb_get_element(jsonbSource,
     248           96 :                                       workspace->index,
     249              :                                       sbsrefstate->numupper,
     250              :                                       op->resnull,
     251              :                                       false);
     252           96 : }
     253              : 
     254              : /*
     255              :  * Evaluate SubscriptingRef assignment for a jsonb element assignment.
     256              :  *
     257              :  * Input container (possibly null) is in result area, replacement value is in
     258              :  * SubscriptingRefState's replacevalue/replacenull.
     259              :  */
     260              : static void
     261          123 : jsonb_subscript_assign(ExprState *state,
     262              :                        ExprEvalStep *op,
     263              :                        ExprContext *econtext)
     264              : {
     265          123 :     SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
     266          123 :     JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
     267              :     Jsonb      *jsonbSource;
     268              :     JsonbValue  replacevalue;
     269              : 
     270          123 :     if (sbsrefstate->replacenull)
     271            6 :         replacevalue.type = jbvNull;
     272              :     else
     273          117 :         JsonbToJsonbValue(DatumGetJsonbP(sbsrefstate->replacevalue),
     274              :                           &replacevalue);
     275              : 
     276              :     /*
     277              :      * In case if the input container is null, set up an empty jsonb and
     278              :      * proceed with the assignment.
     279              :      */
     280          123 :     if (*op->resnull)
     281              :     {
     282              :         JsonbValue  newSource;
     283              : 
     284              :         /*
     285              :          * To avoid any surprising results, set up an empty jsonb array in
     286              :          * case of an array is expected (i.e. the first subscript is integer),
     287              :          * otherwise jsonb object.
     288              :          */
     289            6 :         if (workspace->expectArray)
     290              :         {
     291            3 :             newSource.type = jbvArray;
     292            3 :             newSource.val.array.nElems = 0;
     293            3 :             newSource.val.array.rawScalar = false;
     294              :         }
     295              :         else
     296              :         {
     297            3 :             newSource.type = jbvObject;
     298            3 :             newSource.val.object.nPairs = 0;
     299              :         }
     300              : 
     301            6 :         jsonbSource = JsonbValueToJsonb(&newSource);
     302            6 :         *op->resnull = false;
     303              :     }
     304              :     else
     305          117 :         jsonbSource = DatumGetJsonbP(*op->resvalue);
     306              : 
     307          222 :     *op->resvalue = jsonb_set_element(jsonbSource,
     308          123 :                                       workspace->index,
     309              :                                       sbsrefstate->numupper,
     310              :                                       &replacevalue);
     311              :     /* The result is never NULL, so no need to change *op->resnull */
     312           99 : }
     313              : 
     314              : /*
     315              :  * Compute old jsonb element value for a SubscriptingRef assignment
     316              :  * expression.  Will only be called if the new-value subexpression
     317              :  * contains SubscriptingRef or FieldStore.  This is the same as the
     318              :  * regular fetch case, except that we have to handle a null jsonb,
     319              :  * and the value should be stored into the SubscriptingRefState's
     320              :  * prevvalue/prevnull fields.
     321              :  */
     322              : static void
     323            0 : jsonb_subscript_fetch_old(ExprState *state,
     324              :                           ExprEvalStep *op,
     325              :                           ExprContext *econtext)
     326              : {
     327            0 :     SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
     328              : 
     329            0 :     if (*op->resnull)
     330              :     {
     331              :         /* whole jsonb is null, so any element is too */
     332            0 :         sbsrefstate->prevvalue = (Datum) 0;
     333            0 :         sbsrefstate->prevnull = true;
     334              :     }
     335              :     else
     336              :     {
     337            0 :         Jsonb      *jsonbSource = DatumGetJsonbP(*op->resvalue);
     338              : 
     339            0 :         sbsrefstate->prevvalue = jsonb_get_element(jsonbSource,
     340            0 :                                                    sbsrefstate->upperindex,
     341              :                                                    sbsrefstate->numupper,
     342              :                                                    &sbsrefstate->prevnull,
     343              :                                                    false);
     344              :     }
     345            0 : }
     346              : 
     347              : /*
     348              :  * Set up execution state for a jsonb subscript operation. Opposite to the
     349              :  * arrays subscription, there is no limit for number of subscripts as jsonb
     350              :  * type itself doesn't have nesting limits.
     351              :  */
     352              : static void
     353          192 : jsonb_exec_setup(const SubscriptingRef *sbsref,
     354              :                  SubscriptingRefState *sbsrefstate,
     355              :                  SubscriptExecSteps *methods)
     356              : {
     357              :     JsonbSubWorkspace *workspace;
     358              :     ListCell   *lc;
     359          192 :     int         nupper = sbsref->refupperindexpr->length;
     360              :     char       *ptr;
     361              : 
     362              :     /* Allocate type-specific workspace with space for per-subscript data */
     363          192 :     workspace = palloc0(MAXALIGN(sizeof(JsonbSubWorkspace)) +
     364          192 :                         nupper * (sizeof(Datum) + sizeof(Oid)));
     365          192 :     workspace->expectArray = false;
     366          192 :     ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace));
     367              : 
     368              :     /*
     369              :      * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might
     370              :      * misalign the indexOid pointer
     371              :      */
     372          192 :     workspace->index = (Datum *) ptr;
     373          192 :     ptr += nupper * sizeof(Datum);
     374          192 :     workspace->indexOid = (Oid *) ptr;
     375              : 
     376          192 :     sbsrefstate->workspace = workspace;
     377              : 
     378              :     /* Collect subscript data types necessary at execution time */
     379          549 :     foreach(lc, sbsref->refupperindexpr)
     380              :     {
     381          357 :         Node       *expr = lfirst(lc);
     382          357 :         int         i = foreach_current_index(lc);
     383              : 
     384          357 :         workspace->indexOid[i] = exprType(expr);
     385              :     }
     386              : 
     387              :     /*
     388              :      * Pass back pointers to appropriate step execution functions.
     389              :      */
     390          192 :     methods->sbs_check_subscripts = jsonb_subscript_check_subscripts;
     391          192 :     methods->sbs_fetch = jsonb_subscript_fetch;
     392          192 :     methods->sbs_assign = jsonb_subscript_assign;
     393          192 :     methods->sbs_fetch_old = jsonb_subscript_fetch_old;
     394          192 : }
     395              : 
     396              : /*
     397              :  * jsonb_subscript_handler
     398              :  *      Subscripting handler for jsonb.
     399              :  *
     400              :  */
     401              : Datum
     402          402 : jsonb_subscript_handler(PG_FUNCTION_ARGS)
     403              : {
     404              :     static const SubscriptRoutines sbsroutines = {
     405              :         .transform = jsonb_subscript_transform,
     406              :         .exec_setup = jsonb_exec_setup,
     407              :         .fetch_strict = true,   /* fetch returns NULL for NULL inputs */
     408              :         .fetch_leakproof = true,    /* fetch returns NULL for bad subscript */
     409              :         .store_leakproof = false    /* ... but assignment throws error */
     410              :     };
     411              : 
     412          402 :     PG_RETURN_POINTER(&sbsroutines);
     413              : }
        

Generated by: LCOV version 2.0-1