LCOV - code coverage report
Current view: top level - src/backend/parser - parse_jsontable.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 98.9 % 180 178
Test Date: 2026-03-02 14:15:04 Functions: 100.0 % 10 10
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * parse_jsontable.c
       4              :  *    parsing of JSON_TABLE
       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/parser/parse_jsontable.c
      12              :  *
      13              :  *-------------------------------------------------------------------------
      14              :  */
      15              : 
      16              : #include "postgres.h"
      17              : 
      18              : #include "catalog/pg_type.h"
      19              : #include "nodes/makefuncs.h"
      20              : #include "nodes/nodeFuncs.h"
      21              : #include "optimizer/optimizer.h"
      22              : #include "parser/parse_clause.h"
      23              : #include "parser/parse_collate.h"
      24              : #include "parser/parse_expr.h"
      25              : #include "parser/parse_relation.h"
      26              : #include "parser/parse_type.h"
      27              : #include "utils/fmgrprotos.h"
      28              : #include "utils/json.h"
      29              : #include "utils/lsyscache.h"
      30              : 
      31              : /* Context for transformJsonTableColumns() */
      32              : typedef struct JsonTableParseContext
      33              : {
      34              :     ParseState *pstate;
      35              :     JsonTable  *jt;
      36              :     TableFunc  *tf;
      37              :     List       *pathNames;      /* list of all path and columns names */
      38              :     int         pathNameId;     /* path name id counter */
      39              : } JsonTableParseContext;
      40              : 
      41              : static JsonTablePlan *transformJsonTableColumns(JsonTableParseContext *cxt,
      42              :                                                 List *columns,
      43              :                                                 List *passingArgs,
      44              :                                                 JsonTablePathSpec *pathspec);
      45              : static JsonTablePlan *transformJsonTableNestedColumns(JsonTableParseContext *cxt,
      46              :                                                       List *passingArgs,
      47              :                                                       List *columns);
      48              : static JsonFuncExpr *transformJsonTableColumn(JsonTableColumn *jtc,
      49              :                                               Node *contextItemExpr,
      50              :                                               List *passingArgs);
      51              : static bool isCompositeType(Oid typid);
      52              : static JsonTablePlan *makeJsonTablePathScan(JsonTablePathSpec *pathspec,
      53              :                                             bool errorOnError,
      54              :                                             int colMin, int colMax,
      55              :                                             JsonTablePlan *childplan);
      56              : static void CheckDuplicateColumnOrPathNames(JsonTableParseContext *cxt,
      57              :                                             List *columns);
      58              : static bool LookupPathOrColumnName(JsonTableParseContext *cxt, char *name);
      59              : static char *generateJsonTablePathName(JsonTableParseContext *cxt);
      60              : static JsonTablePlan *makeJsonTableSiblingJoin(JsonTablePlan *lplan,
      61              :                                                JsonTablePlan *rplan);
      62              : 
      63              : /*
      64              :  * transformJsonTable -
      65              :  *          Transform a raw JsonTable into TableFunc
      66              :  *
      67              :  * Mainly, this transforms the JSON_TABLE() document-generating expression
      68              :  * (jt->context_item) and the column-generating expressions (jt->columns) to
      69              :  * populate TableFunc.docexpr and TableFunc.colvalexprs, respectively. Also,
      70              :  * the PASSING values (jt->passing) are transformed and added into
      71              :  * TableFunc.passingvalexprs.
      72              :  */
      73              : ParseNamespaceItem *
      74          262 : transformJsonTable(ParseState *pstate, JsonTable *jt)
      75              : {
      76              :     TableFunc  *tf;
      77              :     JsonFuncExpr *jfe;
      78              :     JsonExpr   *je;
      79          262 :     JsonTablePathSpec *rootPathSpec = jt->pathspec;
      80              :     bool        is_lateral;
      81          262 :     JsonTableParseContext cxt = {pstate};
      82              : 
      83              :     Assert(IsA(rootPathSpec->string, A_Const) &&
      84              :            castNode(A_Const, rootPathSpec->string)->val.node.type == T_String);
      85              : 
      86          262 :     if (jt->on_error &&
      87           30 :         jt->on_error->btype != JSON_BEHAVIOR_ERROR &&
      88           18 :         jt->on_error->btype != JSON_BEHAVIOR_EMPTY &&
      89           18 :         jt->on_error->btype != JSON_BEHAVIOR_EMPTY_ARRAY)
      90            9 :         ereport(ERROR,
      91              :                 errcode(ERRCODE_SYNTAX_ERROR),
      92              :                 errmsg("invalid %s behavior", "ON ERROR"),
      93              :                 errdetail("Only EMPTY [ ARRAY ] or ERROR is allowed in the top-level ON ERROR clause."),
      94              :                 parser_errposition(pstate, jt->on_error->location));
      95              : 
      96          253 :     cxt.pathNameId = 0;
      97          253 :     if (rootPathSpec->name == NULL)
      98          222 :         rootPathSpec->name = generateJsonTablePathName(&cxt);
      99          253 :     cxt.pathNames = list_make1(rootPathSpec->name);
     100          253 :     CheckDuplicateColumnOrPathNames(&cxt, jt->columns);
     101              : 
     102              :     /*
     103              :      * We make lateral_only names of this level visible, whether or not the
     104              :      * RangeTableFunc is explicitly marked LATERAL.  This is needed for SQL
     105              :      * spec compliance and seems useful on convenience grounds for all
     106              :      * functions in FROM.
     107              :      *
     108              :      * (LATERAL can't nest within a single pstate level, so we don't need
     109              :      * save/restore logic here.)
     110              :      */
     111              :     Assert(!pstate->p_lateral_active);
     112          238 :     pstate->p_lateral_active = true;
     113              : 
     114          238 :     tf = makeNode(TableFunc);
     115          238 :     tf->functype = TFT_JSON_TABLE;
     116              : 
     117              :     /*
     118              :      * Transform JsonFuncExpr representing the top JSON_TABLE context_item and
     119              :      * pathspec into a dummy JSON_TABLE_OP JsonExpr.
     120              :      */
     121          238 :     jfe = makeNode(JsonFuncExpr);
     122          238 :     jfe->op = JSON_TABLE_OP;
     123          238 :     jfe->context_item = jt->context_item;
     124          238 :     jfe->pathspec = (Node *) rootPathSpec->string;
     125          238 :     jfe->passing = jt->passing;
     126          238 :     jfe->on_empty = NULL;
     127          238 :     jfe->on_error = jt->on_error;
     128          238 :     jfe->location = jt->location;
     129          238 :     tf->docexpr = transformExpr(pstate, (Node *) jfe, EXPR_KIND_FROM_FUNCTION);
     130              : 
     131              :     /*
     132              :      * Create a JsonTablePlan that will generate row pattern that becomes
     133              :      * source data for JSON path expressions in jt->columns.  This also adds
     134              :      * the columns' transformed JsonExpr nodes into tf->colvalexprs.
     135              :      */
     136          238 :     cxt.jt = jt;
     137          238 :     cxt.tf = tf;
     138          238 :     tf->plan = (Node *) transformJsonTableColumns(&cxt, jt->columns,
     139              :                                                   jt->passing,
     140              :                                                   rootPathSpec);
     141              : 
     142              :     /*
     143              :      * Copy the transformed PASSING arguments into the TableFunc node, because
     144              :      * they are evaluated separately from the JsonExpr that we just put in
     145              :      * TableFunc.docexpr.  JsonExpr.passing_values is still kept around for
     146              :      * get_json_table().
     147              :      */
     148          223 :     je = (JsonExpr *) tf->docexpr;
     149          223 :     tf->passingvalexprs = copyObject(je->passing_values);
     150              : 
     151          223 :     tf->ordinalitycol = -1;      /* undefine ordinality column number */
     152          223 :     tf->location = jt->location;
     153              : 
     154          223 :     pstate->p_lateral_active = false;
     155              : 
     156              :     /*
     157              :      * Mark the RTE as LATERAL if the user said LATERAL explicitly, or if
     158              :      * there are any lateral cross-references in it.
     159              :      */
     160          223 :     is_lateral = jt->lateral || contain_vars_of_level((Node *) tf, 0);
     161              : 
     162          223 :     return addRangeTableEntryForTableFunc(pstate,
     163              :                                           tf, jt->alias, is_lateral, true);
     164              : }
     165              : 
     166              : /*
     167              :  * Check if a column / path name is duplicated in the given shared list of
     168              :  * names.
     169              :  */
     170              : static void
     171          389 : CheckDuplicateColumnOrPathNames(JsonTableParseContext *cxt,
     172              :                                 List *columns)
     173              : {
     174              :     ListCell   *lc1;
     175              : 
     176         1024 :     foreach(lc1, columns)
     177              :     {
     178          653 :         JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc1));
     179              : 
     180          653 :         if (jtc->coltype == JTC_NESTED)
     181              :         {
     182          145 :             if (jtc->pathspec->name)
     183              :             {
     184           73 :                 if (LookupPathOrColumnName(cxt, jtc->pathspec->name))
     185            9 :                     ereport(ERROR,
     186              :                             errcode(ERRCODE_DUPLICATE_ALIAS),
     187              :                             errmsg("duplicate JSON_TABLE column or path name: %s",
     188              :                                    jtc->pathspec->name),
     189              :                             parser_errposition(cxt->pstate,
     190              :                                                jtc->pathspec->name_location));
     191           64 :                 cxt->pathNames = lappend(cxt->pathNames, jtc->pathspec->name);
     192              :             }
     193              : 
     194          136 :             CheckDuplicateColumnOrPathNames(cxt, jtc->columns);
     195              :         }
     196              :         else
     197              :         {
     198          508 :             if (LookupPathOrColumnName(cxt, jtc->name))
     199            6 :                 ereport(ERROR,
     200              :                         errcode(ERRCODE_DUPLICATE_ALIAS),
     201              :                         errmsg("duplicate JSON_TABLE column or path name: %s",
     202              :                                jtc->name),
     203              :                         parser_errposition(cxt->pstate, jtc->location));
     204          502 :             cxt->pathNames = lappend(cxt->pathNames, jtc->name);
     205              :         }
     206              :     }
     207          371 : }
     208              : 
     209              : /*
     210              :  * Lookup a column/path name in the given name list, returning true if already
     211              :  * there.
     212              :  */
     213              : static bool
     214          581 : LookupPathOrColumnName(JsonTableParseContext *cxt, char *name)
     215              : {
     216              :     ListCell   *lc;
     217              : 
     218         2233 :     foreach(lc, cxt->pathNames)
     219              :     {
     220         1667 :         if (strcmp(name, (const char *) lfirst(lc)) == 0)
     221           15 :             return true;
     222              :     }
     223              : 
     224          566 :     return false;
     225              : }
     226              : 
     227              : /* Generate a new unique JSON_TABLE path name. */
     228              : static char *
     229          291 : generateJsonTablePathName(JsonTableParseContext *cxt)
     230              : {
     231              :     char        namebuf[32];
     232          291 :     char       *name = namebuf;
     233              : 
     234          291 :     snprintf(namebuf, sizeof(namebuf), "json_table_path_%d",
     235          291 :              cxt->pathNameId++);
     236              : 
     237          291 :     name = pstrdup(name);
     238          291 :     cxt->pathNames = lappend(cxt->pathNames, name);
     239              : 
     240          291 :     return name;
     241              : }
     242              : 
     243              : /*
     244              :  * Create a JsonTablePlan that will supply the source row for 'columns'
     245              :  * using 'pathspec' and append the columns' transformed JsonExpr nodes and
     246              :  * their type/collation information to cxt->tf.
     247              :  */
     248              : static JsonTablePlan *
     249          368 : transformJsonTableColumns(JsonTableParseContext *cxt, List *columns,
     250              :                           List *passingArgs,
     251              :                           JsonTablePathSpec *pathspec)
     252              : {
     253          368 :     ParseState *pstate = cxt->pstate;
     254          368 :     JsonTable  *jt = cxt->jt;
     255          368 :     TableFunc  *tf = cxt->tf;
     256              :     ListCell   *col;
     257          368 :     bool        ordinality_found = false;
     258          389 :     bool        errorOnError = jt->on_error &&
     259           21 :         jt->on_error->btype == JSON_BEHAVIOR_ERROR;
     260          368 :     Oid         contextItemTypid = exprType(tf->docexpr);
     261              :     int         colMin,
     262              :                 colMax;
     263              :     JsonTablePlan *childplan;
     264              : 
     265              :     /* Start of column range */
     266          368 :     colMin = list_length(tf->colvalexprs);
     267              : 
     268          970 :     foreach(col, columns)
     269              :     {
     270          617 :         JsonTableColumn *rawc = castNode(JsonTableColumn, lfirst(col));
     271              :         Oid         typid;
     272              :         int32       typmod;
     273          617 :         Oid         typcoll = InvalidOid;
     274              :         Node       *colexpr;
     275              : 
     276          617 :         if (rawc->coltype != JTC_NESTED)
     277              :         {
     278              :             Assert(rawc->name);
     279          487 :             tf->colnames = lappend(tf->colnames,
     280          487 :                                    makeString(pstrdup(rawc->name)));
     281              :         }
     282              : 
     283              :         /*
     284              :          * Determine the type and typmod for the new column. FOR ORDINALITY
     285              :          * columns are INTEGER by standard; the others are user-specified.
     286              :          */
     287          617 :         switch (rawc->coltype)
     288              :         {
     289           42 :             case JTC_FOR_ORDINALITY:
     290           42 :                 if (ordinality_found)
     291            3 :                     ereport(ERROR,
     292              :                             (errcode(ERRCODE_SYNTAX_ERROR),
     293              :                              errmsg("only one FOR ORDINALITY column is allowed"),
     294              :                              parser_errposition(pstate, rawc->location)));
     295           39 :                 ordinality_found = true;
     296           39 :                 colexpr = NULL;
     297           39 :                 typid = INT4OID;
     298           39 :                 typmod = -1;
     299           39 :                 break;
     300              : 
     301          322 :             case JTC_REGULAR:
     302          322 :                 typenameTypeIdAndMod(pstate, rawc->typeName, &typid, &typmod);
     303              : 
     304              :                 /*
     305              :                  * Use JTC_FORMATTED so as to use JSON_QUERY for this column
     306              :                  * if the specified type is one that's better handled using
     307              :                  * JSON_QUERY() or if non-default WRAPPER or QUOTES behavior
     308              :                  * is specified.
     309              :                  */
     310          322 :                 if (isCompositeType(typid) ||
     311          256 :                     rawc->quotes != JS_QUOTES_UNSPEC ||
     312          235 :                     rawc->wrapper != JSW_UNSPEC)
     313           87 :                     rawc->coltype = JTC_FORMATTED;
     314              : 
     315              :                 pg_fallthrough;
     316              :             case JTC_FORMATTED:
     317              :             case JTC_EXISTS:
     318              :                 {
     319              :                     JsonFuncExpr *jfe;
     320          445 :                     CaseTestExpr *param = makeNode(CaseTestExpr);
     321              : 
     322          445 :                     param->collation = InvalidOid;
     323          445 :                     param->typeId = contextItemTypid;
     324          445 :                     param->typeMod = -1;
     325              : 
     326          445 :                     jfe = transformJsonTableColumn(rawc, (Node *) param,
     327              :                                                    passingArgs);
     328              : 
     329          445 :                     colexpr = transformExpr(pstate, (Node *) jfe,
     330              :                                             EXPR_KIND_FROM_FUNCTION);
     331          433 :                     assign_expr_collations(pstate, colexpr);
     332              : 
     333          433 :                     typid = exprType(colexpr);
     334          433 :                     typmod = exprTypmod(colexpr);
     335          433 :                     typcoll = exprCollation(colexpr);
     336          433 :                     break;
     337              :                 }
     338              : 
     339          130 :             case JTC_NESTED:
     340          130 :                 continue;
     341              : 
     342            0 :             default:
     343            0 :                 elog(ERROR, "unknown JSON_TABLE column type: %d", (int) rawc->coltype);
     344              :                 break;
     345              :         }
     346              : 
     347          472 :         tf->coltypes = lappend_oid(tf->coltypes, typid);
     348          472 :         tf->coltypmods = lappend_int(tf->coltypmods, typmod);
     349          472 :         tf->colcollations = lappend_oid(tf->colcollations, typcoll);
     350          472 :         tf->colvalexprs = lappend(tf->colvalexprs, colexpr);
     351              :     }
     352              : 
     353              :     /* End of column range. */
     354          353 :     if (list_length(tf->colvalexprs) == colMin)
     355              :     {
     356              :         /* No columns in this Scan beside the nested ones. */
     357           52 :         colMax = colMin = -1;
     358              :     }
     359              :     else
     360          301 :         colMax = list_length(tf->colvalexprs) - 1;
     361              : 
     362              :     /* Recursively transform nested columns */
     363          353 :     childplan = transformJsonTableNestedColumns(cxt, passingArgs, columns);
     364              : 
     365              :     /* Create a "parent" scan responsible for all columns handled above. */
     366          353 :     return makeJsonTablePathScan(pathspec, errorOnError, colMin, colMax,
     367              :                                  childplan);
     368              : }
     369              : 
     370              : /*
     371              :  * Check if the type is "composite" for the purpose of checking whether to use
     372              :  * JSON_VALUE() or JSON_QUERY() for a given JsonTableColumn.
     373              :  */
     374              : static bool
     375          340 : isCompositeType(Oid typid)
     376              : {
     377          340 :     char        typtype = get_typtype(typid);
     378              : 
     379          319 :     return typid == JSONOID ||
     380          301 :         typid == JSONBOID ||
     381          301 :         typid == RECORDOID ||
     382          578 :         type_is_array(typid) ||
     383          677 :         typtype == TYPTYPE_COMPOSITE ||
     384              :     /* domain over one of the above? */
     385           18 :         (typtype == TYPTYPE_DOMAIN &&
     386           18 :          isCompositeType(getBaseType(typid)));
     387              : }
     388              : 
     389              : /*
     390              :  * Transform JSON_TABLE column definition into a JsonFuncExpr
     391              :  * This turns:
     392              :  *   - regular column into JSON_VALUE()
     393              :  *   - FORMAT JSON column into JSON_QUERY()
     394              :  *   - EXISTS column into JSON_EXISTS()
     395              :  */
     396              : static JsonFuncExpr *
     397          445 : transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
     398              :                          List *passingArgs)
     399              : {
     400              :     Node       *pathspec;
     401          445 :     JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr);
     402              : 
     403          445 :     if (jtc->coltype == JTC_REGULAR)
     404          235 :         jfexpr->op = JSON_VALUE_OP;
     405          210 :     else if (jtc->coltype == JTC_EXISTS)
     406           69 :         jfexpr->op = JSON_EXISTS_OP;
     407              :     else
     408          141 :         jfexpr->op = JSON_QUERY_OP;
     409              : 
     410              :     /* Pass the column name so any runtime JsonExpr errors can print it. */
     411              :     Assert(jtc->name != NULL);
     412          445 :     jfexpr->column_name = pstrdup(jtc->name);
     413              : 
     414          445 :     jfexpr->context_item = makeJsonValueExpr((Expr *) contextItemExpr, NULL,
     415              :                                              makeJsonFormat(JS_FORMAT_DEFAULT,
     416              :                                                             JS_ENC_DEFAULT,
     417              :                                                             -1));
     418          445 :     if (jtc->pathspec)
     419          396 :         pathspec = (Node *) jtc->pathspec->string;
     420              :     else
     421              :     {
     422              :         /* Construct default path as '$."column_name"' */
     423              :         StringInfoData path;
     424              : 
     425           49 :         initStringInfo(&path);
     426              : 
     427           49 :         appendStringInfoString(&path, "$.");
     428           49 :         escape_json(&path, jtc->name);
     429              : 
     430           49 :         pathspec = makeStringConst(path.data, -1);
     431              :     }
     432          445 :     jfexpr->pathspec = pathspec;
     433          445 :     jfexpr->passing = passingArgs;
     434          445 :     jfexpr->output = makeNode(JsonOutput);
     435          445 :     jfexpr->output->typeName = jtc->typeName;
     436          445 :     jfexpr->output->returning = makeNode(JsonReturning);
     437          445 :     jfexpr->output->returning->format = jtc->format;
     438          445 :     jfexpr->on_empty = jtc->on_empty;
     439          445 :     jfexpr->on_error = jtc->on_error;
     440          445 :     jfexpr->quotes = jtc->quotes;
     441          445 :     jfexpr->wrapper = jtc->wrapper;
     442          445 :     jfexpr->location = jtc->location;
     443              : 
     444          445 :     return jfexpr;
     445              : }
     446              : 
     447              : /*
     448              :  * Recursively transform nested columns and create child plan(s) that will be
     449              :  * used to evaluate their row patterns.
     450              :  */
     451              : static JsonTablePlan *
     452          353 : transformJsonTableNestedColumns(JsonTableParseContext *cxt,
     453              :                                 List *passingArgs,
     454              :                                 List *columns)
     455              : {
     456          353 :     JsonTablePlan *plan = NULL;
     457              :     ListCell   *lc;
     458              : 
     459              :     /*
     460              :      * If there are multiple NESTED COLUMNS clauses in 'columns', their
     461              :      * respective plans will be combined using a "sibling join" plan, which
     462              :      * effectively does a UNION of the sets of rows coming from each nested
     463              :      * plan.
     464              :      */
     465          952 :     foreach(lc, columns)
     466              :     {
     467          599 :         JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc));
     468              :         JsonTablePlan *nested;
     469              : 
     470          599 :         if (jtc->coltype != JTC_NESTED)
     471          469 :             continue;
     472              : 
     473          130 :         if (jtc->pathspec->name == NULL)
     474           69 :             jtc->pathspec->name = generateJsonTablePathName(cxt);
     475              : 
     476          130 :         nested = transformJsonTableColumns(cxt, jtc->columns, passingArgs,
     477              :                                            jtc->pathspec);
     478              : 
     479          130 :         if (plan)
     480           45 :             plan = makeJsonTableSiblingJoin(plan, nested);
     481              :         else
     482           85 :             plan = nested;
     483              :     }
     484              : 
     485          353 :     return plan;
     486              : }
     487              : 
     488              : /*
     489              :  * Create a JsonTablePlan for given path and ON ERROR behavior.
     490              :  *
     491              :  * colMin and colMin give the range of columns computed by this scan in the
     492              :  * global flat list of column expressions that will be passed to the
     493              :  * JSON_TABLE's TableFunc.  Both are -1 when all of columns are nested and
     494              :  * thus computed by 'childplan'.
     495              :  */
     496              : static JsonTablePlan *
     497          353 : makeJsonTablePathScan(JsonTablePathSpec *pathspec, bool errorOnError,
     498              :                       int colMin, int colMax,
     499              :                       JsonTablePlan *childplan)
     500              : {
     501          353 :     JsonTablePathScan *scan = makeNode(JsonTablePathScan);
     502              :     char       *pathstring;
     503              :     Const      *value;
     504              : 
     505              :     Assert(IsA(pathspec->string, A_Const));
     506          353 :     pathstring = castNode(A_Const, pathspec->string)->val.sval.sval;
     507          353 :     value = makeConst(JSONPATHOID, -1, InvalidOid, -1,
     508              :                       DirectFunctionCall1(jsonpath_in,
     509              :                                           CStringGetDatum(pathstring)),
     510              :                       false, false);
     511              : 
     512          353 :     scan->plan.type = T_JsonTablePathScan;
     513          353 :     scan->path = makeJsonTablePath(value, pathspec->name);
     514          353 :     scan->errorOnError = errorOnError;
     515              : 
     516          353 :     scan->child = childplan;
     517              : 
     518          353 :     scan->colMin = colMin;
     519          353 :     scan->colMax = colMax;
     520              : 
     521          353 :     return (JsonTablePlan *) scan;
     522              : }
     523              : 
     524              : /*
     525              :  * Create a JsonTablePlan that will perform a join of the rows coming from
     526              :  * 'lplan' and 'rplan'.
     527              :  *
     528              :  * The default way of "joining" the rows is to perform a UNION between the
     529              :  * sets of rows from 'lplan' and 'rplan'.
     530              :  */
     531              : static JsonTablePlan *
     532           45 : makeJsonTableSiblingJoin(JsonTablePlan *lplan, JsonTablePlan *rplan)
     533              : {
     534           45 :     JsonTableSiblingJoin *join = makeNode(JsonTableSiblingJoin);
     535              : 
     536           45 :     join->plan.type = T_JsonTableSiblingJoin;
     537           45 :     join->lplan = lplan;
     538           45 :     join->rplan = rplan;
     539              : 
     540           45 :     return (JsonTablePlan *) join;
     541              : }
        

Generated by: LCOV version 2.0-1