Coverage Report

Created: 2025-06-15 06:31

/src/postgres/src/backend/utils/adt/jsonbsubs.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * jsonbsubs.c
4
 *    Subscripting support functions for jsonb.
5
 *
6
 * Portions Copyright (c) 1996-2025, 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
jsonb_subscript_transform(SubscriptingRef *sbsref,
44
              List *indirection,
45
              ParseState *pstate,
46
              bool isSlice,
47
              bool isAssignment)
48
0
{
49
0
  List     *upperIndexpr = NIL;
50
0
  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
0
  foreach(idx, indirection)
57
0
  {
58
0
    A_Indices  *ai = lfirst_node(A_Indices, idx);
59
0
    Node     *subExpr;
60
61
0
    if (isSlice)
62
0
    {
63
0
      Node     *expr = ai->uidx ? ai->uidx : ai->lidx;
64
65
0
      ereport(ERROR,
66
0
          (errcode(ERRCODE_DATATYPE_MISMATCH),
67
0
           errmsg("jsonb subscript does not support slices"),
68
0
           parser_errposition(pstate, exprLocation(expr))));
69
0
    }
70
71
0
    if (ai->uidx)
72
0
    {
73
0
      Oid     subExprType = InvalidOid,
74
0
            targetType = UNKNOWNOID;
75
76
0
      subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
77
0
      subExprType = exprType(subExpr);
78
79
0
      if (subExprType != UNKNOWNOID)
80
0
      {
81
0
        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
0
        for (int i = 0; i < 2; i++)
90
0
        {
91
0
          if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT))
92
0
          {
93
            /*
94
             * One type has already succeeded, it means there are
95
             * two coercion targets possible, failure.
96
             */
97
0
            if (targetType != UNKNOWNOID)
98
0
              ereport(ERROR,
99
0
                  (errcode(ERRCODE_DATATYPE_MISMATCH),
100
0
                   errmsg("subscript type %s is not supported", format_type_be(subExprType)),
101
0
                   errhint("jsonb subscript must be coercible to only one type, integer or text."),
102
0
                   parser_errposition(pstate, exprLocation(subExpr))));
103
104
0
            targetType = targets[i];
105
0
          }
106
0
        }
107
108
        /*
109
         * No suitable types were found, failure.
110
         */
111
0
        if (targetType == UNKNOWNOID)
112
0
          ereport(ERROR,
113
0
              (errcode(ERRCODE_DATATYPE_MISMATCH),
114
0
               errmsg("subscript type %s is not supported", format_type_be(subExprType)),
115
0
               errhint("jsonb subscript must be coercible to either integer or text."),
116
0
               parser_errposition(pstate, exprLocation(subExpr))));
117
0
      }
118
0
      else
119
0
        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
0
      subExpr = coerce_type(pstate,
128
0
                  subExpr, subExprType,
129
0
                  targetType, -1,
130
0
                  COERCION_IMPLICIT,
131
0
                  COERCE_IMPLICIT_CAST,
132
0
                  -1);
133
0
      if (subExpr == NULL)
134
0
        ereport(ERROR,
135
0
            (errcode(ERRCODE_DATATYPE_MISMATCH),
136
0
             errmsg("jsonb subscript must have text type"),
137
0
             parser_errposition(pstate, exprLocation(subExpr))));
138
0
    }
139
0
    else
140
0
    {
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
0
      Assert(isSlice && ai->is_slice);
146
0
      ereport(ERROR,
147
0
          (errcode(ERRCODE_DATATYPE_MISMATCH),
148
0
           errmsg("jsonb subscript does not support slices"),
149
0
           parser_errposition(pstate, exprLocation(ai->uidx))));
150
0
    }
151
152
0
    upperIndexpr = lappend(upperIndexpr, subExpr);
153
0
  }
154
155
  /* store the transformed lists into the SubscriptRef node */
156
0
  sbsref->refupperindexpr = upperIndexpr;
157
0
  sbsref->reflowerindexpr = NIL;
158
159
  /* Determine the result type of the subscripting operation; always jsonb */
160
0
  sbsref->refrestype = JSONBOID;
161
0
  sbsref->reftypmod = -1;
162
0
}
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
jsonb_subscript_check_subscripts(ExprState *state,
176
                 ExprEvalStep *op,
177
                 ExprContext *econtext)
178
0
{
179
0
  SubscriptingRefState *sbsrefstate = op->d.sbsref_subscript.state;
180
0
  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
0
  if (sbsrefstate->numupper > 0 && sbsrefstate->upperprovided[0] &&
190
0
    !sbsrefstate->upperindexnull[0] && workspace->indexOid[0] == INT4OID)
191
0
    workspace->expectArray = true;
192
193
  /* Process upper subscripts */
194
0
  for (int i = 0; i < sbsrefstate->numupper; i++)
195
0
  {
196
0
    if (sbsrefstate->upperprovided[i])
197
0
    {
198
      /* If any index expr yields NULL, result is NULL or error */
199
0
      if (sbsrefstate->upperindexnull[i])
200
0
      {
201
0
        if (sbsrefstate->isassignment)
202
0
          ereport(ERROR,
203
0
              (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
204
0
               errmsg("jsonb subscript in assignment must not be null")));
205
0
        *op->resnull = true;
206
0
        return false;
207
0
      }
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
0
      if (workspace->indexOid[i] == INT4OID)
214
0
      {
215
0
        Datum   datum = sbsrefstate->upperindex[i];
216
0
        char     *cs = DatumGetCString(DirectFunctionCall1(int4out, datum));
217
218
0
        workspace->index[i] = CStringGetTextDatum(cs);
219
0
      }
220
0
      else
221
0
        workspace->index[i] = sbsrefstate->upperindex[i];
222
0
    }
223
0
  }
224
225
0
  return true;
226
0
}
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
jsonb_subscript_fetch(ExprState *state,
236
            ExprEvalStep *op,
237
            ExprContext *econtext)
238
0
{
239
0
  SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
240
0
  JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
241
0
  Jsonb    *jsonbSource;
242
243
  /* Should not get here if source jsonb (or any subscript) is null */
244
0
  Assert(!(*op->resnull));
245
246
0
  jsonbSource = DatumGetJsonbP(*op->resvalue);
247
0
  *op->resvalue = jsonb_get_element(jsonbSource,
248
0
                    workspace->index,
249
0
                    sbsrefstate->numupper,
250
0
                    op->resnull,
251
0
                    false);
252
0
}
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
jsonb_subscript_assign(ExprState *state,
262
             ExprEvalStep *op,
263
             ExprContext *econtext)
264
0
{
265
0
  SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
266
0
  JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace;
267
0
  Jsonb    *jsonbSource;
268
0
  JsonbValue  replacevalue;
269
270
0
  if (sbsrefstate->replacenull)
271
0
    replacevalue.type = jbvNull;
272
0
  else
273
0
    JsonbToJsonbValue(DatumGetJsonbP(sbsrefstate->replacevalue),
274
0
              &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
0
  if (*op->resnull)
281
0
  {
282
0
    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
0
    if (workspace->expectArray)
290
0
    {
291
0
      newSource.type = jbvArray;
292
0
      newSource.val.array.nElems = 0;
293
0
      newSource.val.array.rawScalar = false;
294
0
    }
295
0
    else
296
0
    {
297
0
      newSource.type = jbvObject;
298
0
      newSource.val.object.nPairs = 0;
299
0
    }
300
301
0
    jsonbSource = JsonbValueToJsonb(&newSource);
302
0
    *op->resnull = false;
303
0
  }
304
0
  else
305
0
    jsonbSource = DatumGetJsonbP(*op->resvalue);
306
307
0
  *op->resvalue = jsonb_set_element(jsonbSource,
308
0
                    workspace->index,
309
0
                    sbsrefstate->numupper,
310
0
                    &replacevalue);
311
  /* The result is never NULL, so no need to change *op->resnull */
312
0
}
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
jsonb_subscript_fetch_old(ExprState *state,
324
              ExprEvalStep *op,
325
              ExprContext *econtext)
326
0
{
327
0
  SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
328
329
0
  if (*op->resnull)
330
0
  {
331
    /* whole jsonb is null, so any element is too */
332
0
    sbsrefstate->prevvalue = (Datum) 0;
333
0
    sbsrefstate->prevnull = true;
334
0
  }
335
0
  else
336
0
  {
337
0
    Jsonb    *jsonbSource = DatumGetJsonbP(*op->resvalue);
338
339
0
    sbsrefstate->prevvalue = jsonb_get_element(jsonbSource,
340
0
                           sbsrefstate->upperindex,
341
0
                           sbsrefstate->numupper,
342
0
                           &sbsrefstate->prevnull,
343
0
                           false);
344
0
  }
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
jsonb_exec_setup(const SubscriptingRef *sbsref,
354
         SubscriptingRefState *sbsrefstate,
355
         SubscriptExecSteps *methods)
356
0
{
357
0
  JsonbSubWorkspace *workspace;
358
0
  ListCell   *lc;
359
0
  int     nupper = sbsref->refupperindexpr->length;
360
0
  char     *ptr;
361
362
  /* Allocate type-specific workspace with space for per-subscript data */
363
0
  workspace = palloc0(MAXALIGN(sizeof(JsonbSubWorkspace)) +
364
0
            nupper * (sizeof(Datum) + sizeof(Oid)));
365
0
  workspace->expectArray = false;
366
0
  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
0
  workspace->index = (Datum *) ptr;
373
0
  ptr += nupper * sizeof(Datum);
374
0
  workspace->indexOid = (Oid *) ptr;
375
376
0
  sbsrefstate->workspace = workspace;
377
378
  /* Collect subscript data types necessary at execution time */
379
0
  foreach(lc, sbsref->refupperindexpr)
380
0
  {
381
0
    Node     *expr = lfirst(lc);
382
0
    int     i = foreach_current_index(lc);
383
384
0
    workspace->indexOid[i] = exprType(expr);
385
0
  }
386
387
  /*
388
   * Pass back pointers to appropriate step execution functions.
389
   */
390
0
  methods->sbs_check_subscripts = jsonb_subscript_check_subscripts;
391
0
  methods->sbs_fetch = jsonb_subscript_fetch;
392
0
  methods->sbs_assign = jsonb_subscript_assign;
393
0
  methods->sbs_fetch_old = jsonb_subscript_fetch_old;
394
0
}
395
396
/*
397
 * jsonb_subscript_handler
398
 *    Subscripting handler for jsonb.
399
 *
400
 */
401
Datum
402
jsonb_subscript_handler(PG_FUNCTION_ARGS)
403
0
{
404
0
  static const SubscriptRoutines sbsroutines = {
405
0
    .transform = jsonb_subscript_transform,
406
0
    .exec_setup = jsonb_exec_setup,
407
0
    .fetch_strict = true, /* fetch returns NULL for NULL inputs */
408
0
    .fetch_leakproof = true,  /* fetch returns NULL for bad subscript */
409
    .store_leakproof = false  /* ... but assignment throws error */
410
0
  };
411
412
0
  PG_RETURN_POINTER(&sbsroutines);
413
0
}