LCOV - differential code coverage report
Current view: top level - src/backend/utils/adt - jsonbsubs.c (source / functions) Coverage Total Hit UBC CBC
Current: Differential Code Coverage HEAD vs 15 Lines: 88.7 % 106 94 12 94
Current Date: 2023-04-08 15:15:32 Functions: 85.7 % 7 6 1 6
Baseline: 15
Baseline Date: 2023-04-08 15:09:40
Legend: Lines: hit not hit

           TLA  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 CBC         210 : jsonb_subscript_transform(SubscriptingRef *sbsref,
      47                 :                           List *indirection,
      48                 :                           ParseState *pstate,
      49                 :                           bool isSlice,
      50                 :                           bool isAssignment)
      51                 : {
      52             210 :     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             567 :     foreach(idx, indirection)
      60                 :     {
      61             375 :         A_Indices  *ai = lfirst_node(A_Indices, idx);
      62                 :         Node       *subExpr;
      63                 : 
      64             375 :         if (isSlice)
      65                 :         {
      66              15 :             Node       *expr = ai->uidx ? ai->uidx : ai->lidx;
      67                 : 
      68              15 :             ereport(ERROR,
      69                 :                     (errcode(ERRCODE_DATATYPE_MISMATCH),
      70                 :                      errmsg("jsonb subscript does not support slices"),
      71                 :                      parser_errposition(pstate, exprLocation(expr))));
      72                 :         }
      73                 : 
      74             360 :         if (ai->uidx)
      75                 :         {
      76             360 :             Oid         subExprType = InvalidOid,
      77             360 :                         targetType = UNKNOWNOID;
      78                 : 
      79             360 :             subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
      80             360 :             subExprType = exprType(subExpr);
      81                 : 
      82             360 :             if (subExprType != UNKNOWNOID)
      83                 :             {
      84             156 :                 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             468 :                 for (int i = 0; i < 2; i++)
      93                 :                 {
      94             312 :                     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             153 :                         if (targetType != UNKNOWNOID)
     101 UBC           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 CBC         153 :                         targetType = targets[i];
     108                 :                     }
     109                 :                 }
     110                 : 
     111                 :                 /*
     112                 :                  * No suitable types were found, failure.
     113                 :                  */
     114             156 :                 if (targetType == UNKNOWNOID)
     115               3 :                     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             204 :                 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             357 :             subExpr = coerce_type(pstate,
     131                 :                                   subExpr, subExprType,
     132                 :                                   targetType, -1,
     133                 :                                   COERCION_IMPLICIT,
     134                 :                                   COERCE_IMPLICIT_CAST,
     135                 :                                   -1);
     136             357 :             if (subExpr == NULL)
     137 UBC           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               0 :             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 CBC         357 :         upperIndexpr = lappend(upperIndexpr, subExpr);
     156                 :     }
     157                 : 
     158                 :     /* store the transformed lists into the SubscriptRef node */
     159             192 :     sbsref->refupperindexpr = upperIndexpr;
     160             192 :     sbsref->reflowerindexpr = NIL;
     161                 : 
     162                 :     /* Determine the result type of the subscripting operation; always jsonb */
     163             192 :     sbsref->refrestype = JSONBOID;
     164             192 :     sbsref->reftypmod = -1;
     165             192 : }
     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             228 : jsonb_subscript_check_subscripts(ExprState *state,
     179                 :                                  ExprEvalStep *op,
     180                 :                                  ExprContext *econtext)
     181                 : {
     182             228 :     SubscriptingRefState *sbsrefstate = op->d.sbsref_subscript.state;
     183             228 :     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             228 :     if (sbsrefstate->numupper > 0 && sbsrefstate->upperprovided[0] &&
     193             228 :         !sbsrefstate->upperindexnull[0] && workspace->indexOid[0] == INT4OID)
     194              66 :         workspace->expectArray = true;
     195                 : 
     196                 :     /* Process upper subscripts */
     197             612 :     for (int i = 0; i < sbsrefstate->numupper; i++)
     198                 :     {
     199             393 :         if (sbsrefstate->upperprovided[i])
     200                 :         {
     201                 :             /* If any index expr yields NULL, result is NULL or error */
     202             393 :             if (sbsrefstate->upperindexnull[i])
     203                 :             {
     204               9 :                 if (sbsrefstate->isassignment)
     205               3 :                     ereport(ERROR,
     206                 :                             (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
     207                 :                              errmsg("jsonb subscript in assignment must not be null")));
     208               6 :                 *op->resnull = true;
     209               6 :                 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             384 :             if (workspace->indexOid[i] == INT4OID)
     217                 :             {
     218             150 :                 Datum       datum = sbsrefstate->upperindex[i];
     219             150 :                 char       *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
     220                 : 
     221             150 :                 workspace->index[i] = CStringGetTextDatum(cs);
     222                 :             }
     223                 :             else
     224             234 :                 workspace->index[i] = sbsrefstate->upperindex[i];
     225                 :         }
     226                 :     }
     227                 : 
     228             219 :     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              96 : jsonb_subscript_fetch(ExprState *state,
     239                 :                       ExprEvalStep *op,
     240                 :                       ExprContext *econtext)
     241                 : {
     242              96 :     SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
     243              96 :     JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
     244                 :     Jsonb      *jsonbSource;
     245                 : 
     246                 :     /* Should not get here if source jsonb (or any subscript) is null */
     247              96 :     Assert(!(*op->resnull));
     248                 : 
     249              96 :     jsonbSource = DatumGetJsonbP(*op->resvalue);
     250              96 :     *op->resvalue = jsonb_get_element(jsonbSource,
     251                 :                                       workspace->index,
     252                 :                                       sbsrefstate->numupper,
     253                 :                                       op->resnull,
     254                 :                                       false);
     255              96 : }
     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             123 : jsonb_subscript_assign(ExprState *state,
     265                 :                        ExprEvalStep *op,
     266                 :                        ExprContext *econtext)
     267                 : {
     268             123 :     SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
     269             123 :     JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
     270                 :     Jsonb      *jsonbSource;
     271                 :     JsonbValue  replacevalue;
     272                 : 
     273             123 :     if (sbsrefstate->replacenull)
     274               6 :         replacevalue.type = jbvNull;
     275                 :     else
     276             117 :         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             123 :     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               6 :         if (workspace->expectArray)
     293                 :         {
     294               3 :             newSource.type = jbvArray;
     295               3 :             newSource.val.array.nElems = 0;
     296               3 :             newSource.val.array.rawScalar = false;
     297                 :         }
     298                 :         else
     299                 :         {
     300               3 :             newSource.type = jbvObject;
     301               3 :             newSource.val.object.nPairs = 0;
     302                 :         }
     303                 : 
     304               6 :         jsonbSource = JsonbValueToJsonb(&newSource);
     305               6 :         *op->resnull = false;
     306                 :     }
     307                 :     else
     308             117 :         jsonbSource = DatumGetJsonbP(*op->resvalue);
     309                 : 
     310             123 :     *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              99 : }
     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 UBC           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 CBC         192 : jsonb_exec_setup(const SubscriptingRef *sbsref,
     357                 :                  SubscriptingRefState *sbsrefstate,
     358                 :                  SubscriptExecSteps *methods)
     359                 : {
     360                 :     JsonbSubWorkspace *workspace;
     361                 :     ListCell   *lc;
     362             192 :     int         nupper = sbsref->refupperindexpr->length;
     363                 :     char       *ptr;
     364                 : 
     365                 :     /* Allocate type-specific workspace with space for per-subscript data */
     366             192 :     workspace = palloc0(MAXALIGN(sizeof(JsonbSubWorkspace)) +
     367             192 :                         nupper * (sizeof(Datum) + sizeof(Oid)));
     368             192 :     workspace->expectArray = false;
     369             192 :     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             192 :     workspace->index = (Datum *) ptr;
     376             192 :     ptr += nupper * sizeof(Datum);
     377             192 :     workspace->indexOid = (Oid *) ptr;
     378                 : 
     379             192 :     sbsrefstate->workspace = workspace;
     380                 : 
     381                 :     /* Collect subscript data types necessary at execution time */
     382             549 :     foreach(lc, sbsref->refupperindexpr)
     383                 :     {
     384             357 :         Node       *expr = lfirst(lc);
     385             357 :         int         i = foreach_current_index(lc);
     386                 : 
     387             357 :         workspace->indexOid[i] = exprType(expr);
     388                 :     }
     389                 : 
     390                 :     /*
     391                 :      * Pass back pointers to appropriate step execution functions.
     392                 :      */
     393             192 :     methods->sbs_check_subscripts = jsonb_subscript_check_subscripts;
     394             192 :     methods->sbs_fetch = jsonb_subscript_fetch;
     395             192 :     methods->sbs_assign = jsonb_subscript_assign;
     396             192 :     methods->sbs_fetch_old = jsonb_subscript_fetch_old;
     397             192 : }
     398                 : 
     399                 : /*
     400                 :  * jsonb_subscript_handler
     401                 :  *      Subscripting handler for jsonb.
     402                 :  *
     403                 :  */
     404                 : Datum
     405             402 : 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             402 :     PG_RETURN_POINTER(&sbsroutines);
     416                 : }
        

Generated by: LCOV version v1.16-55-g56c0a2a