LCOV - code coverage report
Current view: top level - src/backend/utils/adt - jsonbsubs.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 93 104 89.4 %
Date: 2024-04-26 16:11:01 Functions: 6 7 85.7 %
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-2024, 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         420 : jsonb_subscript_transform(SubscriptingRef *sbsref,
      44             :                           List *indirection,
      45             :                           ParseState *pstate,
      46             :                           bool isSlice,
      47             :                           bool isAssignment)
      48             : {
      49         420 :     List       *upperIndexpr = NIL;
      50             :     ListCell   *idx;
      51             : 
      52             :     /*
      53             :      * Transform and convert the subscript expressions. Jsonb subscripting
      54             :      * does not support slices, look only and the upper index.
      55             :      */
      56        1134 :     foreach(idx, indirection)
      57             :     {
      58         750 :         A_Indices  *ai = lfirst_node(A_Indices, idx);
      59             :         Node       *subExpr;
      60             : 
      61         750 :         if (isSlice)
      62             :         {
      63          30 :             Node       *expr = ai->uidx ? ai->uidx : ai->lidx;
      64             : 
      65          30 :             ereport(ERROR,
      66             :                     (errcode(ERRCODE_DATATYPE_MISMATCH),
      67             :                      errmsg("jsonb subscript does not support slices"),
      68             :                      parser_errposition(pstate, exprLocation(expr))));
      69             :         }
      70             : 
      71         720 :         if (ai->uidx)
      72             :         {
      73         720 :             Oid         subExprType = InvalidOid,
      74         720 :                         targetType = UNKNOWNOID;
      75             : 
      76         720 :             subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
      77         720 :             subExprType = exprType(subExpr);
      78             : 
      79         720 :             if (subExprType != UNKNOWNOID)
      80             :             {
      81         312 :                 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         936 :                 for (int i = 0; i < 2; i++)
      90             :                 {
      91         624 :                     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         306 :                         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         306 :                         targetType = targets[i];
     105             :                     }
     106             :                 }
     107             : 
     108             :                 /*
     109             :                  * No suitable types were found, failure.
     110             :                  */
     111         312 :                 if (targetType == UNKNOWNOID)
     112           6 :                     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         408 :                 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         714 :             subExpr = coerce_type(pstate,
     128             :                                   subExpr, subExprType,
     129             :                                   targetType, -1,
     130             :                                   COERCION_IMPLICIT,
     131             :                                   COERCE_IMPLICIT_CAST,
     132             :                                   -1);
     133         714 :             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         714 :         upperIndexpr = lappend(upperIndexpr, subExpr);
     153             :     }
     154             : 
     155             :     /* store the transformed lists into the SubscriptRef node */
     156         384 :     sbsref->refupperindexpr = upperIndexpr;
     157         384 :     sbsref->reflowerindexpr = NIL;
     158             : 
     159             :     /* Determine the result type of the subscripting operation; always jsonb */
     160         384 :     sbsref->refrestype = JSONBOID;
     161         384 :     sbsref->reftypmod = -1;
     162         384 : }
     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         456 : jsonb_subscript_check_subscripts(ExprState *state,
     176             :                                  ExprEvalStep *op,
     177             :                                  ExprContext *econtext)
     178             : {
     179         456 :     SubscriptingRefState *sbsrefstate = op->d.sbsref_subscript.state;
     180         456 :     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         456 :     if (sbsrefstate->numupper > 0 && sbsrefstate->upperprovided[0] &&
     190         456 :         !sbsrefstate->upperindexnull[0] && workspace->indexOid[0] == INT4OID)
     191         132 :         workspace->expectArray = true;
     192             : 
     193             :     /* Process upper subscripts */
     194        1224 :     for (int i = 0; i < sbsrefstate->numupper; i++)
     195             :     {
     196         786 :         if (sbsrefstate->upperprovided[i])
     197             :         {
     198             :             /* If any index expr yields NULL, result is NULL or error */
     199         786 :             if (sbsrefstate->upperindexnull[i])
     200             :             {
     201          18 :                 if (sbsrefstate->isassignment)
     202           6 :                     ereport(ERROR,
     203             :                             (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
     204             :                              errmsg("jsonb subscript in assignment must not be null")));
     205          12 :                 *op->resnull = true;
     206          12 :                 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         768 :             if (workspace->indexOid[i] == INT4OID)
     214             :             {
     215         300 :                 Datum       datum = sbsrefstate->upperindex[i];
     216         300 :                 char       *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
     217             : 
     218         300 :                 workspace->index[i] = CStringGetTextDatum(cs);
     219             :             }
     220             :             else
     221         468 :                 workspace->index[i] = sbsrefstate->upperindex[i];
     222             :         }
     223             :     }
     224             : 
     225         438 :     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         192 : jsonb_subscript_fetch(ExprState *state,
     236             :                       ExprEvalStep *op,
     237             :                       ExprContext *econtext)
     238             : {
     239         192 :     SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
     240         192 :     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         192 :     jsonbSource = DatumGetJsonbP(*op->resvalue);
     247         192 :     *op->resvalue = jsonb_get_element(jsonbSource,
     248             :                                       workspace->index,
     249             :                                       sbsrefstate->numupper,
     250             :                                       op->resnull,
     251             :                                       false);
     252         192 : }
     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         246 : jsonb_subscript_assign(ExprState *state,
     262             :                        ExprEvalStep *op,
     263             :                        ExprContext *econtext)
     264             : {
     265         246 :     SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
     266         246 :     JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
     267             :     Jsonb      *jsonbSource;
     268             :     JsonbValue  replacevalue;
     269             : 
     270         246 :     if (sbsrefstate->replacenull)
     271          12 :         replacevalue.type = jbvNull;
     272             :     else
     273         234 :         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         246 :     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          12 :         if (workspace->expectArray)
     290             :         {
     291           6 :             newSource.type = jbvArray;
     292           6 :             newSource.val.array.nElems = 0;
     293           6 :             newSource.val.array.rawScalar = false;
     294             :         }
     295             :         else
     296             :         {
     297           6 :             newSource.type = jbvObject;
     298           6 :             newSource.val.object.nPairs = 0;
     299             :         }
     300             : 
     301          12 :         jsonbSource = JsonbValueToJsonb(&newSource);
     302          12 :         *op->resnull = false;
     303             :     }
     304             :     else
     305         234 :         jsonbSource = DatumGetJsonbP(*op->resvalue);
     306             : 
     307         246 :     *op->resvalue = jsonb_set_element(jsonbSource,
     308             :                                       workspace->index,
     309             :                                       sbsrefstate->numupper,
     310             :                                       &replacevalue);
     311             :     /* The result is never NULL, so no need to change *op->resnull */
     312         198 : }
     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             :                                                    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         384 : jsonb_exec_setup(const SubscriptingRef *sbsref,
     354             :                  SubscriptingRefState *sbsrefstate,
     355             :                  SubscriptExecSteps *methods)
     356             : {
     357             :     JsonbSubWorkspace *workspace;
     358             :     ListCell   *lc;
     359         384 :     int         nupper = sbsref->refupperindexpr->length;
     360             :     char       *ptr;
     361             : 
     362             :     /* Allocate type-specific workspace with space for per-subscript data */
     363         384 :     workspace = palloc0(MAXALIGN(sizeof(JsonbSubWorkspace)) +
     364         384 :                         nupper * (sizeof(Datum) + sizeof(Oid)));
     365         384 :     workspace->expectArray = false;
     366         384 :     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         384 :     workspace->index = (Datum *) ptr;
     373         384 :     ptr += nupper * sizeof(Datum);
     374         384 :     workspace->indexOid = (Oid *) ptr;
     375             : 
     376         384 :     sbsrefstate->workspace = workspace;
     377             : 
     378             :     /* Collect subscript data types necessary at execution time */
     379        1098 :     foreach(lc, sbsref->refupperindexpr)
     380             :     {
     381         714 :         Node       *expr = lfirst(lc);
     382         714 :         int         i = foreach_current_index(lc);
     383             : 
     384         714 :         workspace->indexOid[i] = exprType(expr);
     385             :     }
     386             : 
     387             :     /*
     388             :      * Pass back pointers to appropriate step execution functions.
     389             :      */
     390         384 :     methods->sbs_check_subscripts = jsonb_subscript_check_subscripts;
     391         384 :     methods->sbs_fetch = jsonb_subscript_fetch;
     392         384 :     methods->sbs_assign = jsonb_subscript_assign;
     393         384 :     methods->sbs_fetch_old = jsonb_subscript_fetch_old;
     394         384 : }
     395             : 
     396             : /*
     397             :  * jsonb_subscript_handler
     398             :  *      Subscripting handler for jsonb.
     399             :  *
     400             :  */
     401             : Datum
     402         804 : 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         804 :     PG_RETURN_POINTER(&sbsroutines);
     413             : }

Generated by: LCOV version 1.14