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

Generated by: LCOV version 1.14